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 等等。从中受到启发,我们也可以自定义自己需要的东西。