Tinker是微信官方的Android热补丁解决方案,它支持动态下发代码、So库以及资源,让应用能够在不需要重新安装的情况下实现更新。当然,你也可以使用Tinker来更新你的插件。
它主要包括以下几个部分:
|
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体积 |
较大 |
较小 |
较小 |
较小 |
成功率 |
较高 |
较高 |
一般 |
最高 |
总的来说:
由于原理与系统限制,Tinker有以下已知问题:
在项目的build.gradle中,添加tinker-patch-gradle-plugin
的依赖
buildscript {
dependencies {
classpath ('com.tencent.tinker:tinker-patch-gradle-plugin:1.7.7')
}
}
然后在app的gradle文件app/build.gradle,我们需要添加tinker的库依赖以及apply tinker的gradle插件.
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'
我们将原apk包称为基准apk包,tinkerPatch直接使用基准apk包与新编译出来的apk包做差异,得到最终的补丁包。gradle配置的参数详细解释如下:
参数 |
默认值 |
描述 |
|
全局信息相关的配置项 |
|
tinkerEnable |
true |
是否打开tinker的功能。 |
oldApk |
null |
基准apk包的路径,必须输入,否则会报错。 |
ignoreWarning |
false |
如果出现以下的情况,并且ignoreWarning为false,我们将中断编译。因为这些情况可能会导致编译出来的patch包带来风险: |
useSign |
true |
在运行过程中,我们需要验证基准apk包与补丁包的签名是否一致,我们是否需要为你签名。 |
|
编译相关的配置项 |
|
applyMapping |
null |
可选参数;在编译新的apk时候,我们希望通过保持旧apk的proguard混淆方式,从而减少补丁包的大小。这个只是推荐设置, |
applyResourceMapping |
null |
可选参数;在编译新的apk时候,我们希望通过旧apk的 |
tinkerId |
null |
在运行过程中,我们需要验证基准apk包的tinkerId是否等于补丁包的tinkerId。这个是决定补丁包能运行在哪些基准包上面,一般来说我们可以使用git版本号、versionName等等。 |
keepDexApply |
false |
如果我们有多个dex,编译补丁时可能会由于类的移动导致变更增多。若打开 |
|
dex相关的配置项 |
|
dexMode |
jar |
只能是'raw'或者'jar'。 |
pattern |
[] |
需要处理dex路径,支持*、?通配符,必须使用'/'分割。路径是相对安装包的,例如assets/... |
loader |
[] |
这一项非常重要,它定义了哪些类在加载补丁包的时候会用到。这些类是通过Tinker无法修改的类,也是一定要放在main dex的类。 |
|
lib相关的配置项 |
|
pattern |
[] |
需要处理lib路径,支持*、?通配符,必须使用'/'分割。与dex.pattern一致,路径是相对安装包的,例如assets/... |
|
res相关的配置项 |
|
pattern |
[] |
需要处理res路径,支持*、?通配符,必须使用'/'分割。与dex.pattern一致,路径是相对安装包的,例如assets/..., |
ignoreChange |
[] |
支持*、?通配符,必须使用'/'分割。若满足ignoreChange的pattern,在编译时会忽略该文件的新增、删除与修改。 最极端的情况,ignoreChange与上面的pattern一致,即会完全忽略所有资源的修改。 |
largeModSize |
100 |
对于修改的资源,如果大于largeModSize,我们将使用bsdiff算法。这可以降低补丁包的大小,但是会增加合成时的复杂度。默认大小为100kb |
|
用于生成补丁包中的'package_meta.txt'文件 |
|
configField |
TINKER_ID, NEW_TINKER_ID |
configField("key", "value"), 默认我们自动从基准安装包与新安装包的Manifest中读取tinkerId,并自动写入configField。在这里,你可以定义其他的信息,在运行时可以通过 TinkerLoadResult.getPackageConfigByName得到相应的数值。但是建议直接通过修改代码来实现,例如BuildConfig。 |
|
7zip路径配置项,执行前提是useSign为true |
|
zipArtifact |
null |
例如"com.tencent.mm:SevenZip:1.1.10",将自动根据机器属性获得对应的7za运行文件,推荐使用。 |
path |
7za |
系统中的7za路径,例如"/usr/local/bin/7za"。path设置会覆盖zipArtifact,若都不设置,将直接使用7za去尝试。 |
直接使用task:tinkerPatchVariantName
(例如tinkerPatchDebug、tinkerPatchRelease)即可自动根据Variant选择相应的编译类型,同时它还贴心的为我们完成以下几个操作:
1. 将TINKER_ID自动插入AndroidManifest的meta项,输出路径为build/intermediates/tinker_intermediates/AndroidManifest.xml;
2. 如果minifyEnabled为true,将自动将Tinker的proguard规则添加到proguardFiles中,输出路径为build/intermediates/tinker_intermediates/tinker_proguard.pro,这里你不需要将它们拷贝到自己的
proguard
配置文件中
;
3. 如果multiDexEnabled为true,将自动生成Tinker需要放在主dex的keep规则。在tinker 1.7.6版本之前,你需要手动将生成规则拷贝到自己的
multiDexKeepProguard
文件中
。例如Sample中的multiDexKeepProguardfile("keep_in_main_dex.txt")
。在1.7.6版本之后,这里会通过脚本自动处理,无须手动填写。
4. 把dexOptions的jumboMode打开。
输出路径为:build/intermediates/tinker_intermediates/tinker_multidexkeep.pro。后你可以在build/outputs/tinkerPatch
中找到输出的文件。
有的时候我们希望通过flavor方式打包,在sample中提供了简单的用法事例:
1.通过flavor编译,这个时候我们可以看到bakApk路径是一个按照flavor名称区分的目录;
2.将编译目录路径填写到sample中tinkerBuildFlavorDirectory
,其他的几个字段不需要填写,这里会自动根据路径拼接;
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压缩的补丁包,也是我们通常使用的补丁包。但正式发布的时候,最好不要以 |
log.txt |
在编译补丁包过程的控制台日志 |
dex_log.txt |
在编译补丁包过程关于dex的日志 |
so_log.txt |
在编译补丁包过程关于lib的日志 |
tinker_result |
最终在补丁包的内容,包括diff的dex、lib以及assets下面的meta文件 |
resources_out.zip |
最终在手机上合成的全量资源apk,你可以在这里查看是否有文件遗漏 |
resources_out_7z.zip |
根据7zip最终在手机上合成的全量资源apk |
tempPatchedDexes |
在Dalvik与Art平台,最终在手机上合成的完整Dex,我们可以在这里查看dex合成的产物。 |
每次编译结束,我们都应该查看相关日志,清楚最终在补丁包中的文件。尤其是dex的补丁文件,即使是1k的dex补丁文件,也会带来合成时的时间损耗以及合成完整dex文件ROM空间体积这两部分影响!
程序启动时会加载默认的Application类,这导致我们补丁包是无法对它做修改了。如何规避?在这里我们并没有使用类似InstantRun hook Application
的方式,而是通过代码框架的方式来避免,这也是为了尽量少的去反射,提升框架的兼容性。
这里我们要实现的是完全将原来的Application类隔离起来,即其他任何类都不能再引用我们自己的Application。我们需要做的其实是以下几个工作:
attachBaseContext
方法实现要单独移动到onBaseContextAttached
中;getApplication()
;
为了使真正的Application实现可以在补丁包中修改,我们把Appliction类的所有逻辑移动到ApplicationLike代理类中。
-publicclassYourApplicationextendsApplication {
+publicclassSampleApplicationLikeextendsDefaultApplicationLike
具体实现可参考SampleApplicationLike,其中对Application类的调用可以修改成:
publicvoid registerActivityLifecycleCallbacks(Application.ActivityLifecycleCallbacks callback) {
application.registerActivityLifecycleCallbacks(callback);
}
若你的应用代理Application的ClassLoader、Resource以及AssetsManger,可以使用以下方法设置。
applicationLike.setResources(res);
applicationLike.setClassLoader(classloader);
applicationLike.setTAssets(assets);
事实上,你也可以在你的Application类加入代理,但是在Application中尽量不要引用自己的类,将真正的实现放在外面。
publicclassYourrApplicationextendsApplication {
ActivityLifecycleCallbacks activityLifecycleCallbacks;
publicvoidsetTinkerActivityLifecycleCallbacks(ActivityLifecycleCallbacksactivityLifecycleCallbacks) {
this.activityLifecycleCallbacks = activityLifecycleCallbacks;
}
然后将你的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与,ib与res的Md5
false);
}
}
具体的数值含义如下:
参数 |
默认值 |
描述 |
tinkerFlags |
TINKER_DISABLE |
tinker运行时支持的补丁包中的文件类型: |
delegateClassName |
"com.tencent.tinker.loader |
Application代理类的类名,这里只能使用字符串,不能使用class.getName()。 |
loaderClassName |
"com.tencent.tinker. |
加载Tinker的主类名,对于特殊需求可能需要使用自己的加载类。需要注意的是: |
tinkerLoadVerifyFlag |
false |
由于合成过程中我们已经校验了各个文件的Md5,并将它们存放在/data/data/..目录中。默认每次加载时我们并不会去校验tinker文件的Md5,但是你也可通过开启loadVerifyFlag强制每次加载时校验,但是这会带来一定的时间损耗。 |
Warning: 这里务必不能写成SampleApplicationLike.class.getName(),只能通过传递字符串的方式。为了减少错误的出现,推荐使用Annotation生成Application类
在Tinker中你可以自定义一些类,它们需要在构造Tinker实例时作为参数传递,在TinkerManager的installTinker
中,你可以根据自己的需要自定义其中的一些类:
//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类定义了Tinker在加载补丁时的一些回调,我们为你提供了默认实现DefaultLoadReporter.java.
一般来说, 你可以继承DefaultLoadReporter实现你自己感兴趣的事件回调,例如SampleLoadReporter.java.
我们在sample中也写了一个默认的回调上报例子,可参考SampleTinkerReport.
需要注意的有以下两点:
installTinker
之后,某些进程可能并不需要installTinker。现对各个回调作简要的说明:
函数 |
描述 |
|
这个是无论加载失败或者成功都会回调的接口,它返回了本次加载所用的时间、返回码等信息。默认我们只是简单的输出这个信息,你可以在这里加上监控上报逻辑。 |
|
所有的补丁合成请求都需要先通过PatchListener的检查过滤。这次检查不通过的回调, |
|
补丁包版本升级的回调,只会在主进程调用。默认我们会杀掉其他所有的进程(保证所有进程代码的一致性),并且删掉旧版本的补丁文件。 |
|
在加载过程中,发现部分文件丢失的回调。默认若是dex,dex优化文件或者lib文件丢失,我们将尝试从补丁包去修复这些丢失的文件。若补丁包或者版本文件丢失,将卸载补丁包。 |
onLoadFileMd5Mismatch |
部分文件的md5与meta中定义的不一致。默认我们为了安全考虑,依然会清空补丁。 |
onLoadPatchInfoCorrupted |
patch.info是用来管理补丁包版本的文件,这是info文件损坏的回调。默认我们会卸载补丁包,因为此时我们已经无法恢复了。 |
onLoadPackageCheckFail |
加载过程补丁包的检查失败,这里可以通过错误码区分,例如签名校验失败、tinkerId不一致等原因。默认我们将会卸载补丁包 |
|
在加载过程捕捉到异常, |
所有的错误码都定义在ShareConstants.java,onLoadPackageCheckFail
的相关错误码解析如下:
错误码 |
数值 |
描述 |
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的注释。
对于onLoadPatchVersionChanged与onLoadFileNotFound的复写要较为谨慎,因为版本升级杀掉其他进程与文件丢失发起恢复任务,都是我认为比较重要的操作。
PatchReporter类定义了Tinker在修复或者升级补丁时的一些回调,我们为你提供了默认实现DefaultPatchReporter.java.
一般来说, 你可以继承DefaultPatchReporter实现你自己感兴趣的事件回调,例如SamplePatchReporter.java.
需要注意的是:
函数 |
描述 |
|
这个是无论补丁合成失败或者成功都会回调的接口,它返回了本次合成的类型,时间以及结果等。默认我们只是简单的输出这个信息,你可以在这里加上监控上报逻辑。 |
|
这个是Patch进程启动时的回调,我们可以在这里进行一个统计的工作。 |
onPatchPackageCheckFail |
补丁合成过程对输入补丁包的检查失败,这里可以通过错误码区分,例如签名校验失败、tinkerId不一致等原因。默认我们会删除临时文件。 |
onPatchVersionCheckFail |
对patch.info的校验版本合法性校验。若校验失败,默认我们会删除临时文件。 |
onPatchTypeExtractFail |
从补丁包与原始安装包中合成某种类型的文件出现错误,默认我们会删除临时文件。 |
onPatchDexOptFail |
对合成的dex文件提前进行dexopt时出现异常,默认我们会删除临时文件。 |
onPatchInfoCorrupted |
patch.info是用来管理补丁包版本的文件,这是在更新info文件时发生损坏的回调。默认我们会卸载补丁包,因为此时我们已经无法恢复了。 |
|
在补丁合成过程捕捉到异常, |
PatchReporter中onPatchPackageCheckFail的错误码与LoadReporter的一致。
PatchListener类是用来过滤Tinker收到的补丁包的修复、升级请求,也就是决定我们是不是真的要唤起:patch进程去尝试补丁合成。我们为你提供了默认实现DefaultPatchListener.java。
一般来说, 你可以继承DefaultPatchListener并且加上自己的检查逻辑,例如SamplePatchListener.java。
若检查成功,我们会调用TinkerPatchService.runPatchService
唤起:patch进程,去尝试完成补丁合成操作。反之,会回调检验失败的接口。事实上,你只需要复写patchCheck
函数即可。若检查失败,会在LoadReporter的onLoadPatchListenerReceiveFail中回调。
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类是: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类是用来加载补丁的核心类,你可以实现自己的加载逻辑。但是一般不建议那么做
,如果你一定要你需要保证以下两条规则:
只要简单的将loaderClass参数中的"com.tencent.tinker.loader.TinkerLoader",换成你的实现类的名称即可,这里只能传递字符串。
@DefaultLifeCycle(
application=".SampleApplication", //application类名
flags=ShareConstants.TINKER_ENABLE_ALL, //tinkerFlags
loaderClass="com.tencent.tinker.loader.TinkerLoader") //loaderClassName, 我们这里使用默认即可!
publicclassSampleApplicationLikeextendsDefaultApplicationLike
为了提高TinkerPatchService的进程优先级,我们将它设置为Foreground
。对于sdk>18的版本,使用innerService方式使通知栏不会显示。
Warning, 这里占用了id为-1119860829.若你的app存在与它相同的id,可以使用以下API重新设置
TinkerPatchService.setTinkerNotificationId(id);
Tinker.with(context).setPatchServiceNotificationId(id);
UpgradePatch类是用来升级当前补丁包的处理类,一般来说你也不需要复写它。
可以看到整个Tinker框架非常灵活,基本所有的逻辑都放在可复写的类或回调中,你可以轻松的完成自身需要的自定义工作。
我们需要使用的API大约几种在以下几个类中:
函数 |
描述 |
TinkerInstaller.java |
TinkerInstaller.java封装了一些常用的函数,例如Tinker对象的构建,发起补丁请求以及lib库的加载。 |
Tinker.java |
Tinker.java是Tinker库的Manager类,tinker所有的状态、信息都存放在这里。 |
TinkerLoadResult.java |
TinkerLoadResult.java是用来存放加载补丁包时的相关结果,它本身也是Tinker.java的一个成员变量。 |
TinkerApplicationHelper.java |
TinkerApplicationHelper.java封装了一些无需构建Tinker都可调用的函数,一般我们更推荐使用上面的三个类。 |
TinkerLoadLibrary.java |
TinkerLoadLibrary.java封装了一些反射或者加载补丁Library的方法。 |
TinkerInstaller封装了一些比较重要的函数,现作简单的说明:
Tinker类是整个框架的核心,我们需要构建它的单例。其中intentResult存放是我们加载补丁时的数据,它在install之后才将数据赋值给Tinker与TinkerLoadResult类。
全部使用默认定义类的构造方法:
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);
}
####不使用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的加载做任何的介入。
你可以设置自己的Log输出实现:
publicstaticvoid setLogIml(TinkerLog.LogImp imp) {
TinkerLog.setLogImp(imp);
}
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的实例。它是有可能为空的,使用它请先确保loaded
为true:
TinkerLoadResult loadResult =Tinker.with(context).getTinkerLoadResultIfPresent();
当补丁出现异常或者某些情况,我们可能希望清空全部补丁,调用方法为:
Tinker.with(context).cleanPatch();
当然我们也可以选择卸载某个版本的补丁文件:
Tinker.with(context).cleanPatchByVersion();
在升级版本时我们也无须手动去清除补丁,框架已经为我们做了这件事情。需要注意的是,在补丁已经加载的前提下清除补丁,可能会引起crash。这个时候更好重启一下所有的进程。
其他API这里不再一一概述,请大家自行翻阅Tinker.java。
因为加载补丁是在Tinker的Install之前的,我们将加载的结果保存在intent中,然后在Tinker的install方法中恢复这些结果。这里包括补丁的所有信息,例如加载的dex,library,我们定义的package config以及各个文件的目录等。但是需要注意的是,这里面的变量大多数是nullable。若Tinker的loaded为true,除了dexes
与
libs
(要看补丁包里面是否真的有),其他变量可以确保非空。
获取packageConfig:
publicString getPackageConfigByName(String name) {
if (packageConfig !=null) {
return packageConfig.get(name);
}
returnnull;
}
这里需要注意的是,检查dex的Md5值需要使用SharePatchFileUtil.verifyDexFileMd5方法,这是由于dex有可能是被我们重新打包成jar模式。
//获得基准包的tinkerId
String oldTinkerId =Tinker.with(context).getTinkerLoadResultIfPresent().getTinkerID();
//获得补丁包的tinkerId
String newTinkerId =Tinker.with(context).getTinkerLoadResultIfPresent().getNewTinkerID();
更多接口请参考TinkerLoadResult.java。
在有些时候,你可能想在更后的时机才去installTinker
,甚至在某些进程永远也不会去做这个动作。那样你可以通过TinkerApplicationHelper.java来获得一些当前加载的信息。
基本所有的信息都可以在这里获得,但是由于这里是直接读取结果intent