从`Sample`来看`Tinker`的启动流程
博客专区 > Krbit 的博客 > 博客详情
从`Sample`来看`Tinker`的启动流程
Krbit 发表于1年前
从`Sample`来看`Tinker`的启动流程
  • 发表于 1年前
  • 阅读 711
  • 收藏 1
  • 点赞 0
  • 评论 0

腾讯云 学生专属云服务套餐 10元起购>>>   

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的启动流程,后面你就可以使出你的十八般武艺来为我们的应用实现热修复了。

共有 人打赏支持
粉丝 2
博文 1
码字总数 2107
×
Krbit
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: