首页 > 其他分享 >Android 解耦(三)基于 APT 的解耦

Android 解耦(三)基于 APT 的解耦

时间:2023-07-29 15:33:03浏览次数:34  
标签:基于 APT user 模块 UserService ServiceManager 注解 Android

Android 通过 APT 解耦模块依赖

一、APT 是什么?

APT(Annotation Process Tool)是注解处理工具,它可以在编译期间扫描和处理注解,并生成相应的 Java 代码。APT 是 Java 的一个特性,但在 Android 开发中也有广泛的应用 APT 的优点是:

  • 可以在编译期间检查代码的正确性,避免运行时出现错误
  • 可以减少手写代码的数量,提高开发效率和可读性
  • 可以实现模块间的解耦,降低耦合度和依赖关系

APT 的缺点是:

  • 需要额外配置和使用注解处理器
  • 生成的代码可能不易理解和维护
  • 对于动态变化的数据不适用

二、APT 在 Android 中的应用场景

APT 在 Android 中有很多应用场景,例如:

  • ButterKnife:使用注解绑定视图和事件,简化 UI 开发
  • Dagger2:使用注解实现依赖注入,实现模块间的解耦
  • EventBus:使用注解注册和发送事件,简化组件间的通信
  • Retrofit:使用注解定义网络请求接口,简化网络开发
  • ARouter:使用注解定义路由表,实现模块间的跳转

本文将重点介绍 APT 在 Android 模块化开发中如何实现模块间的解耦。

三、APT 如何实现模块间的解耦

在 Android 模块化开发中,通常会遇到以下问题:

  • 模块之间不能直接引用对方的类或资源,否则会造成循环依赖或冲突
  • 模块之间需要通过接口或抽象类来定义通信协议,但这样会增加代码量和复杂度
  • 模块之间需要通过反射或动态代理来调用对方的方法或服务,但这样会影响性能和安全性

为了解决这些问题,我们可以利用 APT 来生成作为跨模块转发层的中间类具体步骤如下:

  1. 定义一个公共库(common)作为基础模块,提供一些基础功能和通用接口。
  2. 定义一个注解库(annotation)作为公共库的子模块,在其中定义一些自定义注解。
  3. 定义一个编译器库(compiler)作为公共库的子模块,在其中定义一个自定义注解处理器。
  4. 在业务模块(moduleA、moduleB等)中引入公共库,并使用自定义注解标记需要跨模块调用或提供服务的类或方法。
  5. 在编译期间,自定义注解处理器会扫描所有业务模块中标记了自定义注解的类或方法,并根据规则生成相应的中间类。
  6. 在运行期间,业务模块可以通过中间类来调用其他业务模块提供的服务或方法。

下面我们以一个简单示例来说明这个过程。

3.1 公共库 common

公共库 common 提供了以下功能:

  • 定义了一个 ServiceManager 类来管理所有跨模块提供或调

Android 通过 APT 解耦模块依赖

一、APT 是什么?

APT(Annotation Process Tool)是注解处理工具,它可以在编译期间扫描和处理注解,并生成相应的 Java 代码。APT 是 Java 的一个特性,但在 Android 开发中也有广泛的应用 。 APT 的优点是: - 可以在编译期间检查代码的正确性,避免运行时出现错误 - 可以减少手写代码的数量,提高开发效率和可读性 - 可以实现模块间的解耦,降低耦合度和依赖关系 APT 的缺点是: - 需要额外配置和使用注解处理器 - 生成的代码可能不易理解和维护 - 对于动态变化的数据不适用

二、APT 在 Android 中的应用场景

APT 在 Android 中有很多应用场景,例如: - ButterKnife:使用注解绑定视图和事件,简化 UI 开发 - Dagger2:使用注解实现依赖注入,实现模块间的解耦 - EventBus:使用注解注册和发送事件,简化组件间的通信 - Retrofit:使用注解定义网络请求接口,简化网络开发 - ARouter:使用注解定义路由表,实现模块间的跳转 本文将重点介绍 APT 在 Android 模块化开发中如何实现模块间的解耦。

三、APT 如何实现模块间的解耦

在 Android 模块化开发中,通常会遇到以下问题: - 模块之间不能直接引用对方的类或资源,否则会造成循环依赖或冲突 - 模块之间需要通过接口或抽象类来定义通信协议,但这样会增加代码量和复杂度 - 模块之间需要通过反射或动态代理来调用对方的方法或服务,但这样会影响性能和安全性 为了解决这些问题,我们可以利用 APT 来生成作为跨模块转发层的中间类。 具体步骤如下: 1. 定义一个公共库(common)作为基础模块,提供一些基础功能和通用接口。 2. 定义一个注解库(annotation)作为公共库的子模块,在其中定义一些自定义注解。 3. 定义一个编译器库(compiler)作为公共库的子模块,在其中定义一个自定义注解处理器。 4. 在业务模块(moduleA、moduleB等)中引入公共库,并使用自定义注解标记需要跨模块调用或提供服务的类或方法。 5. 在编译期间,自定义注解处理器会扫描所有业务模块中标记了自定义注解的类或方法,并根据规则生成相应的中间类。 6. 在运行期间,业务模块可以通过中间类来调用其他业务模块提供的服务或方法。 下面我们以一个简单示例来说明这个过程。

3.1 公共库 common 公共库

common 提供了以下功能:

  • 定义了一个 ServiceManager 类来管理所有跨模块提供或调

3.2 注解库 annotation

注解库 annotation 定义了以下注解:

  • @Service:用于标记需要跨模块提供服务的类,注解中可以指定服务的名称和描述
  • @Method:用于标记需要跨模块调用的方法,注解中可以指定方法的名称和参数类型
  • @Param:用于标记方法的参数,注解中可以指定参数的名称和类型

例如:

// 定义一个跨模块提供服务的类
@Service(name = "user", desc = "用户服务")
public class UserService {

    // 定义一个跨模块调用的方法
    @Method(name = "login", params = {@Param(name = "username", type = String.class), @Param(name = "password", type = String.class)})
    public boolean login(String username, String password) {
        // 省略登录逻辑
        return true;
    }
}

3.3 编译器库 compiler

编译器库 compiler 定义了一个自定义注解处理器 ServiceProcessor,它继承了 AbstractProcessor 类,并重写了以下方法:

  • init:初始化注解处理器,获取一些工具类对象,如 Elements、Types、Filer 等
  • getSupportedAnnotationTypes:返回支持处理的注解类型集合,如 Service、Method、Param 等
  • process:处理标记了支持的注解类型的元素(Element),并根据规则生成相应的中间类

例如:

// 自定义注解处理器
public class ServiceProcessor extends AbstractProcessor {

    // 初始化工具类对象
    private Elements elementUtils;
    private Types typeUtils;
    private Filer filer;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        elementUtils = processingEnv.getElementUtils();
        typeUtils = processingEnv.getTypeUtils();
        filer = processingEnv.getFiler();
    }

    // 返回支持处理的注解类型集合
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> types = new LinkedHashSet<>();
        types.add(Service.class.getCanonicalName());
        types.add(Method.class.getCanonicalName());
        types.add(Param.class.getCanonicalName());
        return types;
    }

3.2 注解库

annotation 注解库 annotation 定义了以下注解:

  • @Service:用于标记需要跨模块提供服务的类,注解中可以指定服务的名称和描述
  • @Method:用于标记需要跨模块调用的方法,注解中可以指定方法的名称和参数类型
  • @Param:用于标记方法的参数,注解中可以指定参数的名称和类型 例如:
// 定义一个跨模块提供服务的类 
@Service(name = "user", desc = "用户服务") public class UserService {
 // 定义一个跨模块调用的方法 
 @Method(name = "login", params = {
 @Param(name = "username", type = String.class), 
 @Param(name = "password", type = String.class)
 }
 ) 
 public boolean login(String username, String password) { 
 // 省略登录逻辑
  return true; 
  } 
 } 

3.3 编译器库 compiler 编译器库 compiler 定义了一个自定义注解处理器 ServiceProcessor,它继承了 AbstractProcessor 类,并重写了以下方法:

  • init:初始化注解处理器,获取一些工具类对象,如 Elements、Types、Filer 等
  • getSupportedAnnotationTypes:返回支持处理的注解类型集合,如 Service、Method、Param 等
  • process:处理标记了支持的注解类型的元素(Element),并根据规则生成相应的中间类 例如:
// 自定义注解处理器 
public class ServiceProcessor extends AbstractProcessor {
 // 初始化工具类对象
  private Elements elementUtils;
  private Types typeUtils; 
  private Filer filer; 
  @Override public synchronized void init(ProcessingEnvironment processingEnv) {
   super.init(processingEnv); 
   elementUtils = processingEnv.getElementUtils(); 
   typeUtils = processingEnv.getTypeUtils(); 
   filer = processingEnv.getFiler(); 
   } 
   // 返回支持处理的注解类型集合 
   @Override public Set<String> getSupportedAnnotationTypes() {
    Set<String> types = new LinkedHashSet<>(); 
    types.add(Service.class.getCanonicalName()); 
    types.add(Method.class.getCanonicalName()); 
    types.add(Param.class.getCanonicalName()); return types;
     }
}

3.4 运行时库 runtime

运行时库 runtime 定义了一个 ServiceManager 类,它负责管理所有跨模块提供的服务类和方法,以及加载生成的中间类。它提供了以下方法:

  • init:初始化 ServiceManager,扫描所有已注册的服务类,并加载对应的中间类
  • register:注册一个服务类,需要传入服务类的全限定名
  • invoke:调用一个服务方法,需要传入服务名称、方法名称和参数列表

例如:

// 初始化 ServiceManager
ServiceManager.init();

// 注册一个服务类
ServiceManager.register("com.example.UserService");

// 调用一个服务方法
boolean result = (boolean) ServiceManager.invoke("user", "login", "admin", "123456");

3.5 使用示例

假设我们有两个模块:app 和 user。app 模块依赖于 user 模块,user 模块提供了一个 UserService 类,用于实现用户相关的功能。我们想要在 app 模块中调用 UserService 的 login 方法,但是不想直接引用 user 模块。

我们可以使用 APT 来解耦这两个模块的依赖关系,具体步骤如下:

  1. 在 user 模块中定义 UserService 类,并使用 @Service 和 @Method 注解标记该类和 login 方法。
  2. 在 app 模块中添加 annotation、compiler 和 runtime 库的依赖,并在 build.gradle 中配置 annotationProcessor。
  3. 在 app 模块中注册 UserService 类,并使用 ServiceManager 调用 login 方法。

具体代码如下:

// user 模块中定义 UserService 类
@Service(name = "user", desc = "用户服务")
public class UserService {

    @Method(name = "login", params = {@Param(name = "username", type = String.class), @Param(name = "password", type = String.class)})
    public boolean login(String username, String password) {
        // 省略登录逻辑
        return true;
    }
}

// app 模块中添加依赖和配置
dependencies {
    implementation project(':user')
    implementation 'com.kymjs:annotation:1.0'
    annotationProcessor 'com.kymjs:compiler:1.0'
    implementation 'com.kymjs:runtime:1.0'
}

// app 模块中注册并调用 UserService 类
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 初始化 ServiceManager
        ServiceManager.init();

        // 注册 UserService 类
        ServiceManager.register("com.example.UserService");

        // 调用 login 方法
        boolean result = (boolean) ServiceManager.invoke("user", "login", "admin", "123456");

        // 显示结果
        Toast.makeText(this, result ? "登录成功" : "登录失败", Toast.LENGTH_SHORT).show();
    }
}

这样就实现了通过 APT 解耦模块依赖的功能。

3.4 运行时库 runtime

运行时库 runtime 定义了一个 ServiceManager 类,它负责管理所有跨模块提供的服务类和方法,以及加载生成的中间类。它提供了以下方法: - init:初始化 ServiceManager,扫描所有已注册的服务类,并加载对应的中间类 - register:注册一个服务类,需要传入服务类的全限定名 - invoke:调用一个服务方法,需要传入服务名称、方法名称和参数列表 例如:

// 初始化 
ServiceManager ServiceManager.init(); 
// 注册一个服务类 
ServiceManager.register("com.example.UserService");
 // 调用一个服务方法 
 boolean result = (boolean) ServiceManager.invoke("user", "login", "admin", "123456"); 

3.5 使用示例

假设我们有两个模块:app 和 user。app 模块依赖于 user 模块,user 模块提供了一个 UserService 类,用于实现用户相关的功能。我们想要在 app 模块中调用 UserService 的 login 方法,但是不想直接引用 user 模块。 我们可以使用 APT 来解耦这两个模块的依赖关系,具体步骤如下: 1. 在 user 模块中定义 UserService 类,并使用 @Service 和 @Method 注解标记该类和 login 方法。 2. 在 app 模块中添加 annotation、compiler 和 runtime 库的依赖,并在 build.gradle 中配置 annotationProcessor。 3. 在 app 模块中注册 UserService 类,并使用 ServiceManager 调用 login 方法。 具体代码如下:

// user 模块中定义 UserService 类 @Service(name = "user", desc = "用户服务") public class UserService {
  @Method(name = "login", params = {@Param(name = "username", type = String.class), 
  @Param(name = "password", type = String.class)
  }) public boolean login(String username, String password) {
   // 省略登录逻辑 return true; 
   } 
   } 
   // app 模块中添加依赖和配置 
   dependencies {
    implementation project(':user')
    implementation 'com.kymjs:annotation:1.0' 
    annotationProcessor 'com.kymjs:compiler:1.0' 
    implementation 'com.kymjs:runtime:1.0'
     } 
     // app 模块中注册并调用 UserService 类 
     public class MainActivity extends AppCompatActivity { 
     @Override protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState); 
      setContentView(R.layout.activity_main); 
      // 初始化 
      ServiceManager ServiceManager.init();
       // 注册 UserService 类 
       ServiceManager.register("com.example.UserService"); 
       // 调用 login 方法
        boolean result = (boolean) ServiceManager.invoke("user", "login", "admin", "123456"); 
        // 显示结果 
        Toast.makeText(this, result ? "登录成功" : "登录失败", Toast.LENGTH_SHORT).show();
         }
        }

这样就实现了通过 APT 解耦模块依赖的功能。

标签:基于,APT,user,模块,UserService,ServiceManager,注解,Android
From: https://blog.51cto.com/u_16175630/6893142

相关文章

  • 不同层级的Android开发者的不同行为,我们该如何进阶和规划?
    四个层级如下:第一层:普通程序员第二层:熟练开发者、高级开发工程师、技术组长第三层:技术专家、架构师、一线经理第四层:科学家、研究员、首席(资深)架构师、部门研发总监imageAndroid开发者的四个层级按我的理解,无论是Android开发者还是其他的开发者都可以分为四个层级,可依次对应普......
  • 私藏项目实操分享Android开发:获取安卓App版本号的方法步骤
    前言在Android开发过程中,想要开发一个完整功能的App,各个地方的内容都要涉及到,比如获取App的系统版本号就是必须要有的功能。Android的App版本号相关内容比iOS的App版本号内容要多,而且iOS版的App版本信息跟Android的还不一样。本篇文章就来介绍一下Android开发中获取App版本号的方法......
  • 【无人机控制】基于线性二次型调节器LQR实现无人机飞行控制附matlab代码
    ✅作者简介:热爱科研的Matlab仿真开发者,修心和技术同步精进,matlab项目合作可私信。......
  • 基于java实现的会展中心管理系统
    完整资料进入【数字空间】查看——搜索"writebug"1系统设计1.1设计目标在学习了数据库原理和SQLServer2008数据库管理系统后,采用Java编程语言开发工具,设计并实现会展中心管理系统。本课程的目的是培养学生数据库技术的综合应用能力,通过设计开发一个小型的数据库管理系统,将原理与......
  • 基于C语言设计的Bootblock设计
    完整资料进入【数字空间】查看——搜索"writebug"一、Project1Bootloader设计文档中国科学院大学[王苑铮]1.1Bootblock设计流程请至少说明以下内容Bootblock主要完成的功能把kernel从sd卡读取到内存中的指定位置,之后跳转到内核的起始地址让内核开始执行。Bootblock被载......
  • 基于C语言设计的全局光照明模型
    完整资料进入【数字空间】查看——搜索"writebug"Part1Whitted-StyleRayTracingStep0.算法流程为了渲染出一张图片,RayTrace()计算了给定像素点的色彩取值。根据光路可逆原理,可以从人眼作为出发点,沿着指向该pixel的某一点的方向发出一条ray。Step1:射线求交这条ray会碰到一个......
  • 3DSOM软件基于物体的照片构建空间三维模型的方法
      本文介绍基于3DSOM软件,实现侧影轮廓方法的空间三维模型重建。(基于3DSOM的侧影轮廓方法空间三维模型重建)  我们首先从侧影轮廓建模方法开始,对空间三维建模的一些内容加以介绍。本文我们将基于3DSoftwareObjectModeler(3DSOM)这一软件,对上述方法加以完整的操作,并对结果加......
  • ffmpeg 编译安装android和linux
    ffmpeg编译安装android和linux下载:https://github.com/FFmpeg/FFmpeghttps://www.ffmpeg.org/download.htmlenvirenmentndk:https://github.com/android/ndk/wiki/Unsupported-Downloadssudoapt-getinstallbuild-essentialpkg-configsudoapt-getintalllibx264-dev......
  • 基于Python实现RLE格式分割标注文件的格式转换
    下面我将详细讲解“基于Python实现RLE格式分割标注文件的格式转换”的完整攻略。一、RLE格式分割标注文件是什么?RLE格式是一种更加高效的图像语义分割数据表示格式,其数据以一串RLE编码的方式进行存储,而不是以像素点的形式存储,有效减少了数据的体积。RLE格式分割标注文件即是使......
  • asp.net core 2.0 web api基于JWT自定义策略授权
    原文通过登录,来获取Token,再在之后每次请求的Header中追加Authorization为Token的凭据,服务端验证通过即可能获取想要访问的资源。关于JWT的技术,可参考网络上文章,这里不作详细说明,这篇博文,主要说明在asp.netcore2.0中,基于jwt的webapi的权限设置,即在asp.netcore中怎么用JWT,再次......