Tinker 热修复引出的 Apt 功能及使用(定制 Application)

android 项目,稍微大一点的,都希望接入热修复,好处是如果有bug,可以随时修复,不用重新发版本升级;同时,也希望对发出去的版本添加一些新功能。H5是可以做到随时更新,但牵涉到原生的交互,就没那么容易了,因此,前几年热修复是个热门。我所在的项目组是2016年底时添加了热修复功能,一开始是说要自己公司做一个,接到任务的那哥们脸色发白,连续两周,基本没什么产出。我俩工位挨在,我见证了那哥们的崩溃。我俩讨论了关于热修复的做法,当时我也是一知半解的,我推荐使用 Tinker,因为官方宣称微信使用的就是 Tinker,我们还有什么理由好犹豫!给老大反馈后,老大最终同意了使用三方库(他是做 c、c++出身的,不懂android)。然后就是项目中接入了 Tinker 和 TinkerPatch,功能基本实现。


接入 Tinker,第一步就是改造 Application, 官方推荐我们在 gradle 中添加 provided "com.tinkerpatch.tinker:tinker-android-anno:***" 这个配置,使用 @DefaultLifeCycle 动态生成 Application,

    @DefaultLifeCycle(application = "tinker.sample.android.app.SampleApplication",
                      flags = ShareConstants.TINKER_ENABLE_ALL,
                      loadVerifyFlag = false)
    public class SampleApplicationLike extends DefaultApplicationLike {

    }

这样,通过 apt 技术,就动态生成了一个 SampleApplication 类文件。如果我们想自己定义 Application 类,我们可以不使用这个注解,而是直接用代码来写,有一点要注意,写的 Application 要继承 TinkerApplication 。 官方为什么推荐 tinker-android-anno 呢?因为热修复时,基础包和母包的 Application 和配置清单都是不能修改的,如果两者的不一样,热修复会失败,Tinker 官方为了避免我们的失误,不建议我们自定义。但我却有点好奇,tinker-android-anno 是怎么生成 class 文件的。


在 tinker-android-anno 下面有一个 AnnotationProcessor 文件,这个里面有个操作,使用了 DefaultLifeCycle 这个注解,如果对注解不清楚的话,可以看看之前的 https://blog.csdn.net/Deaht_Huimie/article/details/87730785 。 注processDefaultLifeCycle() 方法, 正是在这个方法中,通过注解获取了注解里包含的信息,然后通过判断是否需要包名拼接,生成对应的类名和包名, final InputStream is = AnnotationProcessor.class.getResourceAsStream(APPLICATION_TEMPLATE_PATH); 这行代码很关键,它把一个文件里的内容转换为 IO 流,然后转换为字符串,通过替换固定的字符串,把包名和类名等信息赋值进去,然后再通过 JavaFileObject 来创建个文件,最终生成指定的 class 文件。 APPLICATION_TEMPLATE_PATH 是一个文件名,可以理解为是个模版,它是  resouces/TinkerAnnoApplication.tmpl, 看一下它的内容

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

}


后来,代码逻辑变了,我们需要统计 Application 中 startActivity() 方法的一些信息,这就要在 Application 中重写这个方法了。要么不用注解,自定义 Application;如果还想用注解,那么我们自己自定义个 tinker-android-anno 类型的jar。我们首先在 AS 中建个项目,然后建个 module 包,记得要使用 java Library 类型的,我们在 gradle 中添加如下代码

apply plugin: 'java'

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.google.auto.service:auto-service:1.0-rc2'
}

sourceCompatibility = "1.7"
targetCompatibility = "1.7"

sourceSets {
    main {
        java {
            srcDir 'src/main/java'
        }

        resources {
            srcDir 'src/main/resources'
        }
    }
}


compile 'com.google.auto.service:auto-service:1.0-rc2' 是引入 AbstractProcessor 用的。我们要在 main 下面创建 resources 文件,要在里面添加使用 apt的入口文件的名字,文件名为 javax.annotation.processing.Processor, 里面内容就一行, com.example.AnnotationProcessor2 。我们还需要自定义模版,暂且命名为 TinkerBon2.tmpl,内容仿照 TinkerAnnoApplication.tmpl 中的写即可

package %PACKAGE%;

import android.content.Intent;
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%);
        }

    @Override
    public void startActivity(Intent intent) {
        super.startActivity(intent);
    }

}


自定义注解 DefaultLifeCycle2, 完全抄 DefaultLifeCycle 就可以了

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.SOURCE)
@Inherited
public @interface DefaultLifeCycle2 {
    String application();

    String loaderClass() default "com.tencent.tinker.loader.TinkerLoader";

    int flags();

    boolean loadVerifyFlag() default false;
}

然后就是 AnnotationProcessor2 了,还是仿照 AnnotationProcessor写,只是把里面的 DefaultLifeCycle 替换为 DefaultLifeCycle2,同时把 AnnotationProcessor2.class.getResourceAsStream("") 指给你的参数换成 "/TinkerBon2.tmpl" ,这样,就可以了。

@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class AnnotationProcessor2 extends AbstractProcessor {

    public AnnotationProcessor2() {
    }

    public Set<String> getSupportedAnnotationTypes() {
        LinkedHashSet supportedAnnotationTypes = new LinkedHashSet();
        supportedAnnotationTypes.add(DefaultLifeCycle2.class.getName());
        return supportedAnnotationTypes;
    }

    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        this.processDefaultLifeCycle(roundEnv.getElementsAnnotatedWith(DefaultLifeCycle2.class));
        return true;
    }

    private void processDefaultLifeCycle(Set<? extends Element> elements) {
        Iterator var2 = elements.iterator();

        while(var2.hasNext()) {
            Element e = (Element)var2.next();
            DefaultLifeCycle2 ca = (DefaultLifeCycle2)e.getAnnotation(DefaultLifeCycle2.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("*");
            InputStream is = AnnotationProcessor2.class.getResourceAsStream("/TinkerBon2.tmpl");
            Scanner scanner = new Scanner(is);
            String template = scanner.useDelimiter("\\A").next();
            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 x = this.processingEnv.getFiler().createSourceFile(applicationPackageName + "." + applicationClassName, new
Element[0]);
                this.processingEnv.getMessager().printMessage(Kind.NOTE, "Creating " + x.toUri());
                Writer writer = x.openWriter();

                try {
                    PrintWriter pw = new PrintWriter(writer);
                    pw.print(fileContent);
                    pw.flush();
                } finally {
                    writer.close();
                }
            } catch (IOException var21) {
                this.processingEnv.getMessager().printMessage(Kind.ERROR, var21.toString());
            }
        }

    }
}


然后我们就可以在自己的项目中使用注解了,在 MainActivity 的类的头部,使用注解

@DefaultLifeCycle2(
        //application类名
        application = "com.test.dd.BaseTinkerApplication",
        //tinkerFlags
        flags = 7,
        loadVerifyFlag = true)

同时在配置清单中, applicationg 节点中,注意配置 name 的路径为 com.test.dd.BaseTinkerApplication 。这里还有点要注意,application 的路径,上面我写的是全路径,如果我们只写了 .BaseTinkerApplication,把前面的 com.test.dd 给省略了,那么我们在选择 MainActivity 这样的类时,一定要选择在 com.test.dd 包下面的类,具体的原因看 processDefaultLifeCycle() 方法。 编译代码时,BaseTinkerApplication 类会在 build/generated/source/apt 中。


Apt 可以在编译时生成代码,这样可以起到解耦的作用,它通常是和注解一起使用,还有其他的一些三方库,例如 Dagger2、ButterKnife 等等。从中受到启发,我们也可以自定义自己需要的东西。
 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值