安卓APT技术讲解(上)-实现安卓组件化的解耦

本文介绍了Android组件化中如何通过通用接口实现类的解耦,以及利用APT(Annotation Processing Tool)技术来解决传统方式存在的问题。通过创建相关注解类,使用APT处理注解标记的类,并利用ServiceLoader加载接口类,实现了在编译时动态生成代码并自动初始化,从而降低了代码维护成本和提高了模块间的解耦度。
摘要由CSDN通过智能技术生成

前言:

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

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

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

既然这样,那我们先来看一下如果不实用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<ServiceProviderInterface> 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,让程序帮助我们来写代码。

二.APT实现路由类的解耦

主要分为以下几步:

1.创建相关注解类

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

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

2.1 创建相关注解类

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

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

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

创建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文件中,也果然存在这份文件:

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

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

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

private void aptLaunch() {
        ServiceLoader<ServiceProviderInterface> 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调用处代码:

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

四.扩展:

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

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

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

失落夏天

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值