美文网首页android技术热更新
Android热更新 — Andfix

Android热更新 — Andfix

作者: Geekholt | 来源:发表于2020-05-26 21:45 被阅读0次

    如需转载请评论或简信,并注明出处,未经允许不得转载

    写在前面

    最近公司有一个新项目,在开始项目之前需要调研一些热更新框架,很早之前就听说过Andfix,但是一直没用过。一开始看了Andfix的代码提交记录,最近一次提交已经是2016年了,所以当时基本就打算放弃使用Andfix了,但是当时还是本着多了解一下的想法,跟着官方文档的步骤写了一个Demo。结果在我自己Android8.0的机子上怎么都无法修复bug,后来仔细看了一下官方文档,才发现Andfix最多只能支持Android7.0。之前已经知道他很久没更新,但并不知道仅仅只支持到了Android7.0,这么看来Andfix是真的已经完全被阿里放弃了
    我的习惯是边写Demo边写文章,所以最终文章已经写完了,才发现这么大一个坑。这篇文章大家就不用往下看了(如果对热更新完全没有了解的小白也可以跟着步骤简单走个流程,感受一下热更新的过程,毕竟Andfix还是算非常简单的热更新框架,在Android7.0以下的模拟器上还是可以运行哒)。最终还是发出了这篇文章,也算给自己长个教训,以后玩框架之前一定要仔细看官网文档!!!

    目录

    官网

    https://github.com/alibaba/AndFix

    版本更新状况

    Latest commit0351a4bon 26 Dec 2016

    重要:AndFix支持Android版本从2.3到7.0,ARM和X86体系结构,Dalvik和ART运行时(32位和64位)

    使用场景

    这里借助官方中的一张图,Andfix使用场景比较有限,只能用于方法替换,不能进行类替换。主要原理是通过方法替换,使得有bug的代码不能被执行到

    主要原理

    AndFix judges the methods should be replaced by java custom annotation and replaces it by hooking it. AndFix has a native method art_replaceMethod in ART or dalvik_replaceMethod in Dalvik.

    比较前后两个apk包的不同,将存在不同的方法打上自定义注解,通过注解在运行时判断哪个方法需要被替换,这个是通过native方法进行替换的,所以从这个角度来说,Andfix的兼容性并不强。比如从过去的Dalvik变成ART,以后也有可能有新的虚拟机,就需要针对新的虚拟机运行时机制进行适配

    集成步骤

    本文Demo github地址:https://github.com/Geekholt/Andfix

    初始化Andfix

    1. 添加依赖
    dependencies {
        api 'com.alipay.euler:andfix:0.5.0@aar'
    }
    
    1. 创建Andfix管理类,主要包括了初始化Andfix加载patch文件两个方法
    public class AndFixPatchManager {
    
        private static AndFixPatchManager mInstance = null;
    
        private static PatchManager mPatchManager = null;
    
        public static AndFixPatchManager getInstance() {
            if (mInstance == null) {
                synchronized (AndFixPatchManager.class) {
                    if (mInstance == null) {
                        mInstance = new AndFixPatchManager();
                    }
                }
            }
            return mInstance;
        }
    
        //初始化AndFix方法
        public void initPatch(Context context) {
            mPatchManager = new PatchManager(context);
            mPatchManager.init(Utils.getVersionName(context));
            mPatchManager.loadPatch();
        }
    
        //加载我们的patch文件
        public void addPatch(String path) {
            try {
                if (mPatchManager != null) {
                    mPatchManager.addPatch(path);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    1. Application中初始化Andfix
    public class App extends Application {
        @Override
        public void onCreate() {
            super.onCreate();
            initAndFix();
        }
    
        private void initAndFix() {
            AndFixPatchManager.getInstance().initPatch(this);
        }
    }
    
    1. 加载patch文件,这里我们主动通过一个点击事件来触发patch文件的加载,实际开发中可以设计自己的特定时机进行apatch文件的加载
    public void fixBug(View view) {
        AndFixPatchManager.getInstance().addPatch(getPatchName());
    }
    
    //构造patch文件名
    private String getPatchName() {
        return mPatchDir.concat("fixbug").concat(".apatch");
    }
    

    模拟Bug产生

    public class Utils {
        /**
         * 模拟产生bug方法
         */
        public static void printLog() {
            String error = null;
            //NullPointException
            Log.e("geekholt", error);
        }
    }
    

    使用./gradlew assembleRelease构建出一个带bug的apk,命名为app-release-bug.apk注意apk是需要签名的,因为后面构建apatch的时候会用到签名文件

    将apk通过adb install安装到手机上,验证该apk是存在bug的

    修复Bug

    public class Utils {
        /**
         * 模拟产生bug方法
         */
        public static void printLog() {
            String error = "Hello World!";
            Log.e("geekholt", error);
        }
    }
    

    修复bug后,同样构建出一个修复bug后的apk,命名为app-release-fixbug.apk

    生成apatch文件

    这里需要借助一个工具,可以到官网下载

    下载解压后会出现三个文件

    app-release-bug.apkapp-release-fixbug.apk、和apk签名文件sign.jks一起放到文件夹中,再创建一个output目录用于存放apkpatch文件,最终目录结构如下所示

    cd进入该文件目录,通过./apkpatch.sh查看如何使用

    ➜  apkpatch-1.0.3 ./apkpatch.sh
    ApkPatch v1.0.3 - a tool for build/merge Android Patch file (.apatch).
    Copyright 2015 supern lee <sanping.li@alipay.com>
    
    usage: apkpatch -f <new> -t <old> -o <output> -k <keystore> -p <***> -a <alias> -e <***>
     -a,--alias <alias>     alias.
     -e,--epassword <***>   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.
    
    usage: apkpatch -m <apatch_path...> -k <keystore> -p <***> -a <alias> -e <***>
     -a,--alias <alias>     alias.
     -e,--epassword <***>   entry password.
     -k,--keystore <loc>    keystore path.
     -m,--merge <loc...>    path of .apatch files.
     -n,--name <name>       patch name.
     -o,--out <dir>         output dir.
     -p,--kpassword <***>   keystore password.
    

    使用命令构建apatch文件

    ./apkpatch.sh -f app-release-fixbug.apk -t app-release-bug.apk -o output/ -k sign.jks -p 12345678 -a sign_alias -e 12345678
    

    运行结果如下所示,提示我们printLog已经被修改,说明apatch文件构建成功。进入到output文件,可以看到apacth文件

    add modified Method:V  printLog()  in Class:Lcom/geekholt/andfix/Utils;
    

    安装apatch文件到手机

    在生产环境中,我们封装相应的网络请求,来将apatch安装到手机上的指定目录中。这里为了模拟方便,直接通过adb push命令把apatch文件安装到手机

    adb push /Users/geekholt/Desktop/apkpatch-1.0.3/output/app-release-fixbug-20b6ba47703502921f7649df39a7c5f7.apatch /storage/emulated/0/Android/data/com.geekholt.andfix/cache/apatch/fixbug.apatch
    

    点击修复bug,会执行patchManager.addPatch,完成bug修复

    生产环境下自动完成bug修复思路

    前面说的过程主要是对Andfix的能力进行一个测试,主要是通过adb push将patch安装到手机的指定目录中,再通过手动的方式加载apatch文件

    下面提供一个自动完成下载和安装apatch的思路,可以在MainActivity中启动AndFixService完成全过程

    /**
     * @author geekholt
     * @date 2020/5/27
     * @describe 修复bug服务,包括patch文件下载功能和patch文件加载功能
     */
    public class AndFixService extends Service {
        private static final String TAG = AndFixService.class.getSimpleName();
        private static final String FILE_END = ".apatch";
        private static final int UPDATE_PATCH = 0x02;
        private static final int DOWNLOAD_PATCH = 0x01;
    
        private BasePatch mBasePatchInfo;
        //patch文件存放文件夹路径
        private String mPatchFileDir;
        //patch文件真实路径,mPatchFile = mPatchFileDir + System.currentTimeMillis() + .apatch
        private String mPatchFile;
        private Handler mHandler = new AndFixHandler(this);
    
        private static class AndFixHandler extends Handler {
            private final WeakReference<AndFixService> mService;
    
            public AndFixHandler(AndFixService service) {
                mService = new WeakReference<>(service);
            }
    
            @Override
            public void handleMessage(@NonNull Message msg) {
                if (mService.get() == null) {
                    return;
                }
                switch (msg.what) {
                    case UPDATE_PATCH:
                        mService.get().checkPatchUpdate();
                        break;
                    case DOWNLOAD_PATCH:
                        mService.get().downloadPatch();
                        break;
                }
            }
        }
    
        @Nullable
        @Override
        public IBinder onBind(Intent intent) {
            return null;
        }
    
    
        @Override
        public void onCreate() {
            super.onCreate();
            init();
        }
    
        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            mHandler.sendEmptyMessage(UPDATE_PATCH);
            return START_NOT_STICKY;
        }
    
        /**
         * 完成文件目录的构造
         */
        private void init() {
            mPatchFileDir = getExternalCacheDir().getAbsolutePath() + "/apatch/";
            File patchDir = new File(mPatchFileDir);
    
            try {
                if (patchDir == null || !patchDir.exists()) {
                    patchDir.mkdir();
                }
            } catch (Exception e) {
                e.printStackTrace();
                stopSelf();
            }
        }
    
        /**
         * 检查服务器是否有patch文件
         */
        private void checkPatchUpdate() {
            RequestCenter.requestPatchUpdateInfo(new DisposeDataListener() {
                @Override
                public void onSuccess(Object responseObj) {
                    mBasePatchInfo = (BasePatch) responseObj;
                    ////不为空则表明有更新
                    if (!TextUtils.isEmpty(mBasePatchInfo.data.downloadUrl)) {
                        //下载patch文件
                        mHandler.sendEmptyMessage(DOWNLOAD_PATCH);
                    } else {
                        stopSelf();
                    }
                }
    
                @Override
                public void onFailure(Object reasonObj) {
                    stopSelf();
                }
            });
        }
    
        /**
         * 完成patch文件的下载后,自动addPatch
         */
        private void downloadPatch() {
            //初始化patch文件下载路径
            mPatchFile = mPatchFileDir.concat(String.valueOf(System.currentTimeMillis())).concat(FILE_END);
    
            RequestCenter.downloadFile(mBasePatchInfo.data.downloadUrl, mPatchFile,
                    new DisposeDownloadListener() {
                        @Override
                        public void onProgress(int progrss) {
                            Log.d(TAG, "current progedss: " + progrss);
                        }
    
                        @Override
                        public void onSuccess(Object responseObj) {
                            //将我们下载好的patch文件添加到我们的andfix中
                            AndFixPatchManager.getInstance().addPatch(mPatchFile);
                        }
    
                        @Override
                        public void onFailure(Object reasonObj) {
                            stopSelf();
                        }
                    });
        }
    }
    

    相关文章

      网友评论

        本文标题:Android热更新 — Andfix

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