Kotlin之模块化开发

一、模块化浅谈

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,018评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,046评论 2 372
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,215评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,303评论 1 266
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,181评论 4 357
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,171评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,577评论 3 384
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,260评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,546评论 1 292
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,630评论 2 311
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,390评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,260评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,633评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,920评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,210评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,569评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,767评论 2 335

推荐阅读更多精彩内容