Android 增量更新和升级
在年初的时候,尝试了一把热修复技术,当时选择的是阿里的andfix,使用起来也很简单,这里就不在多少,如果你对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卡,进行打补丁。当然,也可以不拷贝,直接使用该路径。 |
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地址,可通过如下代码获取:
String appDir = getPackageManager().getApplicationInfo(packageName, 0).sourceDir;
destDir1:新版本的apk路径。 destDir2:新版本的apk路径。通过差分包+旧版本APK合成新版本APK。 patchDir:差分包。通过旧版本APK+新版本APK生成差分包。
这里生成差分包和合成新apk,用的是jni做的,代码如下:
生成差分包Native方法
public class DiffUtils {
static DiffUtils instance;
public static DiffUtils getInstance() {
if (instance == null)
instance = new DiffUtils();
return instance;
}
static {
System.loadLibrary("ApkPatchLibrary");
}
/**
* native方法 比较路径为oldPath的apk与newPath的apk之间差异,并生成patch包,存储于patchPath
*/
public native int genDiff(String oldApkPath, String newApkPath, String patchPath);
}
合成新包Native方法:
public class PatchUtils {
static PatchUtils instance;
public static PatchUtils getInstance() {
if (instance == null)
instance = new PatchUtils();
return instance;
}
static {
System.loadLibrary("ApkPatchLibrary");
}
/**
* native方法 使用路径为oldApkPath的apk与路径为patchPath的补丁包,合成新的apk,并存储于newApkPath
*/
public native int patch(String oldApkPath, String newApkPath, String patchPath);
}
这里用到了一个ndk,如果有需要了解如何生成动态库文件的可以访问下面的点击打开链接
热补丁修复步骤:
1,从服务端加载查分包文件(我们这里模拟下,将差分包放到assert文件下)
private class CopyTask extends AsyncTask<String, Void, Integer> {
@Override
protected Integer doInBackground(String... params) {
for (int i = 0; i < params.length; i += 2) {
try {
File file = new File(params[i]);
if (!file.exists())
FileUtils.createFile(file);
InputStream is;
OutputStream os = new FileOutputStream(params[i]);
is = getAssets().open(params[i + 1]);
byte[] buffer = new byte[1024];
int length = is.read(buffer);
while (length > 0) {
os.write(buffer, 0, length);
length = is.read(buffer);
}
os.flush();
is.close();
os.close();
} catch (Exception e) {
handler.obtainMessage(1).sendToTarget();
return null;
}
}
handler.obtainMessage(0).sendToTarget();
return null;
}
@Override
protected void onPostExecute(Integer integer) {
super.onPostExecute(integer);
loadding.setVisibility(View.GONE);
}
}
2,合成新的apk(jni会自动判断是否合成成功)
private class PatchTask extends AsyncTask<String, Void, Integer> {
@Override
protected Integer doInBackground(String... params) {
try {
int result = PatchUtils.getInstance().patch(srcDir, destDir2, patchDir);
if (result == 0) {
handler.obtainMessage(4).sendToTarget();
return WHAT_SUCCESS;
} else {
handler.obtainMessage(5).sendToTarget();
return WHAT_FAIL_PATCH;
}
} catch (Exception e) {
e.printStackTrace();
}
return WHAT_FAIL_PATCH;
}
@Override
protected void onPostExecute(Integer integer) {
super.onPostExecute(integer);
loadding.setVisibility(View.GONE);
}
}
3,安装新的apk
private void install(String dir) {
String command = "chmod 777 " + dir;
Runtime runtime = Runtime.getRuntime();
try {
runtime.exec(command); // 可执行权限
} catch (IOException e) {
e.printStackTrace();
}
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setDataAndType(Uri.parse("file://" + dir), "application/vnd.android.package-archive");
startActivity(intent);
}
服务端工具以及源码位于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
代码地址:点击打开链接
- 如何通过Livy的RESTful API接口向Kerberos环境的CDH集群提交作业
- 如何使用Oozie API接口向非Kerberos环境的CDH集群提交Spark作业
- Joomla 权限提升漏洞(CVE-2016-9838)分析
- Firefox - SVG cross domain cookie vulnerability
- 当代 Web 的 JSON 劫持技巧
- 利用特殊协议加载本地文件, 绕过 HTML5 沙箱, 打开弹窗诸事
- Nginx权限提升漏洞(CVE-2016-1247 )分析
- 初识 Fuzzing 工具 WinAFL
- 如何使用Oozie API接口向Kerberos环境的CDH集群提交Spark2作业
- 如何编译及使用TPC-DS生成测试数据
- ASP.NET MVC编程——缓存
- ASP.NET MVC编程——错误处理与日记
- Jenkins 未授权远程代码执行漏洞(CVE-2017-1000353)
- ASP.NET MVC编程——路由
- java教程
- Java快速入门
- Java 开发环境配置
- Java基本语法
- Java 对象和类
- Java 基本数据类型
- Java 变量类型
- Java 修饰符
- Java 运算符
- Java 循环结构
- Java 分支结构
- Java Number类
- Java Character类
- Java String类
- Java StringBuffer和StringBuilder类
- Java 数组
- Java 日期时间
- Java 正则表达式
- Java 方法
- Java 流(Stream)、文件(File)和IO
- Java 异常处理
- Java 继承
- Java 重写(Override)与重载(Overload)
- Java 多态
- Java 抽象类
- Java 封装
- Java 接口
- Java 包(package)
- Java 数据结构
- Java 集合框架
- Java 泛型
- Java 序列化
- Java 网络编程
- Java 发送邮件
- Java 多线程编程
- Java Applet基础
- Java 文档注释
- [tensorflow损失函数系列]softmax_cross_entropy_with_logits
- RESTful API 设计最佳实践
- Spring 是如何解决循环依赖的?
- 移动webhead参数
- 看了这篇泛型,下次设计链表别傻傻的用int 表示node节点的值了
- 标准TensorFlow格式 TFRecords
- LeetCode 05最长回文子串
- 基于NCNN的3x3可分离卷积再思考盒子滤波
- [译]Buidler 新手教程
- tf.train.shuffle_batch函数解析
- 网页小图标Favicon
- ASP.net Core MVC项目给js文件添加版本号
- 多目标优化非支配关系实现
- linux之shell
- linux下的定时任务处理