前言:这篇模块化与组件化的文章,用2篇文章介绍。可能有些人觉得网上已经有了文章,为什么还要写。第一:为了记录自己的正常也算当做笔记。第二:网上固然有好文,但最近看了一篇居然有150多赞,但是介绍的迷迷糊糊,很多知识点略过。本文重点是让你快速入门,理解以及使用。
本次模块化/组件化讲解总共分2篇(必须先了解ARouter,或第三方路由框架):
1、阿里路由框架ARouter的基本使用
2、Android中通过对gradle的管理实现组件化;并配合ARouter,随意跳转切换
其实在没了解过模块化/组件化之前,我觉得非常高端,甚至不敢触碰。可能是因为其他人的博客很高端。其实接触后发现,其实就是通过gradle的管理实现的。高端的只不过是各模块module之间的通信,可是ARouter已经帮我们解决了所有事情。跟着我一步一步。一起实现,末尾加上本文demo。
在本文文章开始前,先讲个小知识点:
A module 引入了B module,B module引入了C module,如果使用的是implementation方式,那么C对于A来说是不可见的;而使用api方式C是可以被A访问的。同理,把C换成开源库、so文件、aar文件、jar包文件结论也适用。
如果是jar包的,父类要引入子类的话必须要加上路劲如: dirs ‘libs’, ‘…/moduleB/libs’ 。父类引入moduleB中的libs
这里也和大部分网上一样,以微信为例。把微信分为4个模块module:home、chat、search、mine。
通过对gradle配置开光的更改,我们可以单独把home模块,chat模块,search模块,mine模块,自身作为app运行。这样模块化的好处是,解耦。把各模块给开发人员开发,互不影响,且代码管理也不会起冲突等等。
这些app的生成,只是改变gradle的配置开光即可生成。
完整app | home_module单独运行app | chat_module单独运行app |
---|---|---|
从最外层看 | ||
新建项目,我这里的项目是CatModuleStu,在项目build.gradle的目录下,new一个config.gradle。不会的直接复制项目build.gradle,改下名字即可。里面的内容呢,根据我们的app里build.gradle进行更改,app里的build.gradle如下:
apply plugin: 'com.android.application'
android {
compileSdkVersion 28
defaultConfig {
applicationId "com.lihang.catmodulestu"
minSdkVersion 22
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}
意思就是把这些具体的引用用常量表示,每个module都使用这些常量,所以我们的confing.gradle如下:
ext {
//这里是配置开关。是否需要单独运行。注意,这里只能打开一个。因为一次只能运行一个。。
//true 表示需要单独运行。false表示不需要单独运行。
isNeedHomeModule = false
isNeedChatModule = false
isNeedFindModule = false
isNeedMineModule = false
android = [
compileSdkVersion: 28,
buildToolsVersion: "28.0.0",
minSdkVersion : 22,
targetSdkVersion : 28,
versionCode : 1,
versionName : "1.0.0",
applicationId : "com.lihang.catmodulestu",
applicationHomeId: "com.lihang.homemodule",
applicationChatId: "com.lihang.chatmodule"
]
//这个对依赖库版本version的管理,就更加细致化了
version = [
androidSupportSdkVersion: "28.0.0"
]
//系统依赖
dependencies = [
"support:appcompat-v7": "com.android.support:appcompat-v7:${version["androidSupportSdkVersion"]}",
"test:runner" : 'com.android.support.test:runner:1.0.2',
"test.espresso" : 'com.android.support.test.espresso:espresso-core:3.0.2',
"junit" : 'junit:junit:4.12'
]
//第三方库(请原谅我的英语)
//这样依赖库看起来比较清晰(dependencies : 代表系统依赖库;thridencies代表第三依赖库)
thridencies = [
"butterknife" : 'com.jakewharton:butterknife:8.8.1',
"butterknife-compiler": 'com.jakewharton:butterknife-compiler:8.8.1',
"arouter-compiler" : 'com.alibaba:arouter-compiler:1.1.4',
"arouter" : 'com.alibaba:arouter-api:1.3.1',
]
}
这些写好之后,在我们的项目build.gradle最顶部,引用一下我们的config.gradle:
apply from: “config.gradle”
做完这些后,在来看我们的app的build.gradle
apply plugin: 'com.android.application'
android {
compileSdkVersion rootProject.ext.android["compileSdkVersion"]
defaultConfig {
applicationId rootProject.ext.android["applicationId"]
minSdkVersion rootProject.ext.android["minSdkVersion"]
targetSdkVersion rootProject.ext.android["targetSdkVersion"]
versionCode rootProject.ext.android["versionCode"]
versionName rootProject.ext.android["versionName"]
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation rootProject.ext.dependencies["support:appcompat-v7"]
testImplementation rootProject.ext.dependencies["junit"]
androidTestImplementation rootProject.ext.dependencies["test:runner"]
androidTestImplementation rootProject.ext.dependencies["test.espresso"]
}
新建baseModule,把常用的网络请求,图片加载,意思就是其他module共用的东西放进去.包括共用的资源文件,BaseActivity,BaseFragment和application也放在这,上篇说的ARouter的使用,初始化也放在这。这个baseModule是完全作为library的。被其他module和app引用。但是这里有2个坑:
1、引入ARouter的时候,在baseModule的build.gradle里dependencies标签下加上:
api rootProject.ext.thridencies["arouter"]
annotationProcessor rootProject.ext.thridencies["arouter-compiler"]
在android的defaultConfig标签加上
javaCompileOptions {
annotationProcessorOptions {
arguments = [moduleName: project.getName()]
}
}
其他module引用baseModule的时候,同样也要在defaultConfig标签下加上javaCompileOptions。同时还要引入如下依赖,对!你没有看错。如果不加,那么将不成功。:
annotationProcessor rootProject.ext.thridencies["arouter-compiler"]
2、这里用butterknife。比如baseModule引用了butterknife后,其他module引用baseModule的时候会出现个bug、错误提示:元素必须为常量。然后butterknife官方提供了一个处理方案,用R2。但是在切换module作为app运行的时候,R2会报错,意思作为app的时候R2要改成R。假如你module里都用了R2,这个时候要全部改成R。所以不建议baseModule引用butterknife。如有好的解决方法,望告知!!
首先看homeModule的build.gradle:
//这里就用到了config的配置isNeedHomeModule
//开头设置,如果打开开光,当成项目运行,否则当成library引用
if (isNeedHomeModule.toBoolean()) {
apply plugin: 'com.android.application'
} else {
apply plugin: 'com.android.library'
}
android {
compileSdkVersion rootProject.ext.android["compileSdkVersion"]
defaultConfig {
minSdkVersion rootProject.ext.android["minSdkVersion"]
targetSdkVersion rootProject.ext.android["targetSdkVersion"]
if (isNeedHomeModule.toBoolean()) {
//同时在conifg.gradle配置上homeModule的包名。
//当作为application运行的时候,给他配置上独立的包名
applicationId rootProject.ext.android["applicationHomeId"]
}
versionCode rootProject.ext.android["versionCode"]
versionName rootProject.ext.android["versionName"]
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
//ARouter的使用记得要加哦
javaCompileOptions {
annotationProcessorOptions {
arguments = [moduleName: project.getName()]
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
sourceSets {
main {
if (isNeedHomeModule.toBoolean()) {
//这里目前的做法是2套AndroidManifest,作为app运行的时候要指定启动页
manifest.srcFile 'src/main/buildApp/AndroidManifest.xml'
} else {
manifest.srcFile 'src/main/buildModule/AndroidManifest.xml'
}
}
}
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation rootProject.ext.dependencies["support:appcompat-v7"]
testImplementation rootProject.ext.dependencies["junit"]
androidTestImplementation rootProject.ext.dependencies["test:runner"]
androidTestImplementation rootProject.ext.dependencies["test.espresso"]
implementation project(':baseModule')
annotationProcessor rootProject.ext.thridencies["arouter-compiler"]
}
从上代码中我们可以看到有3点注意的地方:
if (isNeedHomeModule.toBoolean()) {
apply plugin: 'com.android.application'
} else {
apply plugin: 'com.android.library'
}
if (isNeedHomeModule.toBoolean()) {
applicationId rootProject.ext.android["applicationHomeId"]
}
if (isNeedHomeModule.toBoolean()) {
//这里目前的做法是2套AndroidManifest,作为app运行的时候要指定启动页
manifest.srcFile 'src/main/buildApp/AndroidManifest.xml'
} else {
manifest.srcFile 'src/main/buildModule/AndroidManifest.xml'
}
这里2个AndroidManifest就不贴了,可以通过我的demo自己去看。其实就是指定一个启动页Activity。我这里为了demo的清晰,就新建了个SelectHomeActivity,然后就是homeModule模块,用到的所有Activity配置什么的都放在这里。如果是作为library,也是把所有的配置放在这。被主app引用的时候,他会自动来这里找的,不用我们担心。
做完这些,当homeModule作为app运行的时候,我们主app当然也要判断,就引用不了homeModule了,build.gradle如下:
dependencies {
... //省略部分代码,便于理解
if (!isNeedHomeModule.toBoolean()) {
implementation project(':homeModule')
}
}
做完这些,就完成了。其他module也是和这个同样的配置。通过更改config.gradle的配置isNeedHomeModule,就可以模块单独之间运行了。
当成library引用后。比如我这个demo是点击下方按钮进行切换。因为我们使用了ARouter,就可以这样(当然这里引用,你也可以直接用类名):
//这样就生成了一个HomeFragment的实例
HomeFragemnt fragment_one = (HomeFragemnt) ARouter.getInstance().build(Constance.FRAGMENT_HOME_PATH).withString("wo", "1").navigation();
这里我说个我自己的理解:
模块化:就像这样,把我们的“微信”,分为4个模块。我理解为模块化。
组件化:就像这里的baseModule,或者你封装的dialog,popwindow。再比如,你用的网上第三方的一些效果。我理解为模块化。和模块化一样,都是为了解耦。