Android 热修复方案Tinker(一) Application改造
基于Tinker V1.7.5
Android 热修复方案Tinker(一) Application改造
Android 热修复方案Tinker(二) 补丁加载流程
Android 热修复方案Tinker(三) Dex补丁加载
Android 热修复方案Tinker(四) 资源补丁加载
Android 热修复方案Tinker(五) SO补丁加载
Android 热修复方案Tinker(六) Gradle插件实现
Android 热修复方案Tinker(七) 插桩实现
带注释的源码
这篇文章主要分析一下Tinker隔离Application.至于为什么要隔离Application?可以参考上一篇 Android 热修复方案分析文章中说到的Qzone方案,要给除了Application子类所有的类注入一个独立dex中的类引用,来避免class被打上CLASS_ISPREVERIFIED标记.而这个独立的dex是在Application启动之后加载的,所以Application子类就不能插入独立dex的引用也就不能进行修复.不仅如此,由于Application子类被打上CLASS_ISPREVERIFIED标记,那么Application子类直接引用的对象就无法打补丁包,会抛出pre-verified异常.
在这种前提下还是想修复尽可能多的类怎么办?对于Qzone方案的解决方法就是把Application子类中的逻辑都抽离到一个独立的类例如ApplicationProxy中,Application只引用这个ApplicationProxy类从而减小影响范围.Tinker使用的是全量更新,补丁也是在Application中加载的,所以Tinker的Application也是不能修改的.并且由于Tinker的全量更新不能兼容加固方案,如果app使用了加固的话可以使用usePreGeneratedPatchDex模式回退到Qzone方案上,这样的话就需要面临同样的问题.
同时还因为Android N混合编译与对热补丁影响解析,这会造成要修复的class被缓存在App image中,App image中的class会插入PathClassLoader中,而PathClassLoader 加载补丁的时候不会替换缓存的class,最终会导致在全量更新的情况下部分类是从base.apk中加载,部分类是从patch.dex中加载,抛出IllegalAccessError.Tinker的解决方案是在运行时改写PathClassLoader来加载类,让App image中的缓存失效.而Application这个入口类还是只能用系统的PathClassLoader来加载,从这个角度来说也需要Application解耦出来.
Application 隔离
Tinker提供了两种方式来隔离Application,分别是DefaultLifeCycle注解和继承TinkerApplication,DefaultApplicationLike.推荐使用DefaultLifeCycle注解来隔离Application,这种方式会编译自动生成Application,减少一些误操作.而继承TinkerApplication,DefaultApplicationLike其实就是自己写出注解生成的代码,所以这篇文章就只介绍注解的方式.
@DefaultLifeCycle(
application = "com.test.MyApplication",
flags = ShareConstants.TINKER_ENABLE_ALL,
loadVerifyFlag = false
)
public class ApplicationLike 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);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
在编译的过程时注解执行完毕之后就会在设定的包路径下生成出真实的Application java文件,默认继承TinkerApplication.将注解中配置的数据传递给构造函数的参数.然后被编译成.class打包到dex文件中.
public class MyApplication extends TinkerApplication {
public MyApplication() {
super(7, "cn.jesse.patchersample.ApplicationLike", "com.tencent.tinker.loader.TinkerLoader", false);
}
}
1
2
3
4
5
6
7
DefaultLifeCycle注解包含四个属性,分别是真实的application路径(application), 补丁loader的路径(loaderClass), 补丁支持不同文件格式flag(flags), 是否每次都校验补丁包MD5的flag(loadVerifyFlag).
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
@Inherited
public @interface DefaultLifeCycle {
/**
* 真实的Application
*/
String application();
/**
* patch loader
*/
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;
}
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
在注解处理器AnnotationProcessor中,根据注解的参数和真实的Application模板,生成真正的Application.
在Android studio中 annotation module必须是java library否则在处理anno的时候会找不到AbstractProcessor.如果需要调试这部分代码的话可以通过配置gradle --deamon和加断点来debug.
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类名称
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 loader的名称
String loaderClassName = ca.loaderClass();
if (loaderClassName.startsWith(".")) {
loaderClassName = lifeCyclePackageName + loaderClassName;
}
//读取Application模板文件,将模板中的%KEY%占位符全部替换成真实的数据
final InputStream is = AnnotationProcessor.class.getResourceAsStream(APPLICATION_TEMPLATE_PATH);
final Scanner scanner = new Scanner(is);
final String template = scanner.useDelimiter("\\A").next();
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());
}
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
Application模板是把一些变化的属性通过%KEY%的形式代替其中包含文件包名,类名,构造函数名,tinker_flag, 代理Application, patcher loader, 和补丁校验位.在注解编译时就可以根据key对包名,Application名和构造函数的参数进行填充.
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%);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Application 相关依赖
用了DefaultLifeCycle注解之后就把真实的Application和代理Application隔离开了.至于两者之间是如何同步声明周期,如何建立依赖关系,以及他们的职责是什么?可以看一下这部分的类图.
真实Application
在最早能获取context的方法attachBaseContext处做相关的初始化工作.像时间统计,利用反射将TinkerLoader对象构建出来并调用tryLoad方法(校验环境后加载补丁),反射创建出代理Application对象,同步Application生命周期和重置safe mode计数.
//记录系统启动时间和App启动时刻
applicationStartElapsedTime = SystemClock.elapsedRealtime();
applicationStartMillisTime = System.currentTimeMillis();
//初始化patch loader, 并且调用tryLoad方法
loadTinker();
//确保代理Application对象已经被创建出来了
ensureDelegate();
//反射调用代理Application的`onBaseContextAttached`方法同步生命周期
try {
Method method = ShareReflectUtil.findMethod(delegate, "onBaseContextAttached", Context.class);
method.invoke(delegate, base);
} catch (Throwable t) {
throw new TinkerRuntimeException("onBaseContextAttached method not found", t);
}
//重置safe mode计数, 当safe mode计数不小于三次时PatcherResultIntent会记录patch失败
if (useSafeMode) {
String processName = ShareTinkerInternals.getProcessName(this);
String preferName = ShareConstants.TINKER_OWN_PREFERENCE_CONFIG + processName;
SharedPreferences sp = getSharedPreferences(preferName, Context.MODE_PRIVATE);
sp.edit().putInt(ShareConstants.TINKER_SAFE_MODE_COUNT, 0).commit();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
代理Application
因为要用ApplicationLike代理真实的Application,所以ApplicationLike就要持有Application, resources, classLoader, assetManager的引用.其中tinkerResultIntent属性中记录着TinkerLoader启动和加载patch的状态.
//Application,resource,assetManager,classLoader的引用
private final Application application;
private Resources[] resources;
private ClassLoader[] classLoader;
private AssetManager[] assetManager;
//用于记录启动加载patcher loader的状态
private final Intent tinkerResultIntent;
//系统的存活时间和app启动时刻
private final long applicationStartElapsedTime;
private final long applicationStartMillisTime;
//注解中的两个flags
private final int tinkerFlags;
private final boolean tinkerLoadVerifyFlag;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
同时还要暴露出常用的Application共有方法,供外部使用.
void onCreate();
void onLowMemory();
void onTrimMemory(int level);
void onTerminate();
void onConfigurationChanged(Configuration newConfig);
void onBaseContextAttached(Context base);
至此Application改造的分析就完成了,上面提到了在Application的attachBaseContext中通过反射调起了TinkerLoader的tryLoad方法.tryLoad方法是加载最终补丁包的入口,下篇文章会沿着这条线继续分析下去.
https://blog.csdn.net/l2show/article/details/53187548
Android 热修复方案Tinker(一) Application改造的更多相关文章
- Android 热修复方案Tinker
转自:http://blog.csdn.net/l2show/article/details/53925543 Android 热修复方案Tinker(一) Application改造 Android ...
- Android热修复方案比较
热修复的特点:无需重新发版,实时高效热修复:用户无感知修复,无需下载新的应用,代价小: 修复成功率高,把损失降到最低. 一.热修复开源方案和使用情况 方案名称 方案开发公司 开发时间 Github星评 ...
- Android 热修复使用Gradle Plugin1.5改造Nuwa插件
随着谷歌的Gradle插件版本号的不断升级,Gradle插件如今最新的已经到了2.1.0-beta1,相应的依赖为com.android.tools.build:gradle:2.0.0-beta6, ...
- Android热修复之微信Tinker使用初探
文章地址:Android热修复之微信Tinker使用初探 前几天,万众期待的微信团队的Android热修复框架tinker终于在GitHub上开源了. 地址:https://github.com/ ...
- 深入探索Android热修复技术原理读书笔记 —— 代码热修复技术
在前一篇文章 深入探索Android热修复技术原理读书笔记 -- 热修复技术介绍中,对热修复技术进行了介绍,下面将详细介绍其中的代码修复技术. 1 底层热替换原理 在各种 Android 热修复方案中 ...
- Android 热修复Nuwa的原理及Gradle插件源码解析
现在,热修复的具体实现方案开源的也有很多,原理也大同小异,本篇文章以Nuwa为例,深入剖析. Nuwa的github地址 https://github.com/jasonross/Nuwa 以及用于 ...
- Android热修复技术选型(不在市场发布新版本的情况下,直接更新app)
2015年以来,Android开发领域里对热修复技术的讨论和分享越来越多,同时也出现了一些不同的解决方案,如QQ空间补丁方案.阿里AndFix以及微信Tinker,它们在原理各有不同,适用场景各异,到 ...
- Android热修复技术选型——三大流派解析
声明,本文转载自微信公众号文章 2015年以来,Android开发领域里对热修复技术的讨论和分享越来越多,同时也出现了一些不同的解决方案,如QQ空间补丁方案.阿里AndFix以及微信Tinker,它们 ...
- Android热修复技术总结
https://blog.csdn.net/xiangzhihong8/article/details/77718004 插件化和热修复技术是Android开发中比较高级的知识点,是中级开发人员通向高 ...
随机推荐
- Android设计模式-观察者模式
原文地址 http://blog.csdn.net/qq_25806863/article/details/69218968 观察者模式是一种使用频率非常高的设计模式,最常用的地方就是订阅-发布系统. ...
- java.util.Random 类
//: object/ForEachFloat.java package object; import java.util.Random; public class ForEachFloat { pu ...
- php单例模式的实例
class Config1 {} class Config { * 必须先声明一个静态私有属性:用来保存当前类的实例 * 1. 为什么必须是静态的?因为静态成员属于类,并被类所有实例所共享 * 2. ...
- 性能测试六:jmeter进阶之Cookie与header管理器
一.http cookie管理器 可以在浏览器中抓取到cookie信息,然后通过http cookie管理器为http请求添加cookie信息 添加cookie管理器后,Jmeter可以自动处理coo ...
- js中数组去重
编写函数norepeat(arr) 将数组的重复元素去掉,并返回新的数组 [注]正序去重,会漏掉一些元素. [注]去重倒序. var arr = [10, 20, 30, 40, 30, 20, 20 ...
- python 全栈开发,Day52(关于DOM操作的相关案例,JS中的面向对象,定时器,BOM,client、offset、scroll系列)
昨日作业讲解: 京东购物车 京东购物车效果: 实现原理: 用2个盒子,就可以完整效果. 先让上面的小盒子向下移动1px,此时就出现了压盖效果.小盒子设置z-index压盖大盒子,将小盒子的下边框去掉, ...
- extern "C" 回顾
引入:在测试"extern "C" 与gcc, g++无关"时,使用到了extern "C"的概念,网上找篇文章回顾一下. 试验如下: te ...
- android系统属性获取及设置
系统属性获取及设置中的设置值 data/data/com.android.providers.settings/databases/settings.db 1.系统属性获取及设置 android.os ...
- Kylin的简介与安装部署
一.Kylin的概述 官方网址:http://kylin.apache.org/cn/ Apache Kylin™是一个开源的分布式分析引擎,提供Hadoop/Spark之上的SQL查询接口及多维分析 ...
- 【AtCoder】CODE FESTIVAL 2017 qual C
A - Can you get AC? No #include <bits/stdc++.h> #define fi first #define se second #define pii ...