美文网首页
AndFix Alibaba开源项目使用及基本原理

AndFix Alibaba开源项目使用及基本原理

作者: BigBigArvin | 来源:发表于2017-05-11 13:55 被阅读62次

    热修复

    随着移动互联网的快速发展,用户对app的品质要求也越来越高,对于app来说如果有bug影响到用户体验,那对于用户和产品的伤害就比较大,所以必须快速的解决bug,但是移动app版本升级又是一个绕不过去的坎,你必须在应用市场上重新发布,用户更新后才行,这过程耗费时间很久成本比较大,而且频繁的升级对于用户是很大的干扰,因此越来越多的app开始使用热更新技术,这样就不需要下载全部app,只需要下载补丁文件即可。而目前比较成熟的热修复框架有1. 阿里AndFix 2.以QQ空间超级补丁技术为基础的(传送)但是其本身的方案目前并没有开源出来,3.微信Tinker,而结合我们自己项目的需求最后选择了ali的AndFix

    AndFix
    首先androidFix支持版本从2.3到7.0, ARM and X86 架构都, Dalvik and ART runtime, 32bit and 64bit也都支持.AndFix不同于QQ空间超级补丁技术和微信Tinker通过增加或替换整个DEX的方案,提供了一种运行时在Native修改Filed指针的方式,实现方法的替换,达到即时生效无需重启,对应用无性能消耗的目的。
    接下来通过例子来说明下其原理和使用。
    其大概原理如官方提供原理图如下:

    20170120114439713.png

    上面大致意思就是
    1.app初始化时加载当前版本下的所有apatch文件。
    2.通过注解获取到补丁文件的补丁方法。
    3.通过反射获取对应的bugMethd.
    4.通过c++层,拿到库文件句柄,并得到对应文件的class对象,得到新旧方法的指针,最后将新方法指针指向目标方。

    接下来看看基本的使用方法:

    1. 添加依赖
    dependencies {
        compile fileTree(dir: 'libs', include: ['*.jar'])
        testCompile 'junit:junit:4.12'
        compile 'com.android.support:appcompat-v7:24.2.0'
        compile 'com.alipay.euler:andfix:0.5.0@aar'
    }
    

    2.如果要混淆则

    -keep class * extends java.lang.annotation.Annotation
    -keepclasseswithmembernames class * {
        native <methods>;
    }
    

    3.代码中的初始化
    一般初始化最好是放在application中:

        public static PatchManager patchManager;
        @Override
        public void onCreate() {
            super.onCreate();
            patchManager = new PatchManager(this);
            PackageManager pm = getPackageManager();
            PackageInfo pi = null;
            try {
                pi = pm.getPackageInfo(getPackageName(), 0);
            } catch (PackageManager.NameNotFoundException e) {
            }
            String versionName = pi.versionName;
            patchManager.init(versionName);//current version
            patchManager.loadPatch();
        }
    

    首先调用patchManager. init()方法初始化
    这个方法主要干了些什么可以看下其源码

    //首先判断存放apatch文件夹是否存在
        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;
            }
            //获取当前补丁的版本 ,如果补丁版本和当前存在的版本一样 就删除当前补丁文件
            //其它的就初始化patch文件,把所有的以.apatch的放入 SortedSet<Patch> mPatchs中,并且会复制到data/你的应用包名下去
            SharedPreferences sp = mContext.getSharedPreferences(SP_NAME,
                    Context.MODE_PRIVATE);
            String ver = sp.getString(SP_VERSION, null);
            if (ver == null || !ver.equalsIgnoreCase(appVersion)) {
                cleanPatch();
                sp.edit().putString(SP_VERSION, appVersion).commit();
            } else {
                initPatchs();
            }
    
        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 = new Patch(file);
                    mPatchs.add(patch);
                } catch (IOException e) {
                    Log.e(TAG, "addPatch", e);
                }
            }
            return patch;
        }
    

    接下来调用 patchManager.loadPatch();
    加载补丁的相关信息

    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) {
                   //获取到存在bug的对象类的名字
                    classes = patch.getClasses(patchName);
                    mAndFixManager.fix(patch.getFile(), mContext.getClassLoader(),
                            classes);
                }
            }
        }
    

    接下来调用fix方法去修复

     public synchronized void fix(File file, ClassLoader classLoader,
                List<String> classes) {
                //检查是否支持热更新
            if (!mSupport) {
                return;
            }
    
            //检测补丁文件的签名是否合法
            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
                    //和本地SharedPreferences存的的md5值进行校验
                    if (mSecurityChecker.verifyOpt(optfile)) {
                        saveFingerprint = false;
                    } else if (!optfile.delete()) {
                        return;
                    }
                }
                //加载dev文件
                final DexFile dexFile = DexFile.loadDex(file.getAbsolutePath(),
                        optfile.getAbsolutePath(), Context.MODE_PRIVATE);
    
                if (saveFingerprint) {
                    mSecurityChecker.saveOptSig(optfile);
                }
    
                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
                    }
                    clazz = dexFile.loadClass(entry, patchClassLoader);
                    if (clazz != null) {
                        fixClass(clazz, classLoader);
                    }
                }
            } catch (IOException e) {
                Log.e(TAG, "pacth", e);
            }
        }
    通过      methodReplace = method.getAnnotation(MethodReplace.class);
    注解拿到对应的补丁的方法相关信息,
    通过反射拿到待修复的class的信息
        Class<?> clazz = mFixedClass.get(key);
                if (clazz == null) {// class not load
                    Class<?> clzz = classLoader.loadClass(clz);
                    // initialize target class
                    clazz = AndFix.initTargetClass(clzz);
                }
    最后通过调用Native 方法去替换对应方法的指针
    AndFix.addReplaceMethod(src, method);
    

    而创建补丁文件,官方专门提供了一个工具apkpatch(下载)生成.apatch补丁文件
    解压后

    20170120145819284.png

    就是这些文件,通过cmd命令定位到该目录下,并且把存在bug的apk文件和修复bug的文件apk放在该文件根目录下,并且把对应的key文件也放进来
    如下:

    20170120150005990.png

    调用 下面命令:
    apkpatch.bat -f xxxx.apk -t xxxx.apk -o output -k xxxx.jks -p andfix -a andfix -e andfix

    apkpatch.bat -f 新apk -t 旧apk -o 输出目录 -k app签名文件 -p 签名文件密码 -a 签名文件别名 -e 别名密码
    最后文件生成成功后,命令行会打印出对应修改的class的文件信息
    生成文件如下:

    20170120150214024.png

    最后说几点实际使用中的问题
    1.如果之前版本存在bug,并且添加过对应的bug补丁的,再次升级的需要把bug修复,此时需要将init中对应的版本号也需要升级,不然会加载之前存放在data中的补丁文件。
    2.一个补丁文件可以修复多个bug。
    3.如果一个版本第一次加载了补丁,第二次修改另外一个bug,补丁任然是相同的名字,那么此次新添加的补丁将无效就需要删除之前的补丁。但是可以同时存在多个不一样名字的补丁,并且加载按照时间顺序来加载。

    总的来说AndFix 使用比较简单, BUG修复的即时性 ,不用重启,补丁包同样采用diff技术,生成的PATCH体积小 ,对应用无侵入,几乎无性能损耗。但是AndFix 不支持新增字段,也不支持对资源的替换。 由于厂商的自定义ROM,对少数机型暂不支持。但是总的来说满足了目前大部分需求。

    相关文章

      网友评论

          本文标题:AndFix Alibaba开源项目使用及基本原理

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