关于Tinker

请出门右转,自己百度吧

接入tinker遇到的问题

首先说明一下使用的tinker版本以及项目AGP版本,tinker 1.9.9 + AGP 3.2.0

1. 主dex益出问题

1
com.android.dex.DexException: Too many classes in --main-dex-list, main dex capacity exceeded

此问题最根本的原因在主dex过大,也就是说需要放置到主dex的类过多。那么为什么接入tinker之后会发生这个问题呢?tinker全部的类也没有这么多啊?

原因

首先看一下系统默认的哪些类会放置到主dex。 最初非常古老的AGP(好像是2.3以下),会默认将application和AndroidManifest.xml中声明的类(四大组件)的直接引用类放置到主dex。也正是这个原因,在老的AGP版本时,我们也层发生过主dex过大的问题。 不过好在后来AGP升级之后,google只是将一些系统启动时必要的类放到主dex。具体哪些是必要的类,我们可以查看一下AGP的原来来寻找答案(后续补充)。不过在我看来,只需要将application中MultiDex.install()执行前的引用类全部放置到主dex即可。

了解这一点后,我们来看一下tinker 的com.tencent.tinker.patch plugin是如何处理分包的?

会直接操作multidexKeep文件,将application直接引用的类全部放置到主dex。

具体在plugin的TinkerMultidexConfigTask中将一下代码放置到分包规则中,由于ApplicationLike实现了ApplicationLifeCycle接口,直接导致ApplicationLike的所有类放置到了主dex(即引入tinker前所有的application中引用类放到了主dex)

1
2
3
-keep public class com.tencent.tinker.entry.ApplicationLifeCycle {
*;
}
解决方案
  1. 修改源代码: 直接修改plugin源码将上述规则干掉即可。
  2. 不修改源代码: hook TinkerMultidexConfigTask此task,使其不执行,然后自己设置主dex的分包规则。
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
//第一步:
gradle.taskGraph.whenReady {
tasks.each { task ->
if (task.name.equals("tinkerProcessDebugMultidexKeep") || task.name.equals("tinkerProcessReleaseMultidexKeep"))
{
task.enabled = false
}
}
}
//第二步
buildTypes {
release {
minifyEnabled true
//指定需要优先放主 dex 的类
multiDexKeepProguard file('multidex-config.pro')
proguardFiles 'proguard.cfg'
signingConfig signingConfigs.release
}
debug {
minifyEnabled false
signingConfig signingConfigs.release
//指定需要优先放主 dex 的类
multiDexKeepProguard file('multidex-config.pro')
}
}
//第三部 修改multidex-config.pro添加如下分包规则
# tinker 由于禁用了tinker自动生成主dex规则的task,因此此处要声明一些必要的需要放置到主dex的类
-keep public class * implements com.tencent.tinker.entry.ApplicationLifeCycle {
<init>(...);
void onBaseContextAttached(android.content.Context);
#onCreate无需添加,因为很多同学会在onCreate书写过多逻辑导致引用类过多
# void onCreate();
}
-keep public class * extends com.tencent.tinker.loader.TinkerLoader {
<init>(...);
}
-keep public class * extends android.app.Application {
<init>();
void attachBaseContext(android.content.Context);
}
-keep class com.tencent.tinker.loader.TinkerTestAndroidNClassLoader {
<init>(...);
}
-keep class com.tencent.tinker.loader.** {
<init>(...);
}

2. Java 8 language support 问题

问题描述

如果你直接使用了jdk 8,最小支持版本在5.0以下,AGP版本时3.2.0以上,并且tinker版本号大于1.9.9,那么默认情况下编译会提示一下错误

1
Java 8 language support, as requested by 'android.enableD8.desugaring= true' in your gradle.properties file, is not supported when 'android.useDexArchive= false'
问题原因

使用了java 8 之后默认编译的.class, 只能保证jvm8以及以上版本虚拟机正常运行,那就无法保证低版本android手机能正常执行了。

为了解决这一个问题,AGP默认要求必须启用D8的脱糖(desugaring) , 其目的一句话描述就是修改.class文件,保证.class文件在低版本虚拟机上的兼容性。

因此,为了能够强制性保证apk在低版本手机上的运行,AGP会强制弹出此错误。

解决方法

有两个解决方式,如下:

  1. 按照错误提示,在gradle.properties文件中添加android.useDexArchive= true
  2. 降低AGP版本到3.1.4以下。

特定项目发生的问题

1. Linux系统打出的apk包作为base包,在Mac OS上生成patch包体积非常大
问题背景

由于我们项目最终生产线上包时,使用的是一台Linux的云主机进行打包操作的; 而开发人员确是使用Mac进行开发工作的。因此,如果用线上包在本地生成patch包,则产生了此问题。

问题原因

AGP 打包时默认会对部分资源做一些特定优化(如会对png图片进行优化压缩), 这部分优化在不通系统,不同机器上可能导致产生的优化后文件不一致,因此做diff对比是会导致不一致,而认为资源发生了变化,顾会导致此问题。

解决方__案

解决此问题的关键在于查看异常的patch包和正常的patch包差异发生在哪儿?然后将差异进行消除,即不让AGP做这部分优化。

例如常见的:png的优化导致的差异如何解决

禁用png优化或者让tinker忽略png图片的对比

1
2
3
4
5
6
7
aaptOptions{
cruncherEnabled false
}
//或配置tinker
res {
ignoreChange = ["*.png"]

Tinker合成Dex的优势

很多看完QQ空间访问的人会有疑问,QQ空间方案,只保留有变化的class,生成dex包,体积比较小,加载有很快,也不需要客户端独立进程合成新dex。Tinker为什么要大费周折的打包时进行diff操作,又在客户端进行merge操作呢?

其实原因很简单,QQ空间的方案是将class不进行CLASS_ISPREVERIFIED标记。但是此操作室友一定隐患和性能问题的。一旦修改了此标记,对类的加载其实是有效率问题,虽然没有明确测试影响有多大,但是原理上分析确实是有,而且是自主导致的,不可避免。

因此,为了能够从根源上杜绝这种问题的发生,tinker才大费周折的生成diff包,然后在客户端浪费一定时间和空间merge成完整dex。