文档章节

从`Sample`来看`Tinker`的启动流程

Krbit
 Krbit
发布于 2016/11/11 12:26
字数 2107
阅读 732
收藏 1
点赞 0
评论 0

Tinker是微信官方为Android的热修复界带来的一个热修复方案,它支持代码的动态下发,对So库和资源修复也有很好的支持,让应用不用重新安装的情况下实现更新。有关Tinker的更多介绍,可以参看这里。我这里只是根据官方提供的Demo来简单介绍下Tinker的启动流程。

我们知道,如果在工程的Manifest中为我们的应用配置了Application的话,在用户启动我们的应用时,首先会调起我们配置的Application,进而完成一些基本的初始化操作。

Tinker为了能够对应用的Application提供修复,使用了一种委托机制,我们可以把在Application的要完成的工作转移到委托类中实现,而我们原Application类只需要继承TinkerApplication类,并提供一个无参构造函数,在构造函数中将我们的委托类传递给父类即可。

Tinker官方为了简化我们的接入工作,为我们提供了一个编译时注解类。我们只要在我们的委托类上通过注解即可完成Application的委托代理工作,然后将注解项中的application的值注册到Manifest中。

下面我们就来看看官方提供的Demo是如何一步步完成这些工作的。

添加插件及依赖

  • 首先,我们打开Sample项目根目录下的build.gradle文件,buildscript中添加Tinker的gradle插件:
classpath "com.tencent.tinker:tinker-patch-gradle-plugin:${TINKER_VERSION}"

其中TINKER_VERSION变量的值在根目录下的gradle.properties中有配置:

TINKER_VERSION=1.7.3
  • 接着在app/build.gradle中引入Tinker依赖,并配置一些Tinker运行需要的参数:
compile("com.tencent.tinker:tinker-android-lib:${TINKER_VERSION}") { changing = true }
provided("com.tencent.tinker:tinker-android-anno:${TINKER_VERSION}") { changing = true }

其它的配置项大家可以参看官方的文档,这里暂且略过。

  • 下面我们来看看我们Application的代理类SampleApplicationLike
@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);
    }

}

我们首先来看看该类声明处的注解DefaultLifeCycle。前面我已经说过,这个注解是官方为我们提供的一个简化配置的注解类。这个注解是个编译时注解类,其实现在tinker-android-anno库中。这个库主要的实现其实就两个类和一个模版文件。我们先来看看这个注解类帮我们做了哪些工作。

打开AnnotationProcessor类,来看看注解的处理过程:

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);

            String applicationClassName = ca.application();
            if (applicationClassName.startsWith(".")) {
                applicationClassName = lifeCyclePackageName + applicationClassName;
            }
            String applicationPackageName = applicationClassName.substring(0, applicationClassName.lastIndexOf('.'));
            applicationClassName = applicationClassName.substring(applicationClassName.lastIndexOf('.') + 1);

            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();
            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());

            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注解的解析工作。下面我们就来参照着SampleApplicationLike类的注解项来理解这个解析工作。

首先我们遍历所有被DefaultLifeCycle标注的注解元素(这里我们只有SampleApplicationLike一个元素),拿到该元素的包名和类名。然后根据DefaultLifeCycle的配置项,获取应用类的名称,若名称以.开头,则自动拼接上包名。按照同样的方式拿到我们配置的loader类、flags以及loadVerifyFlag标志。接下来读取我们的模板文件,根据注解的配置项,替换到模板中的参数,生成我们自己的Application类。其目标文件很简单,就是生成一个Application类,并添加一个无参构造函数,将我们配置的loader等传给父类调用。

public class %APPLICATION% extends TinkerApplication {

    public %APPLICATION%() {
        super(%TINKER_FLAGS%, "%APPLICATION_LIFE_CYCLE%", "%TINKER_LOADER_CLASS%", %TINKER_LOAD_VERIFY_FLAG%);
    }

}

当我们的注解类生效时,就可以在我们应用的app/build/generated/source/apt/debug目录下看到我们配置的Application类了,而这个类名的确是我们在注解DefaultLifeCycle#application中配置的类。我们没有配置loader类,注解DefaultLifeCycle为我们提供了一个默认的加载类com.tencent.tinker.loader.TinkerLoader,它与委托类SampleApplicationLike一起传递给了父类TinkerApplication

按照传统的思路,当用户启动我们的应用时,首先会启动我们注册的Application类完成一些基础工作。到这里,我们已经看到了我们自己的ApplicationSampleApplication,而这个类继承自TinkerApplication类。这也就是说,我们所有的工作要么放到SampleApplication类中实现,要么改写TinkerApplication类。如果你认真读了上文,你会发现这两个类都不在我们自己的工程项目中(SampleApplication类是由注解类自动生成,这里简单认为它不属于我们自己的工程项目)。那么,我们要想在Application中配置一些东西,要如何完成呢?对,我们不是有自己的委托类吗,使用我们的委托类SampleApplicationLike,让Application类调用我们的委托类不就可以了吗?说干就干,我们来看看如何调起我们委托类的方法。

打开TinkerApplication类文件。上面说了,通过注解类,我们将我们的委托类SampleApplicationLike已经传到了TinkerApplication中,在其无参构造函数中赋值给了它的成员变量:

 protected TinkerApplication(int tinkerFlags, String delegateClassName,
                                String loaderClassName, boolean tinkerLoadVerifyFlag) {
        this.tinkerFlags = tinkerFlags;
        this.delegateClassName = delegateClassName;
        this.loaderClassName = loaderClassName;
        this.tinkerLoadVerifyFlag = tinkerLoadVerifyFlag;

    }

TinkerApplication类中,我们看到了那些熟悉的Application周期方法,如onCreate方法等。下面我们就从我们最熟悉的onCreate方法入手,看看我们的委托类是如何被调用来完成我们的初始化工作的:

 @Override
    public final void onCreate() {
        super.onCreate();
        ensureDelegate();
        delegateMethod("onCreate");
    }

OK,我们来看ensureDelegate方法:

 private synchronized void ensureDelegate() {
        if (delegate == null) {
            delegate = createDelegate();
        }
    }

初次调用,delegate当然为null(其实,这里已经被初始化过了,后文我会解释),我们来看createDelegate方法:

private Object createDelegate() {
        try {
            // Use reflection to create the delegate so it doesn't need to go into the primary dex.
            // And we can also patch it
            Class<?> delegateClass = Class.forName(delegateClassName, false, getClassLoader());
            Constructor<?> constructor = delegateClass.getConstructor(Application.class, int.class, boolean.class, long.class, long.class,
                Intent.class, Resources[].class, ClassLoader[].class, AssetManager[].class);
            return constructor.newInstance(this, tinkerFlags, tinkerLoadVerifyFlag,
                applicationStartElapsedTime, applicationStartMillisTime,
                tinkerResultIntent, resources, classLoader, assetManager);
        } catch (Throwable e) {
            throw new TinkerRuntimeException("createDelegate failed", e);
        }
    }

createDelegate方法中,通过反射来创建一个委托类,从上文我们已经知道,这里要创建的委托类就是我们的SampleApplicationLike。获取到SampleApplicationLike委托类实例以后,将对onCreate方法的调用委托给了SampleApplicationLikeonCreate方法,其它周期方法与此类似。这样我们就明白了,为什么在SampleApplicationLike中复写对应的方法就可以完成我们的初始化工作了。

回到SampleApplicationLike类,我们看到了一个新面孔onBaseContextAttached,这个方法似乎不是我们Application的方法?别急,我们再去TinkerApplication看看,找到我们的老朋友attachBaseContext,这个方法大家可能不是太熟悉,这个方法是先于onCreate方法被调用的,在该方法中调用了类中的onBaseContextAttached方法,我们要找的是它吗?打开看看,好吧,貌似不是吧?在它里面,首先初始化了我们的loader类和委托类,这也就是解释了为什么在onCreate中调用ensureDelegatedelegate不为null的原因。紧接着就是调用我们委托类的onBaseContextAttached方法了。哈哈,找到了,原来我们SampleApplicationLike中的onBaseContextAttached是在这里调用了。由此我们知道,如果有朋友需要在attachBaseContext是做一些初始化工作,就可以在我们委托类的onBaseContextAttached中完成了。同样的,其它配置工作就可以复写不同的周期方法来完成了。

以上即为Tinker的启动流程,后面你就可以使出你的十八般武艺来为我们的应用实现热修复了。

© 著作权归作者所有

共有 人打赏支持
Krbit
粉丝 1
博文 1
码字总数 2107
作品 0
Android热修复技术选型——三大流派解析

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

阿里百川 ⋅ 2016/09/12 ⋅ 0

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

本文已在我的公众号hongyangAndroid首发。 转载请标明出处: gold.xitu.io/post/589736… 本文出自张鸿洋的博客 一、概述 放了一个大长假,happy,先祝大家2017年笑口常开。 热修复这项技术,...

Android鸿洋 ⋅ 2017/02/06 ⋅ 0

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

本文已在我的公众号hongyangAndroid首发。 转载请标明出处: http://blog.csdn.net/lmj623565791/article/details/54882693 本文出自张鸿洋的博客 一、概述 放了一个大长假,happy,先祝大家...

lmj623565791 ⋅ 2017/02/06 ⋅ 0

【腾讯Bugly干货分享】微信热补丁Tinker的实践演进之路

本文来自于腾讯bugly开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/57ad7a70eaed47bb2699e68e Dev Club 是一个交流移动开发技术,结交朋友,扩展人脉的社群,成员...

腾讯Bugly ⋅ 2016/08/19 ⋅ 0

Android 热修复 Tinker Gradle Plugin 解析

本文已在我的公众号hongyangAndroid原创首发。 一、概述 前面写了两篇分析了tinker的loader部分源码以及dex diff/patch算法相关解析,那么为了保证完整性,最后一篇主要写tinker-patch-grad...

Android鸿洋 ⋅ 2017/05/24 ⋅ 0

Android 热修复 Tinker Gradle Plugin解析

本文已在我的公众号hongyangAndroid原创首发。 转载请标明出处: http://blog.csdn.net/lmj623565791/article/details/72667669 本文出自张鸿洋的博客 一、概述 前面写了两篇分析了tinker的l...

lmj623565791 ⋅ 2017/05/23 ⋅ 0

微信 Tinker 负责人张绍文关于 Android 热修复直播分享记录

微信 Tinker 负责人张绍文关于 Android 热修复直播分享记录 来源:微信技术团队的公众号WeMobileDev 热补丁技术是当前非常热门的Android开发技术,其中比较出名的方案有支付宝的AndFix以及Q...

3kqing ⋅ 2016/09/06 ⋅ 0

全面了解Android热修复技术

作者:赵裕, 腾讯移动客户端开发 商业转载请联系腾讯WeTest获得授权,非商业转载请注明出处。 原文链接:http://wetest.qq.com/lab/view/338.html WeTest 导读 本文探讨了Android热修复技术...

腾讯WeTest ⋅ 2017/09/26 ⋅ 0

Android热修复技术

更新版本一直以来是移动端的一大痛点,各大公司也推出了相应的解决方案。 1)AndFix(阿里巴巴):兼容性不太好,亲试过,上线反馈崩溃问题特别严重。 2)Tinker(微信):集成起来是相当的麻烦...

屠夫章哥 ⋅ 2017/05/25 ⋅ 0

Android实战——Tinker的集成和使用

前言 对于热修复我相信很多小伙伴都已经知道它们普遍的操作套路,Tinker主要是依赖自己的gradlePlugin生成拆分包,所以其拆分包的生成就由Gradle来完成,当然也可以通过命令行的方式,这里就...

qq_30379689 ⋅ 2017/11/19 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

R计算IV

参考文章 #读取文件 rawdata = read.csv("/path/to/csv/file",header=T) colnames(rawdata)[18] <- "y" //重命名因变量y #数据分区 训练集测试集 trainIdx <- sample(nrow(rawdata), round(......

火力全開 ⋅ 7分钟前 ⋅ 0

SQL老司机,在SQL中计算 array & map & json数据

摘要: 场景 通常,我们处理数据,一列数据类型要么是字符串,要么是数字,这些都是primitive类型的数据。 场景 通常,我们处理数据,一列数据类型要么是字符串,要么是数字,这些都是primi...

阿里云云栖社区 ⋅ 7分钟前 ⋅ 0

SQL老司机,在SQL中计算 array & map & json数据

摘要: 场景 通常,我们处理数据,一列数据类型要么是字符串,要么是数字,这些都是primitive类型的数据。 场景 通常,我们处理数据,一列数据类型要么是字符串,要么是数字,这些都是primi...

猫耳m ⋅ 17分钟前 ⋅ 0

关于ireport自定义变量类型为list的时候

自己摸石头过河,我真的应该去趟市中心图书馆,借本真正靠谱的教材 网上的东西,只有0.01%是有用的,还有0.99%是垃圾,剩下的99%是垃圾的复制品。。 哎!~ 问题是这样的,报表带sql,从db中获...

炑炑milina ⋅ 18分钟前 ⋅ 0

Spring mvc ContextLoaderListener 原理解析

对于熟悉Spring MVC功能,首先应从web.xml 开始,在web.xml 文件中我们需要配置一个监听器 ContextLoaderListener,如下。 <!-- 加载spring上下文信息,最主要的功能是解析applicationContex...

轨迹_ ⋅ 19分钟前 ⋅ 0

阿里云发布企业数字化及上云外包平台服务:阿里云众包平台

摘要: 阿里云正式发布旗下众包平台业务(网址:https://zhongbao.aliyun.com/),支持包括:网站定制开发,APP、电商系统等软件开发,商标、商品LOGO、VI、产品包装设计、营销推广、大数据人...

阿里云官方博客 ⋅ 20分钟前 ⋅ 0

Redis安装异常解决办法

官网地址:http://redis.io/ 官网下载地址:http://redis.io/download 1. 下载Redis源码(tar.gz),并上传到Linux 2. 解压缩包:tar zxvf redis-2.8.17.tar.gz 3. 进入解压缩后的文件夹:c...

slagga ⋅ 25分钟前 ⋅ 0

006. 深入JVM学习—年轻代

1. 年轻代图片 年轻代(Young)属于JVM堆内存空间的一个组成部分 所有使用关键字new新实例化的对象一定会在伊甸园区进行保存,而对于存活区保存的一定是已经在伊甸园区存在一段时间并且经过了...

影狼 ⋅ 26分钟前 ⋅ 0

如何成为一个合格的程序员

偶尔的,我会被人问道:如何成为一名优秀的程序员,更或者,如何成为一名程序员。每次人们问起,我都力图给出不同的答案。因此,我的答案是各种各样的。下面就是我认为的成为一名优秀的程序员...

柳猫 ⋅ 27分钟前 ⋅ 0

cups error_log日志暴增

日志内容 File \"/usr/lib/cups/notifier/dbus\" has insecure permissions 解决(未验证适用范围) sudo service cups stopsudo rm /etc/cups/subscriptions.conf*sudo rm -r /var/cac......

一介码夫_Hum ⋅ 30分钟前 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部