Android 增量更新和升级

在年初的时候,尝试了一把热修复技术,当时选择的是阿里的andfix,使用起来也很简单,虽然网上将热修复的文章很多,不过我还是想说原理,然后配合代码,我想这样大家理解更加深刻。

原理

其实就是用ClassLoader加载机制,覆盖掉有问题的方法。我们知道一个ClassLoader可以包含多个dex文件,每个dex文件是一个Element,多个dex文件排列成一个有序的数组dexElements,当找类的时候,会按顺序遍历dex文件,然后从当前遍历的dex文件中找类,如果找类则返回。那么我们热修复的原理就是用新的dex去替换有问题的dex,这里借用qq团队的一张图,可能更方便的说明热修复的原理。
如果有想对ClassLoader做深入了解的同学,可以去看我之前一篇对ClassLoader的分析: 点击打开链接

热修复(打补丁)

打补丁:服务端通过新版本APK和旧版本APK生成patch补丁(也成为差分包),客户端更新的时候只需要下载差分包到本地,然后从system/app取出旧版本APK,通过差分包来合成新版本的APK,这个过程实际上就是打补丁。
打补丁的步骤:
拷贝资源拷贝旧版本APK以及新版本APK到SD卡。为了后面进行生成差分包
安装旧版本APK安装旧版本的APK
生成补丁生成差分包。这个实际上应该是在服务端完成
打补丁通过差分包及旧版本APK生成新版本APK
安装新版本APK安装生成的新版本APK
获取某个应用的APK安装文件在真正的增量更新过程中,旧版本Apk应该从/data/app底下获取,拷贝到SD卡,进行打补丁。当然,也可以不拷贝,直接使用该路径。
为了方便,我们就在本地放两个本地,为了方便讲解,我们先定义4个变量。
String srcDir = Environment.getExternalStorageDirectory().toString() + "/DaemonProcess-1.apk";
String destDir1 = Environment.getExternalStorageDirectory().toString() + "/DaemonProcess-2.apk";
String destDir2 = Environment.getExternalStorageDirectory().toString() + "/DaemonProcess-3.apk";
String patchDir = Environment.getExternalStorageDirectory().toString() + "/DaemonProcess.patch";

srcDir:旧版本apk路径。也就是已安装的旧版应用的APK地址。为了便于演示,这边直接写死路径。
实际开发中要想真正获取旧版apk地址,可通过如下代码获取:
[objc]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. String appDir = getPackageManager().getApplicationInfo(packageName, 0).sourceDir;  
destDir1:新版本的apk路径。
destDir2:新版本的apk路径。通过差分包+旧版本APK合成新版本APK。
patchDir:差分包。通过旧版本APK+新版本APK生成差分包。

这里生成差分包和合成新apk,用的是jni做的,代码如下:
生成差分包Native方法
[objc]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. public class DiffUtils {  
  2.   
  3.     static DiffUtils instance;  
  4.   
  5.     public static DiffUtils getInstance() {  
  6.         if (instance == null)  
  7.             instance = new DiffUtils();  
  8.         return instance;  
  9.     }  
  10.   
  11.     static {  
  12.         System.loadLibrary("ApkPatchLibrary");  
  13.     }  
  14.   
  15.     /** 
  16.      * native方法 比较路径为oldPath的apk与newPath的apk之间差异,并生成patch包,存储于patchPath 
  17.      */  
  18.     public native int genDiff(String oldApkPath, String newApkPath, String patchPath);  
  19. }  
合成新包Native方法:
[objc]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. public class PatchUtils {  
  2.   
  3.     static PatchUtils instance;  
  4.   
  5.     public static PatchUtils getInstance() {  
  6.         if (instance == null)  
  7.             instance = new PatchUtils();  
  8.         return instance;  
  9.     }  
  10.   
  11.     static {  
  12.         System.loadLibrary("ApkPatchLibrary");  
  13.     }  
  14.   
  15.     /** 
  16.      * native方法 使用路径为oldApkPath的apk与路径为patchPath的补丁包,合成新的apk,并存储于newApkPath 
  17.      */  
  18.     public native int patch(String oldApkPath, String newApkPath, String patchPath);  
  19. }  

这里用到了一个ndk,如果有需要了解如何生成动态库文件的可以访问下面的 点击打开链接


热补丁修复步骤:
1,从服务端加载查分包文件(我们这里模拟下,将差分包放到assert文件下)
[objc]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. private class CopyTask extends AsyncTask<String, Void, Integer> {  
  2.   
  3.         @Override  
  4.         protected Integer doInBackground(String... params) {  
  5.   
  6.             for (int i = 0; i < params.length; i += 2) {  
  7.                 try {  
  8.                     File file = new File(params[i]);  
  9.                     if (!file.exists())  
  10.                         FileUtils.createFile(file);  
  11.   
  12.                     InputStream is;  
  13.                     OutputStream os = new FileOutputStream(params[i]);  
  14.                     is = getAssets().open(params[i + 1]);  
  15.                     byte[] buffer = new byte[1024];  
  16.                     int length = is.read(buffer);  
  17.                     while (length > 0) {  
  18.                         os.write(buffer, 0, length);  
  19.                         length = is.read(buffer);  
  20.                     }  
  21.                     os.flush();  
  22.                     is.close();  
  23.                     os.close();  
  24.                 } catch (Exception e) {  
  25.                     handler.obtainMessage(1).sendToTarget();  
  26.                     return null;  
  27.                 }  
  28.             }  
  29.             handler.obtainMessage(0).sendToTarget();  
  30.             return null;  
  31.         }  
  32.   
  33.         @Override  
  34.         protected void onPostExecute(Integer integer) {  
  35.             super.onPostExecute(integer);  
  36.             loadding.setVisibility(View.GONE);  
  37.         }  
  38.     }  
2,合成新的apk(jni会自动判断是否合成成功)
[objc]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. private class PatchTask extends AsyncTask<String, Void, Integer> {  
  2.   
  3.         @Override  
  4.         protected Integer doInBackground(String... params) {  
  5.   
  6.             try {  
  7.   
  8.                 int result = PatchUtils.getInstance().patch(srcDir, destDir2, patchDir);  
  9.                 if (result == 0) {  
  10.                     handler.obtainMessage(4).sendToTarget();  
  11.                     return WHAT_SUCCESS;  
  12.                 } else {  
  13.                     handler.obtainMessage(5).sendToTarget();  
  14.                     return WHAT_FAIL_PATCH;  
  15.                 }  
  16.             } catch (Exception e) {  
  17.                 e.printStackTrace();  
  18.             }  
  19.             return WHAT_FAIL_PATCH;  
  20.         }  
  21.   
  22.         @Override  
  23.         protected void onPostExecute(Integer integer) {  
  24.             super.onPostExecute(integer);  
  25.             loadding.setVisibility(View.GONE);  
  26.         }  
  27.     }  
3,安装新的apk
[objc]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. private void install(String dir) {  
  2.       String command = "chmod 777 " + dir;  
  3.       Runtime runtime = Runtime.getRuntime();  
  4.       try {  
  5.           runtime.exec(command); // 可执行权限  
  6.       } catch (IOException e) {  
  7.           e.printStackTrace();  
  8.       }  
  9.   
  10.       Intent intent = new Intent(Intent.ACTION_VIEW);  
  11.       intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);  
  12.       intent.setDataAndType(Uri.parse("file://" + dir), "application/vnd.android.package-archive");  
  13.       startActivity(intent);  
  14.   }  

服务端工具以及源码位于Server目录下。目前只在Linux64位的系统下编译,其他系统大家可自行编译。Linux下的可直接修改makefile,windows下可用VC编译。

Diff工具:生成差分包
<!--命令             oldApk              newApk              patch-->
./linux-x86_64/Diff DaemonProcess-1.apk DaemonProcess-2.apk dp.patch
Patch工具:合并
<!--命令              oldApk              newApk              patch-->
./linux-x86_64/Patch DaemonProcess-1.apk DaemonProcess-3.apk dp.patch
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值