Tinker详解 -- 微信Android 热补丁方案

Tinker详解 – 微信Android 热补丁方案

简介

 

 

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

它主要包括以下几个部分:

  1. gradle编译插件: tinker-patch-gradle-plugin    ★
  2. 核心sdk库: tinker-android-lib                  ★
  3. 非gradle编译用户的命令行版本: tinker-patch-cli.jar

 

方案比较

 

Tinker

Qzone

AndFix

Robust

类替换

yes

yes

no

no

So替换

yes

no

no

no

资源替换

yes

yes

no

no

全平台支持

yes

yes

yes

yes

即时生效

no

no

yes

yes

性能损耗

较小

较大

较小

较小

补丁包大小

较小

较大

一般

一般

开发透明

yes

yes

no

no

复杂度

较低

较低

复杂

复杂

gradle支持

yes

no

no

no

Rom体积

较大

较小

较小

较小

成功率

较高

较高

一般

最高

 

 

总的来说:

  1. AndFix作为native解决方案,首先面临的是稳定性与兼容性问题,更重要的是它无法实现类替换,它是需要大量额外的开发成本的;
  2. Robust兼容性与成功率较高,但是它与AndFix一样,无法新增变量与类只能用做的bugFix方案;
  3. Qzone方案可以做到发布产品功能,但是它主要问题是插桩带来Dalvik的性能问题,以及为了解决Art下内存地址问题而导致补丁包急速增大的。

 

 

Tinker的已知问题

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

  1. Tinker不支持修改AndroidManifest.xml,Tinker不支持新增四大组件
  2. 由于Google Play的开发者条款限制,不建议在GP渠道动态更新代码;
  3. Android N上,补丁对应用启动时间有轻微的影响
  4. 不支持部分三星android-21机型,加载补丁时会主动抛出"TinkerRuntimeException:checkDexInstall failed";
  5. 由于各个厂商的加固实现并不一致,在1.7.6以及之后的版本,tinker不再支持加固的动态更新;
  6. 对于资源替换,不支持修改remoteView。例如transition动画,notification icon以及桌面图标。

 

 

 

 

 

接入相关

添加gradle依赖

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

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

然后在appgradle文件app/build.gradle,我们需要添加tinker的库依赖以及apply tinkergradle插件.

dependencies {
         //可选,用于生成application
         provided('com.tencent.tinker:tinker-android-anno:1.7.7')
    //tinker的核心库
    compile('com.tencent.tinker:tinker-android-lib:1.7.7') 
}
...
...
//apply tinker插件
apply plugin: 'com.tencent.tinker.patch'

 

gradle参数详解

我们将原apk包称为基准apk包,tinkerPatch直接使用基准apk包与新编译出来的apk包做差异,得到最终的补丁包。gradle配置的参数详细解释如下:

参数

默认值

描述

tinkerPatch

全局信息相关的配置项

tinkerEnable

true

是否打开tinker的功能。

oldApk

null

基准apk包的路径,必须输入,否则会报错。

ignoreWarning

false

如果出现以下的情况,并且ignoreWarningfalse,我们将中断编译。因为这些情况可能会导致编译出来的patch包带来风险:
1. minSdkVersion
小于14,但是dexMode的值为"raw";
2.
新编译的安装包出现新增的四大组件(Activity, BroadcastReceiver...)
3.
定义在dex.loader用于加载补丁的类不在main dex;
4.
定义在dex.loader用于加载补丁的类出现修改;
5. resources.arsc
改变,但没有使用applyResourceMapping编译。

useSign

true

在运行过程中,我们需要验证基准apk包与补丁包的签名是否一致,我们是否需要为你签名。

buildConfig

编译相关的配置项

applyMapping

null

可选参数;在编译新的apk时候,我们希望通过保持旧apkproguard混淆方式,从而减少补丁包的大小。这个只是推荐设置,不设置applyMapping也不会影响任何的assemble编译

applyResourceMapping

null

可选参数;在编译新的apk时候,我们希望通过旧apkR.txt文件保持ResId的分配,这样不仅可以减少补丁包的大小,同时也避免由于ResId改变导致remote view异常

tinkerId

null

在运行过程中,我们需要验证基准apk包的tinkerId是否等于补丁包的tinkerId。这个是决定补丁包能运行在哪些基准包上面,一般来说我们可以使用git版本号、versionName等等。

keepDexApply

false

如果我们有多个dex,编译补丁时可能会由于类的移动导致变更增多。若打开keepDexApply模式,补丁包将根据基准包的类分布来编译。

dex

dex相关的配置项

dexMode

jar

只能是'raw'或者'jar' 
对于'raw'模式,我们将会保持输入dex的格式。
对于'jar'模式,我们将会把输入dex重新压缩封装到jar。如果你的minSdkVersion小于14,你必须选择‘jar’模式,而且它更省存储空间,但是验证md5时比'raw'模式耗时。默认我们并不会去校验md5,一般情况下选择jar模式即可。

pattern

[]

需要处理dex路径,支持*?通配符,必须使用'/'分割。路径是相对安装包的,例如assets/...

loader

[]

这一项非常重要,它定义了哪些类在加载补丁包的时候会用到。这些类是通过Tinker无法修改的类,也是一定要放在main dex的类。
这里需要定义的类有:
1.
你自己定义的Application类;
2. Tinker
库中用于加载补丁包的部分类,即com.tencent.tinker.loader.* 
3.
如果你自定义了TinkerLoader,需要将它以及它引用的所有类也加入loader中;
4.
其他一些你不希望被更改的类,例如Sample中的BaseBuildInfo类。这里需要注意的是,这些类的直接引用类也需要加入到loader中。或者你需要将这个类变成非preverify
5. 
使用1.7.6版本之后版本,参数12会自动填写。

lib

lib相关的配置项

pattern

[]

需要处理lib路径,支持*?通配符,必须使用'/'分割。与dex.pattern一致,路径是相对安装包的,例如assets/...

res

res相关的配置项

pattern

[]

需要处理res路径,支持*?通配符,必须使用'/'分割。与dex.pattern一致,路径是相对安装包的,例如assets/...务必注意的是,只有满足pattern的资源才会放到合成后的资源包。

ignoreChange

[]

支持*?通配符,必须使用'/'分割。若满足ignoreChangepattern,在编译时会忽略该文件的新增、删除与修改。 最极端的情况,ignoreChange与上面的pattern一致,即会完全忽略所有资源的修改。

largeModSize

100

对于修改的资源,如果大于largeModSize,我们将使用bsdiff算法。这可以降低补丁包的大小,但是会增加合成时的复杂度。默认大小为100kb

packageConfig

用于生成补丁包中的'package_meta.txt'文件

configField

TINKER_ID, NEW_TINKER_ID

configField("key", "value"), 默认我们自动从基准安装包与新安装包的Manifest中读取tinkerId,并自动写入configField。在这里,你可以定义其他的信息,在运行时可以通过

TinkerLoadResult.getPackageConfigByName得到相应的数值。但是建议直接通过修改代码来实现,例如BuildConfig

sevenZip

7zip路径配置项,执行前提是useSigntrue

zipArtifact

null

例如"com.tencent.mm:SevenZip:1.1.10",将自动根据机器属性获得对应的7za运行文件,推荐使用。

path

7za

系统中的7za路径,例如"/usr/local/bin/7za"path设置会覆盖zipArtifact,若都不设置,将直接使用7za去尝试。

 

 

tinkerPatch task详解

直接使用task:tinkerPatchVariantName(例如tinkerPatchDebugtinkerPatchRelease)即可自动根据Variant选择相应的编译类型,同时它还贴心的为我们完成以下几个操作:

1.   TINKER_ID自动插入AndroidManifestmeta项,输出路径为build/intermediates/tinker_intermediates/AndroidManifest.xml;

2.   如果minifyEnabledtrue,将自动将Tinkerproguard规则添加到proguardFiles中,输出路径为build/intermediates/tinker_intermediates/tinker_proguard.pro这里你不需要将它们拷贝到自己的proguard配置文件中;

3.   如果multiDexEnabledtrue,将自动生成Tinker需要放在主dexkeep规则。在tinker 1.7.6版本之前,你需要手动将生成规则拷贝到自己的multiDexKeepProguard文件中。例如Sample中的multiDexKeepProguardfile("keep_in_main_dex.txt")。在1.7.6版本之后,这里会通过脚本自动处理,无须手动填写。

4.   dexOptionsjumboMode打开。

输出路径为:build/intermediates/tinker_intermediates/tinker_multidexkeep.pro后你可以在build/outputs/tinkerPatch中找到输出的文件。

 

 

 

Flavor打包

有的时候我们希望通过flavor方式打包,在sample中提供了简单的用法事例:

1.通过flavor编译,这个时候我们可以看到bakApk路径是一个按照flavor名称区分的目录;

2.将编译目录路径填写到sampletinkerBuildFlavorDirectory,其他的几个字段不需要填写,这里会自动根据路径拼接;

ext {
    tinkerBuildFlavorDirectory = "${bakPath}/app-1014-13-35-12"
}

3.运行tinkerPatchAllFlavorDebug或者tinkerPatchAllFlavorRelease即可得到所有flavor的补丁包。

 

 

 

输出文件详解

tinkerPatch输出目录build/outputs/tinkerPatch中,我们关心的文件有:

文件名

描述

patch_unsigned.apk

没有签名的补丁包

patch_signed.apk

签名后的补丁包

patch_signed_7zip.apk

签名后并使用7zip压缩的补丁包,也是我们通常使用的补丁包。但正式发布的时候,最好不要以.apk结尾,防止被运营商挟持。

log.txt

在编译补丁包过程的控制台日志

dex_log.txt

在编译补丁包过程关于dex的日志

so_log.txt

在编译补丁包过程关于lib的日志

tinker_result

最终在补丁包的内容,包括diffdexlib以及assets下面的meta文件

resources_out.zip

最终在手机上合成的全量资源apk,你可以在这里查看是否有文件遗漏

resources_out_7z.zip

根据7zip最终在手机上合成的全量资源apk

tempPatchedDexes

DalvikArt平台,最终在手机上合成的完整Dex我们可以在这里查看dex合成的产物。

每次编译结束,我们都应该查看相关日志,清楚最终在补丁包中的文件。尤其是dex的补丁文件,即使是1kdex补丁文件,也会带来合成时的时间损耗以及合成完整dex文件ROM空间体积这两部分影响!

 

 

Tinker 自定义扩展

自定义Application

程序启动时会加载默认的Application类,这导致我们补丁包是无法对它做修改了。如何规避?在这里我们并没有使用类似InstantRun hook Application的方式,而是通过代码框架的方式来避免,这也是为了尽量少的去反射,提升框架的兼容性。

这里我们要实现的是完全将原来的Application类隔离起来,即其他任何类都不能再引用我们自己的Application。我们需要做的其实是以下几个工作:

  1. 将我们自己Application类以及它的继承类的所有代码拷贝到自己的ApplicationLike继承类中,例如SampleApplicationLike。你也可以直接将自己的Application改为继承ApplicationLike;
  2. Application的attachBaseContext方法实现要单独移动到onBaseContextAttached中;
  3. 对ApplicationLike中,引用application的地方改成getApplication();
  4. 对其他引用Application或者它的静态对象与方法的地方,改成引用ApplicationLike的静态对象与方法;

 

 

Application代理类

为了使真正的Application实现可以在补丁包中修改,我们把Appliction类的所有逻辑移动到ApplicationLike代理类中。

-publicclassYourApplicationextendsApplication {
+publicclassSampleApplicationLikeextendsDefaultApplicationLike

 

 

具体实现可参考SampleApplicationLike,其中对Application类的调用可以修改成:

 

publicvoid registerActivityLifecycleCallbacks(Application.ActivityLifecycleCallbacks callback) {
    application.registerActivityLifecycleCallbacks(callback);
}

若你的应用代理ApplicationClassLoaderResource以及AssetsManger,可以使用以下方法设置。

applicationLike.setResources(res);
applicationLike.setClassLoader(classloader);
applicationLike.setTAssets(assets);

事实上,你也可以在你的Application类加入代理,但是在Application中尽量不要引用自己的类,将真正的实现放在外面。

publicclassYourrApplicationextendsApplication {
         ActivityLifecycleCallbacks activityLifecycleCallbacks;
         publicvoidsetTinkerActivityLifecycleCallbacks(ActivityLifecycleCallbacksactivityLifecycleCallbacks) {
                 this.activityLifecycleCallbacks = activityLifecycleCallbacks;
         }

修改你的Application

然后将你的Application类继承TinkerApplication.java除了构造方法之外,你最好不要引入其他的类,这将导致它们无法通过补丁修改。

publicclassSampleApplicationextendsTinkerApplication {
    publicSampleApplication() {
      super(
        //tinkerFlags, tinker支持的类型,dex,library,还是全部都支持!
        ShareConstants.TINKER_ENABLE_ALL,
        //ApplicationLike的实现类,只能传递字符串
        "tinker.sample.android.app.SampleApplicationLike",
        //Tinker的加载器,一般来说用默认的即可
        "com.tencent.tinker.loader.TinkerLoader",
        //tinkerLoadVerifyFlag, 运行加载时是否校验dex,ibresMd5
        false);
    }  
}

 

 

 

具体的数值含义如下:

参数

默认值

描述

tinkerFlags

TINKER_DISABLE

tinker运行时支持的补丁包中的文件类型:
1. ShareConstants.TINKER_DISABLE:
不支持任何类型的文件;
2. ShareConstants.TINKER_DEX_ONLY:
只支持dex文件;
3. ShareConstants.TINKER_LIBRARY_ONLY:
只支持library文件;
4. ShareConstants.TINKER_DEX_AND_LIBRARY:
只支持dexres的修改;
5. ShareConstants.TINKER_ENABLE_ALL:
支持任何类型的文件,也是我们通常的设置的模式。

delegateClassName

"com.tencent.tinker.loader
.app.DefaultApplicationLike"

Application代理类的类名,这里只能使用字符串,不能使用class.getName()

loaderClassName

"com.tencent.tinker.
loader.TinkerLoader"

加载Tinker的主类名,对于特殊需求可能需要使用自己的加载类。需要注意的是:
这个类以及它使用的类都是不能被补丁修改的,并且我们需要将它们加到dex.loader[]
一般来说,我们使用默认即可。

tinkerLoadVerifyFlag

false

由于合成过程中我们已经校验了各个文件的Md5,并将它们存放在/data/data/..目录中。默认每次加载时我们并不会去校验tinker文件的Md5,但是你也可通过开启loadVerifyFlag强制每次加载时校验,但是这会带来一定的时间损耗。

Warning: 这里务必不能写成SampleApplicationLike.class.getName(),只能通过传递字符串的方式。为了减少错误的出现,推荐使用Annotation生成Application

 

 

 

可选的自定义类

Tinker中你可以自定义一些类,它们需要在构造Tinker实例时作为参数传递,在TinkerManagerinstallTinker中,你可以根据自己的需要自定义其中的一些类:

//or you can just use DefaultLoadReporter
LoadReporter loadReporter =newSampleLoadReporter(appLike.getApplication());
//or you can just use DefaultPatchReporter
PatchReporter patchReporter =newSamplePatchReporter(appLike.getApplication());
//or you can just use DefaultPatchListener
PatchListener patchListener =newSamplePatchListener(appLike.getApplication());
//you can set your own upgrade patch if you need
AbstractPatch upgradePatchProcessor =newUpgradePatch();
TinkerInstaller.install(appLike,
         loadReporter, patchReporter, patchListener,
    SampleResultService.class, upgradePatchProcessor);

你也可以使用sampleInstallTinker,即全部使用默认参数。

各个类具体的功能与使用方法如下:

自定义LoadReporter

LoadReporter定义了Tinker在加载补丁时的一些回调,我们为你提供了默认实现DefaultLoadReporter.java.

一般来说, 你可以继承DefaultLoadReporter实现你自己感兴趣的事件回调,例如SampleLoadReporter.java.

我们在sample中也写了一个默认的回调上报例子,可参考SampleTinkerReport.

需要注意的有以下两点

  1. 回调运行在加载的进程,它有可能是各个不一样的进程。我们可以通过tinker.isMainProcess或者tinker.isPatchProcess知道当前是否是主进程,patch补丁合成进程。
  2. 回调发生的时机是我们调用installTinker之后,某些进程可能并不需要installTinker。

现对各个回调作简要的说明:

函数

描述

onLoadResult

这个是无论加载失败或者成功都会回调的接口,它返回了本次加载所用的时间、返回码等信息。默认我们只是简单的输出这个信息,你可以在这里加上监控上报逻辑。

onLoadPatchListenerReceiveFail

所有的补丁合成请求都需要先通过PatchListener的检查过滤。这次检查不通过的回调,它运行在发起请求的进程。默认我们只是打印日志

onLoadPatchVersionChanged

补丁包版本升级的回调,只会在主进程调用。默认我们会杀掉其他所有的进程(保证所有进程代码的一致性),并且删掉旧版本的补丁文件。

onLoadFileNotFound

在加载过程中,发现部分文件丢失的回调。默认若是dexdex优化文件或者lib文件丢失,我们将尝试从补丁包去修复这些丢失的文件。若补丁包或者版本文件丢失,将卸载补丁包。

onLoadFileMd5Mismatch

部分文件的md5meta中定义的不一致。默认我们为了安全考虑,依然会清空补丁。

onLoadPatchInfoCorrupted

patch.info是用来管理补丁包版本的文件,这是info文件损坏的回调。默认我们会卸载补丁包,因为此时我们已经无法恢复了。

onLoadPackageCheckFail

加载过程补丁包的检查失败,这里可以通过错误码区分,例如签名校验失败、tinkerId不一致等原因。默认我们将会卸载补丁包

onLoadException

在加载过程捕捉到异常,十分希望你可以把错误信息反馈给我们。默认我们会直接卸载补丁包

所有的错误码都定义在ShareConstants.javaonLoadPackageCheckFail 的相关错误码解析如下:

错误码

数值

描述

ERROR_PACKAGE_CHECK_SIGNATURE_FAIL

-1

签名校验失败

ERROR_PACKAGE_CHECK_PACKAGE_META_NOT_FOUND

-2

找不到"assets/package_meta.txt"文件

ERROR_PACKAGE_CHECK_DEX_META_CORRUPTED

-3

"assets/dex_meta.txt"信息损坏

ERROR_PACKAGE_CHECK_LIB_META_CORRUPTED

-4

"assets/so_meta.txt"信息损坏

ERROR_PACKAGE_CHECK_APK_TINKER_ID_NOT_FOUND

-5

找不到基准apk AndroidManifest中的TINKER_ID

ERROR_PACKAGE_CHECK_PATCH_TINKER_ID_NOT_FOUND

-6

找不到补丁中"assets/package_meta.txt"中的TINKER_ID

ERROR_PACKAGE_CHECK_TINKER_ID_NOT_EQUAL

-7

基准版本与补丁定义的TINKER_ID不相等

ERROR_PACKAGE_CHECK_RESOURCE_META_CORRUPTED

-8

"assets/res_meta.txt"信息损坏

ERROR_PACKAGE_CHECK_TINKERFLAG_NOT_SUPPORT

-9

tinkerFlag不支持补丁中的某些类型的更改,例如补丁中存在资源更新,但是使用者指定不支持资源类型更新。

onLoadException的错误码具体如下:

错误码

数值

描述

ERROR_LOAD_EXCEPTION_UNKNOWN

-1

没有捕获到的java crash

ERROR_LOAD_EXCEPTION_DEX

-2

在加载dex过程中捕获到的crash

ERROR_LOAD_EXCEPTION_RESOURCE

-3

在加载res过程中捕获到的crash

ERROR_LOAD_EXCEPTION_UNCAUGHT

-4

没有捕获到的非java crash,这个是补丁机制的安全模式

回调中定义的fileType定义如下:

文件类型

数值

描述

TYPE_PATCH_FILE

1

补丁文件

TYPE_PATCH_INFO

2

"patch.info"补丁版本配置文件

TYPE_DEX

3

Dalvik合成全量的Dex文件

TYPE_DEX_OPT

4

odex文件

TYPE_LIBRARY

5

library文件

TYPE_RESOURCE

6

资源文件

加载过程的具体的错误类型与错误码可查看DefaultLoadReporter.java的注释。

对于onLoadPatchVersionChangedonLoadFileNotFound的复写要较为谨慎,因为版本升级杀掉其他进程与文件丢失发起恢复任务,都是我认为比较重要的操作。

自定义PatchReporter

PatchReporter定义了Tinker在修复或者升级补丁时的一些回调,我们为你提供了默认实现DefaultPatchReporter.java.

一般来说, 你可以继承DefaultPatchReporter实现你自己感兴趣的事件回调,例如SamplePatchReporter.java.

需要注意的是:

函数

描述

onPatchResult

这个是无论补丁合成失败或者成功都会回调的接口,它返回了本次合成的类型,时间以及结果等。默认我们只是简单的输出这个信息,你可以在这里加上监控上报逻辑。

onPatchServiceStart

这个是Patch进程启动时的回调,我们可以在这里进行一个统计的工作。

onPatchPackageCheckFail

补丁合成过程对输入补丁包的检查失败,这里可以通过错误码区分,例如签名校验失败、tinkerId不一致等原因。默认我们会删除临时文件。

onPatchVersionCheckFail

patch.info的校验版本合法性校验。若校验失败,默认我们会删除临时文件。

onPatchTypeExtractFail

从补丁包与原始安装包中合成某种类型的文件出现错误,默认我们会删除临时文件。

onPatchDexOptFail

对合成的dex文件提前进行dexopt时出现异常,默认我们会删除临时文件。

onPatchInfoCorrupted

patch.info是用来管理补丁包版本的文件,这是在更新info文件时发生损坏的回调。默认我们会卸载补丁包,因为此时我们已经无法恢复了。

onPatchException

在补丁合成过程捕捉到异常,十分希望你可以把错误信息反馈给我们。默认我们会删除临时文件,并且将tinkerFlag设为不可用。

PatchReporteronPatchPackageCheckFail的错误码与LoadReporter的一致。

自定义PatchListener

PatchListener是用来过滤Tinker收到的补丁包的修复、升级请求,也就是决定我们是不是真的要唤起:patch进程去尝试补丁合成。我们为你提供了默认实现DefaultPatchListener.java

一般来说, 你可以继承DefaultPatchListener并且加上自己的检查逻辑,例如SamplePatchListener.java

若检查成功,我们会调用TinkerPatchService.runPatchService唤起:patch进程,去尝试完成补丁合成操作。反之,会回调检验失败的接口。事实上,你只需要复写patchCheck函数即可。若检查失败,会在LoadReporteronLoadPatchListenerReceiveFail中回调。

publicint patchCheck(String path)

DefaultPatchListener为例,说明默认我们检查的条件,你可以定义自己的错误码,也可以沿用这里的错误码。

错误码

数值

描述

ERROR_PATCH_DISABLE

-1

当前tinkerFlag为不可用状态。

ERROR_PATCH_NOTEXIST

-2

输入的临时补丁包文件不存在。

ERROR_PATCH_RUNNING

-3

当前:patch补丁合成进程正在运行。

ERROR_PATCH_INSERVICE

-4

不能在:patch补丁合成进程,发起补丁的合成请求。

其他

SamplePatchListener里面,我们还检查了当前Rom剩余空间,最大内存,是否是GooglePlay渠道等条件。

自定义AbstractResultService

AbstractResultService:patch补丁合成进程将合成结果返回给主进程的类。我们为你提供了默认实现DefaultTinkerResultService.java

一般来说, 你可以继承DefaultTinkerResultService实现自己的回调,例如SampleResultService.java当然,你也需要在AndroidManifest上添加你的Service

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

默认我们在DefaultTinkerResultService会杀掉:patch进程,假设当前是补丁升级并且成功了,我们会杀掉当前进程,让补丁包更快的生效。若是修复类型的补丁包并且失败了,我们会卸载补丁包。下面对PatchResult的定义做详细说明:

函数

描述

isSuccess

补丁合成操作是否成功。

rawPatchFilePath

原始的补丁包路径。

costTime

本次补丁合成的耗时。

e

本次补丁合成是否出现异常,null为没有异常。

patchVersion

补丁文件的md5, 有可能为空@Nullable

SampleResultService.java中,我们没有立刻杀掉当前进程去应用补丁,而选择在当前应用在退入后台或手机锁屏时这两个时机。你也可以在自杀前,通过发送service或者broadcast inent来尽快重启进程。

自定义TinkerLoader

TinkerLoader是用来加载补丁的核心类,你可以实现自己的加载逻辑。但是一般不建议那么做,如果你一定要你需要保证以下两条规则:

  1. 将你的实现的类以及它用到的所有类都加入到dex.loader中;
  2. 保证上述的类都在main dex中。

只要简单的将loaderClass参数中的"com.tencent.tinker.loader.TinkerLoader",换成你的实现类的名称即可,这里只能传递字符串。

@DefaultLifeCycle(
application=".SampleApplication",                       //application类名
flags=ShareConstants.TINKER_ENABLE_ALL,                 //tinkerFlags
loaderClass="com.tencent.tinker.loader.TinkerLoader")   //loaderClassName, 我们这里使用默认即可!
publicclassSampleApplicationLikeextendsDefaultApplicationLike

Tinker Notification id设置

为了提高TinkerPatchService的进程优先级,我们将它设置为Foreground。对于sdk>18的版本,使用innerService方式使通知栏不会显示。

Warning, 这里占用了id-1119860829.若你的app存在与它相同的id,可以使用以下API重新设置

TinkerPatchService.setTinkerNotificationId(id);
Tinker.with(context).setPatchServiceNotificationId(id);

自定义UpgradePatch

UpgradePatch是用来升级当前补丁包的处理类,一般来说你也不需要复写它。

可以看到整个Tinker框架非常灵活,基本所有的逻辑都放在可复写的类或回调中,你可以轻松的完成自身需要的自定义工作。

 

 

Tinker API概览

我们需要使用的API大约几种在以下几个类中:

函数

描述

TinkerInstaller.java

TinkerInstaller.java封装了一些常用的函数,例如Tinker对象的构建,发起补丁请求以及lib库的加载。

Tinker.java

Tinker.javaTinker库的Manager类,tinker所有的状态、信息都存放在这里。

TinkerLoadResult.java

TinkerLoadResult.java是用来存放加载补丁包时的相关结果,它本身也是Tinker.java的一个成员变量。

TinkerApplicationHelper.java

TinkerApplicationHelper.java封装了一些无需构建Tinker都可调用的函数,一般我们更推荐使用上面的三个类。

TinkerLoadLibrary.java

TinkerLoadLibrary.java封装了一些反射或者加载补丁Library的方法。

TinkerInstaller相关接口

TinkerInstaller封装了一些比较重要的函数,现作简单的说明:

Tinker实例的构建

Tinker类是整个框架的核心,我们需要构建它的单例。其中intentResult存放是我们加载补丁时的数据,它在install之后才将数据赋值给TinkerTinkerLoadResult类。

全部使用默认定义类的构造方法:

publicstaticvoid install(ApplicationLike applicationLike) {
          Tinker.with(tinkerApplication).install(applicationLike.getTinkerResultIntent());
}

若使用了自定义类,可选择多参数的install方法,具体用法可参考SampleApplicationLike

你一定要先install Tinker之后,才能使用Tinker相关的API。不然你只能使用TinkerApplicationHelper.java中的API

发起补丁修复请求

正如之前所说的,所有的补丁升级请求都将会分发到PatchListener去处理。

发起升级补丁请求,即收到一个新的补丁包,多次补丁也是调用下面这个接口:

publicstaticvoid onReceiveUpgradePatch(Context context, String patchLocation) {
    Tinker.with(context).getPatchListener().onPatchReceived(patchLocation);
}

Library库的加载

####不使用Hack的方式更新的Library库文件我们帮你保存在tinker下面的子目录下,但是我们并没有为你区分abi(部分手机判断不准确)。所以若想加载最新的库,你有两种方法,第一个是直接尝试去Tinker更新的库文件中加载,第二个参数是库文件相对安装包的路径。

TinkerLoadLibrary.loadLibraryFromTinker(getApplicationContext(), "assets/x86", "libstlport_shared");

但是我们更推荐的是,使用TinkerInstaller.loadLibrary接管你所有的库加载,它会自动先尝试去Tinker中的库文件中加载,但是需要注意的是当前这种方法只支持lib/armeabi目录下的库文件

//load lib/armeabi library
TinkerLoadLibrary.loadArmLibrary(getApplicationContext(), "libstlport_shared");
//load lib/armeabi-v7a library
TinkerLoadLibrary.loadArmV7Library(getApplicationContext(), "libstlport_shared");

若存在Tinker还没install之前调用加载补丁中的Library库,可使用TinkerApplicationHelper.java的接口

//load lib/armeabi library
TinkerApplicationHelper.loadArmLibrary(tinkerApplicationLike, "libstlport_shared");
//load lib/armeabi-v7a library
TinkerApplicationHelper.loadArmV7Library(tinkerApplicationLike, "libstlport_shared");

若想对第三方代码的库文件更新,可先使用TinkerLoadLibrary.load*Library对第三方库做提前的加载!更多使用方法可参考MainActivity.java

####使用Hack的方式以上使用方式似乎并不能做到开发者透明,这是因为我们想尽量少的去hook系统框架减少兼容性的问题。Tinker也提供了一键反射Library Path的方式供大家选择:

// tinker library中的armeabi注册到系统的library path中。
TinkerLoadLibrary.installNavitveLibraryABI(context, "armeabi");

当然,当前手机系统的abi 需要大家自行判断传入即可,这样我们就无需再对library的加载做任何的介入。

设置LogIml实现

你可以设置自己的Log输出实现:

publicstaticvoid setLogIml(TinkerLog.LogImp imp) {
         TinkerLog.setLogImp(imp);
}

Tinker类相关接口

Tinker类是整个库的核心,基本所有的补丁相关的信息我们都可以在这里获取到。TinkerApplication也有一些获取tinkerFlag的相关API,它们可以在Tinker未被初始化前使用。

获取单例的方法

获取单例的方法非常简单:

Tinker manager =Tinker.with(context);

获取加载状态

loaded成员变量是标记是否有补丁加载成功的标记,只有它为true时,才能保证TinkerLoadResult的各个变量非空。

boolean isLoaded =Tinker.with(context).isTinkerLoaded();
boolean isInstalled =Tinker.with(context).isTinkerInstalled();

获得加载的结果,也就是TinkerLoadResult的实例。它是有可能为空的,使用它请先确保loadedtrue

TinkerLoadResult loadResult =Tinker.with(context).getTinkerLoadResultIfPresent();

清除补丁

当补丁出现异常或者某些情况,我们可能希望清空全部补丁,调用方法为:

Tinker.with(context).cleanPatch();

当然我们也可以选择卸载某个版本的补丁文件:

Tinker.with(context).cleanPatchByVersion();

在升级版本时我们也无须手动去清除补丁,框架已经为我们做了这件事情。需要注意的是,在补丁已经加载的前提下清除补丁,可能会引起crash。这个时候更好重启一下所有的进程。

其他API这里不再一一概述,请大家自行翻阅Tinker.java

TinkerLoadResult相关接口

因为加载补丁是在TinkerInstall之前的,我们将加载的结果保存在intent中,然后在Tinkerinstall方法中恢复这些结果。这里包括补丁的所有信息,例如加载的dexlibrary,我们定义的package config以及各个文件的目录等。但是需要注意的是,这里面的变量大多数是nullable。若Tinkerloadedtrue,除了dexeslibs(要看补丁包里面是否真的有),其他变量可以确保非空。

获取packageConfig

publicString getPackageConfigByName(String name) {
    if (packageConfig !=null) {
        return packageConfig.get(name);
    }
    returnnull;
}

这里需要注意的是,检查dexMd5值需要使用SharePatchFileUtil.verifyDexFileMd5方法,这是由于dex有可能是被我们重新打包成jar模式。

//获得基准包的tinkerId
String oldTinkerId =Tinker.with(context).getTinkerLoadResultIfPresent().getTinkerID();
//获得补丁包的tinkerId
String newTinkerId =Tinker.with(context).getTinkerLoadResultIfPresent().getNewTinkerID();

更多接口请参考TinkerLoadResult.java

TinkeApplicationHelp相关接口

在有些时候,你可能想在更后的时机才去installTinker,甚至在某些进程永远也不会去做这个动作。那样你可以通过TinkerApplicationHelper.java来获得一些当前加载的信息。

基本所有的信息都可以在这里获得,但是由于这里是直接读取结果intent

 

 

 

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值