Android热修复学习之旅——Andfix框架完全解析
bmnk0073
8年前
<p>在之前的博客 《Android热修复学习之旅——HotFix完全解析》 中,我们学习了热修复的实现方式之一,通过dex分包方案的原理还有HotFix框架的源码分析,本次我将讲解热修复的另外一种思路,那就是通过native方法,使用这种思路的框架代表就是阿里的Andfix,本篇博客,我们将深入分析Andfix的实现。</p> <h2>Andfix的使用</h2> <p>下面一段代码就是Andfix的使用代码,为了方便大家理解,重要内容已进行注释</p> <pre> 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); } } }</pre> <p>其实就是通过一个PatchManager加载修复包,接下来我们分析一下PatchManager的代码</p> <pre> /** * @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>(); }</pre> <p>里面很重要的类就是AndFixManager,接下来我们看一下AndFixManager的初始化代码</p> <pre> 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; } }</pre> <p>概括一下AndFixManager的初始化,主要做了以下的工作:</p> <p>1.判断Android机型是否适支持AndFix,</p> <p>2.初始化修复包安全校验的工作</p> <h2>Andfix源码分析</h2> <p>首先看一下isSupport方法内部的逻辑</p> <pre> 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; }</pre> <p>可以看到判断的条件主要是3个:</p> <p>1.判断系统是否是YunOs系统</p> <pre> @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; } }</pre> <p>2.判断是Dalvik还是Art虚拟机,来注册Native方法</p> <pre> /** * 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; } }</pre> <p>如果版本符合的话,会调用native的setup</p> <pre> 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); } }</pre> <p>同样在jboolean setup中分为art_setup和dalvik_setup</p> <p>art_setup方法</p> <pre> extern jboolean __attribute__ ((visibility ("hidden"))) art_setup(JNIEnv* env, int level) { apilevel = level; return JNI_TRUE; }</pre> <p>dalvik_setup方法</p> <pre> 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; } }</pre> <p>主要做了两件事,准备后续的replaceMethod函数中使用:</p> <p>1、在libdvm.so动态获取dvmDecodeIndirectRef_fnPtr函数指针和获取dvmThreadSelf_fnPtr函数指针。</p> <p>2、调用dest的 Method.getDeclaringClass方法获取method的类对象clazz。</p> <p>3.根据sdk版本判断是否支持(支持Android2.3-7.0系统版本)</p> <pre> // 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; }</pre> <p>然后我们看一下初始化签名安全判断类的代码</p> <pre> public SecurityChecker(Context context) { mContext = context; init(mContext); }</pre> <p>init方法要是获取当前应用的签名及其他信息,为了判断与patch文件的签名是否一致</p> <pre> // 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); } }</pre> <p>接下来是分析mPatchManager.init方法</p> <pre> 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(); } }</pre> <p>主要是进行版本号的对比,如果不一致则删除本地所有的patch文件,同时保存新的版本号,否则就直接把本地的patch文件加载到内存</p> <pre> 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."); } } }</pre> <pre> private void initPatchs() { File[] files = mPatchDir.listFiles(); for (File file : files) { addPatch(file); } }</pre> <pre> /** * 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; }</pre> <p>Patch类无疑是进行修复的关键,所以我们需要查看Patch的代码</p> <pre> public Patch(File file) throws IOException { mFile = file; init(); }</pre> <pre> @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(); } } }</pre> <p>init方法主要的逻辑就是通过读取.patch文件,每个修复包apatch文件其实都是一个jarFile文件,然后获得其中META-INF/PATCH.MF文件,PATCH.MF文件中都是key-value的形式,获取key是-Classes的所有的value,这些value就是所有要修复的类,他们是以“,”进行分割的,将它们放入list列表,将其存储到一个集合中mClassesMap,list列表中存储的就是所有要修复的类名</p> <p>还有另一个addpath方法,接受的是文件路径参数:</p> <pre> /** * 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); } }</pre> <p>总结一下两个addPatch方法的不同之处:</p> <p>addPatch(file)方法:需要结合上面的initPatchs方法一起使用,他调用的场景是:本地mPatchDir目录中已经有了修复包文件,并且版本号没有发生变化,这样每次启动程序的时候就会调用初始化操作,在这里会遍历mPatchDir目录中所有的修复包文件,然后调用这个方法添加到全局文件列表中,也即是mPatchs中。</p> <p>addPatch(String path)方法:这个方法使用的场景是版本号发生变化,或者是本地目录中没有修复包文件。比如第一次操作的时候,会从网络上下载修复包文件,下载成功之后会把这个文件路径通过这个方法调用即可,执行完之后也会主动调用加载修复包的操作了,比如demo中第一次在SD卡中放了一个修复包文件:</p> <pre> // 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); }</pre> <p>接下来,看一下mPatchManager.loadPatch();</p> <pre> /** * 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); } } }</pre> <p>这个方法主要是通过Patch类获取修复包所有的修复类名称,之前已经介绍了Patch类的初始化操作,在哪里会解析修复包的MF文件信息,获取到修复包需要修复的类名然后保存到列表中,这里就通过getClasses方法来获取指定修复包名称对应的修复类名称列表,然后调用AndFixManager的fix方法</p> <p>接下来就是分析mAndFixManager.fix方法</p> <pre> /** * fix * * @param patchPath * patch path */ public synchronized void fix(String patchPath) { fix(new File(patchPath), mContext.getClassLoader(), null); }</pre> <pre> ** * 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); } }</pre> <p>概括一下fix方法做的几件事:</p> <p>1.使用mSecurityChecker进行修复包的校验工作,这里的校验就是比对修复包的签名和应用的签名是否一致:</p> <pre> /** * @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); } } }</pre> <p>2.使用DexFile和自定义类加载器来加载修复包文件</p> <pre> //这里重新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); } }</pre> <p>这里创建一个新的classLoader的原因是,我们需要获取修复类中bug的方法名称,而这个方法名称是通过修复方法的注解来获取到的,所以得先进行类的加载然后获取到他的方法信息,最后通过分析注解获取方法名,这里用的是反射机制来进行操作的。使用自定义的classLoader为了过滤我们需要加载的类</p> <p>接下来是fixClass方法的逻辑</p> <pre> /** * 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); } } }</pre> <p>通过反射获取指定类名需要修复类中的所有方法类型,然后在获取对应的注解信息,上面已经分析了通过DexFile加载修复包文件,然后在加载上面Patch类中的getClasses方法获取到的修复类名称列表来进行类的加载,然后在用反射机制获取类中所有的方法对应的注解信息,通过注解信息获取指定修复的方法名称,看一下注解的定义:</p> <pre> @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface MethodReplace { String clazz(); String method(); }</pre> <p>两个方法:一个是获取当前类名称,一个是获取当前方法名称</p> <pre> /** * 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); } }</pre> <p>这里说明一下,获得有bug方法的这段代码:</p> <pre> Method src = clazz.getDeclaredMethod(meth, method.getParameterTypes());</pre> <p>通过方法名和本地已有的该方法的参数信息获取有bug的方法,然后将有bug的方法和修复的方法一起传入进行修复</p> <p>注意:上面的操作,传入的是修复新的方法信息以及需要修复的旧方法名称,不过这里得先获取到旧方法类型,可以看到修复的新旧方法的签名必须一致,所谓签名就是方法的名称,参数个数,参数类型都必须一致,不然这里就报错的。进而也修复不了了。</p> <p>接下来就是交给native方法了,由于Android4.4后才用的Art虚拟机,之前的系统都是Dalvik虚拟机,因此Natice层写了2个方法,对不同的系统做不同的处理方式。</p> <pre> #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); } }</pre> <p>Dalvik replaceMethod的实现:</p> <pre> 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; }</pre> <p>简单来说,就是通过上层传递过来的新旧方法类型对象,通过JNIEnv的FromReflectedMethod方法获取对应的方法结构体信息,然后将其信息进行替换即可</p> <p>其余art的native方法,读者可以自行阅读,因为原理也是差不多.</p> <h2>如何生成patch包</h2> <p>细心的同学发现,我们还没说如何生成patch包,可以通过apatch进行生成</p> <p>使用神器apatch进行线上发布的release包和这次修复的fix包进行比对,获取到修复文件apatch</p> <pre> 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</pre> <p>使用命令的时候需要用到签名文件,因为在前面分析代码的时候知道会做修复包的签名验证。这里得到了一个修复包文件如下:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/83f22fe47e7f91e0cc182dfc4e4e9fde.png"></p> <p>而且会产生一个diff.dex文件和smali文件夹,而我们用压缩软件可以打开apatch文件看看:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/2b442f2515dd9a1698ae7a345ba3aea3.png"></p> <p>可以看到这里的classes.dex文件其实就是上面的diff.dex文件,只是这里更像是Android中的apk文件目录格式,同样有一个META-INF目录,这里存放了签名文件以及需要修复类信息的PATCH.MF文件:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/f033f3b0b7f20fcad62de24d5274faaa.png"></p> <p>至此,Andfix框架已基本分析完毕。</p> <p> </p> <p>来自:http://blog.csdn.net/u012124438/article/details/64623253</p> <p> </p>