Tinker集成到自己的项目里Tinker应用

Tinker集成过程重点

官方对于集成重要的几步已经有说明,详情请查看官方文档。这里只列出一些容易出错的几点。

1. 调用assembleDebug编译命令
将cmd 目录定位到项目根目录下(如c:testcode\tinker\tinker-sample-android)

gradlew assembleDebug

2. 编译报错:can’t get git rev, you should add git to system path or just input test value, such as ‘testTinkerId’

分析:出错代码在:build.gradle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def gitSha() {
try {
//获取最后一次提交的hash值作为TinkerID,并写入到manifest.xml;
//这里因为没有提交,所以获取不到,为了调试方便,直接写一个值。
//!!注意:TinkerId不能设置为非常短的数字,例如“1.0”
return "5.6.020160928"
String gitRev = 'git rev-parse --short HEAD'.execute().text.trim()
if (gitRev == null) {
throw new GradleException("can't get git rev, you should add git to system path or just input test value, such as 'testTinkerId'")
}
return gitRev
} catch (Exception e) {
throw new GradleException("can't get git rev, you should add git to system path or just input test value, such as 'testTinkerId'")
}
}

此方法的调用:

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

可以看出getSha() 是用于获取一个字符串,作为TINKER_ID。

报错原因是:第一次clone下来代码,没有提交过,所以git HEAD里 获取不到 最后一次提交的hash值。导致tinkerId 写入manifest.xml失败。
如果需要使用这个值,操作方法为:

  • 安装git,及环境变量(安装是否成功检测方案:git –version)
  • 在simple-demo下修改一个文件,然后提交。
  • 再次执行编译。

3. 打Patch包:

直接使用task:tinkerPatchVariantName(例如tinkerPatchDebug、tinkerPatchRelease)即可自动根据Variant选择相应的编译类型,补丁包与相关日志会保存在/build/outputs/tinkerPatch/

1
gradlew tinkerPathDebug

4. 自定义Application类

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

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

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

5.Application代理类

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

1
2
3
4
5
public class YourApplication extends Application {
ActivityLifecycleCallbacks activityLifecycleCallbacks;
public void setTinkerActivityLifecycleCallbacks(ActivityLifecycleCallbacks activityLifecycleCallbacks) {
this.activityLifecycleCallbacks = activityLifecycleCallbacks;
}
  • 然后将你的Application类继承TinkerApplication.java。除了构造方法之外,你最好不要引入其他的类,这将导致它们无法通过补丁修改。
1
2
3
4
5
6
7
8
9
10
11
12
13
public class SampleApplication extends TinkerApplication {
public SampleApplication() {
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);
}
}
  • 使用Annotation生成Application类:

为了隐藏你的Application类,我们更加推荐你使用tinker-android-anno在运行时生成你的Application类。这样保证你无法修改你的Application类,不会因为错误操作导致引入更多无法修改的类。

若采用Annotation生成Application,需要将原来的Application类删掉。到此为止,Tinker初步的接入已真正的完成,你已经可以愉快的使用Tinker来实现补丁功能了。

1
2
3
4
5
6
@DefaultLifeCycle(
application = ".SampleApplication", //application类名
flags = ShareConstants.TINKER_ENABLE_ALL, //tinkerFlags
loaderClass = "com.tencent.tinker.loader.TinkerLoader", //loaderClassName, 我们这里使用默认即可!
loadVerifyFlag = false) //tinkerLoadVerifyFlag
public class SampleApplicationLike extends DefaultApplicationLike

最终APP中不需要Application,只用一个ApplicationLike即可。Demo中完整代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
@SuppressWarnings("unused")
@DefaultLifeCycle(application = "tinker.sample.android.app.SampleApplication",
flags = ShareConstants.TINKER_ENABLE_ALL,
loadVerifyFlag = false)
public class SampleApplicationLike extends DefaultApplicationLike {
private static final String TAG = "Tinker.SampleApplicationLike";
public SampleApplicationLike(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);
}
/**
* install multiDex before install tinker
* so we don't need to put the tinker lib classes in the main dex
*
* @param base
*/
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
@Override
public void onBaseContextAttached(Context base) {
super.onBaseContextAttached(base);
//you must install multiDex whatever tinker is installed!
MultiDex.install(base);
SampleApplicationContext.application = getApplication();
SampleApplicationContext.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);
}
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
public void registerActivityLifecycleCallbacks(Application.ActivityLifecycleCallbacks callback) {
getApplication().registerActivityLifecycleCallbacks(callback);
}
}

6. Release的使用方法

Tinker的使用方式如下,以gradle接入的release包为例:

  • 每次编译或发包将安装包与mapping文件备份;
  • 若有补丁包的需要,按自身需要修改你的代码、库文件等;
  • 将备份的基准安装包与mapping文件输入到tinkerPatch的配置中;
  • 运行tinkerPatchRelease,即可自动编译最新的安装包,并与输入基准包作差异,得到最终的补丁包。
  • 将补丁包通过接口下发给有bug的客户端版本。客户端更新后做本地标识,避免重复修复patch.

注意

如果multiDexEnabled为true,将自动生成Tinker需要放在主dex的keep规则,你需要拷贝它到自己的multiDexKeepProguard文件中。这是因为它是一个单独的文件,而proguardFiles是一个list。输出路径为:build/intermediates/tinker_intermediates/tinker_multidexkeep.pro。 后你可以在build/outputs/tinkerPatch中找到输出的文件。

参考文档

  1. 腾讯官方接入指南
  2. 腾讯官方wiki首页