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

bmnk0073 7年前
   <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>