热修复之 Tinker 的接入(四)

之前三篇文章已经简要介绍了热修复的基本原理和相关知识,接下来的三篇文章主要介绍 Tinker 相关的知识,这篇文章主要介绍 Tinker 的接入。



1. Tinker 的接入

在 Android 工程中接入 Tinker 主要分为以下几个步骤:

  1. 在项目的 build.gradle 中,添加对 tinker-gradle-plugin 插件的依赖

    1
    2
    3
    4
    5
    buildscript {
    dependencies {
    classpath ('com.tencent.tinker:tinker-patch-gradle-plugin:1.9.1')
    }
    }
  2. 应用 tinker-gradle-plugin 插件

    app/build.gradle 文件中依赖 tinker 热修复库

    1
    2
    3
    4
    5
    6
    7
    8
    9
    dependencies {
    //optional, help to generate the final application
    provided('com.tencent.tinker:tinker-android-anno:1.9.1')
    //tinker's main Android lib
    compile('com.tencent.tinker:tinker-android-lib:1.9.1')
    }
    ...
    ...
    apply plugin: 'com.tencent.tinker.patch'
  3. 初始化 Tinker
    在 Application 中初始化 Tinker,推荐的初始化方式如下

    1
    2
    -public class YourApplication extends Application {
    +public class SampleApplicationLike extends DefaultApplicationLike {
    1
    2
    3
    4
    @DefaultLifeCycle(
    application = "tinker.sample.android.app.SampleApplication", //application name to generate
    flags = ShareConstants.TINKER_ENABLE_ALL) //tinkerFlags above
    public class SampleApplicationLike extends DefaultApplicationLike

    在 AndroidManifest.xml 文件中申明自定义的 Application,如下所示:

    1
    2
    3
    4
    5
    6
    7
    <application
    android:name=".app.SampleApplication"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:theme="@style/AppTheme">
    ......
    </application>
  4. 修改 app/build.gradle

    在 app/build.gradle 文件中,设置配置参数,具体可以参见 tinker-sample-android

  5. 生成补丁包文件

    按照上述方式接入之后,每次 build 时,都会在 build/bakApk 目录下生成本地打包的 apk、R.txt、mapping.txt 文件。如果要生成补丁包 patch 文件,可以通过以下命令生成补丁包 patch 文件,

    1
    ./gradlew tinkerPatchRelease

    生成的补丁包 patch 文件目录为:build/outputs/tinkerPatch

    需要注意的是,在执行 ./gradlew tinkerPatchRelease 命令时,需要在 app/build.gradle 中设置相比较的 apk、R.txt、mapping.txt 文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    ext {
    //for some reason, you may want to ignore tinkerBuild, such as instant run debug build?
    tinkerEnabled = true
    //for normal build
    //old apk file to build patch apk
    tinkerOldApkPath = "${bakPath}/app-debug-1018-17-32-47.apk"
    //proguard mapping file to build patch apk
    tinkerApplyMappingPath = "${bakPath}/app-debug-1018-17-32-47-mapping.txt"
    //resource R.txt to build patch apk, must input if there is resource changed
    tinkerApplyResourcePath = "${bakPath}/app-debug-1018-17-32-47-R.txt"
    }

2. Tinker 中 Application 类的生成

在 Tinker 中提供了两种方式生成 Application 的子类

因为手动编写继承自 TinkerApplication 和 DefaultApplicationLike 的子类,可能会出现各种问题。相对来说,通过 DefaultLifeCycle 注解的方式生成 Application 子类会更安全,也是 Tinker 官方推荐的方式,其实通过 DefaultLifeCycle 的方式也是通过编译时注解的方式生成了 TinkerApplication 和 DefaultApplicationLike 的子类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@DefaultLifeCycle(
application = "com.lijiankun24.TinkerPractice.MyApplication",
flags = ShareConstants.TINKER_ENABLE_ALL,
loadVerifyFlag = false
)
public class MyApplicationLike extends DefaultApplicationLike {
public MyApplicationLike(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);
}
}

DefaultLifeCycle 注解的源码如下所示:

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
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
@Inherited
public @interface DefaultLifeCycle {
/**
* 生成真正的 Application
*/
String application();
/**
* patch loader, 默认是 com.tencent.tinker.loader.TinkerLoader
*/
String loaderClass() default "com.tencent.tinker.loader.TinkerLoader";
/**
* 支持的文件类型
* ShareConstants.TINKERDISABLE: 不支持任何类型的文件
* ShareConstants.TINKERDEXONLY: 只支持dex文件
* ShareConstants.TINKERLIBRARYONLY: 只支持library文件
* ShareConstants.TINKERDEXANDLIBRARY: 只支持dex与res的修改
* ShareConstants.TINKERENABLEALL: 支持任何类型的文件,也是我们通常的设置的模式
*/
int flags();
/**
* 是否每次都校验补丁包的 MD5
*/
boolean loadVerifyFlag() default false;
}

好,那接下来看一下 com.lijiankun24.TinkerPractice.MyApplication 是怎么通过编译时注解生成的。

在 app/build.gradle 中引入 Tinker 的依赖时,有如下一个依赖

1
2
3
4
5
6
dependencies {
//optional, help to generate the final application
provided('com.tencent.tinker:tinker-android-anno:1.9.1')
//tinker's main Android lib
compile('com.tencent.tinker:tinker-android-lib:1.9.1')
}

很明显是通过 com.tencent.tinker:tinker-android-anno 库生成的,源码:tinker-android-anno,入口为 com.tencent.tinker.anno.AnnotationProcessor,代码如下所示:

典型的编译时注解的项目,主要是在 process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) 方法中

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
/**
* Tinker Annotations Processor
*
* Created by zhaoyuan on 16/3/31.
*/
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class AnnotationProcessor extends AbstractProcessor {
private static final String SUFFIX = "$$DefaultLifeCycle";
private static final String APPLICATION_TEMPLATE_PATH = "/TinkerAnnoApplication.tmpl";
@Override
public Set<String> getSupportedAnnotationTypes() {
final Set<String> supportedAnnotationTypes = new LinkedHashSet<>();
supportedAnnotationTypes.add(DefaultLifeCycle.class.getName());
return supportedAnnotationTypes;
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
processDefaultLifeCycle(roundEnv.getElementsAnnotatedWith(DefaultLifeCycle.class));
return true;
}
private void processDefaultLifeCycle(Set<? extends Element> elements) {
// DefaultLifeCycle
for (Element e : elements) {
DefaultLifeCycle ca = e.getAnnotation(DefaultLifeCycle.class);
// 得到代理类的名称和包名
String lifeCycleClassName = ((TypeElement) e).getQualifiedName().toString();
String lifeCyclePackageName = lifeCycleClassName.substring(0, lifeCycleClassName.lastIndexOf('.'));
lifeCycleClassName = lifeCycleClassName.substring(lifeCycleClassName.lastIndexOf('.') + 1);
// 生成真正的 Application 子类的全名,即 `com.lijiankun24.tinkerpractice.MyApplication`
String applicationClassName = ca.application();
if (applicationClassName.startsWith(".")) {
applicationClassName = lifeCyclePackageName + applicationClassName;
}
// 通过拆分,得到真正的 Application 子类的包名和类名
String applicationPackageName = applicationClassName.substring(0, applicationClassName.lastIndexOf('.'));
applicationClassName = applicationClassName.substring(applicationClassName.lastIndexOf('.') + 1);
// 得到 patch ClassLoader 的类名
String loaderClassName = ca.loaderClass();
if (loaderClassName.startsWith(".")) {
loaderClassName = lifeCyclePackageName + loaderClassName;
}
System.out.println("*");
// 读取模板文件的内容到一个字符串
final InputStream is = AnnotationProcessor.class.getResourceAsStream(APPLICATION_TEMPLATE_PATH);
final Scanner scanner = new Scanner(is);
final String template = scanner.useDelimiter("\\A").next();
// 将模板文件中的 %KEY% 占位符替换成真正的数据
final String fileContent = template
.replaceAll("%PACKAGE%", applicationPackageName)
.replaceAll("%APPLICATION%", applicationClassName)
.replaceAll("%APPLICATION_LIFE_CYCLE%", lifeCyclePackageName + "." + lifeCycleClassName)
.replaceAll("%TINKER_FLAGS%", "" + ca.flags())
.replaceAll("%TINKER_LOADER_CLASS%", "" + loaderClassName)
.replaceAll("%TINKER_LOAD_VERIFY_FLAG%", "" + ca.loadVerifyFlag());
// 创建真实的 Application 子类文件,并将替换之后的 Application 子类的内容写入到真实 Application 子类文件中
try {
JavaFileObject fileObject = processingEnv.getFiler().createSourceFile(applicationPackageName + "." + applicationClassName);
processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "Creating " + fileObject.toUri());
Writer writer = fileObject.openWriter();
try {
PrintWriter pw = new PrintWriter(writer);
pw.print(fileContent);
pw.flush();
} finally {
writer.close();
}
} catch (IOException x) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, x.toString());
}
}
}
}

可以分为三个步骤:

  • 首先找到被DefaultLifeCycle标识的Element(为类对象TypeElement),得到该对象的包名,类名等信息,然后通过该对象,拿到@DefaultLifeCycle对象,获取该注解中声明属性的值
  • 读取一个模板文件,读取为字符串,将各个占位符通过步骤1中的值替代
  • 通过JavaFileObject将替换完成的字符串写文件,其实就是本例中的Application子类

模板文件内容为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package %PACKAGE%;
import com.tencent.tinker.loader.app.TinkerApplication;
/**
*
* Generated application for tinker life cycle
*
*/
public class %APPLICATION% extends TinkerApplication {
public %APPLICATION%() {
super(%TINKER_FLAGS%, "%APPLICATION_LIFE_CYCLE%", "%TINKER_LOADER_CLASS%", %TINKER_LOAD_VERIFY_FLAG%);
}
}

对应的 ApplicationLike 源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@DefaultLifeCycle(
application = "com.lijiankun24.TinkerPractice.MyApplication",
flags = ShareConstants.TINKER_ENABLE_ALL,
loadVerifyFlag = false
)
public class MyApplicationLike extends DefaultApplicationLike {
public MyApplicationLike(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);
}
}

主要的占位符有:

  • 包名,如果 application 属性值以点开始,则同包;否则则截取
  • 类名,application 属性值中的类名
  • %TINKER_FLAGS% 对应 flags
  • %APPLICATION_LIFE_CYCLE%,编写的 ApplicationLike 的全路径
  • “%TINKER_LOADER_CLASS%”,这个值我们没有设置,实际上对应@DefaultLifeCycle的loaderClass属性,默认值为com.tencent.tinker.loader.TinkerLoader
  • %TINKER_LOAD_VERIFY_FLAG% 对应 loadVerifyFlag

于是最终生成的代码为:

1
2
3
4
5
6
7
8
9
10
/**
* Generated application for tinker life cycle
*/
public class MyApplication extends TinkerApplication {
public MyApplication() {
super(7, "com.lijiankun24.tinkerpractice.MyApplicationLike",
"com.tencent.tinker.loader.TinkerLoader", false);
}
}

Tinker 通过这种方式初始化 Application,主要有两个目的:

  • 为了减少错误的出现,推荐使用 Annotation 生成 Application 类
  • 在前面三篇文章中都介绍了,为了避免类被打上 CLASS_ISPREVERIFIED 的标记,需要在类的构造方法中注入一个独立 dex 中的类的引用。而这个独立的 dex 是在 Application 启动之后加载的,所以 Application 并不能注入这个独立 dex 中的类,那么 Application 就会被打上 CLASS_ISPREVERIFIED 的标记,从而 Application 子类就没有办法被修复。为了解决此问题,则将 Application 子类中的逻辑都抽离到一个独立的类例如 ApplicationLike 中。

    这样,在 Application 子类中已经加载了补丁包和独立的 dex 文件,虽然 Application 子类不能被热修复,但是 ApplicationLike 类可以被热修复,最大程度的解决了 Application 子类不能被热修复的问题。


参考资料:

Android 热修复 Tinker接入及源码浅析Hongyang

Android 热修复方案Tinker(一) Application改造Jesse-csdn