XModulable

Project Url: xpleemoon/XModulable
Introduction: 组件化/模块化容器 sdk
More: Author   ReportBugs   OfficialWebsite   
Tags:

Latest Version

SDK XModulable-api XModulable-compiler XModulable-annotation
最新版本 Download Download Download

概念

组件/模块化的套路通常是:

  • 组件/模块之间互不依赖、相互隔离
  • app 壳将组件注册到路由层
  • 上层通过路由层查找组件/模块,通过组件/模块暴露的服务实现通信交互

本例中的组件化/模块化:

  • 路由采用ARouter实现
  • XModulable SDK 负责组件/模块的注册和查找,这里的模块可视为组件/模块服务的容器:
    • @XModule——组件/模块声明
    • @InjectXModule——组件/模块注入声明
    • XModulable——作为组件/模块的注册、查找和依赖注入
  • 业务组件/模块独立运行,只需要更改 module.gradle 对应的业务组件/模块isStandalone为 true 即可

使用方法

1. 添加依赖配置

  android {
      defaultConfig {
          ...
          javaCompileOptions {
              annotationProcessorOptions {
                    arguments = [ XModule : project.getName() ]
              }
          }
      }
  }

  dependencies {
      // gradle3.0 以上建议使用 implementation(或者 api) 'com.xpleemoon.xmodulable:XModulable-api:x.x.x'
      compile 'com.xpleemoon.xmodulable:XModulable-api:x.x.x'
      annotationProcessor 'com.xpleemoon.xmodulable:XModulable-compiler:x.x.x'
      ...
  }

2. 实现组件

  @XModule(name = "XX 组件名")
  public class XXModule implements IModule{

  }

3. 初始化 sdk

  if (isDebug) {
      XModulable.openDebug();
  }
  XModulable.init(this);

4. 获取组件

组件获取有两种方式:依赖注入和手动查询获取。

  1. 依赖注入:

    public class TestActivity extends BaseActivity {
       @InjectXModule(name = "xxx")
       XXModule mXXModule;
    
       @Override
       protected void onCreate(@Nullable Bundle savedInstanceState) {
           super.onCreate(savedInstanceState);
           XModulable.inject(this);
       }
    }
    
  2. 手动获取:

    XModulable.getInstance().getModule("XX 组件名")
    

5. 添加混淆规则

  -keep class * implements com.xpleemoon.xmodulable.api.template.XModuleLoader
  -keep class * implements com.xpleemoon.xmodulable.api.IModule
  -keep class **$$XModulableInjector { *; }

组件化/模块化

  • 组件:基于可重用的目的,对功能进行封装,一个功能就是一个组件,例如网络、IO、图片加载等等这些都是组件
  • 模块:基于业务独立的目的,对一系列有内聚性的业务进行整理,将其与其他业务进行切割、拆分,从主工程或原所在位置抽离为一个相互独立的部分

由于模块是独立解耦可重用的特性,在实施组件化/模块化的过程中,我们需要解决三个主要问题:

1. 模块通信——因为业务模块是相互隔离的,它们完全不知道也无法感知其他业务模块是否存在,所以需要一种尽最大可能的隔离、耦合度相对最低、代价相对最小的可行方案来实现通信
2. 模块独立运行——在后续迭代维护的过程中,各个业务线的人员能够职责更加清晰
3. 模块灵活组合运行——能够适应产品需求,灵活拆分组合打包上线

XModulable 架构图

解决抛出的三个问题之前,先过下[XModulable]的架构图,上图中的 module 对应层级:

  • app 壳层——依赖业务层,可灵活组合业务层模块
  • 业务层——im、live 和 main,面向 common 层实现业务层服务接口,向 common 注册和查询业务模块
  • common 层——依赖基础组件层;承接业务层,暴露业务层服务接口,同时为业务层提供模块路由服务
  • basic 层——basicRes 和 basicLib
    • basicRes——包含通用资源和各 UI 组件
    • basicLib——包含网路组件、图片加载组件、各种工具等功能组件
  • XModulable只是一个小的 demo 而已,而图中展示的是我对于每一层的完整构想,所以当去源码的时候发现有些是缺失的:common 缺失了 AOP 代码、basciRes 缺失了 UI 组件,basicLib 缺失了几乎所有的组件。

1. 模块通信

模块化的通信(UI 跳转和数据传递),需要抓住几个基本点:隔离解耦代价小(易维护)、传递复杂数据FragmentViewFile……)。实现独立互不依赖模块的通信,很容易能够想到以下几种方式:

  • Android 传统通信(比如 aidl、广播、自定义 url……)
    • 无法避免高度耦合、以及随着项目扩张导致难以维护的问题
    • 还有另外一关键个问题就是只能进行一些非常简单的数据传递,像FragmentViewFile……这些数据(或者叫对象也行),完全无法通信传递,但是这些数据在实际的 app 中恰恰是组成一个 app 的关键节点。比如说 app 的主站中有一个MainActivity,它是一个ViewPager+TabLayout的结构,其中的每一个页面都是来自于不同模块的 Fragment,这个时候我们的通信就完全无法满足了。
  • 第三方通信(比如EventBusRxBus……)
    • 容易陷入茫茫的 event 通知和接收中,增加调试和维护的成本
    • 能够传递一些复杂的数据,通过 event 事件来携带其它数据对象,但是代码耦合性相应的会增加
  • 第三方路由库(比如ARouter、OkDeepLink、DeepLinkDispatch……)基本都能够实现隔离解耦代价小(易维护)。至于数据传递的话默认只支持一些简单数据,但是我们可以结合面向接口编程,公共层暴露接口,业务层面向公共层的接口去实现对应的接口方法(UI 跳转、数据读写……),最后当业务层使用的时候只需要通过路由到接口,就可以完成复杂数据的通信。以ARouter为例,可以在 common 层暴露业务模块的服务接口(IProviderARouter提供的服务接口,只要实现了该接口的自定义服务,ARouter都能进行路由操作),然后交由对应的业务模块去实现 common 层对应的服务接口,最后在业务模块中使用ARouter进行路由其他业务模块暴露的服务接口来实现。

从上面的分析来看,路由+面向接口编程是实现组件化/模块化的不二之选,但是这里又有一个问题——假设哪天抽风想要更换路由库或者可能某种特殊需求不同的业务模块使用了不容的路由库,那怎么办呢?没关系,我们这时候需要对路由库做一层封装,使业务模块内的路由都相互隔离,也就是一个业务模块内部的路由操作对其他业务模块来说是一个黑箱操作。我的封装思路是这样的:加一个XModule(可以把它想象成一个容器)的概念,在 common 层暴露服务接口的同时暴露XModule(它的具体实现也是有对应的业务模块决定的),每一业务模块都对应一个XModule,用于承载 common 层暴露的服务接口,业务模块之间的通信第一步必须先获取XModule,然后再通过这个容器去拿到服务。

综上所述,最终的组件化/模块化采用的是封装+路由+面向接口编程。以 live 业务模块为例,从源码的角度看下它们是实现这套思路的。在 common 层把 live 业务模块想要暴露给其他业务模块的服务LiveService进行了暴露,同时在 common 层暴露了一个LiveModule(live 业务模块的服务容器,承载了LiveService),l,live 业务模块面向 common 层对应的接口进行实现(LiveModuleImplLiveServiceImpl)。这样的话,上层业务就可以通过XModulable SDK获取到LiveModule,然后通过LiveModule承载的服务进行调用。

  // common 层 live 暴露的 XModule(LiveModule)和服务接口(LiveService)

  public abstract class LiveModule extends BaseModule {

      public abstract LiveService getLiveService();
  }

  public interface LiveService extends BaseService {
      Fragment createLiveEntranceFragment();

      void startLive();
  }

  // 业务模块层——live 针对 common 层暴露的实现 LiveModuleImpl 和 LiveServiceImpl

  @XModule(name = ModuleName.LIVE)
  public class LiveModuleImpl extends LiveModule {
      @Autowired
      LiveService liveService;

      @Override
      public LiveService getLiveService() {
          return liveService;
      }
  }

  @Route(path = PathConstants.PATH_SERVICE_LIVE)
  public class LiveServiceImpl implements LiveService {
      @Override
      public void init(Context context) {

      }

      @Override
      public Fragment createLiveEntranceFragment() {
          return new LiveEntranceFragment();
      }

      @Override
      public void startLive() {
          ARouter.getInstance().build(PathConstants.PATH_VIEW_LIVE).navigation();
      }
  }

2. 模块独立运行

业务模块在 Android Studio 中其实就是一个 module,从 gradle 的角度来说,module 不是以 application plugin 方式运行,就是以 library plugin 方式运行,所以为了业务模块也能够独立运行,就需要控制 gradle 能够在 application plugin 和 library plugin 两种形式下切换,同时还要提供单独运行时的源码。

首先在项目的 build.gradle 中创建业务模块配置,isStandAlone表示业务模块是否独立运行:

  ext {
      applicationId = "com.xpleemoon.sample.modulable"

      // 通过更改 isStandalone 的值实现业务模块是否独立运行,以及 app 壳工程对组件的灵活依赖
      modules = [
              main: [
                      isStandalone : false,
                      applicationId: "${applicationId}.main",
              ],
              im  : [
                      isStandalone : false,
                      applicationId: "${applicationId}.im",
              ],
              live: [
                      isStandalone : true,
                      applicationId: "${applicationId}.live"
              ],
      ]
  }

然后设置对应业务模块的 build.gradle:

  def currentModule = rootProject.modules.live
  // isStandalone 的值决定了当前业务模块是否独立运行
  if (currentModule.isStandalone) {
      apply plugin: 'com.android.application'
  } else {
      apply plugin: 'com.android.library'
  }

  android {
   省略...
      defaultConfig {
          if (currentModule.isStandalone) {
              // 当前组件独立运行,需要设置 applicationId
              applicationId currentModule.applicationId
          }
          省略...

          def moduleName = project.getName()
          // 业务组件资源前缀,避免冲突
          resourcePrefix "${moduleName}_"

          javaCompileOptions {
              annotationProcessorOptions {
                  arguments = [
                          // ARouter 处理器所需参数
                          moduleName   : moduleName,
                          // XModulable 处理器所需参数
                          XModule: moduleName
                  ]
              }
          }

      }
  省略...
      sourceSets {
          main {
              // 单独运行所需要配置的源码文件
              if (currentModule.isStandalone) {
                  manifest.srcFile 'src/standalone/AndroidManifest.xml'
                  java.srcDirs = ['src/main/java/', 'src/standalone/java/']
                  res.srcDirs = ['src/main/res', 'src/standalone/res']
              }
          }
      }
  }
  省略...

最后,在业务模块中编写 build.gradle 中sourceSets声明单独运行所需要的额外源码文件,比如ApplicationSplashActivityManifest

完成上面的过程后,就可以选择对应的业务模块 live 运行

业务模块独立运行

3. 模块灵活组合运行

模块的灵活组合,其实也非常简单,只需要更改业务模块配置在项目 build.gradle 的isStandalone值,然后在 app 壳的 build.gradle 中通过业务模块的isStandalone来决定是否依赖就行,关键代码如下:

  dependencies {
  省略...
      def modules = rootProject.modules
      def isMainStandalone = modules.main.isStandalone
      def isIMStandalone = modules.im.isStandalone
      def isLiveStandalone = modules.live.isStandalone
      // 判断业务组件是否独立运行,实现业务组件的灵活依赖
      if (isMainStandalone && isIMStandalone && isLiveStandalone) {
          api project(':common')
      } else {
          if (!isMainStandalone) {
              implementation project(':main')
          }
          if (!isIMStandalone) {
              implementation project(':im')
          }
          if (!isLiveStandalone) {
              implementation project(':live')
          }
      }
  }
Apps
About Me
GitHub: Trinea
Facebook: Dev Tools