Android热修复(阿里热修复)

一、概述
当一个产品上线后,如果出现了bug,传统的方式是,收集bug,修复,重新发布新版本。这样用户还得重新下载apk,在这介绍的是不用重新安装apk修复bug,这样的技术就称之为热修复技术。

二、热修复原理

principle.png

至于更加具体的原理,请点击这里https://github.com/alibaba/AndFix

三、实现步骤
1、生成补丁包
假如我们收到了用户上传的崩溃信息,我们改完需要修复的Bug,这个时候就会有一个新的的apk我们就叫它为new.apk,原来的那个有Bug的apk你也有我们就叫它old.apk。这个时候我们就可以利用阿里github上面提供的工具生成一个xxxx.apatch包用于修复Bug。

1.命令是:apkpatch.bat -f <new> -t <old> -o <output> -k <keystore> -p <*> -a <alias> -e <*>
2.-f : 没有Bug的新版本apk
3.-t : 有bug的旧版本apk
4.-o : 生成的补丁文件所放的文件夹
5.-k : 签名打包密钥
6.-p : 签名打包密钥密码
7.-a : 签名密钥别名
8.-e : 签名别名密码(这样一般和密钥密码一致)

2、通过补丁包修复bug
怎么获取apatch包呢?我们肯定是请求接口获取下载我们的修复好的apatch包,下载下来之后我们就可以调用方法进行修复了,我们可以暂时放在本地测试一下,在进行这步之前添加依赖https://github.com/alibaba/AndFix,这些代码肯定之前就得写好:

public class BaseApplication extends Application {
    // Patch管理类
    public static PatchManager mPatchManager;

    @Override
    public void onCreate() {
        super.onCreate();
        // 捕捉崩溃信息
        ExceptionCrashHandler.getInstance().init(this);

        // Ali热修复
        try {
            mPatchManager = new PatchManager(this);
            // 初始化patch版本
            String pkName = this.getPackageName();
            String versionName = getPackageManager().getPackageInfo(pkName, 0).versionName;
            // 初始化版本名称
            mPatchManager.init(versionName);
            // 加载之前的patch
            mPatchManager.loadPatch();
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
    }
}
public class MainActivity extends BaseSkinActivity {

    @Override
    protected void initData() {
        // 获取上次的崩溃信息
        File crashFile = ExceptionCrashHandler.getInstance().getCrashFile();
        // 上传到服务器,后面再说.......
    }

    @Override
    protected void initView() {

    }

    @Override
    protected void setContentView() {
        setContentView(R.layout.activity_main);
    }

    @Override
    protected void initTitle() {

    }

    @Onclick(R.id.test)
    public void test(View view) {
        // 没有修复之前会报异常闪退
        Toast.makeText(this, Utils.test(), Toast.LENGTH_LONG).show();
    }

    @Onclick(R.id.ali_fix)
    public void aliHotFix(View view) {
        try {
            // 测试 目前暂且放在本地
            String patchFileString =  Environment.getExternalStorageDirectory()+"/fix.apatch";
            Log.e("TAG", patchFileString);
            // 修复apatch,不需要重启可立即生效
            BaseApplication.mPatchManager.addPatch(patchFileString);
            Toast.makeText(this, "Bug修复成功", Toast.LENGTH_LONG).show();
        } catch (Exception e) {
            e.printStackTrace();
            Toast.makeText(this, "Bug修复失败", Toast.LENGTH_LONG).show();
        }
    }
}

3、底层原理
把fix.aptach包解压反编译里面的dex文件


Paste_Image.png

可见它其实是把两个apk进行比较然后找出里面不一样的方法,然后打成一个xxx..aptach包其实就是一个压缩文件,我们究竟怎么去获取dex里面的所有方法呢?解压后其实里面还有一个很重要的文件META-INF\PATCH.MF文件:

Manifest-Version: 1.0
Patch-Name: new
Created-Time: 13 Feb 2017 08:19:23 GMT
From-File: new.apk
To-File: old.apk
Patch-Classes: 
// 所有的类都会在这里,有多个就会显示多个,需要用java代码把他解析出来拿到这个内容......
com.example.administrator.exceptioncrashhandler.Utils_C
 F
Created-By: 1.0 (ApkPatch)

BaseApplication.mPatchManager.addPatch(patchFileString)这里面的Java代码中修改过的代码被加上一个注解:


Paste_Image.png

底层的修复c代码实现:

static void replaceMethod(JNIEnv* env, jclass clazz, jobject src,
        jobject dest) {
    if (isArt) {
        art_replaceMethod(env, src, dest);
    } else {
        dalvik_replaceMethod(env, src, dest);
    }
}

由于Android4.4后才用的Art虚拟机,之前的系统都是Dalvik虚拟机,因此Natice层写了2个方法,对不同的系统做不同的处理方式,我就只贴Dalvik部分了都类似,只不过Art需要版本判断不同Api的版本有所不同。

extern jboolean __attribute__ ((visibility ("hidden"))) dalvik_setup(
        JNIEnv* env, int apilevel) {
  // libdvm.so 加载系统的so库,视频里我们再去看一张图
    void* dvm_hand = dlopen("libdvm.so", RTLD_NOW);
    if (dvm_hand) {
        dvmDecodeIndirectRef_fnPtr = dvm_dlsym(dvm_hand,
                apilevel > 10 ?
                        "_Z20dvmDecodeIndirectRefP6ThreadP8_jobject" :
                        "dvmDecodeIndirectRef");
        if (!dvmDecodeIndirectRef_fnPtr) {
            return JNI_FALSE;
        }
        dvmThreadSelf_fnPtr = dvm_dlsym(dvm_hand,
                apilevel > 10 ? "_Z13dvmThreadSelfv" : "dvmThreadSelf");
        if (!dvmThreadSelf_fnPtr) {
            return JNI_FALSE;
        }
        jclass clazz = env->FindClass("java/lang/reflect/Method");
        jClassMethod = env->GetMethodID(clazz, "getDeclaringClass",
                        "()Ljava/lang/Class;");

        return JNI_TRUE;
    } else {
        return JNI_FALSE;
    }
}

extern void __attribute__ ((visibility ("hidden"))) dalvik_replaceMethod(
        JNIEnv* env, jobject src, jobject dest) {
    jobject clazz = env->CallObjectMethod(dest, jClassMethod);
    ClassObject* clz = (ClassObject*) dvmDecodeIndirectRef_fnPtr(
            dvmThreadSelf_fnPtr(), clazz);
    clz->status = CLASS_INITIALIZED;

    Method* meth = (Method*) env->FromReflectedMethod(src);
    Method* target = (Method*) env->FromReflectedMethod(dest);
    LOGD("dalvikMethod: %s", meth->name);

//    meth->clazz = target->clazz;
    meth->accessFlags |= ACC_PUBLIC;
    meth->methodIndex = target->methodIndex;
    meth->jniArgInfo = target->jniArgInfo;
    meth->registersSize = target->registersSize;
    meth->outsSize = target->outsSize;
    meth->insSize = target->insSize;

    meth->prototype = target->prototype;
    meth->insns = target->insns;
  // 准确的说不能算是修复只能说是指向已修复的方法
    meth->nativeFunc = target->nativeFunc;
}

最后,使用该方法修复bug需要注意的几点:
1.每次生成之后一定要测试;
2.尽量的不要分包,不要分多个dex
3.混淆的时候,设计到NDK AndFix.java 不要混淆
4.生成包之后一般会加固什么的,这个时候生成的差分包,一定要在之前去生成。
5.既然是去修复方法,第一个不能增加成员变量,不能增加方法

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,509评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,806评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,875评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,441评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,488评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,365评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,190评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,062评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,500评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,706评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,834评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,559评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,167评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,779评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,912评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,958评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,779评论 2 354

推荐阅读更多精彩内容