热修复——Tinker 的集成与使用

热修复——Tinker 的集成与使用_第1张图片

作者 | CSDN_LQR

地址 | https://juejin.im/post/5a27bdaf6fb9a044fa19bcfc

声明 | 本文是 CSDN_LQR 原创,已获授权发布,未经原作者允许请勿转载



简述

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


上面是Tinker官方Wiki的原话,意思嘛相信大家都看得明白,但注意啦,它并没有说Tinker可以让补丁实时生效(也叫无感知更新),它必须在打上补丁后重启App(重启进程),补丁才会发挥作用,这跟阿里的热修复方案有着本质的区别。在开始集成Tinker之前,我们有必要了解清楚,Tinker有那些不足,下面是Tinker的已知问题:


  1. Tinker不支持修改AndroidManifest.xml,Tinker不支持新增四大组件(1.9.0支持新增非export的Activity);

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

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

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

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


上述不足是由于原理与系统限制,我们在编程中要清楚这些,尽量避免以上问题的出现。


尽管Tinker有着这些“小缺点”,但也丝毫不影响Tinker在国内众多热修复方案中的地位,一方面Tinker是开源的(这意味着Tinker本身免费),另一方面则是Tinker已运行在微信的数亿Android设备上(说明该方案相当稳定)。下面开始进行对Tinker的集成与使用。


Tinker组件依赖

1、在项目的build.gradle中:

添加tinker-patch-gradle-plugin的依赖

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


2、在app的gradle文件(app/build.gradle)中:

需要注意一点,Tinker需要使用到MulitDex,原话在Bugly文档的热更新API接口部分https://bugly.qq.com/docs/user-guide/api-hotfix/?v=20170504092424

1)添加tinker的库依赖

Gradle版本小于2.3的这么写:

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


Gradle版本大等于2.3的这么写:

dependencies {
   implementation "com.android.support:multidex:1.0.1"
   //tinker的核心库
   implementation("com.tencent.tinker:tinker-android-lib:1.9.1") { changing = true }
   //可选,用于生成application类
   annotationProcessor("com.tencent.tinker:tinker-android-anno:1.9.1") { changing = true }
   compileOnly("com.tencent.tinker:tinker-android-anno:1.9.1") { changing = true }
}


2)开启multiDex

defaultConfig {
       ...
       multiDexEnabled true
}


3)应用tinker的gradle插件

这部分可先不管,在第三部分《Tinker的配置及任务》的第2节《配置Tinker与任务》中会添加。可跳过这部分继续往下看。

//apply tinker插件
apply plugin: 'com.tencent.tinker.patch



Tinker的配置及任务

1、开启支持大工程模式

Tinker文档中推荐将jumboMode设置为true。

android {
   dexOptions {
       // 支持大工程模式
       jumboMode = true
   }
   ...
}


2、配置Tinker与任务

将下面的配置全部复制粘贴到app的gradle文件(app/build.gradle)末尾,内容很多,但现在只需要看懂bakPath与ext括号内的东东就好了。

// Tinker配置与任务
def bakPath = file("${buildDir}/bakApk/")
ext {
   // 是否使用Tinker(当你的项目处于开发调试阶段时,可以改为false)
   tinkerEnabled = true
   // 基础包文件路径(名字这里写死为old-app.apk。用于比较新旧app以生成补丁包,不管是debug还是release编译)
   tinkerOldApkPath = "${bakPath}/old-app.apk"
   // 基础包的mapping.txt文件路径(用于辅助混淆补丁包的生成,一般在生成release版app时会使用到混淆,所以这个mapping.txt文件一般只是用于release安装包补丁的生成)
   tinkerApplyMappingPath = "${bakPath}/old-app-mapping.txt"
   // 基础包的R.txt文件路径(如果你的安装包中资源文件有改动,则需要使用该R.txt文件来辅助生成补丁包)
   tinkerApplyResourcePath = "${bakPath}/old-app-R.txt"
   //only use for build all flavor, if not, just ignore this field
   tinkerBuildFlavorDirectory = "${bakPath}/flavor"
}
def getOldApkPath() {
   return hasProperty("OLD_APK") ? OLD_APK : ext.tinkerOldApkPath
}


其中,有几点配置在这里说明一下,方便理解后续的操作(当tinkerEnabled = true的情况下):

  • app的生成目录是:主Module(一般是名为app)/build/bakApk文件夹。

  • 补丁包的生成路径:主Module(一般是名为app)/build/outputs/apk/tinkerPatch/debug/patch_signed_7zip.apk。

  • 基础包的名字:old-app.apk,放于bakApk文件夹下。

  • 基础包的mapping.txt和R.txt文件一般在编译release签名的apk时才会用到。

  • 在用到mapping.txt文件时,需要重命名为old-app-mapping.txt,放于bakApk文件夹下。

  • 在用到R.txt文件时,需要重命名为old-app-R.txt,放于bakApk文件夹下。


对于mapping.txt和R.txt文件,在配置中有说明,请回配置中仔细看。
上面只是我项目中的配置,这些其实都是可以自定义的,建议在搞清楚配置内容之后再去自定义修改。


什么是基础包??


基础包就是已经上架的apk文件(假设是1.0版本)。这其实很好理解,在新版本的App上架之前(假设是2.0版本),我们会用到Tinker来修复1.0版App中存在的bug,这时就需要用到Tinker来产生补丁包文件,而补丁包文件的本质,就是修复好Bug的App与1.0版本App之间的文件差异。在2.0版本上架之前,我们可能会多次产生新的补丁包,用于修复在用户手机上的1.0版App,所以补丁包必须以1.0版App作为参考标准,也就是说用户手机上的app就是基础包,即当前应用市场上的apk文件(前面说的1.0版本)。


Tinker 封装与拓展

1、拷贝文件

将Demo中提供的tinker包下的所有文件及文件夹都拷贝到自己项目中。

热修复——Tinker 的集成与使用_第2张图片


这些文件其实就是Tinker官方Demo中的文件完全复制过来的,只是多加了一些注释。


简单说明下,这几个文件的作用:

  • SampleUncaughtExceptionHandler:Tinker的全局异常捕获器。

  • MyLogImp:Tinker的日志输出实现类。

  • SampleLoadReporter:加载补丁时的一些回调。

  • SamplePatchListener:过滤Tinker收到的补丁包的修复、升级请求。

  • SamplePatchReporter:修复或者升级补丁时的一些回调。

  • SampleTinkerReport:修复结果(成功、冲突、失败等)。

  • SampleResultService::patch补丁合成进程将合成结果返回给主进程的类。

  • TinkerManager:Tinker管理器(安装、初始化Tinker)。

  • TinkerUtils:拓展补丁条件判定、锁屏或后台时应用重启功能的工具类。


这些只是对Tinker功能的拓展和封装罢了,都是可选的,但这些文件对项目的功能完善会有所帮助,建议加入到自己的项目中。
如果你仅仅只是为了修复bug,而不做过多的工作(如:上传打补丁信息到服务器等),则无须理会这些文件的作用,当然你也可以自己封装。


对于这些自定义类及错误码的详细说明,请参考:「Tinker官方Wiki:可选的自定义类」。


2、清单文件中添加服务

前面添加的文件中,有一个SampleResultService文件,是四大组件之一,所以必须在清单文件中声明。


<service
   android:name="com.lqr.tinker.service.SampleResultService"
   android:exported="false"/>


编写Application的代理类

Tinker表示,Application无法动态修复,所以有两种选择:

  1. 使用「继承TinkerApplication + DefaultApplicationLike」。

  2. 使用「DefaultLifeCycle注解 + DefaultApplicationLike」。


当然,如果你觉得你自定义的Application不会用到热修复,可无视这部分;
但下方代码中的initTinker()方法记得要拷贝到你项目中,用于初始化Tinker。


第1种方式感觉比较鸡肋,这里使用第2种(Tinker官方推荐的方式):「DefaultLifeCycle注解 + TinkerApplicationLike」,DefaultLifeCycle注解生成Application,下面就来编写Application的代理类:


1、编写TinkerApplicationLike

将下方的代码拷贝到项目中,注释简单明了,不多解释:

@SuppressWarnings("unused")
@DefaultLifeCycle(application = "com.lqr.tinker.MyApplication",// application类名。只能用字符串,这个MyApplication文件是不存在的,但可以在AndroidManifest.xml的application标签上使用(name)
       flags = ShareConstants.TINKER_ENABLE_ALL,// tinkerFlags
       loaderClass = "com.tencent.tinker.loader.TinkerLoader",//loaderClassName, 我们这里使用默认即可!(可不写)
       loadVerifyFlag = false)//tinkerLoadVerifyFlag
public class TinkerApplicationLike extends DefaultApplicationLike {
   private Application mApplication;
   private Context mContext;
   private Tinker mTinker;
   // 固定写法
   public TinkerApplicationLike(Application application, int tinkerFlags, boolean tinkerLoadVerifyFlag, long applicationStartElapsedTime, long applicationStartMillisTime, Intent tinkerResultIntent) {
       super(application, tinkerFlags, tinkerLoadVerifyFlag, applicationStartElapsedTime, applicationStartMillisTime, tinkerResultIntent);
   }
   // 固定写法
   @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
   public void registerActivityLifecycleCallbacks(Application.ActivityLifecycleCallbacks callback) {
       getApplication().registerActivityLifecycleCallbacks(callback);
   }
   @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
   @Override
   public void onBaseContextAttached(Context base) {
       super.onBaseContextAttached(base);
       mApplication = getApplication();
       mContext = getApplication();
       initTinker(base);
       // 可以将之前自定义的Application中onCreate()方法所执行的操作搬到这里...
   }
   private void initTinker(Context base) {
       // tinker需要你开启MultiDex
       MultiDex.install(base);
       TinkerManager.setTinkerApplicationLike(this);
       // 设置全局异常捕获
       TinkerManager.initFastCrashProtect();
       //开启升级重试功能(在安装Tinker之前设置)
       TinkerManager.setUpgradeRetryEnable(true);
       //设置Tinker日志输出类
       TinkerInstaller.setLogIml(new MyLogImp());
       //安装Tinker(在加载完multiDex之后,否则你需要将com.tencent.tinker.**手动放到main dex中)
       TinkerManager.installTinker(this);
       mTinker = Tinker.with(getApplication());
   }
}


2、搬运自定义Application中的操作

把项目中在自定义Application的操作移到TinkerApplicationLike的onCreate()或onBaseContextAttached()方法中。

public class TinkerApplicationLike extends DefaultApplicationLike {
   ...
   @Override
   public void onCreate() {
       super.onCreate();
       // 将之前自定义的Application中onCreate()方法所执行的操作搬到这里...
   }
   @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
   @Override
   public void onBaseContextAttached(Context base) {
       super.onBaseContextAttached(base);
       mApplication = getApplication();
       mContext = getApplication();
       initTinker(base);
       // 或搬到这里...
   }
}


3、清单文件中注册

将@DefaultLifeCycle中application对应的值,即"com.lqr.tinker.MyApplication",赋值给清单文件的application标签的name属性,如下:

    android:name="com.lqr.tinker.MyApplication"
   android:allowBackup="true"
   android:icon="@mipmap/ic_launcher"
   android:label="@string/app_name"
   android:roundIcon="@mipmap/ic_launcher_round"
   android:supportsRtl="true"
   android:theme="@style/AppTheme">
   ...
</application>


注意:
此时name属性会报红,因为项目源码中根本不存在MyApplication.java文件,但不必担心,因为它是动态生成的,Build一下项目就好了,不管它也无所谓。


对于Application代理类的详细说明,请参考:「Tinker官方Wiki:Application代理类」。


常用 API

到这里就已经集成好Tinker了,但只是本地集成而已,下面就来进行补丁包的制作,并让心爱的app打上补丁吧。


服务端下发补丁包到app的文章之后会陆续发布更新。


现在来了解下代码中会用到的几个Tinker的重要API。

1、请求打补丁

TinkerInstaller.onReceiveUpgradePatch(context, 补丁包的本地路径);

2、卸载补丁

Tinker.with(getApplicationContext()).cleanPatch();// 卸载所有的补丁
Tinker.with(getApplicationContext()).cleanPatchByVersion(版本号)// 卸载指定版本的补丁


3、杀死应用的其他进程

ShareTinkerInternals.killAllOtherProcess(getApplicationContext());

4、Hack方式修复so

TinkerLoadLibrary.installNavitveLibraryABI(this, abi);

abi:cpu架构类型

5、非Hack方式修复so


TinkerLoadLibrary.loadLibraryFromTinker(getApplicationContext(), "lib/" + abi, so库的模块名); // 加载任意abi库
TinkerLoadLibrary.loadArmLibrary(getApplicationContext(), so库的模块名); // 只适用于加载armeabi库
TinkerLoadLibrary.loadArmV7Library(getApplicationContext(), so库的模块名); // 只适用于加载armeabi-v7a库


loadArmLibrary()与loadArmV7Library()本质是调用了loadLibraryFromTinker(),有兴趣的可以查看下源码。


对于Tinker所有API的详细说明,请参考:「Tinker官方Wiki:Tinker-API概览」。


测试

因为布局简单且不是重点,这里就给出一张Demo的运行图片,剩下的就靠想像了。

热修复——Tinker 的集成与使用_第3张图片


...


字数有限制,测试剩余内容和下面两大部分请点击阅读原文查看


打包

其他


github 地址

https://github.com/GitLqr/HotFixDemo


Demo中的Module说明:

  1. app:热修复原理Demo

  2. tinker-local:本地集成Tinker热修复Demo

  3. jnitest:生成简单so文件的Demo


?


与之相关

1 仿房产销冠 APP 销控表界面-多 RecyclerView 同步滚动

2 微信 Tinker 在 Android 中集成以及使用



热修复——Tinker 的集成与使用_第4张图片

你可能感兴趣的:(热修复——Tinker 的集成与使用)