转载请标明原文地址: http://blog.csdn.net/yalinfendou/article/details/78822749

Android 模块化完整方案实现

Github:Android-Router

1 模块化实现(module+router)

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

2 本套模块化方案实现特点

  • 支持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之间通信


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.isDebugTypeif (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 {    void loadModuleApplicationService(); //初始化一些第三方SDK服务    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 router实现


通常来讲,module之间可以URL Scheme隐式intent实现跳转,不过这种方式扩展性太差,网上也有很多开源的router框架,比如ARouter,考虑到ARouter相对来说比较重,并且项目需要添加一些更加灵活的功能,比如路由引导模块,大容量数据传递之类的功能,所以就自己实现了一个router模块。 

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

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

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


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


    Router.registerRouters(new Router.RouterTable() {        @Override        public List buildRuleList() {            List ruleList = new ArrayList<>();            ruleList.add(new Rule("user", "main", com.joybar.moduleuser.MainActivity                    .class));            ruleList.add(new Rule("shop", "main", com.joybar.moduleshop.MainActivity                    .class));        }    });


B. 简单的启动如下


    Router.create()            .buildRule(new Rule("shop", "main"))            .navigate(context);

2. 编译时注解路由实现
  1. 首先自定义编译时注解:@RegisterRouter
  2. 定义注解解释器:编译器会在编译时检查AbstractProcessor的子类,通过创建RouterProcessor并继承AbstractProcessor,在process方法中,主要做两件事:

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

主要代码如下:


    String module =typeElement.getAnnotation(RegisterRouter.class).module();    String path = typeElement.getAnnotation(RegisterRouter.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中定义了以下两个带有不同参数的启动方法,并声明注解 @RegisterLaunch


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


声明注解 @RegisterLaunch后,在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并传入对应参数了。 
自动生成路由引导模块的原理也很简单,根据注解@RegisterRouter拿到Activity 类相应的信息,通过 @RegisterLaunch拿到相关的启动参数信息,再通过javapoet,在指定路径下生成相关的类和方法,具体实现这里就不赘述了,详见源码

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

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

5. module之间通讯

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

4.3 资源名冲突

通过设置resourcePrefix即可解决 


if (!isDebug) {            resourcePrefix 'user_'        }



最终的代码结构图:


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

源码地址:

Github:Android-Router

更多相关文章

  1. 浅谈Java中Collections.sort对List排序的两种方法
  2. python list.sort()根据多个关键字排序的方法实现
  3. Android运行底层linux外部命令的实现
  4. 逐帧(Frame)动画
  5. Android通知推送 ——采用MQTT协议实现Android推送
  6. (五)Android(安卓)UI相关知识总结(转)
  7. ReactNative(嵌入到android)调用android原生组件与原生模块(比如某
  8. Android(安卓)AsyncTask
  9. Android(安卓)如何使用GPU硬件加速

随机推荐

  1. android编程中setLayoutParams方法设置
  2. android中动画的学习
  3. Android仿微信activity滑动关闭
  4. 视频的播放
  5. Android中Intent延时跳转的方法
  6. Android实现透明的颜色效果
  7. Android(安卓)EditText得到焦点失去焦点
  8. android中用socket 接收服务器的消息
  9. appcompat-v7 版本造成的问题No resource
  10. 2.20 android连接wifi,解决mWifiManager.a