2023 Android 模块化完整方案实现  

简介: 本套模块化方案实现来源于公司的业务需求,因为公司业务太多,代码越来越臃肿,越来越难维护,为了提升开发效率,减低代码的维护成本,所以采取了模块化开发方案。既然是模块化开发,必然要考虑到各个module的开发,调试,迭代拓展及维护,module之间不仅需要做到业务代码隔离,还需要方便的跳转(路由引导模块),方便的传递数据(包括大容量的数据),能够独立编译调。最后,各个module,完成之后,一起打包到主APP即可。

1 模块化实现(subModule+TheRouter)

本套模块化方案实现来源于公司的业务需求,因为公司业务太多,代码越来越臃肿,越来越难维护,为了提升开发效率,减低代码的维护成本,所以采取了模块化开发方案。
既然是模块化开发,必然要考虑到各个module的开发,调试,迭代拓展及维护,module之间不仅需要做到业务代码隔离,还需要方便的跳转(路由引导模块),方便的传递数据(包括大容量的数据),能够独立编译调。最后,各个module,完成之后,一起打包到主APP即可。

2 本套模块化方案实现基于货拉拉开源的TheRouter实现,特点有

   

  • 支持module单独作为Application编译调试
  • 支持module在debug和release状态下对Application的调用方法完全一致,
  • 支持动态注入路由
  • 支持注解方式注入路由
  • 支持module之间传大容量的数据
  • 路由引导模块:自动生成module之间的跳转调用方法
  • moduleEventBus:实现module之间通信

3 项目代码主体架构设计

app: 一个空壳,本身不实现任何业务逻辑,最终打包成完整的release APK
moduleshop:实现shop相关的业务逻辑,可单独编译成APK
moduleuser:实现user相关的业务逻辑,可单独编译成APK,和其它module通过router通信
routerguidercore:为各个module生成自动调用的方法   
moduleEventBus:实现module之间通信

05113848_63b646486a1471407.webp

4 代码实现方案

4.1 module的apllication实现

我们希望实现以下功能
   

module能单独作为Application编译

    module有自己的Apllication,并在里面初始化一些第三方SDK服务
    module在debug和release状态下,业务层代码对application方法调用完全一样
    module在release状态下,能够调用主App的application

4.1.1 首先gradle配置如下配置

def isDebug = rootProject.ext.isDebugType
if (isDebug) {
    apply plugin: 'com.android.application'
} else {
    apply plugin: 'com.android.library'
}

这样,在开发时,是一个application,在发布时,是一个library。处于debug状态时,通过 ./gradlew :moduleuser:assemble(mac)命令即可编译打包。

4.1.2 在modulebase中创建ApplicationService

public interface ApplicationService {
    //初始化一些第三方SDK服务
    void loadModuleApplicationService(); 

    Application getApplication(); //获取主APP的Application或者module在debug时自己的application
}

ApplicationService放在moduleBase里面, 无论是主App的Application还是module的application,都要实现ApplicationService接口。

4.1.3 在moduleshop中创建ShopDebugApplication,ShopReleaseApplication和ShopApplication

如图所示:

public class ShopDebugApplication extends Application implements ApplicationService {
...
}
 
public class ShopReleaseApplication implements ApplicationService {
...
}

为什么需要创建三个Application? 其实代码真正调用的是 ShopApplication,在ShopApplication里面,根据debug或者release状态,去调用ShopDebugApplication或者ShopReleaseApplication的方法,这样就能保证业务层代码对application方法调用完全一致。代码如下:

@Override
public void loadModuleApplicationService() {
    if (BuildConfig.IS_DEBUG_TYPE) {
        ShopDebugApplication.getInstance().loadModuleApplicationService();
    } else {
        ShopReleaseApplication.getInstance().loadModuleApplicationService();
    }
}

@Override
public Application getApplication() {
    if (BuildConfig.IS_DEBUG_TYPE) {
        return ShopDebugApplication.getInstance().getApplication();
    } else {
        return ShopReleaseApplication.getInstance().getApplication();
    }
}

ShopDebugApplication是debug调试状态下的Application,ShopReleaseApplication是发布状态的Application。在loadModuleApplicationService方法中,可以初始化一些第三方SDK。 ShopDebugApplication的getApplication() 返回自身实例。 ShopReleaseApplication的getApplication()则通过反射拿到主App的Applicattion。

4.1.4

因为Application需要在library和application之间切换,所以需要配置两套AndroidManifest.xml
gradle配置如下:

if (isDebug) {
    manifest.srcFile 'src/main/debug/AndroidManifest.xml'
} else {
    manifest.srcFile 'src/main/AndroidManifest.xml'
}

4.2 module之间跳转

4.2.1 TheRouter实现

通常来讲,module之间可以通过intent实现跳转,不过这种方式扩展性太差。所以我们基于货拉拉开源的TheRouter,并且项目需要添加一些更加灵活的功能,比如路由引导模块,大容量数据传递之类的功能。

TheRouter实现原理主要是通过拿到需要启动Activity的信息,组装intent再跳转。这里有两种实现方式,编译时注解自动生成路由表和动态注册路由表实现。

1. 动态态注册路由表实现

主要是通过注册activity路由表,启动时,通过路由表拿到待启动的activity的class,组装一个intent,实现跳转

A. 首先在Application或者启动的activity中注册

@Route(path="homexxxxxx")
public class MainActivity extends Activity{
    
}

B. 简单的启动如下

TheRouter.build(path)
    .withString(xxx,xxx)
    .navigate(context);

2. 编译时注解路由实现

    首先自定义编译时注解:@ServiceProvider

    定义注解解释器:编译器会在编译时检查AbstractProcessor的子类,通过创建RouterProcessor并继承AbstractProcessor,在process方法中,主要做两件事:

    1.得到@ServiceProvider注解,并解析注解并获取需要的module和path
    2.使用JavaFileObject类生成java代码

主要代码如下:

String module =typeElement.getAnnotation(ServiceProvider.class).module();
String path = typeElement.getAnnotation(ServiceProvider.class).path();
String fullName = typeElement.getQualifiedName().toString();
MethodSpec addRouter = computeAddRouter(Config.ROUTER_MANAGER_METHOD_NAME,module, path, typeElement.getQualifiedName().toString());
TypeSpec routerManger = TypeSpec.classBuilder(fullName.replace(".", "_") + Config.ROUTER_MANAGER_CLASS_NAME_SUFFIX)
        .addModifiers(Modifier.PUBLIC)
        .addMethod(main)
        .addMethod(addRouter)
        .build();
JavaFile javaFile = JavaFile.builder(Config.ROUTER_MANAGER_PKN, routerManger)
        .build();
javaFile.writeTo(mFiler);

3. 路由引导模块实现

这是一个需求性很强烈的功能,主要是方便module之间的调用。比如说同事A定义了一个module A,里面又定义了很多Activity,每个Activty启动时传入的参数又不一样,同事B调用module A时的某个Activity时,在缺乏完整详细文档的情况下,完全无从下手,为了方便module之间的跳转调用,所以实现了一个路由引导模块,能够自动生成module A所有的对其它module公开的调用方法,使用举例如下:

在moduleshop的ReceiveParamActivity中定义了以下两个带有不同参数的启动方法,并声明注解 @ServiceProvider

@ServiceProvider
public static void launch(Context context,String address){
...
}

@ServiceProvider
public static void launch(Context context,String name,int id){
    ...
    context.startActivity(intent);
}

声明注解 @ServiceProvider后,在Builder#main中,执行

public class Builder {
 
    public static void main(String[] args) {
        CodeMaker.autoGenerateModuleMethodName("moduleshop");
    }
}

在指定的路径下会自动生成以下RouterTable$$Moduleshop类,这个类中,有moduleshop所有的调用方法

public final class RouterTable$$Moduleshop {
 
  public static RouterGuider launchReceiveParam(String address) {
    // This class was generated automatically 2017-12-11 20:06:21
    // module=shop,path=receive_param
    RouterGuider routerGuider =  new RouterGuider("shop", "receive_param");
    routerGuider.withString("address", address);
    return routerGuider;
  }
 
  public static RouterGuider launchReceiveParam(String name, Integer id) {
    // This class was generated automatically 2017-12-11 20:06:21
    // module=shop,path=receive_param
    RouterGuider routerGuider =  new RouterGuider("shop", "receive_param");
    routerGuider.withString("name", name);
    routerGuider.withInt("id", id);
    return routerGuider;
  }
}

这样,其它module的开发同事就能直接调用RouterTable$$Moduleshop.launchReceiveParam("obo", 25).navigate(context)或者launchReceiveParam("杭州"),就能启动moduleshop的ReceiveParamActivity并传入对应参数了。
自动生成路由引导模块的原理也很简单,根据注解@ServiceProvider拿到Activity 类相应的信息,通过 @ServiceProvider拿到相关的启动参数信息,再通过javapoet,在指定路径下生成相关的类和方法,具体实现这里就不赘述了,详见源码
 

4. module之间大容量数据传递

Activity之间传递数据通常是使用Bundle,但是Bundle传递数据时是有大小限制的。主要是通过注解@ServiceProvider,通过解析注解将大容量对象保存到一个静态map集合中,然后通过反射把大容量对象传入其它module
 

5. module之间通讯

在同一进程间通信,EventBus无意非常流行,因为嫌弃EventBus太重,所以就实现了一个简单的moduleEventbus,其实现原理完全照搬EventBus,其使用方法和EventBus也完全一致,分为 定义事件类型,订阅,发布事件,订阅事件处理,解除订阅几个步骤,不过核心代码精简到100多行,具体实现详见代码

4.3 资源名冲突

通过设置resourcePrefix即可解决

if (!isDebug) {
    resourcePrefix 'user_'
}

最终的代码结构图:

05113848_63b64648d7e0561664.webp

各个module完成之后,正常打包即可发布。值得一提的是,module之间的拆分颗粒度不可过细或者过粗。过细会导致module太多,过粗则违背了模块化开发的初衷。所以,在一开始拆分module时,一定要先理清项目的业务,对于一些公用的业务模块,可以放到业务基础组件中,module拆分也不可能一蹴而就,需要随着业务需求不断的优化调整。

TheRouter开源地址

官网:https://therouter.cn/
GitHub:https://github.com/HuolalaTech/hll-wp-therouter-android

目录
相关文章
|
7月前
|
存储 缓存 前端开发
Android Github 上面优秀的两种阴影方案,完美兼容高低版本问题
Android Github 上面优秀的两种阴影方案,完美兼容高低版本问题
|
8月前
|
Web App开发 编解码 网络协议
Android平台一对一音视频通话方案对比:WebRTC VS RTMP VS RTSP
Android平台一对一音视频通话方案对比:WebRTC VS RTMP VS RTSP
283 0
|
9月前
|
存储 设计模式 ARouter
现代化 Android 开发:组件化与模块化的抉择
本文为现代化 Android 开发系列文章第四篇。
213 0
|
编解码 Android开发
Android | 老生常谈!屏幕适配原理 & 方案总结笔记
Android | 老生常谈!屏幕适配原理 & 方案总结笔记
479 0
Android | 老生常谈!屏幕适配原理 & 方案总结笔记
|
10月前
|
Java 测试技术 API
Android透明状态栏和导航栏方案最终版
Android透明状态栏和导航栏方案最终版
549 0
|
Android开发
Android11.0(R) 手机锁屏炫酷充电动画————自定义View方案
Android11.0(R) 手机锁屏炫酷充电动画————自定义View方案
217 0
|
传感器 JSON 编解码
功耗优化 · 方案篇 · Android功耗优化指导规范(2)
功耗优化 · 方案篇 · Android功耗优化指导规范
762 0
|
存储 JSON 缓存
卡顿监测 · 方案篇 · Android卡顿监测指导原则(2)
卡顿监测 · 方案篇 · Android卡顿监测指导原则
107 0
卡顿监测 · 方案篇 · Android卡顿监测指导原则(2)
|
缓存 前端开发 Java
卡顿监测 · 方案篇 · Android卡顿监测指导原则
卡顿监测 · 方案篇 · Android卡顿监测指导原则
363 0
卡顿监测 · 方案篇 · Android卡顿监测指导原则