【Android】微信热修复 Tinker 的集成和使用

简介

Tinker: n. 〈英〉小炉匠,补锅匠,修补匠

Tinker 是微信官方开源的 Android 热修复框架,支持在无需升级APK的前提下更新 dex, library and resources 文件。它也就是今年9月24才刚刚开源,几天功夫star数就超过3000,可见在开发者中的影响力有多大,也说明这是一个刚需。

Tinker GitHub: https://github.com/Tencent/tinker

使用步骤

一个小坑

很多人遇到的第一个错误就是提示 tinkerId is not set ,这个在tinker-sample-android的app/build.gradle 中默认设置为Git的提交版本号,如下

def getTinkerIdValue() {
    return hasProperty("TINKER_ID") ? TINKER_ID : gitSha()
}

如果不是通过git clone方式下载的就可能出现这个错误,其实可以简单粗暴的方式解决,那就是在app/build.gradle中把tinker id写死:

def getTinkerIdValue() {
    return hasProperty("TINKER_ID") ? TINKER_ID : "tinker_id_2333"
}

下面介绍一下如何一步步的把Tinker集成到自己的项目中,以及会遇到哪些问题该如何解决。

一、工程根目录的build.gradle中添加依赖

在项目的build.gradle中,添加tinker-patch-gradle-plugin的依赖

buildscript {
    dependencies {
        classpath ('com.tencent.tinker:tinker-patch-gradle-plugin:1.7.3')
    }
}

此时如果gradle Sync不成功可能是因为没有加入 jcenter仓库

buildscript {
    repositories {
        mavenLocal()
        jcenter()    //注意这里,因为maven仓库里没有
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.2.2'
        classpath "com.tencent.tinker:tinker-patch-gradle-plugin:${TINKER_VERSION}"
    }
    // default values for all sub projects
}

二、在app/build.gradle中的dependencies节点添加依赖

dependencies {
    //可选,用于生成application类 
    provided('com.tencent.tinker:tinker-android-anno:1.7.3')
    //tinker的核心库
    compile('com.tencent.tinker:tinker-android-lib:1.7.3') 
}

三、复制官方sample工程app/build.gradle中的其他相关配置

把官方 tinker-sample-android 工程中的app/build.gradle复制到自己的app/build.gradle中,特别是最下面的task 代码块,否则无法生成patch。

四、替换自己的Application类

这一块需要特殊说明一下,tinker为了达到修改应用自己的Application的目的,使用代码框架封装继承DefaultApplicationLike的方式来实现对Application的修改,主要为了减少反射的使用和提高兼容性,具体说明参考 Tinker Wiki:自定义Application类

在替换更改之前,强烈建议先把项目中的Application类做个备份。因为需要采用Annotation自动生成Application,原来的Application类需要删掉

然后我们修改项目的 Application ,使之继承DefaultApplicationLike; 这块的确有点奇葩,这个DefaultApplicationLike不是继承自Application,需要用注解来设置项目中真正的Application,Tinker插件会自动生成真正的Application

@DefaultLifeCycle(application = "com.cuc.android.aps.MyApplication",//通过注解,由tinker自动生成MyApplication
        flags = ShareConstants.TINKER_ENABLE_ALL,                 //tinkerFlags
        loaderClass = "com.tencent.tinker.loader.TinkerLoader",   //loaderClassName, 我们这里使用默认
        loadVerifyFlag = false)

public class ApplicationFromTinkerLike extends DefaultApplicationLike {
    public ApplicationFromTinkerLike(Application application, int tinkerFlags, boolean tinkerLoadVerifyFlag, long applicationStartElapsedTime, long applicationStartMillisTime, Intent tinkerResultIntent, Resources[] resources, ClassLoader[] classLoader, AssetManager[] assetManager) {
        super(application, tinkerFlags, tinkerLoadVerifyFlag, applicationStartElapsedTime, applicationStartMillisTime, tinkerResultIntent, resources, classLoader, assetManager);
    }
}

上边的com.cuc.android.aps.MyApplication就是真正的Application,不用我们自己写,是自动生成的。然后修改manifest.xml将application指向com.cuc.android.aps.MyApplication就行,开始会报错,build一下项目就好了。

五、在刚改好的 ApplicationFromTinkerLike 中重载onBaseContextAttached方法

并在该方法中增加以下代码调用初始化tinker

@Override
public void onBaseContextAttached(Context base) {
    super.onBaseContextAttached(base);

    //you must install multiDex whatever tinker is installed!
    MultiDex.install(base);
    TinkerInstaller.install(this);
}

或者,我们可以直接将Sample工程中的文件(特别是Utils包下的)拷贝到我们自己的工程中,就像我一样,方便后期使用。比如SampleResultService、TinkerManager这几个类

然后重载onBaseContextAttached方法,可以像我一样写成下面这样

@Override
public void onBaseContextAttached(Context base) {
    super.onBaseContextAttached(base);
    //you must install multiDex whatever tinker is installed!
    MultiDex.install(base);

    MyApplicationContext.application = (MyApplication) getApplication();
    MyApplicationContext.context = getApplication();
    TinkerManager.setTinkerApplicationLike(this);
    TinkerManager.initFastCrashProtect();
    //should set before tinker is installed
    TinkerManager.setUpgradeRetryEnable(true);

    //optional set logIml, or you can use default debug log
    TinkerInstaller.setLogIml(new MyLogImp());

    //installTinker after load multiDex
    //or you can put com.tencent.tinker.** to main dex
    TinkerManager.installTinker(this);
}

至此,自定义Application,也就是将Application中的实现移动到SampleApplicationLike中已经完成。

六、可以开始写测试patch的代码啦

使用下面代码来load patch

TinkerInstaller.onReceiveUpgradePatch(this.getApplication(), Environment.getExternalStorageDirectory().getAbsolutePath() + "/patch_signed_7zip.apk");

在自己的工程中增加两个按钮,其中一个按钮用来显示EditText中的内容,另一个按钮用来加载补丁,在加载补丁按钮点击事件中执行加载patch的操作,为后期修复代码bug做准备,代码为:

Button toastInfo = (Button) top.findViewById(R.id.toastInfo);
toastInfo.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        //清除补丁
        Toast.makeText(SysUtils.getApp(),"clean patch!",Toast.LENGTH_LONG).show();
        Tinker.with(SysUtils.getApp()).cleanPatch();
    }
});
Button loadPatchButton = (Button) top.findViewById(R.id.loadPatch);
loadPatchButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        //加载补丁(加载成功以后patch文件会自动删掉)
        TinkerInstaller.onReceiveUpgradePatch(SysUtils.getApp(), Environment.getExternalStorageDirectory().getAbsolutePath() + "/patch_signed_7zip.apk");
    }
});

打patch包的步骤

1、调用assembleDebug编译原始包

AndroidStudio 命令行下运行

$ ./gradlew assembleDebug

编译过的包会保存在build/bakApk中。然后我们将它安装到手机,可以看到补丁并没有加载。

2、修改代码,添加新功能或者更改功能

例如在MainActivity中添加一个I am on patch onCreate的Toast.

3、然后修改build.gradle中的参数

将步骤一编译保存的安装包路径拷贝到tinkerPatch中的tinkerOldApkPath参数中,根据需要也得同时修改tinkerApplyResourcePathtinkerApplyMappingPath

/**
 * you can use assembleRelease to build you base apk
 * use tinkerPatchRelease -POLD_APK=  -PAPPLY_MAPPING=  -PAPPLY_RESOURCE= to build patch
 * add apk from the build/bakApk
 */
ext {
    //for some reason, you may want to ignore tinkerBuild, such as instant run debug build?
    tinkerEnabled = true

    //for normal build
    //old apk file to build patch apk
    tinkerOldApkPath = "${bakPath}/app-debug-1116-15-53-17.apk"
    //proguard mapping file to build patch apk
    tinkerApplyMappingPath = "${bakPath}/app-debug-1116-15-53-17-mapping.txt"
    //resource R.txt to build patch apk, must input if there is resource changed
    tinkerApplyResourcePath = "${bakPath}/app-debug-1116-15-53-17-R.txt"

    //only use for build all flavor, if not, just ignore this field
    tinkerBuildFlavorDirectory = "${bakPath}/app-debug-1107-10-33-32"
}

4、调用tinkerPatchDebug, 生成补丁包

$ ./gradlew tinkerPatchDebug

补丁包与相关日志会保存在/build/outputs/tinkerPatch/中,我们将其中的patch_signed_7zip.apk推送到手机的sdcard中。

$ adb push ./app/build/outputs/tinkerPatch/debug/patch_signed_7zip.apk /storage/sdcard0/

5、运行app,执行LOAD PATCH代码块

如果看到patch success, please restart process的toast,即可锁屏或者KILL 应用进程。

6、重新启动App

我们可以看到,补丁包的确已经加载成功了。


使用Tinker的注意事项

  • 1、Tinker_id的大版本升级问题

  • 2、如果生成patch失败,并且原因如下:
Warning: ignoreWarning is false, but we found loader classes are found in old secondary dex.

那么需要把相应的报错类声明在项目的keep_in_main_dex.txt 文件中,保证它编译时会被放置到主dex中。参考Tinker Issue #96


【参考资料】


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值