增量更新热修复与插件化

增量更新

原理:
通过在网上下载差异包,下载下来之后,与旧的APK进行融合,合成新的Apk文件,因此,不需要下载新的完整的APK,节省了用户的流量

差异包大小:旧的APK13M,新的APK15M,差异包大于2M左右

优点:节省用户流量,节省下载时间
缺点:需要对每个旧的版本生成差异包,比较耗费工作量,
如果用户安装的不是官方版本,那就没有办法使用差分包

增量更新工具:http://www.daemonology.net/bsdiff/

热修复

http://www.cnblogs.com/common1140/p/5287040.html
http://blog.csdn.net/qq_31530015/article/details/51785228?locationNum=11

一、什么是热修复
热修复说白了就是”打补丁”,比如你们公司上线一个app,用户反应有重大bug,需要紧急修复。如果按照通
常做法,那就是程序猿加班搞定bug,然后测试,重新打包并发布。这样带来的问题就是成本高,效率低。于是,热
修复就应运而生.一般通过事先设定的接口从网上下载无Bug的代码来替换有Bug的代码。这样就省事多了,用
户体验也好。
二、热修复的原理
1.Android的类加载机制
android的类加载器分为两种,PathClassLoader和DexClassLoader,两者都继承自BaseDexClassLoader
PathClassLoader代码位于libcore\dalvik\src\main\Java\dalvik\system\PathClassLoader.java
DexClassLoader代码位于libcore\dalvik\src\main\java\dalvik\system\DexClassLoader.java
BaseDexClassLoader代码位于libcore\dalvik\src\main\java\dalvik\system\BaseDexClassLoader.java
● PathClassLoader
● 用来加载系统类和应用类
● DexClassLoader
用来加载jar、apk、dex文件.加载jar、apk也是最终抽取里面的Dex文件进行加载.

2.热修复机制
看下PathClassLoader代码
public class PathClassLoader extends BaseDexClassLoader {

public PathClassLoader(String dexPath, ClassLoader parent) {
    super(dexPath, null, null, parent);
}

public PathClassLoader(String dexPath, String libraryPath,
        ClassLoader parent) {
    super(dexPath, null, libraryPath, parent);
}

}
DexClassLoader代码
public class DexClassLoader extends BaseDexClassLoader {

public DexClassLoader(String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent) {
    super(dexPath, new File(optimizedDirectory), libraryPath, parent);
}

}
两个ClassLoader就两三行代码,只是调用了父类的构造函数.
public class BaseDexClassLoader extends ClassLoader {
private final DexPathList pathList;

public BaseDexClassLoader(String dexPath, File optimizedDirectory,
        String libraryPath, ClassLoader parent) {
    super(parent);
    this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
}

@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
    List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
    Class c = pathList.findClass(name, suppressedExceptions);
    if (c == null) {
        ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList);
        for (Throwable t : suppressedExceptions) {
            cnfe.addSuppressed(t);
        }
        throw cnfe;
    }
    return c;
}

在BaseDexClassLoader 构造函数中创建一个DexPathList类的实例,这个DexPathList的构造函数会创建一个dexElements 数组
public DexPathList(ClassLoader definingContext, String dexPath, String libraryPath, File optimizedDirectory) {

this.definingContext = definingContext;
ArrayList suppressedExceptions = new ArrayList();
//创建一个数组
this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory, suppressedExceptions);

}
然后BaseDexClassLoader 重写了findClass方法,调用了pathList.findClass,跳到DexPathList类中.
/* package */final class DexPathList {

public Class findClass(String name, List suppressed) {
//遍历该数组
for (Element element : dexElements) {
//初始化DexFile
DexFile dex = element.dexFile;

        if (dex != null) {
            //调用DexFile类的loadClassBinaryName方法返回Class实例
            Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
            if (clazz != null) {
                return clazz;
            }
        }
    }       
    return null;
}
...

}
会遍历这个数组,然后初始化DexFile,如果DexFile不为空那么调用DexFile类的loadClassBinaryName方法返回Class实例.
归纳上面的话就是:ClassLoader会遍历这个数组,然后加载这个数组中的dex文件.
而ClassLoader在加载到正确的类之后,就不会再去加载有Bug的那个类了,我们把这个正确的类放在Dex文件中,让这个Dex文件排在dexElements数组前面即可.
这里有个问题,可参考QQ空间团队的 安卓App热补丁动态修复技术介绍
概括来讲:如果引用者和被引用者的类(直接引用关系)在同一个Dex时,那么在虚拟机启动时,被引用类就会被打上CLASS_ISPREVERIFIED标志,这样被引用的类就不能进行热修复操作了.
那么我们就要阻止被引用类打上CLASS_ISPREVERIFIED标志.QQ空间的方法是在所有引用到该类的构造函数中插入一段代码,代码引用到别的类.
三、热修复的例子
我用的是阿里开源的热修复框架AndFix热修复框架地址
其实它的原理也是动态加载class文件,然后调用反射完成修复.可参考我上一篇写的
Java的ClassLoader加载机制
AndFix是 “Android Hot-Fix”的缩写。它支持Android 2.3到6.0版本,并且支持arm与X86系统架构的设备。完美支持Dalvik与ART的Runtime。AndFix 的补丁文件是以 .apatch 结尾的文件。
我这是用eclipse写的Demo.
1.把AndFix抽取成library依赖的形式

2.新建一个AndFixDemo项目,依赖AndFix这个library
2.1
新建一个MyApplication继承Application
public class MyApplication extends Application {

private static final String TAG = "MyApplication";

/**
 * apatch文件
 */
private static final String APATCH_PATH = "/Dennis.apatch";

private PatchManager mPatchManager;

@Override
public void onCreate() {
    super.onCreate();
    // 初始化
    mPatchManager = new PatchManager(this);
    mPatchManager.init("1.0"); // 版本号

    // 加载 apatch
    mPatchManager.loadPatch();

    //apatch文件的目录
    String patchFileString = Environment.getExternalStorageDirectory().getAbsolutePath() + APATCH_PATH;
    File apatchPath = new File(patchFileString);

    if (apatchPath.exists()) {
        Log.i(TAG, "补丁文件存在");
        try {
            //添加apatch文件
            mPatchManager.addPatch(patchFileString);
        } catch (IOException e) {
            Log.i(TAG, "打补丁出错了");
            e.printStackTrace();
        }
    } else {
        Log.i(TAG, "补丁文件不存在");
    }

}

}
实际当中肯定是通过网络接口下载apatch文件,我这里为了方便演示就放在了SD卡根目录
2.2
在MainActivity用一个按钮弹出吐司,上面是有Bug的代码,下面是修正后的代码

分别打包成Bug.apk和NoBug.apk

2.3
然后要用到一个生成补丁的工具apkpatch
解压

_MACOSX是给OSX系统用的
.bat是给window系统用的
我用得是.bat
把之前生成的Bug.apk和NoBug.apk,还有打包所使用的keystore文件放到apkpatch-1.0.3目录下
打开cmd,进入到apkpatch-1.0.3目录下,输入如下指令
apkpatch.bat -f NoBug.apk -t Bug.apk -o Dennis -k keystore -p 111111 -a 111111 -e 111111
每个参数含义如下
-f 新版本的apk
-t 旧版本的apk
-o 输出apatch文件的文件夹,可以随意命名
-k 打包的keystore文件名
-p keystore的密码
-a keystore 用户别名
-e keystore 用户别名的密码

如果出现add modified …….就表示成功了,去apkpatch-1.0.3目录看下,新增了Dennis目录

我把这个文件改为Dennis.apatch
2.4
手机装上Bug.apk运行起来

然后把Dennis.apatch 放到SD卡根目录,退出app,再进入,按下按钮

最后附上Demo还有apk和apatch 文件 打开链接

插件化

猜你喜欢

转载自blog.csdn.net/qq_32890771/article/details/77150887
今日推荐