TINKER 集成

Tinker是啥

Tinker是微信官方的Android热补丁解决方案,它支持动态下发代码、So库以及资源,让应用能够在不需要重新安装的情况下实现更新。当然,你也可以使用Tinker来更新你的插件。Tinker Github

为什么使用Tinker

当前市面的热补丁方案有很多,其中比较出名的有阿里的AndFix、美团的Robust以及QZone的超级补丁方案。但它们都存在无法解决的问题,这也是正是我们推出Tinker的原因。如下,就可以知道Tinker的强大之处。

Tinker QZone AndFix Robust
类替换 yes yes no no
So替换 yes no no no
资源替换 yes yes no no
全平台支持 yes yes no yes
即时生效 no no yes yes
性能损耗 较小 较大 较小 较小
补丁包大小 较小 较大 一般 一般
开发透明 yes yes no no
复杂度 较低 较低 复杂 复杂
Rom体积 Dalvik较大 较小 较小 较小
成功率 较高 较高 一般 最高

Tinker的已知问题

由于原理与系统限制,Tinker有以下已知问题:

Tinker不支持修改AndroidManifest.xml,Tinker不支持新增四大组件

由于Google Play的开发者条款限制,不建议在GP渠道动态更新代码;

在Android N上,补丁对应用启动时间有轻微的影响;

不支持部分三星android-21机型,加载补丁时会主动抛出”TinkerRuntimeException:checkDexInstall failed”;

对于资源替换,不支持修改remoteView。例如transition动画,notification icon以及桌面图标。

Tinker集成过程(使用bugly集成)

项目工程的.gradle 中添加

classpath "com.tencent.bugly:tinker-support:1.2.0"

app的.gradle中添加

implementation 'com.tencent.bugly:crashreport_upgrade:1.4.2'
implementation 'com.tencent.tinker:tinker-android-lib:1.9.14'

应用bugly配置好的gradle文件

apply from: 'tinker-support.gradle'

app gradle 结构

tinker-support.gradle 代码

apply plugin: 'com.tencent.bugly.tinker-support'

def bakPath = file("${buildDir}/bakApk/")
/**
 * 此处填写每次构建生成的基准包目录
 */
def baseApkDir = "huozhu-1104-11-36-34"
/**
* 对于插件各参数的详细解析请参考
*/
tinkerSupport {
    // 开启tinker-support插件,默认值true
    enable = true
  // 指定归档目录,默认值当前module的子目录tinker
   autoBackupApkDir = "${bakPath}"
   // 是否启用覆盖tinkerPatch配置功能,默认值false
   // 开启后tinkerPatch配置不生效,即无需添加tinkerPatch
  overrideTinkerPatchConfiguration = true
   // 编译补丁包时,必需指定基线版本的apk,默认值为空
   // 如果为空,则表示不是进行补丁包的编译
   // @{link tinkerPatch.oldApk }
    baseApk = "${bakPath}/${baseApkDir}/huozhu-release.apk"
   // 对应tinker插件applyMapping
   baseApkProguardMapping = "${bakPath}/${baseApkDir}/huozhu-release-mapping.txt"
   // 对应tinker插件applyResourceMapping
  baseApkResourceMapping = "${bakPath}/${baseApkDir}/huozhu-release-R.txt"
  // 构建基准包和补丁包都要指定不同的tinkerId,并且必须保证唯一性
   tinkerId = "base_11.04.11.49"
  // 构建多渠道补丁时使用
  // buildAllFlavorsDir = "${bakPath}/${baseApkDir}"
  // 是否启用加固模式,默认为false.(tinker-spport 1.0.7起支持)
 // isProtectedApp = true
  // 是否开启反射Application模式
  enableProxyApplication = false
  // 是否支持新增非export的Activity(注意:设置为true才能修改AndroidManifest文件)
 supportHotplugComponent = true
}
/**
 * 一般来说,我们无需对下面的参数做任何的修改
 * 对于各参数的详细介绍请参考:
 * https://github.com/Tencent/tinker/wiki/Tinker-%E6%8E%A5%E5%85%A5%E6%8C%87%E5%8D%97
 */
tinkerPatch {
//oldApk ="${bakPath}/${appName}/app-release.apk"
     ignoreWarning = false
     useSign = true
     dex {
         dexMode = "jar"
         pattern = ["classes*.dex"]
         loader = []
     }
     lib {
         pattern = ["lib/*/*.so"]
     }
     res {
          pattern = ["res/*", "r/*", "assets/*", "resources.arsc", "AndroidManifest.xml"]
          ignoreChange = []
          largeModSize = 100
      }
   packageConfig {

   }
  sevenZip {
      zipArtifact = "com.tencent.mm:SevenZip:1.1.10"
    //        path = "/usr/local/bin/7za"
    }
   buildConfig {
      keepDexApply = false
      //tinkerId = "1.0.1-base"
      //applyMapping = "${bakPath}/${appName}/app-release-mapping.txt" //  可选,设置mapping文件,建议保持旧apk的proguard混淆方式
    //applyResourceMapping = "${bakPath}/${appName}/app-release-R.txt" // 可选,设置R.txt文件,通过旧apk文件保持ResId的分配
   }
}

配置application

新建SampleApplicationLike 继承自DefaultApplicationLike ,开启反射application模式

enableProxyApplication = false   //tinker-support.gradle 中的配置

在onBaseContextAttached 添加MultiDex 和Tinker

public void onBaseContextAttached(Context base) {
    super.onBaseContextAttached(base);
   // you must install multiDex whatever tinker is installed!
   MultiDex.install(base);
   // 安装tinker
  TinkerManager.installTinker(this);
}

应用的Application 继承自TinkerApplication 并重写默认构造方法
public MyApplication() {
super(ShareConstants.TINKER_ENABLE_ALL, “xx.xx.SampleApplicationLike”,
“com.tencent.tinker.loader.TinkerLoader”, false);
}

Tinker 生成插件包(可使用bugly中的tinker-support)

先生成基准包,也就是上线后有bug的包;默认在build/bakApk 中 assemble一次就会生成一次

基准包目录结构

目录结构
目录结构

huozhu-release.apk 因为是组件化的目录结构 所以是huozhu-release.apk 而不是app-release.apk 而且注意 tinker-support.gradle 配置的指定文件名称是否一致;

huozhu-release-mapping.txt :开启混淆后会生成mapping文件

基准包生成后将 地址 复制到配置中

/**
* 此处填写每次构建生成的基准包目录
*/
def baseApkDir = "huozhu-1104-11-36-34"

同时指定一个唯一的tinkerId 可以理解为 每一次打补丁包都要指定一个tinkerId

修改部分代码 然后执行生成补丁

生成补丁

因为是bugly中的tinker 所以直接使用tinker-support中的buildTinkerPatchRelease ,在outputs根目录生的patch生成了可用补丁

如果是tinker中的tinkerPatchRelease 则在outputs/apk/tinkerPatch中生成了补丁

插件目录
插件目录

加载补丁包

在app首页或者任意位置去加载补丁包

这里封装了一下下载补丁的方法 直接调用即可

PatchUtils.getInstance().checkPatch(context);

核心方法是

TinkerInstaller.onReceiveUpgradePatch(context, file.getAbsolutePath());

Tinker中已经有各种判断 补丁是否正确等判断,所以直接加载就可以。

加载监听:

TinkerManager.getInstance().setTinkerListener(new TinkerManager.TinkerListener() {
        @Override
        public void onDownloadSuccess(String s) {
            Log.d("onDownloadSuccess", s);
        }

        @Override
        public void onDownloadFailure(String s) {
            Log.d("onDownloadFailure", s);
        }

        @Override
        public void onPatchStart() {

        }

        @Override
        public void onApplySuccess(String s) {
            Log.d("onApplySuccess", s);
            Toast.makeText(context, "更新完成 即将重启", Toast.LENGTH_SHORT).show();
            new Handler().postDelayed(new Runnable() {
                @Override
                public void run() {
                    PackageManager packageManager = context.getPackageManager();
                    Intent intent = packageManager.getLaunchIntentForPackage(context.getPackageName());
                    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                    context.startActivity(intent);
                    AppUtils.exitApp(context);
                    System.exit(0);
                }
            }, 2000);


        }

        @Override
        public void onApplyFailure(String s) {
            Log.d("onApplyFailure", s);
        }

        @Override
        public void onPatchRollback() {

        }
    });

遇到的问题

1.当项目中含有第三方lib库时候,加载补丁会报错

java.lang.UnsatisfiedLinkError: Shared library "/data/app/***-3IhVXmnAlMEQk_hwO6_0Mg==/lib/arm/libc++_shared.so" already opened by ClassLoader

解决办法:第三方库加载前,有tinker加载

TinkerLoadLibrary.loadArmLibrary(getApplication(), "BaiduMapSDK_base_v6_0_0");
TinkerLoadLibrary.loadArmLibrary(getApplication(), "BaiduMapSDK_map_v6_0_0");
TinkerLoadLibrary.loadArmLibrary(getApplication(), "BaiduTraceSDK_v3_0_7");

2.新添加的activity 找不到声明

基础包必须设置supportComponent=true,并且新增Activity的Exported属性必须为false。

后续

如何对多渠道、加固包等做处理,以及少扣绩效