您的位置:首页 > 移动开发 > Android开发

Android热修复学习之旅——Andfix框架完全解析

2017-03-22 00:09 591 查看
Android热修复学习之旅开篇——热修复概述

Android热修复学习之旅——HotFix完全解析

Android热修复学习之旅——Tinker接入全攻略

在之前的博客《Android热修复学习之旅——HotFix完全解析》中,我们学习了热修复的实现方式之一,通过dex分包方案的原理还有HotFix框架的源码分析,本次我将讲解热修复的另外一种思路,那就是通过native方法,使用这种思路的框架代表就是阿里的Andfix,本篇博客,我们将深入分析Andfix的实现。

Andfix的使用

下面一段代码就是Andfix的使用代码,为了方便大家理解,重要内容已进行注释

public class MainApplication extends Application {
private static final String TAG = "euler";

private static final String APATCH_PATH = "/out.apatch";//被修复的文件都是以.apatch结尾
/**
* patch manager
*/
private PatchManager mPatchManager;

@Override
public void onCreate() {
super.onCreate();
// initialize
//初始化PatchManager,也就是修复包的管理器,因为修复包可能有多个,所以这里需要一个管理器进行管理
mPatchManager = new PatchManager(this);
mPatchManager.init("1.0");
Log.d(TAG, "inited.");

// load patch
//开始加载修复包
mPatchManager.loadPatch();
Log.d(TAG, "apatch loaded.");

// add patch at runtime
try {
// .apatch file path
//存放patch补丁文件的路径,这里使用的sd卡,真实项目中肯定是从服务器下载到sd卡中
String patchFileString = Environment.getExternalStorageDirectory()
.getAbsolutePath() + APATCH_PATH;
mPatchManager.addPatch(patchFileString);
Log.d(TAG, "apatch:" + patchFileString + " added.");
} catch (IOException e) {
Log.e(TAG, "", e);
}

}
}


其实就是通过一个PatchManager加载修复包,接下来我们分析一下PatchManager的代码

/**
* @param context
*            context
*/
public PatchManager(Context context) {
mContext = context;
//初始化AndFixManager
mAndFixManager = new AndFixManager(mContext);
//初始化存放patch补丁文件的目录
mPatchDir = new File(mContext.getFilesDir(), DIR);
//初始化存在Patch类的集合
mPatchs = new ConcurrentSkipListSet<Patch>();
//初始化存放类对应的类加载器集合
mLoaders = new ConcurrentHashMap<String, ClassLoader>();
}


里面很重要的类就是AndFixManager,接下来我们看一下AndFixManager的初始化代码

public AndFixManager(Context context) {
mContext = context;
//判断Android机型是否适支持AndFix
mSupport = Compat.isSupport();
if (mSupport) {
//初始化签名安全判断类,此类主要是进行修复包安全校验的工作
mSecurityChecker = new SecurityChecker(mContext);
//初始化patch文件存放的目录
mOptDir = new File(mContext.getFilesDir(), DIR);
if (!mOptDir.exists() && !mOptDir.mkdirs()) {// make directory fail
mSupport = false;
Log.e(TAG, "opt dir create error.");
} else if (!mOptDir.isDirectory()) {// not directory
//如果不是文件目录就删除
mOptDir.delete();
mSupport = false;
}
}


概括一下AndFixManager的初始化,主要做了以下的工作:

1.判断Android机型是否适支持AndFix,

2.初始化修复包安全校验的工作

Andfix源码分析

首先看一下isSupport方法内部的逻辑

public static synchronized boolean isSupport() {
if (isChecked)
return isSupport;

isChecked = true;
// not support alibaba's YunOs
if (!isYunOS() && AndFix.setup() && isSupportSDKVersion()) {
isSupport = true;
}

if (inBlackList()) {
isSupport = false;
}

return isSupport;
}


可以看到判断的条件主要是3个:

1.判断系统是否是YunOs系统

@SuppressLint("DefaultLocale")
private static boolean isYunOS() {
String version = null;
String vmName = null;
try {
Method m = Class.forName("android.os.SystemProperties").getMethod(
"get", String.class);
version = (String) m.invoke(null, "ro.yunos.version");
vmName = (String) m.invoke(null, "java.vm.name");
} catch (Exception e) {
// nothing todo
}
if ((vmName != null && vmName.toLowerCase().contains("lemur"))
|| (version != null && version.trim().length() > 0)) {
return true;
} else {
return false;
}
}


2.判断是Dalvik还是Art虚拟机,来注册Native方法

/**
* initialize
*
* @return true if initialize success
*/
public static boolean setup() {
try {
final String vmVersion = System.getProperty("java.vm.version");
boolean isArt = vmVersion != null && vmVersion.startsWith("2");
int apilevel = Build.VERSION.SDK_INT;
return setup(isArt, apilevel);
} catch (Exception e) {
Log.e(TAG, "setup", e);
return false;
}
}


如果版本符合的话,会调用native的setup

static jboolean setup(JNIEnv* env, jclass clazz, jboolean isart,
jint apilevel) {
isArt = isart;
LOGD("vm is: %s , apilevel is: %i", (isArt ? "art" : "dalvik"),
(int )apilevel);
if (isArt) {
return art_setup(env, (int) apilevel);
} else {
return dalvik_setup(env, (int) apilevel);
}
}


同样在jboolean setup中分为art_setup和dalvik_setup

art_setup方法

extern jboolean __attribute__ ((visibility ("hidden"))) art_setup(JNIEnv* env,
int level) {
apilevel = level;
return JNI_TRUE;
}


dalvik_setup方法

extern jboolean __attribute__ ((visibility ("hidden"))) dalvik_setup(
JNIEnv* env, int apilevel) {
//打开系统的"libdvm.so"文件
void* dvm_hand = dlopen("libdvm.so", RTLD_NOW);
if (dvm_hand) {
//获取dvmDecodeIndirectRef_fnPtr和dvmThreadSelf_fnPtr俩个函数
//这两个函数可以通过类对象获取ClassObject结构体
dvmDecodeIndirectRef_fnPtr = dvm_dlsym(dvm_hand,
apilevel > 10 ?
"_Z20dvmDecodeIndirectRefP6ThreadP8_jobject" :
"dvmDecodeIndirectRef");
if (!dvmDecodeIndirectRef_fnPtr) {
return JNI_FALSE;
}
dvmThreadSelf_fnPtr = dvm_dlsym(dvm_hand,
apilevel > 10 ? "_Z13dvmThreadSelfv" : "dvmThreadSelf");
if (!dvmThreadSelf_fnPtr) {
return JNI_FALSE;
}
//通过Java层Method对象的getDeclaringClass方法
//后续会调用该方法获取某个方法所属的类对象
//因为Java层只传递了Method对象到native层
jclass clazz = env->FindClass("java/lang/reflect/Method");
jClassMethod = env->GetMethodID(clazz, "getDeclaringClass",
"()Ljava/lang/Class;");

return JNI_TRUE;
} else {
return JNI_FALSE;
}
}


主要做了两件事,准备后续的replaceMethod函数中使用:

1、在libdvm.so动态获取dvmDecodeIndirectRef_fnPtr函数指针和获取dvmThreadSelf_fnPtr函数指针。

2、调用dest的 Method.getDeclaringClass方法获取method的类对象clazz。

3.根据sdk版本判断是否支持(支持Android2.3-7.0系统版本)

// from android 2.3 to android 7.0
private static boolean isSupportSDKVersion() {
if (android.os.Build.VERSION.SDK_INT >= 8
&& android.os.Build.VERSION.SDK_INT <= 24) {
return true;
}
return false;
}


然后我们看一下初始化签名安全判断类的代码

public SecurityChecker(Context context) {
mContext = context;
init(mContext);
}


init方法要是获取当前应用的签名及其他信息,为了判断与patch文件的签名是否一致

// initialize,and check debuggable
//主要是获取当前应用的签名及其他信息,为了判断与patch文件的签名是否一致
private void init(Context context) {
try {
PackageManager pm = context.getPackageManager();
String packageName = context.getPackageName();

PackageInfo packageInfo = pm.getPackageInfo(packageName,
PackageManager.GET_SIGNATURES);
CertificateFactory certFactory = CertificateFactory
.getInstance("X.509");
ByteArrayInputStream stream = new ByteArrayInputStream(
packageInfo.signatures[0].toByteArray());
X509Certificate cert = (X509Certificate) certFactory
.generateCertificate(stream);
mDebuggable = cert.getSubjectX500Principal().equals(DEBUG_DN);
mPublicKey = cert.getPublicKey();
} catch (NameNotFoundException e) {
Log.e(TAG, "init", e);
} catch (CertificateException e) {
Log.e(TAG, "init", e);
}
}


接下来是分析mPatchManager.init方法

public void init(String appVersion) {
if (!mPatchDir.exists() && !mPatchDir.mkdirs()) {// make directory fail
Log.e(TAG, "patch dir create error.");
return;
} else if (!mPatchDir.isDirectory()) {// not directory
mPatchDir.delete();
return;
}
//使用SP存储关于patch文件的信息
SharedPreferences sp = mContext.getSharedPreferences(SP_NAME,
Context.MODE_PRIVATE);
//根据你传入的版本号和之前的对比,做不同的处理
String ver = sp.getString(SP_VERSION, null);
if (ver == null || !ver.equalsIgnoreCase(appVersion)) {
//删除本地patch文件
cleanPatch();
//并把传入的版本号保存
sp.edit().putString(SP_VERSION, appVersion).commit();
} else {
//初始化patch列表,把本地的patch文件加载到内存
initPatchs();
}
}


主要是进行版本号的对比,如果不一致则删除本地所有的patch文件,同时保存新的版本号,否则就直接把本地的patch文件加载到内存

private void cleanPatch() {
File[] files = mPatchDir.listFiles();
for (File file : files) {
//删除所有的本地缓存patch文件
mAndFixManager.removeOptFile(file);
if (!FileUtil.deleteFile(file)) {
Log.e(TAG, file.getName() + " delete error.");
}
}
}


private void initPatchs() {
File[] files = mPatchDir.listFiles();
for (File file : files) {
addPatch(file);
}
}


/**
* add patch file
*
* @param file
* @return patch
*/
private Patch addPatch(File file) {
Patch patch = null;
if (file.getName().endsWith(SUFFIX)) {
try {
//创建Patch对象
patch = new Patch(file);
//把patch实例存储到内存的集合中,在PatchManager实例化集合
mPatchs.add(patch);
} catch (IOException e) {
Log.e(TAG, "addPatch", e);
}
}
return patch;
}


Patch类无疑是进行修复的关键,所以我们需要查看Patch的代码

public Patch(File file) throws IOException {
mFile = file;
init();
}


@SuppressWarnings("deprecation")
private void init() throws IOException {
JarFile jarFile = null;
InputStream inputStream = null;
try {
//使用JarFile读取Patch文件
jarFile = new JarFile(mFile);
//获取META-INF/PATCH.MF文件
JarEntry entry = jarFile.getJarEntry(ENTRY_NAME);
inputStream = jarFile.getInputStream(entry);
Manifest manifest = new Manifest(inputStream);
Attributes main = manifest.getMainAttributes();
//获取PATCH.MF文件中的属性Patch-Name
mName = main.getValue(PATCH_NAME);
//获取PATCH.MF属性Created-Time
mTime = new Date(main.getValue(CREATED_TIME));

mClassesMap = new HashMap<String, List<String>>();
Attributes.Name attrName;
String name;
List<String> strings;
for (Iterator<?> it = main.keySet().iterator(); it.hasNext();) {
attrName = (Attributes.Name) it.next();
name = attrName.toString();
//判断name的后缀是否是-Classes,并把name对应的值加入到集合中,对应的值就是class类名的列表
if (name.endsWith(CLASSES)) {
strings = Arrays.asList(main.getValue(attrName).split(","));
if (name.equalsIgnoreCase(PATCH_CLASSES)) {
mClassesMap.put(mName, strings);
} else {
mClassesMap.put(
//为了移除掉"-Classes"的后缀
name.trim().substring(0, name.length() - 8),// remove
// "-Classes"
strings);
}
}
}
} finally {
if (jarFile != null) {
jarFile.close();
}
if (inputStream != null) {
inputStream.close();
}
}

}


init方法主要的逻辑就是通过读取.patch文件,每个修复包apatch文件其实都是一个jarFile文件,然后获得其中META-INF/PATCH.MF文件,PATCH.MF文件中都是key-value的形式,获取key是-Classes的所有的value,这些value就是所有要修复的类,他们是以“,”进行分割的,将它们放入list列表,将其存储到一个集合中mClassesMap,list列表中存储的就是所有要修复的类名

还有另一个addpath方法,接受的是文件路径参数:

/**
* add patch at runtime
*
* @param path
*            patch path
* @throws IOException
*/
public void addPatch(String path) throws IOException {
File src = new File(path);
File dest = new File(mPatchDir, src.getName());
if(!src.exists()){
throw new FileNotFoundException(path);
}
if (dest.exists()) {
Log.d(TAG, "patch [" + path + "] has be loaded.");
return;
}
//把文件拷贝到专门存放patch文件的文件夹中
FileUtil.copyFile(src, dest);// copy to patch's directory
Patch patch = addPatch(dest);
if (patch != null) {
//使用loadPatch进行加载
loadPatch(patch);
}
}


总结一下两个addPatch方法的不同之处:

addPatch(file)方法:需要结合上面的initPatchs方法一起使用,他调用的场景是:本地mPatchDir目录中已经有了修复包文件,并且版本号没有发生变化,这样每次启动程序的时候就会调用初始化操作,在这里会遍历mPatchDir目录中所有的修复包文件,然后调用这个方法添加到全局文件列表中,也即是mPatchs中。

addPatch(String path)方法:这个方法使用的场景是版本号发生变化,或者是本地目录中没有修复包文件。比如第一次操作的时候,会从网络上下载修复包文件,下载成功之后会把这个文件路径通过这个方法调用即可,执行完之后也会主动调用加载修复包的操作了,比如demo中第一次在SD卡中放了一个修复包文件:

// add patch at runtime
try {
// .apatch file path
//存放patch补丁文件的路径,这里使用的sd卡,真实项目中肯定是从服务器下载到sd卡中
String patchFileString = Environment.getExternalStorageDirectory()
.getAbsolutePath() + APATCH_PATH;
mPatchManager.addPatch(patchFileString);
Log.d(TAG, "apatch:" + patchFileString + " added.");
} catch (IOException e) {
Log.e(TAG, "", e);
}


接下来,看一下mPatchManager.loadPatch();

/**
* load patch,call when application start
*
*/
public void loadPatch() {
mLoaders.put("*", mContext.getClassLoader());// wildcard
Set<String> patchNames;
List<String> classes;
for (Patch patch : mPatchs) {
patchNames = patch.getPatchNames();
for (String patchName : patchNames) {
//获取patch对应的class类的集合List
classes = patch.getClasses(patchName);
//调用mAndFixManager.fix修复bug
mAndFixManager.fix(patch.getFile(), mContext.getClassLoader(),
classes);
}
}
}


这个方法主要是通过Patch类获取修复包所有的修复类名称,之前已经介绍了Patch类的初始化操作,在哪里会解析修复包的MF文件信息,获取到修复包需要修复的类名然后保存到列表中,这里就通过getClasses方法来获取指定修复包名称对应的修复类名称列表,然后调用AndFixManager的fix方法

接下来就是分析mAndFixManager.fix方法

/**
* fix
*
* @param patchPath
*            patch path
*/
public synchronized void fix(String patchPath) {
fix(new File(patchPath), mContext.getClassLoader(), null);
}


**
* fix
*
* @param file
*            patch file
* @param classLoader
*            classloader of class that will be fixed
* @param classes
*            classes will be fixed
*/
public synchronized void fix(File file, ClassLoader classLoader,
List<String> classes) {
if (!mSupport) {
return;
}
//判断patch文件的签名,检查修复包的安全性
if (!mSecurityChecker.verifyApk(file)) {// security check fail
return;
}

try {
File optfile = new File(mOptDir, file.getName());
boolean saveFingerprint = true;
if (optfile.exists()) {
// need to verify fingerprint when the optimize file exist,
// prevent someone attack on jailbreak device with
// Vulnerability-Parasyte.
// btw:exaggerated android Vulnerability-Parasyte
// http://secauo.com/Exaggerated-Android-Vulnerability-Parasyte.html if (mSecurityChecker.verifyOpt(optfile)) {
saveFingerprint = false;
} else if (!optfile.delete()) {
return;
}
}
//使用dexFile 加载修复包文件,所以说patch文件其实本质是dex文件
final DexFile dexFile = DexFile.loadDex(file.getAbsolutePath(),
optfile.getAbsolutePath(), Context.MODE_PRIVATE);

if (saveFingerprint) {
mSecurityChecker.saveOptSig(optfile);
}
//这里重新new了一个ClasLoader,并重写findClass方法
ClassLoader patchClassLoader = new ClassLoader(classLoader) {
@Override
protected Class<?> findClass(String className)
throws ClassNotFoundException {
Class<?> clazz = dexFile.loadClass(className, this);
if (clazz == null
&& className.startsWith("com.alipay.euler.andfix")) {
return Class.forName(className);// annotation’s class
// not found
}
if (clazz == null) {
throw new ClassNotFoundException(className);
}
return clazz;
}
};
Enumeration<String> entrys = dexFile.entries();
Class<?> clazz = null;
while (entrys.hasMoreElements()) {
String entry = entrys.nextElement();
if (classes != null && !classes.contains(entry)) {
continue;// skip, not need fix
}
//加载有bug的类文件
clazz = dexFile.loadClass(entry, patchClassLoader);
if (clazz != null) {
//fixClass方法对有bug的文件进行替换
fixClass(clazz, classLoader);
}
}
} catch (IOException e) {
Log.e(TAG, "pacth", e);
}
}


概括一下fix方法做的几件事:

1.使用mSecurityChecker进行修复包的校验工作,这里的校验就是比对修复包的签名和应用的签名是否一致:

/**
* @param path
*            Apk file
* @return true if verify apk success
*/
public boolean verifyApk(File path) {
if (mDebuggable) {
Log.d(TAG, "mDebuggable = true");
return true;
}

JarFile jarFile = null;
try {
jarFile = new JarFile(path);

JarEntry jarEntry = jarFile.getJarEntry(CLASSES_DEX);
if (null == jarEntry) {// no code
return false;
}
loadDigestes(jarFile, jarEntry);
Certificate[] certs = jarEntry.getCertificates();
if (certs == null) {
return false;
}
return check(path, certs);
} catch (IOException e) {
Log.e(TAG, path.getAbsolutePath(), e);
return false;
} finally {
try {
if (jarFile != null) {
jarFile.close();
}
} catch (IOException e) {
Log.e(TAG, path.getAbsolutePath(), e);
}
}
}


2.使用DexFile和自定义类加载器来加载修复包文件

//这里重新new了一个ClasLoader,并重写findClass方法
ClassLoader patchClassLoader = new ClassLoader(classLoader) {
@Override
protected Class<?> findClass(String className)
throws ClassNotFoundException {
Class<?> clazz = dexFile.loadClass(className, this);
if (clazz == null
&& className.startsWith("com.alipay.euler.andfix")) {
return Class.forName(className);// annotation’s class
// not found
}
if (clazz == null) {
throw new ClassNotFoundException(className);
}
return clazz;
}
};
Enumeration<String> entrys = dexFile.entries();
Class<?> clazz = null;
while (entrys.hasMoreElements()) {
String entry = entrys.nextElement();
if (classes != null && !classes.contains(entry)) {
continue;// skip, not need fix
}
//加载修复包patch中的文件信息,获取其中要修复的类名,然后进行加载
clazz = dexFile.loadClass(entry, patchClassLoader);
if (clazz != null) {
//fixClass方法对有bug的文件进行替换
fixClass(clazz, classLoader);
}
}


这里创建一个新的classLoader的原因是,我们需要获取修复类中bug的方法名称,而这个方法名称是通过修复方法的注解来获取到的,所以得先进行类的加载然后获取到他的方法信息,最后通过分析注解获取方法名,这里用的是反射机制来进行操作的。使用自定义的classLoader为了过滤我们需要加载的类

接下来是fixClass方法的逻辑

/**
* fix class
*
* @param clazz
*            class
*/
private void fixClass(Class<?> clazz, ClassLoader classLoader) {
Method[] methods = clazz.getDeclaredMethods();
MethodReplace methodReplace;
String clz;
String meth;
for (Method method : methods) {
//遍历所有的方法,获取方法的注解,因为有bug的方法在生成的patch的类中的方法都是有注解的
methodReplace = method.getAnnotation(MethodReplace.class);
if (methodReplace == null)
continue;
//获取注解中clazz的值
clz = methodReplace.clazz();
//获取注解中method的值
meth = methodReplace.method();
if (!isEmpty(clz) && !isEmpty(meth)) {
//进行替换
replaceMethod(classLoader, clz, meth, method);
}
}
}


通过反射获取指定类名需要修复类中的所有方法类型,然后在获取对应的注解信息,上面已经分析了通过DexFile加载修复包文件,然后在加载上面Patch类中的getClasses方法获取到的修复类名称列表来进行类的加载,然后在用反射机制获取类中所有的方法对应的注解信息,通过注解信息获取指定修复的方法名称,看一下注解的定义:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MethodReplace {
String clazz();

String method();
}


两个方法:一个是获取当前类名称,一个是获取当前方法名称

/**
* replace method
*
* @param classLoader classloader
* @param clz class
* @param meth name of target method
* @param method source method
*/
private void replaceMethod(ClassLoader classLoader, String clz,
String meth, Method method) {
try {
String key = clz + "@" + classLoader.toString();
//判断此类是否已经被fix
Class<?> clazz = mFixedClass.get(key);
if (clazz == null) {// class not load
Class<?> clzz = classLoader.loadClass(clz);
// initialize target class
clazz = AndFix.initTargetClass(clzz);//初始化class
}
if (clazz != null) {// initialize class OK
mFixedClass.put(key, clazz);
//根据反射获取到有bug的类的方法(有bug的apk)
Method src = clazz.getDeclaredMethod(meth,
method.getParameterTypes());
//src是有bug的方法,method是补丁方法
AndFix.addReplaceMethod(src, method);
}
} catch (Exception e) {
Log.e(TAG, "replaceMethod", e);
}
}


这里说明一下,获得有bug方法的这段代码:

Method src = clazz.getDeclaredMethod(meth,
method.getParameterTypes());


通过方法名和本地已有的该方法的参数信息获取有bug的方法,然后将有bug的方法和修复的方法一起传入进行修复

注意:上面的操作,传入的是修复新的方法信息以及需要修复的旧方法名称,不过这里得先获取到旧方法类型,可以看到修复的新旧方法的签名必须一致,所谓签名就是方法的名称,参数个数,参数类型都必须一致,不然这里就报错的。进而也修复不了了。

接下来就是交给native方法了,由于Android4.4后才用的Art虚拟机,之前的系统都是Dalvik虚拟机,因此Natice层写了2个方法,对不同的系统做不同的处理方式。

#andfix.cpp
static void replaceMethod(JNIEnv* env, jclass clazz, jobject src,
jobject dest) {
if (isArt) {
art_replaceMethod(env, src, dest);
} else {
dalvik_replaceMethod(env, src, dest);
}
}


Dalvik replaceMethod的实现:

extern void __attribute__ ((visibility ("hidden"))) dalvik_replaceMethod(
JNIEnv* env, jobject src, jobject dest) {
jobject clazz = env->CallObjectMethod(dest, jClassMethod);
//ClassObject结构体包含很多信息,在native中这个值很有用
ClassObject* clz = (ClassObject*) dvmDecodeIndirectRef_fnPtr(
dvmThreadSelf_fnPtr(), clazz);
clz->status = CLASS_INITIALIZED;//更改状态为类初始化完成的状态
//通过java层传递的方法对象,在native层获得他们的结构体
Method* meth = (Method*) env->FromReflectedMethod(src);
Method* target = (Method*) env->FromReflectedMethod(dest);
LOGD("dalvikMethod: %s", meth->name);

// meth->clazz = target->clazz;
//核心方法如下,就是替换新旧方法结构体中的信息
meth->accessFlags |= ACC_PUBLIC;
meth->methodIndex = target->methodIndex;
meth->jniArgInfo = target->jniArgInfo;
meth->registersSize = target->registersSize;
meth->outsSize = target->outsSize;
meth->insSize = target->insSize;

meth->prototype = target->prototype;
meth->insns = target->insns;
meth->nativeFunc = target->nativeFunc;
}


简单来说,就是通过上层传递过来的新旧方法类型对象,通过JNIEnv的FromReflectedMethod方法获取对应的方法结构体信息,然后将其信息进行替换即可

其余art的native方法,读者可以自行阅读,因为原理也是差不多.

如何生成patch包

细心的同学发现,我们还没说如何生成patch包,可以通过apatch进行生成

使用神器apatch进行线上发布的release包和这次修复的fix包进行比对,获取到修复文件apatch

java -jar apkpatch.jar -f app-release-fix.apk -t app-release-online.apk -o C:\Users\mayu-g\Desktop\apkpatch-1.0.3 -k myl.keystore -p 123456 -a mayunlong -e 123456


使用命令的时候需要用到签名文件,因为在前面分析代码的时候知道会做修复包的签名验证。这里得到了一个修复包文件如下:



而且会产生一个diff.dex文件和smali文件夹,而我们用压缩软件可以打开apatch文件看看:



可以看到这里的classes.dex文件其实就是上面的diff.dex文件,只是这里更像是Android中的apk文件目录格式,同样有一个META-INF目录,这里存放了签名文件以及需要修复类信息的PATCH.MF文件:



至此,Andfix框架已基本分析完毕。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息