文档章节

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

Krbit
 Krbit
发布于 2016/11/11 12:26
字数 2107
阅读 1.5K
收藏 1

「深度学习福利」大神带你进阶工程师,立即查看>>>

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
私信 提问
加载中
请先登录后再评论。
Netty那点事(三)Channel与Pipeline

Channel是理解和使用Netty的核心。Channel的涉及内容较多,这里我使用由浅入深的介绍方法。在这篇文章中,我们主要介绍Channel部分中Pipeline实现机制。为了避免枯燥,借用一下《盗梦空间》的...

黄亿华
2013/11/24
2W
22
beego API开发以及自动化文档

beego API开发以及自动化文档 beego1.3版本已经在上个星期发布了,但是还是有很多人不了解如何来进行开发,也是在一步一步的测试中开发,期间QQ群里面很多人都问我如何开发,我的业余时间实在...

astaxie
2014/06/25
2.7W
22
Ruby虚拟机--YARV

YARV(Yet Another Ruby VM),该项目的唯一目的就是要打造世界上最快的Ruby虚拟机。从早期的一些评测来看,YARV为Ruby带来了巨大的性能提升,而它也成为了后来Ruby 1.9的官方解释器,自然不...

匿名
2013/02/17
1.4K
0
NoSQL 数据服务器--Reveldb

reveldb 一个基于 google leveldb 的 NoSQL 数据服务器,网络连接采用了 libevent 的 HTTP 接口,因此 reveldb 天生就适合处理 HTTP 请求。但更确切地说,reveldb 并没有直接采用 libevent 的...

大卷卷
2013/01/04
1.3K
0
价值100W的经验分享: 基于JSPatch的iOS应用线上Bug的即时修复方案,附源码.

限于iOS AppStore的审核机制,一些新的功能的添加或者bug的修复,想做些节日专属的活动等,几乎都是不太可能的.从已有的经验来看,也是有了一些比较常用的解决方案.本文先是会简单说明对比大部分...

ios122
2015/12/07
2K
1

没有更多内容

加载失败,请刷新页面

加载更多

android 获取mac地址

android获取Mac地址的两种方式,适用于6.0版本以下 public static String tryGetWifiMac(Context context) { WifiManager wm = (WifiManager) context.getApplicationContext().getSyst......

osc_8cqhsn24
1分钟前
0
0
阿里HR: 你会 Android 实现侧滑菜单-design吗? CN看了,原来这么简单呀!

google提供的Design开发包里,有很多实用好看的新控件,这里介绍下使用DrawerLayout+NavigationView实现侧滑菜单效果 要使用Design包,只要在项目的build.gradle中添加下依赖就好(记得更新S...

osc_evac23lh
2分钟前
0
0
IOS开发控件视图day15:UIPageControl设置定时器自动翻页,以及更改小圆点背景图片

.h文件 @property(nonatomic,strong)UIScrollView *scrollView0;@property (strong, nonatomic) UIPageControl *pageControl0;@property (nonatomic,strong) NSTimer *nstime; .m文件(具......

osc_494omtst
3分钟前
0
0
Codeforces Round #663 (Div. 2) (CD)

C. Cyclic Permutations 思路:全排列减去单峰排列即为答案。 单峰排列即:峰左边下标的左边没有比它大的,峰右边的下标的右边没有比它大的。 单峰排列个数: 2 n − 1 2^{n-1} 2n−1,除 n ...

osc_gh0ost1g
5分钟前
0
0
Python Pandas面试题及答案

Pandas是一个开源库,可在Python中提供高性能的数据处理。 Pandas这个名称源自“面板数据”一词,这表示来自多维数据的计量经济学。 它可用于Python中的数据分析,并由Wes McKinney在2008年开...

程序员面试吧
5分钟前
0
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部