今天心血来潮,写一个最近在探究的热更新。网上有很多资料和教程,github也有阿里官方的介绍(官方库),但是每个人遇到的情况不同 ,可能会出现各种问题,这里我就以我的情况记录一下,以备自己和大家参考借鉴。
首先描述一下我的环境:
电脑:mac os x 10.11.3
安卓开发工具:android studio 2.2.2。
测试手机:readmi3
android版本:5.1.1 MIUI 8.0.1
首先,我也是查阅了很多资料和blog,了解了热修复的概念和基本实现原理。我就简单介绍一下热修复,所谓热修复,就如同AS的instant run ,可以在不重新安装apk的情况下,更改代码,实现代码更新。实现方式有很多种,市面上以阿里巴巴andfix和腾讯Tinker的比较出名。我这里亲测了andfix,使用比较简单,效果也出来了,只不过有一定限制,只能修改(增删改)类中的方法,对资源文件及其他文件无法修改。不过我相信虽然有此限制,但是阿里的团队都在用,说明是好用的。至于如何能做到够用,我猜测是使用预测类和方法来做到热更新。
下面就开始介绍我的试验流程
1 下载官方的demo,直接提取其中的导入sample文件夹中的AndFixDemo至AS,我是以eclipse的类型导入的,然后就自动转成了AS工程结构。
2 引入andfix库,两种方式,第一种直接gradle引入,但是无法修改源码,会有个问题,之后会提到
dependencies {
compile 'com.alipay.euler:andfix:0.5.0@aar'
}
第二种就是引入第三方库
这种方式分两步,1 引入Andfix库,这个官方给的不是很好,我就参考了别的大神给的,虽然
AndFix
类中有个小bug ,修复后如下:
Runtime.getRuntime().loadLibrary("andfix");
当然载入的是so库,2 就是要在jniLibs中添加这个so文件,我为了减小apk大小 或者是偷懒,只在armeabi中添加了libandfix.so
我最后选择了第二种方式,因为如果使用第一种,那么PatchManager 的
public void addPatch(String path) throws IOException {
默认就会只更新相同名称的apatch一次,也就不能很好地实现多次fix,当然,如果每次名称不一样,那就还是会加载两个文件。看了PatchManager源码知道:
private static final String DIR = "apatch";//补丁文件夹
mPatchDir = new File(mContext.getFilesDir(), DIR);
andfix会把add的patch文件都复制到mPatchDir中,但是如果名字相同就不会再次去重复add,也就是说如果要保证多次fix,需要保证每次add的apatch文件名不同。FileUtil.copyFile(src, dest);// copy to patch's directory
所以我就参考大神的做法,把PatchManager中的addPatch方法修改成:
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 [" + src.getName() + "] has be loaded."); boolean deleteResult = dest.delete(); if (deleteResult) Log.e(TAG, "patch [" + dest.getPath() + "] has be delete."); else { Log.e(TAG, "patch [" + dest.getPath() + "] delete error"); return; } } FileUtil.copyFile(src, dest);// copy to patch's directory Patch patch = addPatch(dest); if (patch != null) { loadPatch(patch); } }基本思路是:每次addPatch的文件如果存在,就会先删除这个文件,然后重新add进去,这样就保证了每次修复文件的名称可以一样。
当然还有一步,为了减少不必要存储,我们把
File f = new File(this.getFilesDir(), DIR + APATCH_PATH); if (f.exists()) { boolean result = new File(patchFileString).delete(); if (!result) Log.e(TAG, patchFileString + " delete fail"); }从sd卡中push或下载的文件删除,代码如下
// .apatch file path mPatchManager.addPatch(patchFileString); Log.d(TAG, "apatch:" + patchFileString + " added."); //这里我加了个方法,复制加载补丁成功后,删除sdcard的补丁,避免每次进入程序都重新加载一次 File f = new File(this.getFilesDir(), DIR + APATCH_PATH); if (f.exists()) { boolean result = new File(patchFileString).delete(); if (!result) Log.e(TAG, patchFileString + " delete fail"); }
3 接下来就是在MainApplication中初始化andfix和loadPatch,addPatch.
public void onCreate() { super.onCreate(); String patchFileString = Environment.getExternalStorageDirectory() .getAbsolutePath() + APATCH_PATH; // initialize mPatchManager = new PatchManager(this); mPatchManager.init(BuildConfig.VERSION_NAME); Log.d(TAG, "inited."); // load patch mPatchManager.loadPatch(); Log.d(TAG, "apatch loaded."); // add patch at runtime try { // .apatch file path mPatchManager.addPatch(patchFileString); Log.d(TAG, "apatch:" + patchFileString + " added."); //这里我加了个方法,复制加载补丁成功后,删除sdcard的补丁,避免每次进入程序都重新加载一次 File f = new File(this.getFilesDir(), DIR + APATCH_PATH); if (f.exists()) { boolean result = new File(patchFileString).delete(); if (!result) Log.e(TAG, patchFileString + " delete fail"); } } catch (IOException e) { Log.e(TAG, "", e); } }4 现在可以在MainActivity实现热修复操作了。为了直观,我没有使用log的方式观察结果,我用了toast方式,在oncreate方法中toast一个当前时间,然后生成签名apk,作为old.apk 安装在手机上,然后修改toast的时间为当前时间,再次生成签名apk,作为new.apk.统统放在了桌面。
然后我们要把两个apk的不同通过工具生成apatch文件,我们就需要工具了apkpatch,这是官方提供的。怎么用呢?
首先我把下载的文件夹解压放在了桌面,右键文件在此文件夹下打开终端,不知道这个的可以百度。然后输入
./apkpatch.sh -f /Users/guolinyao/Desktop/new.apk -t /Users/guolinyao/Desktop/old.apk -o /Users/guolinyao/Desktop/ -k /Users/guolinyao/Documents/高顿实习平台/Hishixi签名文件/hishixi.keystore -p hishixi -a haishixi -e hishixi
当然我们的路径是不一样的:关于写法 规则在这里:
usage: 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.
生成了patch文件
我直接改成了out.apatch因为在初始化时,addpatch方法传的也是这个文件名,要对应!!!
接下来
把out.apatch放入scared
adb push out.apatch的文件路径 sdcard/ 这个也不能错!
可以通过adb命令adb shell >cd sdcard > ls 查看是否存在该文件
好了,现在再次打开刚安装的app,你就会发现toast的时间变了。
最常规的过程讲完了,现在开始讲讲几个小心得。首先,关于加固,我是加固应用的,同样可以使用andfix但是需要在加固前apk制作apatch文件;关于版本更新,andfix默认在版本更新时会删除所有apatch文件;关于混淆,官方说明了;关于patch文件如何分发,我是想在服务端给一个标志是否下载patch文件,每次进入app判断,大家有好的建议希望能够一起分享哦!