组件化是安卓目前很流行的一门技术,其目的是避免复杂的业务逻辑交织到一起,相互影响。通过解耦,让每个子项目都是一个独立的工程,即使其余模块出现问题,也不会影响这个子模块的运行。
我们首先抛出第一个问题,什么要实现通用接口实现类的解耦?不解耦可以吗?
既然这样,那我们先来看一下如果不实用APT解耦,我们该怎么做?举一个现实的场景:应用启动时,各个模块需要初始化。
首先构造ServiceProviderInterface接口,定义如下两个方法,分别代表主线程要执行的初始化任务和子线程要执行的初始化任务。
- public interface ServiceProviderInterface {
- public void onMainThread(Context context);
- public void onSelfThread(Context context);
- }
然后每个模块去实现这个接口,我们这里就举两个例子:
AServiceProviderImpl:
- public class AServiceProviderImpl implements ServiceProviderInterface {
- @Override
- public void onMainThread(Context context) {
- //A模块主线程初始化任务
- Log.i("lxltest", "onMainThread A");
- }
-
- @Override
- public void onSelfThread(Context context) {
- /A模块子线程初始化任务
- Log.i("lxltest", "onSelfThread A");
- }
- }
BServiceProviderImpl:
- public class BServiceProviderImpl implements ServiceProviderInterface {
- @Override
- public void onMainThread(Context context) {
- Log.i("lxltest", "onMainThread B");
- }
-
- @Override
- public void onSelfThread(Context context) {
- Log.i("lxltest", "onSelfThread B");
- }
- }
然后我们应该在Application中启动时完成如下操作:
- //Application.onCreate时调用
- private void normalLaunch() {
- ServiceProviderInterface aServiceProvider = new AServiceProviderImpl();
- ServiceProviderInterface bServiceProvider = new BServiceProviderImpl();
-
- List
list = new ArrayList<>(); - list.add(aServiceProvider);
- list.add(bServiceProvider);
-
- for (ServiceProviderInterface serviceProviderInterface : list) {
- serviceProviderInterface.onMainThread(this);
- }
- new Thread(() -> {
- for (ServiceProviderInterface serviceProviderInterface : list) {
- serviceProviderInterface.onSelfThread(instance);
- }
- }).start();
- }
首先把A,B都new出来,然后加入到集合中,在启动时去遍历调用。
看起来也挺简单,但是其实却存在这样几个问题:
1.由于项目之间实现了解藕,而Application一般在主项目中,而A,B在子项目中。主项目是不会依赖子项目的,所以如果实现了组件化,这里很有可能无法直接引用AServiceProviderImpl和BServiceProviderImpl
2.每次创建一个新的ServiceProviderInterface接口的实现类,都要改一下Application中的代码,容易忘记,一旦忘了就会报错。
3.有的公司模块化比较彻底,很有可能主项目和子项目直接是两个项目,代码提交权限都受限制。
4.这里只举了两个例子,两个模块,如果是20个模块呢?甚至200个模块呢?这手写代码的成本就有点高了。
所以,有没有通用的解决方案呢?当然有,这个方案就是APT,让程序帮助我们来写代码。
主要分为以下几步:
1.创建相关注解类
2.使用APT处理注解标记的类
3.利用ServiceLoader完成相关接口类的加载。
首先创建一个注解library,其余模块都依赖这个注解library。
然后在library中创建注解类,注解的作用就是在编译时主动去识别相关被标记的类,知道该处理哪些类。
创建router_processor模块,这个模块类型选择java项目。
引入auto-service,build.gradle中配置如下:
- plugins {
- id 'java-library'
- }
-
- java {
- sourceCompatibility = JavaVersion.VERSION_1_7
- targetCompatibility = JavaVersion.VERSION_1_7
- }
-
- dependencies {
- implementation 'com.squareup:javapoet:1.11.1'
- api 'com.google.auto.service:auto-service:1.0-rc5'
- annotationProcessor 'com.google.auto.service:auto-service:1.0-rc5'
- implementation project(path: ':annotationLibrary')
-
- }
最后创建注解处理类:ApplicationInitProcessor,代码如下:
- /**
- * 注解处理类,处理com.xt.router_api.ApplicationInit类型注解。
- * 凡是被@ApplicationInit注解声明的类,加入到services/com.xt.client.inter.ServiceProviderInterface中,启动自动实现初始化。
- */
- @AutoService(Processor.class)
- @SupportedAnnotationTypes("com.xt.router_api.ApplicationInit")
- @SupportedOptions("moduleName")
- public class ApplicationInitProcessor extends AbstractProcessor {
- Filer filer;
- String TAG = "ApplicationInitProcessor";
- boolean isAdd = false;
- String servicesPath;
-
- @Override
- public synchronized void init(ProcessingEnvironment processingEnv) {
- super.init(processingEnv);
- filer = processingEnv.getFiler();
- }
-
- @Override
- public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnvironment) {
- if (annotations.isEmpty()) {
- return false;
- }
-
- processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "TAG");
- //所有带ApplicationInit注解的类
- Set<? extends Element> set = roundEnvironment.getElementsAnnotatedWith(ApplicationInit.class);
- for (Element e : set) {
- String name = e.getSimpleName().toString();
- Element enclosingElement = e.getEnclosingElement();
- String packageName;
- if (enclosingElement instanceof PackageElement) {
- packageName = ((PackageElement) enclosingElement).getQualifiedName().toString();
- } else {
- packageName = ((TypeElement) enclosingElement).getQualifiedName().toString();
- }
- addToList(packageName + "." + name);
- }
- return true;
- }
-
- //针对注解中的内容生成类
- private void addToList(String name) {
-
- /**
- * 临时文件目录
- * DemoClient/app/build/intermediates/java_res/debug/out/META-INF/services/com.xt.client.inter.ServiceProviderInterface
- */
-
- try {
- if (servicesPath == null) {
- FileObject resource = filer.createResource(StandardLocation.SOURCE_OUTPUT, "a", "a");
- String path = resource.toUri().getPath();
- servicesPath = path.substring(0, path.indexOf("app") + 3);
- servicesPath += "/build/intermediates/java_res/debug/out/META-INF/services";
- }
- File folder = new File(servicesPath);
- if (!folder.exists()) {
- folder.mkdir();
- }
- File file = new File(folder.getAbsolutePath() + File.separator + "com.xt.client.inter.ServiceProviderInterface");
-
- StringBuilder builder = new StringBuilder();
- if (isAdd) {
- builder.append("\n");
- }
- //第一次的的时候覆盖,后面追加
- FileWriter fileWriter = new FileWriter(file, isAdd);
- isAdd = true;
- builder.append(name);
- System.out.println(TAG + ":append:" + builder);
- fileWriter.append(builder.toString());
- fileWriter.flush();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
里面主要有两个方法:
init:项目开始编译时会调用这个方法。
process:每次遇到ApplicationInit类型注解时,都会调用这个方法进行处理。
实际项目中,这里我遇到一个问题,就是每次遇到ApplicationInit注解类,都会调用process方法,但是 com.xt.client.inter.ServiceProviderInterface文件我只需要创建一份。但是又不能直接判断如果文件不为空直接追加,万一是上一次的缓存文件呢?
所以我这里采用了一个变通的方法,判断如果process是第一次执行,则执行覆盖逻辑,后续全部执行追加逻辑。(如果有更好的方式,也欢迎给我建议)
- //第一次的的时候覆盖,后面追加
- FileWriter fileWriter = new FileWriter(file, isAdd);
这样重新rebuild一下项目,我们就可以发现在下面文件夹中,会生成一份文件。
app/build/intermediates/java_res/debug/out/META-INF/services/
这份文件内容如下:
说明文件生成成功了。我们在看最终的APK文件中,也果然存在这份文件:
最终的用法就很简单了,直接使用ServiceLoader加载对应接口类就好,ServiceLoader会自动通过反射生成相关的实现类,并且加入到返回集合中。
这里稍微扩展一点,ServiceLoader其实去读取的com.xt.client.inter.ServiceProviderInterface文件中的内容,所以上一步的生成时很必要的。
- private void aptLaunch() {
- ServiceLoader
load = ServiceLoader.load(ServiceProviderInterface.class); - for (ServiceProviderInterface serviceProviderInterface : load) {
- serviceProviderInterface.onMainThread(this);
- }
- new Thread(() -> {
- for (ServiceProviderInterface serviceProviderInterface : load) {
- serviceProviderInterface.onSelfThread(instance);
- }
- }).start();
- }
DEMO工程:
https://github.com/aa5279aa/android_all_demo
router_processor库:
https://github.com/aa5279aa/android_all_demo/tree/master/DemoClient/router_processor
Application调用处代码:
其实利用APT还可以做很多事情,甚至可以在编译的时候动态生成JAVA文件并且进行编译。
我们下一篇就来讲解如果动态的生成java文件并且编译打包到APK中。