美文网首页
Android热修复

Android热修复

作者: 青果果 | 来源:发表于2018-03-28 16:38 被阅读0次

    热修复对比

    本文参考https://www.jianshu.com/p/5f390be47ce8

    阿里开源地址AndFix:https://github.com/alibaba/AndFix
    腾讯开源地址tinker:https://github.com/Tencent/tinker

    阿里的AndFix、美团的Robust以及QZone的超级补丁方案

    Tinker是微信官方的Android热补丁解决方案,它支持动态下发代码、So库以及资源,让应用能够在不需要重新安装的情况下实现更新

    阿里AndFix

    AndFix作为native解决方案,首先面临的是稳定性与兼容性问题,更重要的是它无法实现类替换,它是需要大量额外的开发成本的

    缺点:

    只能修改方法

    1. 不能增加方法
    2. 不能增加类
    3. 不能增加资源
      使用方法就不介绍了,直接去GitHub地址上按照上面的方式接入,很简单几个步骤

    apatch生成方式:

    1. 下载这个工具 apkpatch-1.0.3
    2. apkpatch -f <new> -t <old> -o <output> -k <keystore> -p <> -a <alias> -e <>
           -a,--alias <alias>     keystore entry alias.
           -e,--epassword <***>   keystore entry password.
           -f,--from <loc>        new Apk file path.
           -k,--keystore <loc>    keystore path.
           -n,--name <name>       patch name.
           -o,--out <dir>         output dir.
           -p,--kpassword <***>   keystore password.
           -t,--to <loc>          old Apk file path.
    

    比如 我的demo的命令:
    apkpatch.bat -f fix.apk -t old.apk -o out -k myjoke.jks -p 123456 -a key0 -e 123456

    原理:新老apk生成apatch 插分包 ,启动加载apatch包,替换有BUG的方法
    底层机制:

    1. 在方法上面加注解
    @MethodReplace(clazz="com.qingguoguo.connotationjoke.MainActivity", method="onClick")
      public void onClick(View paramView)
      {
        Log.i(MainActivity.TAG, "修复后");
        ToastUtils.showShort("修复后:" + 1);
      }
    
    1. 修复方法
    static void replaceMethod(JNIEnv* env, jclass clazz, jobject src,       jobject dest) {
        if (isArt) {        
                  art_replaceMethod(env, src, dest);    } 
            else {      
                    dalvik_replaceMethod(env, src, dest);   }
    }
    
    1. 4.4版本以后 isArt art架构 4.4版本前期dalvik架构
    extern void __attribute__ ((visibility ("hidden"))) dalvik_replaceMethod(
    JNIEnv* env, jobject src, jobject dest) {
              jobject clazz = env->CallObjectMethod(dest, jClassMethod);
    ClassObject* clz = (ClassObject*) dvmDecodeIndirectRef_fnPtr(
    dvmThreadSelf_fnPtr(), clazz);
    clz->status = CLASS_INITIALIZED;
    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;
    }
    
    

    AndFix使用过程中注意事项

    1. 每次生成差分包后一定要测试;
    2. 尽量的不要分包,不要分多个dex
    3. 涉及到NDK AndFix.java 不能混淆
    4. 在加固,加壳之前生成差分包。
    5. 只能修复方法,不能增加成员变量,不能增加方法

    仿tinker机制的热修复

    底层原理是,通过替换dex文件的dexElements
    通过Activity类的加载流程浅析,来引入

    public final class ActivityThread {
     java.lang.ClassLoader cl = appContext.getClassLoader();
                activity = mInstrumentation.newActivity(
                        cl, component.getClassName(), r.intent);
                StrictMode.incrementExpectedActivityCount(activity.getClass());
                r.intent.setExtrasClassLoader(cl);
                r.intent.prepareToEnterProcess();
                if (r.state != null) {
                    r.state.setClassLoader(cl);
                }
      }
    
    public Activity newActivity(ClassLoader cl, String className,    Intent intent)         
                               throws InstantiationException, IllegalAccessException,         
                                         ClassNotFoundException {  
          // 通过classLoader找到activity的calss,利用反射实例化对象  TestActivity       
             return (Activity)cl.loadClass(className).newInstance();
    }
    

    那看看这个loadClass()方法怎么来的
    点进去后发现来到ClassLoader 类,而ClassLoader是一个抽象类


    ClassLoader.png

    那就看看它的上面newActivity传进来的是什么ClassLoader

    public abstract class ClassLoader {
    ...
     public Class<?> loadClass(String name) throws ClassNotFoundException {
            return loadClass(name, false);
        }
    ...
    }
    
     //上面传进newActivity的是
     java.lang.ClassLoader cl = appContext.getClassLoader();
    

    是来自ContextImpl 类的方法返回的是 ClassLoader.getSystemClassLoader()
    继续看源码

    class ContextImpl extends Context {
    ...
        @Override
        public ClassLoader getClassLoader() {
            return mClassLoader != null ? mClassLoader :
             (mPackageInfo != null ? mPackageInfo.getClassLoader() :
                         ClassLoader.getSystemClassLoader());
            }
    ...}
    
    public abstract class ClassLoader {
         static private class SystemClassLoader {
            public static ClassLoader loader = ClassLoader.createSystemClassLoader();
        }
    ...
     @CallerSensitive
        public static ClassLoader getSystemClassLoader() {
            return SystemClassLoader.loader;
        }
    ...
    /**
         * Encapsulates the set of parallel capable loader types.
         */
        private static ClassLoader createSystemClassLoader() {
            String classPath = System.getProperty("java.class.path", ".");
            String librarySearchPath = System.getProperty("java.library.path", "");
    
            // String[] paths = classPath.split(":");
            // URL[] urls = new URL[paths.length];
            // for (int i = 0; i < paths.length; i++) {
            // try {
            // urls[i] = new URL("file://" + paths[i]);
            // }
            // catch (Exception ex) {
            // ex.printStackTrace();
            // }
            // }
            //
            // return new java.net.URLClassLoader(urls, null);
    
            // TODO Make this a java.net.URLClassLoader once we have those?
            return new PathClassLoader(classPath, librarySearchPath, BootClassLoader.getInstance());
        }
    ...
    }
    

    找到了最后还是ClassLoader类创建了PathClassLoader
    说明最后传进newActivity()方法的是PathClassLoader的对象
    类的关系是:
    PathClassLoader---->BaseDexClassLoader --->ClassLoader
    loadClass()方法在ClassLoader类中

    ClassLoader类中loadClass()方法,最后调用了BaseDexClassLoader()中的findClass()方法
    实际是通过BaseDexClassLoader类的属性pathList来加载类

    protected Class<?> loadClass(String name, boolean resolve)
    359        throws ClassNotFoundException
    360    {
    361            // First, check if the class has already been loaded
    362            Class c = findLoadedClass(name);
    363            if (c == null) {
    364                long t0 = System.nanoTime();
    365                try {
    366                    if (parent != null) {
    367                        c = parent.loadClass(name, false);
    368                    } else {
    369                        c = findBootstrapClassOrNull(name);
    370                    }
    371                } catch (ClassNotFoundException e) {
    372                    // ClassNotFoundException thrown if class not found
    373                    // from the non-null parent class loader
    374                }
    375
    376                if (c == null) {
    377                    // If still not found, then invoke findClass in order
    378                    // to find the class.
    379                    long t1 = System.nanoTime();
    380                    c = findClass(name);
    381
    382                    // this is the defining class loader; record the stats
    383                }
    384            }
    385            return c;
    386    }
    
    public class BaseDexClassLoader extends ClassLoader {
    30    private final DexPathList pathList;
    31
    32    /**
    33     * Constructs an instance.
    34     *
    35     * @param dexPath the list of jar/apk files containing classes and
    36     * resources, delimited by {@code File.pathSeparator}, which
    37     * defaults to {@code ":"} on Android
    38     * @param optimizedDirectory directory where optimized dex files
    39     * should be written; may be {@code null}
    40     * @param librarySearchPath the list of directories containing native
    41     * libraries, delimited by {@code File.pathSeparator}; may be
    42     * {@code null}
    43     * @param parent the parent class loader
    44     */
    45    public BaseDexClassLoader(String dexPath, File optimizedDirectory,
    46            String librarySearchPath, ClassLoader parent) {
    47        super(parent);
    48        this.pathList = new DexPathList(this, dexPath, librarySearchPath, optimizedDirectory);
    49    }
    50
    51    @Override
    52    protected Class<?> findClass(String name) throws ClassNotFoundException {
    53        List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
    54        Class c = pathList.findClass(name, suppressedExceptions);
    55        if (c == null) {
    56            ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList);
    57            for (Throwable t : suppressedExceptions) {
    58                cnfe.addSuppressed(t);
    59            }
    60            throw cnfe;
    61        }
    62        return c;
    63    }
    64
    
    Class c = pathList.findClass(name, suppressedExceptions);
    

    DexPathList类中的属性dexElements是用来保存dex的数组
    每个dex文件其实就是DexFile对象
    遍历dexElements,然后通过DexFile去加载class文件
    加载成功就返回,否则返回null,

     */
    50/*package*/ final class DexPathList {
    51    private static final String DEX_SUFFIX = ".dex";
    52    private static final String zipSeparator = "!/";
    55    private final ClassLoader definingContext;
    62    private Element[] dexElements;
    65    private final Element[] nativeLibraryPathElements;
    68    private final List<File> nativeLibraryDirectories;
    69
    71    private final List<File> systemNativeLibraryDirectories;
    76    private IOException[] dexElementsSuppressedExceptions;
          public Class findClass(String name, List<Throwable> suppressed) {
    414        for (Element element : dexElements) {
    415            DexFile dex = element.dexFile;
    416
    417            if (dex != null) {
    418                Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
    419                if (clazz != null) {
    420                    return clazz;
    421                }
    422            }
    423        }
    424        if (dexElementsSuppressedExceptions != null) {
    425            suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
    426        }
    427        return null;
    428    }
    429
    
    

    看到了吗?

         if (dex != null) {
                   Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
                  if (clazz != null) {
                      return clazz;
                  }
          }
    

    类的加载最后是同过dex.loadClassBinaryName
    而dex是来自于dexElements

    顺着来说,也就是类的加载,是先从类加载器的PathList的dexElements属性中,获取dex文件
    然后遍历出要找的类,找到就返回,找不到就返回null

    那我们就可以在这里做手脚了,理一下思路,怎么操作
    从服务器获取修复bug后的fix.dex文件
    加载dexElements并且把它插入的正在运行的类加载器的dexElements最前面
    这样findClass遍历的时候就会先找到没有bug的class !!!

    //说到这里就在多扯几句话吧,类加载器不仅可用系统的,还可以自定义
    //loadClass中的判断  if (c == null)  就用 c = findClass(name);
    //类加载双亲委托模式 父加载器为null就走 c = findClass(name),而我们就可以重写findClass方法来实现
    //自定义类加载器
    //自定义类加载器的作用:
    (1)加密:Java代码可以轻易的被反编译,如果你需要把自己的代码进行加密以防止反编译,
         可以先将编译后的代码用某种加密算法加密,类加密后就不能再用Java的ClassLoader去加载类了,
         这时就需要自定义ClassLoader在加载类的时候先解密类,然后再加载
    (2)从非标准的来源加载代码:如果你的字节码是放在数据库、甚至是在云端,就可以自定义类加载器,
        从指定的来源加载类。
     
    双亲委派模型的好处:
    (1)主要是为了安全性,避免用户自己编写的类动态替换 Java的一些核心类,比如 String。
    (2)同时也避免了类的重复加载,因为 JVM中区分不同类,不仅仅是根据类名,
         相同的 class文件被不同的 ClassLoader加载就是不同的两个类。
    
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
           Class c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                      if (parent != null) {
                         c = parent.loadClass(name, false);
                    } else {
                         c = findBootstrapClassOrNull(name);
                    }
               } catch (ClassNotFoundException e) {
                   }
                  if (c == null) {
                      long t1 = System.nanoTime();
                   c = findClass(name);
                   }
              }
            return c;
      }
    

    原理大概就是这样,代码,文章最前面的链接里面有
    也贴一个我自己写的GitHub地址吧
    https://github.com/qingguoguo/ConnotationJoke

    相关文章

      网友评论

          本文标题:Android热修复

          本文链接:https://www.haomeiwen.com/subject/arrycftx.html