• 利用APT技术实现安卓组件化的解耦(上)


    前言:

    组件化是安卓目前很流行的一门技术,其目的是避免复杂的业务逻辑交织到一起,相互影响。通过解耦,让每个子项目都是一个独立的工程,即使其余模块出现问题,也不会影响这个子模块的运行。

    一.为什么要实现通用接口实现类的解耦

    我们首先抛出第一个问题,什么要实现通用接口实现类的解耦?不解耦可以吗?

    既然这样,那我们先来看一下如果不实用APT解耦,我们该怎么做?举一个现实的场景:应用启动时,各个模块需要初始化。

    首先构造ServiceProviderInterface接口,定义如下两个方法,分别代表主线程要执行的初始化任务和子线程要执行的初始化任务。

    1. public interface ServiceProviderInterface {
    2. public void onMainThread(Context context);
    3. public void onSelfThread(Context context);
    4. }

    然后每个模块去实现这个接口,我们这里就举两个例子:

    AServiceProviderImpl:

    1. public class AServiceProviderImpl implements ServiceProviderInterface {
    2. @Override
    3. public void onMainThread(Context context) {
    4. //A模块主线程初始化任务
    5. Log.i("lxltest", "onMainThread A");
    6. }
    7. @Override
    8. public void onSelfThread(Context context) {
    9. /A模块子线程初始化任务
    10. Log.i("lxltest", "onSelfThread A");
    11. }
    12. }

    BServiceProviderImpl:

    1. public class BServiceProviderImpl implements ServiceProviderInterface {
    2. @Override
    3. public void onMainThread(Context context) {
    4. Log.i("lxltest", "onMainThread B");
    5. }
    6. @Override
    7. public void onSelfThread(Context context) {
    8. Log.i("lxltest", "onSelfThread B");
    9. }
    10. }

    然后我们应该在Application中启动时完成如下操作:

    1. //Application.onCreate时调用
    2. private void normalLaunch() {
    3. ServiceProviderInterface aServiceProvider = new AServiceProviderImpl();
    4. ServiceProviderInterface bServiceProvider = new BServiceProviderImpl();
    5. List list = new ArrayList<>();
    6. list.add(aServiceProvider);
    7. list.add(bServiceProvider);
    8. for (ServiceProviderInterface serviceProviderInterface : list) {
    9. serviceProviderInterface.onMainThread(this);
    10. }
    11. new Thread(() -> {
    12. for (ServiceProviderInterface serviceProviderInterface : list) {
    13. serviceProviderInterface.onSelfThread(instance);
    14. }
    15. }).start();
    16. }

    首先把A,B都new出来,然后加入到集合中,在启动时去遍历调用。

    看起来也挺简单,但是其实却存在这样几个问题:

    1.由于项目之间实现了解藕,而Application一般在主项目中,而A,B在子项目中。主项目是不会依赖子项目的,所以如果实现了组件化,这里很有可能无法直接引用AServiceProviderImpl和BServiceProviderImpl

    2.每次创建一个新的ServiceProviderInterface接口的实现类,都要改一下Application中的代码,容易忘记,一旦忘了就会报错。

    3.有的公司模块化比较彻底,很有可能主项目和子项目直接是两个项目,代码提交权限都受限制。

    4.这里只举了两个例子,两个模块,如果是20个模块呢?甚至200个模块呢?这手写代码的成本就有点高了。

    所以,有没有通用的解决方案呢?当然有,这个方案就是APT,让程序帮助我们来写代码。

    二.APT实现路由类的解耦

    主要分为以下几步:

    1.创建相关注解类

    2.使用APT处理注解标记的类

    3.利用ServiceLoader完成相关接口类的加载。

    2.1 创建相关注解类

    首先创建一个注解library,其余模块都依赖这个注解library。

    然后在library中创建注解类,注解的作用就是在编译时主动去识别相关被标记的类,知道该处理哪些类。

    2.2 使用APT处理注解标记的类

    创建router_processor模块,这个模块类型选择java项目。

    引入auto-service,build.gradle中配置如下:

    1. plugins {
    2. id 'java-library'
    3. }
    4. java {
    5. sourceCompatibility = JavaVersion.VERSION_1_7
    6. targetCompatibility = JavaVersion.VERSION_1_7
    7. }
    8. dependencies {
    9. implementation 'com.squareup:javapoet:1.11.1'
    10. api 'com.google.auto.service:auto-service:1.0-rc5'
    11. annotationProcessor 'com.google.auto.service:auto-service:1.0-rc5'
    12. implementation project(path: ':annotationLibrary')
    13. }

    最后创建注解处理类:ApplicationInitProcessor,代码如下:

    1. /**
    2. * 注解处理类,处理com.xt.router_api.ApplicationInit类型注解。
    3. * 凡是被@ApplicationInit注解声明的类,加入到services/com.xt.client.inter.ServiceProviderInterface中,启动自动实现初始化。
    4. */
    5. @AutoService(Processor.class)
    6. @SupportedAnnotationTypes("com.xt.router_api.ApplicationInit")
    7. @SupportedOptions("moduleName")
    8. public class ApplicationInitProcessor extends AbstractProcessor {
    9. Filer filer;
    10. String TAG = "ApplicationInitProcessor";
    11. boolean isAdd = false;
    12. String servicesPath;
    13. @Override
    14. public synchronized void init(ProcessingEnvironment processingEnv) {
    15. super.init(processingEnv);
    16. filer = processingEnv.getFiler();
    17. }
    18. @Override
    19. public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnvironment) {
    20. if (annotations.isEmpty()) {
    21. return false;
    22. }
    23. processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "TAG");
    24. //所有带ApplicationInit注解的类
    25. Set<? extends Element> set = roundEnvironment.getElementsAnnotatedWith(ApplicationInit.class);
    26. for (Element e : set) {
    27. String name = e.getSimpleName().toString();
    28. Element enclosingElement = e.getEnclosingElement();
    29. String packageName;
    30. if (enclosingElement instanceof PackageElement) {
    31. packageName = ((PackageElement) enclosingElement).getQualifiedName().toString();
    32. } else {
    33. packageName = ((TypeElement) enclosingElement).getQualifiedName().toString();
    34. }
    35. addToList(packageName + "." + name);
    36. }
    37. return true;
    38. }
    39. //针对注解中的内容生成类
    40. private void addToList(String name) {
    41. /**
    42. * 临时文件目录
    43. * DemoClient/app/build/intermediates/java_res/debug/out/META-INF/services/com.xt.client.inter.ServiceProviderInterface
    44. */
    45. try {
    46. if (servicesPath == null) {
    47. FileObject resource = filer.createResource(StandardLocation.SOURCE_OUTPUT, "a", "a");
    48. String path = resource.toUri().getPath();
    49. servicesPath = path.substring(0, path.indexOf("app") + 3);
    50. servicesPath += "/build/intermediates/java_res/debug/out/META-INF/services";
    51. }
    52. File folder = new File(servicesPath);
    53. if (!folder.exists()) {
    54. folder.mkdir();
    55. }
    56. File file = new File(folder.getAbsolutePath() + File.separator + "com.xt.client.inter.ServiceProviderInterface");
    57. StringBuilder builder = new StringBuilder();
    58. if (isAdd) {
    59. builder.append("\n");
    60. }
    61. //第一次的的时候覆盖,后面追加
    62. FileWriter fileWriter = new FileWriter(file, isAdd);
    63. isAdd = true;
    64. builder.append(name);
    65. System.out.println(TAG + ":append:" + builder);
    66. fileWriter.append(builder.toString());
    67. fileWriter.flush();
    68. } catch (IOException e) {
    69. e.printStackTrace();
    70. }
    71. }
    72. }

    里面主要有两个方法:

    init:项目开始编译时会调用这个方法。

    process:每次遇到ApplicationInit类型注解时,都会调用这个方法进行处理。

    实际项目中,这里我遇到一个问题,就是每次遇到ApplicationInit注解类,都会调用process方法,但是 com.xt.client.inter.ServiceProviderInterface文件我只需要创建一份。但是又不能直接判断如果文件不为空直接追加,万一是上一次的缓存文件呢?

    所以我这里采用了一个变通的方法,判断如果process是第一次执行,则执行覆盖逻辑,后续全部执行追加逻辑。(如果有更好的方式,也欢迎给我建议)

    1. //第一次的的时候覆盖,后面追加
    2. FileWriter fileWriter = new FileWriter(file, isAdd);

    这样重新rebuild一下项目,我们就可以发现在下面文件夹中,会生成一份文件。

    app/build/intermediates/java_res/debug/out/META-INF/services/

    这份文件内容如下:

     说明文件生成成功了。我们在看最终的APK文件中,也果然存在这份文件:

    2.3 利用ServiceLoader完成相关接口类的加载

    最终的用法就很简单了,直接使用ServiceLoader加载对应接口类就好,ServiceLoader会自动通过反射生成相关的实现类,并且加入到返回集合中。

    这里稍微扩展一点,ServiceLoader其实去读取的com.xt.client.inter.ServiceProviderInterface文件中的内容,所以上一步的生成时很必要的。

    1. private void aptLaunch() {
    2. ServiceLoader load = ServiceLoader.load(ServiceProviderInterface.class);
    3. for (ServiceProviderInterface serviceProviderInterface : load) {
    4. serviceProviderInterface.onMainThread(this);
    5. }
    6. new Thread(() -> {
    7. for (ServiceProviderInterface serviceProviderInterface : load) {
    8. serviceProviderInterface.onSelfThread(instance);
    9. }
    10. }).start();
    11. }

    三.相关代码:

    DEMO工程:

    https://github.com/aa5279aa/android_all_demo

    router_processor库:

    https://github.com/aa5279aa/android_all_demo/tree/master/DemoClient/router_processor

    Application调用处代码:

    https://github.com/aa5279aa/android_all_demo/blob/master/DemoClient/app/src/main/java/com/xt/client/application/DemoApplication.java

    四.扩展:

    其实利用APT还可以做很多事情,甚至可以在编译的时候动态生成JAVA文件并且进行编译。

    我们下一篇就来讲解如果动态的生成java文件并且编译打包到APK中。

  • 相关阅读:
    STM32定时器篇——Systick定时器的使用(实现delay延时函数)
    【微服务】微服务之Feign 与 Ribbon
    《中华人民共和国网络安全法》
    还能这么玩?将Prompt Tuning用于细粒度的图像检索!
    vue 对axios进行封装
    byte数据与Int和bit转换类
    如何再kali中下载iwebsec靶场
    快速幂原理以及python内置pow函数
    Android 使用kotlin+注解+反射+泛型实现MVP架构
    跳出Lambda表达式forEach()循环解决思路
  • 原文地址:https://blog.csdn.net/AA5279AA/article/details/126428043