学习笔记:Android彻底组件化方案实践——Android技术杂货铺

来源:互联网 发布:讨鬼传2捏脸数据 女 编辑:程序博客网 时间:2024/06/10 07:04

Android彻底组件化方案实践——Android技术杂货铺

模块化的实现的两种途径:

  • 组件化:组件可以单独运行,项目中缺少任何一个组件都可以运行。组件在编译的时候可以动态添加或修改,在运行的时候不可以。
  • 插件化:插件在编译和运行的时候都可以动态添加或修改。

实现组件化需考虑的问题:

  • 代码解耦
  • 组件单独运行
  • 数据传递
  • UI跳转
  • 组件的生命周期
  • 集成调试
  • 代码隔离

1.代码解耦

使用IDE中的multiple module这个功能,我们很容易把代码进行初步的拆分。

  • 基础库library,这些代码被其他组件直接引用。比如网络库module可以认为是一个library。
  • Component,这种module是一个完整的功能模块。

2.组件的单独调试

  • 其实单独调试比较简单,只需要把apply plugin: ‘com.android.library’切换成
    apply plugin: ‘com.android.application’就可以,但是我们还需要修改一下AndroidManifest文件,因为一个单独调试需要有一个入口的actiivity。 我们可以设置一个变量isRunAlone,标记当前是否需要单独调试,根据isRunAlone的取值,使用不同的gradle插件和AndroidManifest文件,甚至可以添加Application等Java文件,以便可以做一下初始化的操作。
  • 为了避免不同组件之间资源名重复,在每个组件的build.gradle中增加resourcePrefix “xxx_”,从而固定每个组件的资源前缀。
if(isRunAlone.toBoolean()){    apply plugin: 'com.android.application'}else{   apply plugin: 'com.android.library'}.....    resourcePrefix "readerbook_"    sourceSets {        main {            if (isRunAlone.toBoolean()) {                manifest.srcFile 'src/main/runalone/AndroidManifest.xml'                java.srcDirs = ['src/main/java','src/main/runalone/java']                res.srcDirs = ['src/main/res','src/main/runalone/res']            } else {                manifest.srcFile 'src/main/AndroidManifest.xml'            }        }    }

3.组件的数据传输

  • 实现方式:采用接口+实现的结构
    每个组件声明自己提供的服务Service,这些Service都是一些抽象类或者接口,组件负责将这些Service实现并注册到一个统一的路由Router中去。如果要使用某个组件的功能,只需要向Router请求这个Service的实现,具体的实现细节我们全然不关心,只要能返回我们需要的结果就可以了。这与Binder的C/S架构很相像。
    因为我们组件之间的数据传递都是基于接口编程的,接口和实现是完全分离的,所以组件之间就可以做到解耦,我们可以对组件进行替换、删除等动态管理。

  • 这种方式需要解决的几个问题:

    a.组件怎么暴露自己的服务?在项目中我们简单起见,专门建立了一个componentservice的依赖库,里面定义了每个组件向外提供的service和一些公共model。将所有组件的service整合在一起,是为了在拆分初期操作更为简单,后面需要改为自动化的方式来生成。这个依赖库需要严格遵循开闭原则,以避免出现版本兼容等问题。

    b.service的具体实现是由所属组件注册到Router中的,那么是在什么时间注册的呢?这个就涉及到组件的加载等生命周期

    c.一个很容易犯的小错误就是通过持久化的方式来传递数据,例如file、sharedpreference等方式,这个是需要避免的。

if(isRunAlone.toBoolean()){    apply plugin: 'com.android.application'}else{   apply plugin: 'com.android.library'}.....    resourcePrefix "readerbook_"    sourceSets {        main {            if (isRunAlone.toBoolean()) {                manifest.srcFile 'src/main/runalone/AndroidManifest.xml'                java.srcDirs = ['src/main/java','src/main/runalone/java']                res.srcDirs = ['src/main/res','src/main/runalone/res']            } else {                manifest.srcFile 'src/main/AndroidManifest.xml'            }        }    }

4.组件之间的UI跳转

  • 实现方式:短链
    可以说UI的跳转也是组件提供的一种特殊的服务,可以归属到上面的数据传递中去。不过一般UI的跳转我们会单独处理,一般通过短链的方式来跳转到具体的Activity。每个组件可以注册自己所能处理的短链的schme和host,并定义传输数据的格式。然后注册到统一的UIRouter中,UIRouter通过schme和host的匹配关系负责分发路由。
  • UI跳转部分的具体实现是通过在每个Activity上添加注解,然后通过apt形成具体的逻辑代码。这个也是目前Android中UI路由的主流实现方式。

5.组件的生命周期

  • 生命周期状态:加载、卸载、降维
    这里写图片描述

6.集成调试

  • 每个组件单独调试通过并不意味着集成在一起没有问题,因此在开发后期我们需要把几个组件机集成到一个app里面去验证。由于我们上面的机制保证了组件之间的隔离,所以我们可以任意选择几个组件参与集成。这种按需索取的加载机制可以保证在集成调试中有很大的灵活性,并且可以加大的加快编译速度。
      我们的做法是这样的,每个组件开发完成之后,发布一个relaese的aar到一个公共仓库,一般是本地的maven库。然后主项目通过参数配置要集成的组件就可以了。
      
      这里写图片描述
     
      
    7.代码隔离

      我们可以使用compile project(xxx:reader.aar)来引入组件吗?虽然我们在数据传输章节使用了接口+实现的架构,组件之间必须针对接口编程,但是一旦我们引入了reader.aar,那我们就完全可以直接使用到其中的实现类啊,这样我们针对接口编程的规范就成了一纸空文。
      我们希望只在assembleDebug或者assembleRelease的时候把aar引入进来,而在开发阶段,所有组件都是看不到的,这样就从根本上杜绝了引用实现类的问题。我们把这个问题交给gradle来解决,我们创建一个gradle插件,然后每个组件都apply这个插件,插件的配置代码也比较简单:
      

//根据配置添加各种组件依赖,并且自动化生成组件加载代码 if (project.android instanceof AppExtension) {     AssembleTask assembleTask = getTaskInfo(project.gradle.startParameter.taskNames)                 if (assembleTask.isAssemble                    && (assembleTask.modules.contains("all") || assembleTask.modules.contains(module))) {                        //添加组件依赖          project.dependencies.add("compile","xxx:reader-release@aar")                      //字节码插入的部分也在这里实现     }}    private AssembleTask getTaskInfo(List<String> taskNames) {        AssembleTask assembleTask = new AssembleTask();               for (String task : taskNames) {                       if (task.toUpperCase().contains("ASSEMBLE")) {                assembleTask.isAssemble = true;                String[] strs = task.split(":")                assembleTask.modules.add(strs.length > 1 ? strs[strs.length - 2] : "all");            }        }               return assembleTask    }

组件化的拆分步骤和动态需求

拆分原则:

  • 从产品需求到开发阶段再到运营阶段都有清晰边界的功能开始拆分,比如读书模块、直播模块等,这些开始分批先拆分出去
  • 在拆分中,造成组件依赖主项目的依赖的模块继续拆出去,比如账户体系等
  • 最终主项目就是一个Host,包含很小的功能模块(比如启动图)以及组件之间的拼接逻辑
原创粉丝点击