Android模块化实践

随着APP的不断迭代,业务愈来愈复杂,代码量愈来愈多,单个APP的模式已开始影响开发效率,并且原来的单模块很难进行业务迁移。因此决定采用模块化/组件化的思想对APP进行重构。android

组件化与模块化

什么是组件化/模块化

组件化和模块化是当前软件开发中经常使用的与平台无关的的解耦手段,被普遍应用在软件的架构层面。这二者一般是相辅相成,经过组合的方式来使用。它们只是架构方面的一种思想,在代码的实现层面上没有多大区别。我的以为差异也就如下两点:git

  • 复用性:组件更注重复用性,如开发中用到的网络组件,图片组件等, 在每一个项目中均可使用;
  • 应用范围:复用性决定了应用范围,也就是说,组件一般指的是底层模块,公共组件等。而模块既可表示上层的业务,也可表示组件中的某个业务功能,如图片组件中的缓存模块,下载模块等。因此模块的应用范围更广。

为何须要组件化/模块化

在早期的Java开发中,提倡将整个项目结构按照程序的逻辑结构进行分层,好比表示数据的Dao层,表示控制器的Control层,表示View的View层,可是随着业务的不断迭代,发现这种分层方式有很大的弊端,代码难以定位,且后期难以维护。随后就出现了以业务结构划分的模式,这种结构完全解决了以上问题。因此当前的APP基本上使用的都是这种以业务划分的模式,但随着App的不断迭代,业务变得愈来愈复杂,代码量愈来愈多,维护也变得愈来愈困难。还有一个明显的问题,Gradle在编译的时候花费的时间愈来愈长,这大大下降了APP的开发效率。既然单个APP难以解决这个问题,那能够将项目进行拆分,每一个人只须要负责开发、维护本身的模块便可。如何拆分呢?使用模块化技术按业务逻辑将APP进行划分,使得这些被拆分出来的模块可单独运行,这样就提升了编译速度,下降了维护成本。github

组件化/模块化的优势:
  • 解耦,重用;
  • 下降维护成本,提升开发效率;

模块化的项目结构

此次重构采用层次化的方式,模块化的思想,对APP进行了完全的重构,具体的项目结构以下图所示:数据库

image

从结构上来看,APP被划分红5层,每层的功能具体以下:api

APP壳工程

这是一个空的项目,其中只包含了一个Application的子类和一个IntentService的子类,主要用来对APP中使用的各类组件进行初始化,IntentService的做用是为了提升APP冷启动的速度,将各类组件的初始化放在后台线程异步执行,这里须要注意的是,对于在Applicaiton或SplashActivity中就会使用的组件,最好直接在Application中进行初始化,不然会抛出未初始化的异常。缓存

业务层

这里的业务层被划分为Main模块和其余模块(至于划分几个模块,根据本身APP的业务,选择合适的粒度进行划分便可)。这里的Main模块主要包括:新用户引导页,启动页,主页。具体业务方面的页面,都放在具体的业务模块中。微信

公共组件层

公共组件层主要包括APP中使用的第三方组件,这些组件基本都是如今APP的通用功能,为上层的业务层提供支持。至于模块划分,虽然这些都是单独的组件,可是每一个作为一个Module未免有些繁琐了,因此仍是推荐放在一个Module中。在选择第三方的库时,须要作必定的调研,尽可能选择大公司,使用用户多的SDK,同时在使用时最好封装一下,这样后面更换时也方便。网络

基础业务层

基础业务层主要用来统一APP的代码结构,UI风格等,主要包含如下三个方面:架构

Android组件的二次封装

主要是对Activity/Fragment的封装,提供了不需网络请求的BaseActivity/BaseFragment和须要网络请求的BaseProgressActivity/BaseProgressFragment, 为页面的代码提供统一的结构,页面的样式提供统一的风格。app

业务通用UI

主要包含各类样式的Dialog, 自定义View等,根据APP的设计风格提供统一的样式;

图片操做库

图片操做库ImageSet是对图片组件库(包括Fresco, Glide, Universer ImageLoader)的封装,同时提供了调用系统相机/相册选择&裁剪照片,相似微信选择图片的组件,图片上传,图片压缩等功能。这个小模块其实也可放在Common组件层,只是以为这里面也有一些业务相关的功能,因此就放在了这一层。

固然,基础业务层还还包括APP设计风格中须要用到的各类动画,样式,颜色值,尺寸值等资源。在进行业务开发时,统一使用这些资源,为后续修改整个APP风格提供可能。

Common组件层

这里包含了一些通用的组件,包括各类经常使用的工具类,通用的UI库,数据源的封装(包括网络,文件,数据库)。这是一个APP的基本架构,里面包含的类基本不须要改动。因此在对工具类和通用UI进行定义时,须要考虑放置的位置是否准确。

以上是整个项目使用组件化/模块化后基本结构的详细介绍,可是在开发过程当中仍是遇到了不少问题。

遇到的问题

模式切换

从Android工程的结构能够看出,app模块和新建的其余Module的结构基本一致,最大的区别就是build.gradle的结构:

// app模块中build.gralde的结构:
apply plugin: 'com.android.application'

// 其余Module中build.gralde的结构:
apply plugin: 'com.android.library'
复制代码

因此Module是否可以运行,关键就在于plugin的类型。将新建Module的build.gradle中的'com.android.library' 改为 'com.android.application',同步以后选择相应的模块运行便可。

因此模式的切换只须要根据条件进行判断便可,咱们可在gradle.properties中定义一个常量,控制Module的运行模式:

gradle.properties中定义IS_MODULE:

IS_MODULE = false
复制代码

而后在Module的build.gradle中添加条件判断:

if (IS_MODULE.toBoolean()) {
    apply plugin: 'com.android.library'
} else {
    apply plugin: 'com.android.application'
}
复制代码

这样在进行模式切换时,只须要修改IS_MODULE的值便可。

AndroidManifest的合并问题

APP在进行打包时,会将全部依赖的Module中的AndroidManifest文件进行合并,具体的合并规则参考合并多个清单文件。合并最基本的原则:只能有一个Application配置了name属性,只能有一个Activity被配置成了主Activity。可是Module中若是不配置Applicaition中name属性,就不能进行相应的初始化,若是不指定主Activity,APP也没法运行。这里可以使用两种方案来解决:

  • Module依然当作library使用,Module中的AndroidManifest也不须要指定Application的name属性和主Activity,直接加载Main模块(SplashActivity做为主Activity),在SplashActivity中动态修改要加载的模块。
  • 在Module中其余路径下新建一个AndroidManifest文件(其中为application标签指定了name属性,同时指定了主Activity),而后在build.gradle中根据IS_MODULE的值动态指定AndroidManifest的路径,这样Module在不一样模式下使用不一样的AndroidManifest文件就避免了合并出错的问题。但这种方案每一个Module须要提供单独的Application类。

module/build.gradle

sourceSets {
    main {
        if (IS_MODULE.toBoolean()) {
            manifest.srcFile '../模块名/src/main/module/AndroidManifest.xml'
        } else {
            manifest.srcFile '../模块名/src/main/AndroidManifest.xml'
        }
    }
}
复制代码

若是选择了双AndroidManifest文件的方式,那么在做为Library的AndroidManifest(默认的)中组件的定义应尽可能简单,包括主题都不要定义,这样合并时就不多出现合并错误。

若是在AndroidManifest.xml中定义了元数据,如极光推送:

<meta-data
    android:name="JPUSH_CHANNEL"
    android:value="${JPUSH_CHANNEL}" />
<meta-data
    android:name="JPUSH_APPKEY"
    android:value="${JPUSH_APPKEY}" />
复制代码

那么除了在当前Module的build.gradle中定义JPUSH_CHANNEL和JPUSH_APPKEY:

manifestPlaceholders =[
    JPUSH_PKGNAME : "com.sxu.library",
    JPUSH_APPKEY : "appkey",
    JPUSH_CHANNEL : "channel"
]
复制代码

还须要在全部包含此Module的build.gradle中定义这两个常量,不然会出现manifest合并错误。

build.gradle的定义问题

引用Module问题

在引用可切换运行模式的Module时,要根据isModule的值动态添加依赖,若是isModule为true时,引用的模块是以Application的形式存在,此时引入时就会报错。

设置applicationId

applicationId属性在Application模式下才有效,因此在Module中设置时也须要添加isModule的条件判断。

依赖管理

为了便于对依赖和各类SDK版本的管理,最好将这些版本号定义在gradle.properties中统一管理,这样便于后续的修改。

模块之间的通讯

组件之间的通讯可以使用EventBus来实现, 可在每一个模块中新建一个Event类,将同模块中通讯须要的类都定义在这个Event类中。至于模块间通讯须要的类,可定义在公共组件层的Event类中(虽然不是很合理,但暂未想到更好的方案)。

模块对ApplicationContext的引用

在Module的开发中,咱们可能须要引用ApplicationContext对象,但咱们没有Context对象,没法直接获取到,此时可经过如下三种方式解决:

  • 为须要ApplicationContext对象的类提供init静态方法,在Application的onCreate中调用:
  • 在Common组件层中提供一个继承自Application的BaseApplication, 其中包含一个静态的Context对象,在APP中重写Application时继承BaseApplication并对这个静态的Context对象进行赋值;
  • 在Common组件中提供一个ContentProvider组件,使用静态的Context对象保存ApplicationContext对象(ContentProvider在系统建立Application对象后就会加载,具体细节查看APP的启动过程的源码);

模块的依赖

模块之间的依赖

除了Common组件层外,其余的层尽可能遵循 【同层不依赖,下层不依赖上层】的原则。

同层之间的依赖主要表如今业务层,这是不可避免的,但咱们须要避免互相引用的问题,在业务层,咱们可以使用隐式跳转的方式或使用阿里开源的路由框架ARouter实现。

模块以外的依赖

Gradle3.4中提供了新的依赖配置的关键字:

implementation:依赖项在编译时对所在模块可用,在运行时对依赖该模块的模块可用;

api: 依赖项在编译时对所在模块可用,在编译时和运行时对依赖该模块的模块可用;
复制代码

Gradle3.4中提供的两个关键字至关于对是以前的compile关键字进行细化。使用Gradle3.4以前的版本,引入依赖项时都使用compile关键字,compile关键字容易引发屡次引入的问题。使用Gradle3.4以后的版本引入library时,对于外部不须要直接引用的library,最好使用implementation关键字,而对于外部须要引用的library, 可以使用api(此时就至关于compile)。

资源的命名问题

为了不资源冲突的问题,咱们可在Module中的build.gradle配置资源名的前缀,一方面避免资源冲突,另外一方面,也便于标识资源所在的模块:

android {
    resourcePrefix "moduleName_"
}
复制代码

其余问题

语法问题

Module中生成的资源Id不是final类型的,因此在onClick中不能使用switch语句块,只能使用if...else if结构代替。

重构与版本迭代的问题

重构与版本迭代之间冲突是不可避免的问题。 一般重构的时候还须要版本迭代,此时可根据状况进行人员分配:

  • 有足够重构的时间:让大多数人进行重构,只留一两个进行版本迭代。重构完成后将新版本的内容进行合并;
  • 没有足够重构时间:这种状况就不要作总体重构,而是根据模块逐渐进行。

重构是一项体力活,也是一项出力不讨好的活,毕竟重构以后不可避免地会出现不少Bug, 若是用户量庞大,那后果可能很严重,因此在重构时最好阅读一下原来的代码,认真梳理一下业务逻辑再进行。

单Activity模式

在重构的过程当中对其中一个模块尝试使用了单Activity模式(页面统一使用Fragment实现)。体验感受不错,值得一试。

总结

模块化/组件化是一种与技术无关的架构思想,合理的应用可大大下降项目的耦合度。为了可以快速开发一款新的应用,现已开源了一个通用的APP框架SimpleProject,采用分层+模块化的思想,详见Android模块化框架介绍

相关文章
相关标签/搜索