基于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改造的更多相关文章

  1. Android 热修复方案Tinker

    转自:http://blog.csdn.net/l2show/article/details/53925543 Android 热修复方案Tinker(一) Application改造 Android ...

  2. Android热修复方案比较

    热修复的特点:无需重新发版,实时高效热修复:用户无感知修复,无需下载新的应用,代价小: 修复成功率高,把损失降到最低. 一.热修复开源方案和使用情况 方案名称 方案开发公司 开发时间 Github星评 ...

  3. Android 热修复使用Gradle Plugin1.5改造Nuwa插件

    随着谷歌的Gradle插件版本号的不断升级,Gradle插件如今最新的已经到了2.1.0-beta1,相应的依赖为com.android.tools.build:gradle:2.0.0-beta6, ...

  4. Android热修复之微信Tinker使用初探

      文章地址:Android热修复之微信Tinker使用初探 前几天,万众期待的微信团队的Android热修复框架tinker终于在GitHub上开源了. 地址:https://github.com/ ...

  5. 深入探索Android热修复技术原理读书笔记 —— 代码热修复技术

    在前一篇文章 深入探索Android热修复技术原理读书笔记 -- 热修复技术介绍中,对热修复技术进行了介绍,下面将详细介绍其中的代码修复技术. 1 底层热替换原理 在各种 Android 热修复方案中 ...

  6. Android 热修复Nuwa的原理及Gradle插件源码解析

    现在,热修复的具体实现方案开源的也有很多,原理也大同小异,本篇文章以Nuwa为例,深入剖析.  Nuwa的github地址 https://github.com/jasonross/Nuwa 以及用于 ...

  7. Android热修复技术选型(不在市场发布新版本的情况下,直接更新app)

    2015年以来,Android开发领域里对热修复技术的讨论和分享越来越多,同时也出现了一些不同的解决方案,如QQ空间补丁方案.阿里AndFix以及微信Tinker,它们在原理各有不同,适用场景各异,到 ...

  8. Android热修复技术选型——三大流派解析

    声明,本文转载自微信公众号文章 2015年以来,Android开发领域里对热修复技术的讨论和分享越来越多,同时也出现了一些不同的解决方案,如QQ空间补丁方案.阿里AndFix以及微信Tinker,它们 ...

  9. Android热修复技术总结

    https://blog.csdn.net/xiangzhihong8/article/details/77718004 插件化和热修复技术是Android开发中比较高级的知识点,是中级开发人员通向高 ...

随机推荐

  1. STM32F412应用开发笔记之十:多组分气体分析仪设计验证

    本次将NUCLEO-F412ZG应用于我们的多组分气体分析仪的实现试验,从整体上测试实际项目的应用情况. 一.项目概述 多组分气体分析仪是我公司近期研发的三个主要产品之一.采用模块化设计,可增减配置, ...

  2. bzoj营业额统计

    这个也是板子题吧,很水,求前驱后继即可 /* 插入,求前驱和后继 */ #include<iostream> #include<cstring> #include<cst ...

  3. pytest十二:cmd命令行参数

    命令行参数是根据命令行选项将不同的值传递给测试函数,比如平常在 cmd 执行”pytest —html=report.html”,这里面的”—html=report.html“就是从命令行传入的参数对 ...

  4. jQuery中的CSS(二)

    一:获取样式和设置样式

  5. android系统属性获取及设置

    系统属性获取及设置中的设置值 data/data/com.android.providers.settings/databases/settings.db 1.系统属性获取及设置 android.os ...

  6. jsp+servlet实现文件的上传和下载

    实现文件的上传和下载首先需要理解几个知识,这样才可以很好的完成文件的上传和下载: (1):上传文件是上传到服务器上,而保存到数据库是文件名 (2):上传文件是以文件转换为二进制流的形式上传的 (3): ...

  7. [转] 理解Web路由

    1. 什么是路由 在Web开发过程中,经常会遇到『路由』的概念.那么,到底什么是路由?简单来说,路由就是URL到函数的映射. 2. router和route的区别 route就是一条路由,它将一个UR ...

  8. POJ 3280 Cheapest Palindrome【DP】

    题意:对一个字符串进行插入删除等操作使其变成一个回文串,但是对于每个字符的操作消耗是不同的.求最小消耗. 思路: 我们定义dp [ i ] [ j ] 为区间 i 到 j 变成回文的最小代价.那么对于 ...

  9. NodeMCU入门(1):刷入At固件,透传数据到TcpServer和Yeelink平台

    准备工作 1. NodeMCU  LUA ESP8266 CP2102  WIFI Internet Development Board,仔细看背面可以看出自带cp2102模块,可以通过普通的手机充电 ...

  10. PLSQL程序设计(Oracle)

    Hello World set serveroutput on; declare --说明部分 begin --程序 dbms_output.put_line('Hello World'); end; ...