在年初的时候,尝试了一把热修复技术,当时选择的是阿里的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卡,进行打补丁。当然,也可以不拷贝,直接使用该路径。

为了方便,我们就在本地放两个本地,为了方便讲解,我们先定义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地址,可通过如下代码获取:
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

代码地址:点击打开链接


Android 增量更新和升级的更多相关文章

  1. Android 增量更新(BSDiff / bspatch)

    Android 增量更新 BSDiff / bspatchhttp://www.daemonology.net/bsdiff/android的代码目录下 \external\bsdiff bsdiff ...

  2. Android 增量更新研究

    Android 增量更新实例(Smart App Updates) http://blog.csdn.net/duguang77/article/details/17676797 Android AP ...

  3. 一句话的Android增量更新框架(增量更新)

    转自:http://www.jianshu.com/p/a9ec8fa780e2 Android应用更新要使用完整的新版本Apk安装,增量更新则是提供一个新旧版本偏差数据的patch包供应用下载,然后 ...

  4. Android 增量更新实例(Smart App Updates)

    原地址:http://my.oschina.net/liucundong/blog/160436 官方说明 实现原理 实现 (1)生成差异包 (2)使用旧apk+差异包,在客户端合成新apk 注意事项 ...

  5. Android 增量更新

    title: Android NDK之增量更新 1.增量更新使用到的库bsdiff和bzip2 bsdiff库是一个开源的二进制差分工具,通过对比Apk的二进制,从而进行差分包的生成. bsdiff库 ...

  6. Android 增量更新完全解析 是增量不是热修复(转)

    转自:http://blog.csdn.net/lmj623565791/article/details/52761658 本文在我的微信公众号:鸿洋(hongyangAndroid)首发. 转载请标 ...

  7. android 增量更新原理

    原理如下:服务器端设计增量表,记录数据操作顺序id,和增删改查信息.在进行数据库表操作的时候同时进行将信息保存在增量表. android客户端在请求的时候上传最后保存的id.服务端判断最后的id,返回 ...

  8. Android增量更新

    http://blog.csdn.net/tu_bingbing/article/details/8538592 (转)

  9. Android studio使用增量更新进行版本升级

    今天将Android Studio更新了一下,特此记录一下升级过程,以后可能还会用得着. 首先通过菜单栏进入 Help --> Check for update 查看下当前版本是否需要更新.事实 ...

随机推荐

  1. [HEOI2015]小Z的房间

    Description 你突然有了一个大房子,房子里面有一些房间.事实上,你的房子可以看做是一个包含n*m个格子的格状矩形,每个格子是一个房间或者是一个柱子.在一开始的时候,相邻的格子之间都有墙隔着. ...

  2. hdu 5130(2014广州 圆与多边形相交模板)

    题意:一个很多个点p构成的多边形,pb <= pa * k时p所占区域与多边形相交面积 设p(x,y),       (x - xb)^2+(y - yb)^2 / (x - xa)^2+(y ...

  3. hihiocoder 1255(搜索)(2015ACM/ICPC北京站)

    题意: 给你四个矩形,判断能否从中选出3个组成一个矩形 思路: 1.搜索,如果两个能组成一个新的,则将他们合并,继续搜索 2.暴力判断 最开始没注意到3,一直以为要用4个,WR #include &l ...

  4. Entity Framework DBContext 增删改查深度解析

    Entity Framework DBContext 增删改查深度解析 有一段时间没有更新博客了,赶上今天外面下雨,而且没人约球,打算把最近对Entity Framework DBContext使用的 ...

  5. js登录,回车登录

    $(document).ready(function(){ $("#loginBtn").click(doLoginEvent); loadCookies(); //回车登录 do ...

  6. 三种方法,刷新 Android 的 MediaStore!让你保存的图片立即出现在相册里!

    公众号原标题:测试:"系统相册里怎么看不到我刚保存的图片,是我操作不对吗?" 一.序 Hi,大家好,我是承香墨影! App 内,创建一个文件并保存文件到本地的需求,是很常见的 I/ ...

  7. bootstrap插件fileinput.js 出现出现$("#xxxx").fileinput({}); 不生效的情况解决

    如果出现$("#xxxx").fileinput({}); 不生效的情况请将fileinput.js中最后几行注释掉: /* $(document).ready(function ...

  8. # electron-vue 尝试做个网易云音乐

    当跑起来electron第一刻 我发现这个浏览器头是不是有点丑 是不是可以隐藏起来呢,答案当然是可以的 src/main/index.js mainWindow = new BrowserWindow ...

  9. 使用svn无法cleanup和lock问题

    step1: 到 sqlite官网 (http://www.sqlite.org/download.html) 下载 sqlite3.exe 找到 Precompiled Binaries for W ...

  10. c#默认类的修饰符。

    类的默认修饰符是internal  当其修饰符为internal时,其他模块不能访问该类, 所以class类的修饰符一般为public