一、模块化浅谈
1. 什么是模块化开发?
模块化就是将一个程序按照其功能做拆分,分成相互独立的模块,以便于每个模块只包含与其功能相关的内容。比如登录功能可以是一个模块。
2. 模块化开发的优势?
-
解耦性强:随着业务的增多,代码变的越来越复杂,每个模块之间的
代码耦合变得越来越严重,解耦问题急需解决。 - 编译时间大大减少:以为业务场景对,代码越来越大,同时编译时间也会越来越长。
- 提高团队协同开发:团队协同开发存在较多的冲突.不得不花费更多的时间去沟通和协调,影响开发效率 。
二、模块化开发的架构分层
本项目是一个移动端的后台运营管理系统。按照功能划分,暂有统计模块、直播模块、音频模块和个人中心。
命名建议:
底层:Library
中间层:Module + 业务或功能名字
上层:App + 项目名字
1. 基本结构
app模块是每个项目初始都有的,实质上仍然是一个module。Android Stuido项目中,我们和代码打交道的大部分时间都在module中。当然,具体的module机制我们不去深究,有时间可以另开一坑去研究。下面的三个module大家可以理解为kotlin中的library,当然这样说不准确。
app作为主要的module,主要承担了应用启动以及最上层的通用逻辑。比如在这个例子中,应用启动、首页展示和模块切换的业务逻辑都在这个模块。
而下属的三个子模块承担了各自细分的业务,并且可以被app模块或者其他模块引用,每个模块只负责和考虑自己的业务。以此达到业务逻辑和代码分离的逻辑。
按照之前的业务逻辑划分,项目结构可以先大致分为如下的结构:
2. 系统层分离
仅仅这样是不够的。因为在AS中,module的引用是单向的。如果A module引用了B module,那么对A来讲,B是可见的,B的所有公开功方法理论上都可以在A中使用,但是对B来说,A是不可见的。所以,这样的结构出现的问题就是我们有大量的底层通用方法都放在app模块中,对于子模块来讲是不可见的,子模块无法引用封装好的底层方法,例如网络请求,图片加载,文件拷贝等,这肯定是不行的。所以这个结构还得再优化。
按照module单向引用的原则,我们可以把公共底层通用方法单独分出来作为一个moduleBase,同时这个moduleBase也是最底层的module,保证对于其他module来说它都是可见的。这样还有一个好处就是这个moduleBase中的方法和配置大部分都是可以高度复用的。在新开其他项目的时候这个模块可以直接迁移到新项目中,而不用在为代码分离和剔除浪费时间。所以,项目的结构进一步细化成了如下的结构:
3. 公共层分离
上述的模块如果在使用中,有很大概率会遇到一个问题,部分的实体类、自定义view、布局文件或者资源文件在各个模块都需要用到,但是这些如果放在系统层的moduleBase里面,又会破坏系统层的通用性。所以,我们还需要一个公共层Provider来专门提供上层业务逻辑模块的公共资源。直接上图:
4. 扩展
待完善...
三、如何进行安卓模块化开发
1. 创建Module模块
将所需的模块在对应项目的Project目录下拷贝出来,粘贴到要开发的项目的Project根目录下即可。也可以直接在project下新建一个Module。
File --> New --> New Module --> Android Library (建议选择这个) --> Finish
一个模块这样就创建完成了。默认的名字是app,根据项目的需要将模块的名字进行修改,直接Refactor > Rename...即可
2. 将Module模块引入主项目中
设置setting.gradle 中
include ':app', ':UserCenter'
设置后发现项目目录下增加了一个模块
在主模块的build.gradle中设置
api project(':UserCenter')
3. 模块中application和library状态切换配置
1. 设置一个开关控制application和library状态切换
我们在开发的时候,Module如果是一个库,会使用com.android.library插件,如果是一个应用,则使用com.android.application插件,接下来根据这个变量来进行判断并且实现状态切换。
Project根目录下gradle.properties中设置变量来控制。
isUserModule = false
isUserModule=false:表示这个模块是一个UserCenter的Module;
isUserModule=true:表示这个模块是一个app;
2. 依赖项目中build.gradle配置
在模块的build.gradle的开头处设置。
//顶部插件引入
if(isUserModule.toBoolean()){
apply plugin: 'com.android.library'
}else {
apply plugin: 'com.android.application'
}
android {
...
sourceSets{
main{
if (isUserModule.toBoolean()){
manifest.srcFile 'src/main/release/AndroidManifest.xml'
//release模式下排除debug文件夹中的所有Java文件
java{
exclude 'debug/**'
}
}else{
manifest.srcFile 'src/main/debug/AndroidManifest.xml'
}
}
}
}
3. 提供两套 AndroidManifest.xml并进行动态切换
mainfest文件也需要提供两套
android {
...
sourceSets{
main{
if (isUserModule.toBoolean()){
manifest.srcFile 'src/main/release/AndroidManifest.xml'
//release模式下排除debug文件夹中的所有Java文件
java{
exclude 'debug/**'
}
}else{
manifest.srcFile 'src/main/debug/AndroidManifest.xml'
}
}
}
}
四、Kotlin模块化开发过程中遇到的问题
1. 资源名称冲突
Android Studio 默认 library 的所有的 resource 为 public , 所以在模块化开发过程中总会遇到资源冲突问题。列出两种解决方法。
-
方法一:
保护某些 resources 不被外部访问,可以创建res/values/public.xml,因为 public 是关键词,搜易需要用 new file 的方式创建。至少添加一行,为添加的视为 private
<resources>
<public name="mylib_app_name" type="string"/>
</resources>
-
方法二:
在 library 的 build.gradle 中添加 resourcePrefix , 则所有的资源须以此 prefix 开头,否则报错。注意,图片资源虽然不提示报错误,但是也需要修改名字。
android {
...
buildTypes {
...
}
resourcePrefix 'my_prefix_'
}
2.重复依赖
将所有的依赖都写在library层(BaseLibrary、Provider)的module,将所有的依赖统一成一个入口给上层的app去引用。
3.控制台报错解决
Error:Module 'qsp_release:libLive:unspecified' depends on one or more Android Libraries but is a jar
报这个错误的场景,Moudle A 的build.gradle下
apply plugin: 'java'
...
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile project(':library')
...
}
可以看出 Moudle A 是一个jar形式的依赖模块,而library是apply plugin: 'com.android.library'所以引用报如上错误。
4. 使用Dagger2时,需要分别在app层级下的build.gradle中添加如下代码
apply plugin: 'kotlin-kapt'//kapt 插件
dependencies {
//Dagger2
kapt "com.google.dagger:dagger-compiler:$dagger_version"
}
5. implementation、api与多模块依赖
自从gradle升级3.+版本后,gradle原来的依赖方法全部都被替换了,之前的compile替换成了implementation和api,新建工程时发现gradle默认使用的也是implementation。
其中 api 与之前的 compile 功能基本一致,不再赘述;implementation 就比较高级了,其作用就是,使用 implementation 添加的依赖不会再编译期间被其他组件引用到,但在运行期间是完全可见的。这也是一种代码隔离。举个例子:
组件A依赖lib1,既A implementation lib1
组件B依赖组件A,既B api A
在 gradle3.0.0 之前,B是完全可以引用到 lib1 里面的类的,但是现在B在编译期间就做不到了,只能在运行期可以。
在kotlin中,为了避免以后依赖库引用无效的悲剧发生 ,尽量用 implementation ,只有要用到依赖中的依赖,再用api
6. compileDebugKotlin 解决方案之一(api错用导致的重复引用)
首先先补下 依赖的知识 ,依赖可以用2种方式implementation和 api,举例场景:
app 依赖 bModule , bModule依赖cModule
api:app既可以引用bModule的方法和类又可以引用cModule中代码
implementation: app能引用bModule中的方法和类 app不能引用cModule中的方法 这样的好处是提高编译效率
我项目的依赖关系是:
app api bModule
app implementation cModule
导致 重复引用cModule,一直报错compileDebugKotlin
解决办法就是app implementation bModule
为了避免以后这种悲剧发生 尽量只用 implementation ,只有要用到依赖中的依赖,再用api