前言
在进行安装开发的时候我们经常会对项目进行不同业务逻辑处理分包,例如专门处理网络、数据库、业务逻辑代码的,如果我们不分包则所有东西写在一起,势必在开发过程中会浪费很多时间在寻找文件上,而分包的思想源于模块化,例如一些utils类,专门放到一个包下面,这样在使用的时候就能很快找到。节省开发时间,降低后期维护成本,那么组件化是什么?
正文
在上面我们提到模块化,可以根据不同的功能进行进一步的封装,放到一个新建的模块下面,例如登录注册模块,个人中心模块之类的,但是模块化时就会涉及到一个分体,不同模块之间存在业务逻辑上的耦合,这种耦合开发过程中还不是最痛苦的,痛苦的是你需要复用其中某一个模块的时候,你需要一点一点的剥离出去,工作量甚至不亚于重新写,因为这时你的脑海里需要有原来项目的逻辑,还有的新项目的逻辑,还要想一下怎么迁移过去合理,为了解决模块化耦合严重的问题,就出现了组件化。
模块化是一个App主模块依赖多个Android子模块,只能运行App主模块;组件化是多个App主模块,各个模块都能独立运行测试。那么是否要使用模块化呢?这取决于你的项目体量了,如果一开始你不确定项目体量。那么就只用模块化,确定的化就使用组件化,也可以模块化转组件化。这个看实际情况而定,而如果项目太小了,你甚至都不需要用什么模块化组件化,一个MainActivity就搞定了,何必搞那么复杂呢?
总而言之,言而总之。这个组件化知识,你可能现在用不上,但是你学会了总有用武之地,正所谓,技多不压身嘛!下面开始实操环节,可能比你想象的还要简单呢?
一、创建项目
首先我们创建一个名为StudyComponent的项目,项目创建好之后就可以看到app模块了。
这就是我们的app主模块,也就是所谓的App的壳,它里面要有一个个组件构成,那么下面我们需要创建组件,这里做一个假设,我的App有登录注册和个人中心两个内容,怎么把这两个内容变成两个组件呢?首先要做的就是创建这两个组件,首先创建login组件,先将项目从Android模式切换到Project模式,然后鼠标右键点击StudyComponent → New → Module,出现弹窗,这里选择的是Phone & Tablet的方式。
点击Next,会需要你创建一个Activity,选择Empty Activity。
点击Next,给你的Activity命名,这里要将Activity的名字改变一下,因为组件化,在打包的时候是不允许有重复资源的,MainActivity我们在app组件中已经有了,所以其他组件中要避免重复。
点击Next,等待项目创建完成,创建完成之后你会看到login组件,同时可以自由的切换当前所运行的是app组件还是login组件,现在可以试试看。
参考创建login组件的方式,我们再创建一个personal组件,里面的Activity就改成PersonalActivity即可,其他的都差不多。
现在就有三个组件了,三个组件目前各自独立,那么我们调试运行的时候可以通过切换不同的组件进行,打包要怎么办呢?
二、项目模式切换
我们希望在打包的时候app组件依赖login和personal组件,那么我们就需要对这两个组件进行切换,切换之前我们需要知道它们当前是什么模式,在哪里看呢?
这里的application就表示你这是工程项目,而如果要被app组件依赖的话,则需要变成library,那么怎么变呢?在思考怎么变之前,我们首先应该知道library是什么样子,所以我们应该创建一个基础模块,所有的组件又依赖这个基础模块。
flowchart TD
A(App)
A--依赖-->B(login)
A--依赖-->C(personal)
B--依赖-->D(basic)
C--依赖-->D(basic)
通过这个流程图就能很好的理解这个思想了,下面我们创建basic模块。
创建模块的方式和之前一样,只不过这里我们就要选择Android Library进行创建了,它里面是没有让你去创建Activity的,点击Finish即可。
① 对比build.gradle
下面要做的就是对比一下组件和模块下的build.gradle中的区别,看看那些相同,那些不同,先看login组件的build.gradle。
plugins {
id 'com.android.application'
}
android {
namespace 'com.llw.login'
compileSdk 32
defaultConfig {
applicationId "com.llw.login"
minSdk 23
targetSdk 32
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'com.google.android.material:material:1.5.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}
再看base下的build.gralde
plugins {
id 'com.android.library'
}
android {
namespace 'com.llw.base'
compileSdk 32
defaultConfig {
minSdk 23
targetSdk 32
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles "consumer-rules.pro"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'com.google.android.material:material:1.5.0'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}
那么现在就能得出结论了,从上到下都有三个闭包,分别是plugins{}、android{}、dependencies{},先从plugins{}开始分析。
(一)plugins{}
组件中:
id 'com.android.application'
模块中:
id 'com.android.library'
那么我们需要根据一个变量来更改是application还是library。
(二)android{}
再分析android{}:
组件中:
android {
namespace 'com.llw.login'
compileSdk 32
defaultConfig {
applicationId "com.llw.login"
minSdk 23
targetSdk 32
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
...
}
模块中:
android {
namespace 'com.llw.base'
compileSdk 32
defaultConfig {
minSdk 23
targetSdk 32
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles "consumer-rules.pro"
}
...
}
这个闭包中,相同的内容就不看了,看那些不同的,首先namespace不用改动,compileSdk 、minSdk 、targetSdk 可以集中用一个变量管理,这样改的时候也方便一步到位。然后就是不同的地方,applicationId、versionCode、versionName在模块中没有,组件中有,这个需要注意。dependencies{}就没有啥好说的,可以说完全一样,集中处理就可以了。
② 对比AndroidManifest.xml
还有一处不同就是组件下的AndroidManifest.xml中是有内容的:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.StudyComponent">
<activity
android:name=".LoginActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.lib_name"
android:value="" />
</activity>
</application>
</manifest>
而在模块下的AndroidManifest.xml中的内容:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>
这意味中我们需要在组件中再准备一个模块的AndroidManifest.xml文件,根据当前是组件还是模块进行不同的引用。到目前为止我们都没有写过代码,下面开始写代码,写代码之前。你可以切换app、login、personal分别运行一下,你就会发现有三个应用,每个应用你都可以单独运行。
③ 管理项目参数
先梳理一下,我们目前有三个组件:app、login、personal,一个模块:basic。模块和组件里面有很多内容是一样的,例如版本号、编译SDK版本什么的,还有一些依赖库版本,jdk版本,如果我没有每一个的去改无疑很麻烦,所以我们将这些信息定义到一个文件中,如果有依赖库的版本改变了只要改这个文件就可以了,那就很方便了,右键点击你的StudyComponent → New → File ,输入config.gradle。
回车,在我们的工程目录下创建了一个config.gradle文件,这也是一个gradle文件,里面的代码需要我们自己去写,注意看这个文件的所在位置和你的工程build.gradle是同一级别的,如果你不是这样的,说明你创建文件的位置错了,需要重新创建。
gradle里面是有一种编程语言的,是Groovy,感兴趣的话可以去了解一下。先把里面的代码写进去再来说明一下,config.gradle的代码如下:
//项目工程配置
ext {
//基本信息配置
android = [
compileSdk : 32, //编译SDK版本
minSdk : 23, //最低运行SDK版本
targetSdk : 32, //目标SDK版本
versionCode : 1, //项目版本号
versionName : "1.0", //项目版本名
isApplication: true //是否为Application
]
//编译JDK配置
compileOptions = [
sourceCompatibility: JavaVersion.VERSION_11,
targetCompatibility: JavaVersion.VERSION_11,
]
//依赖配置
dependencies = [
publicImplementation : [
'androidx.appcompat:appcompat:1.4.1',
'com.google.android.material:material:1.6.0',
'androidx.constraintlayout:constraintlayout:2.1.4'
],
publicTestImplementation : [
'junit:junit:4.13.2'
],
publicAndroidTestImplementation: [
'androidx.test.ext:junit:1.1.3',
'androidx.test.espresso:espresso-core:3.4.0'
],
other : [
':basic'
]
]
}
这里的代码是Groovy的语法,ext你可以看做一个类名,android、compileOptions 、dependencies可以看做ext的内部类,中括号里面的就是类的属性值,属性名不能重复。属性值这里我们就定义了一些项目中需要用的属性,通过注释你应该明白这些属性是什么意思了,要使用它,首先需要让我们的工程知道有这样一个文件,在工程的build.gradle中新增如下所示代码:
apply from: "config.gradle"
添加位置如下图所示:
然后Sync Now,下面使用这个文件,
(一)修改app的build.gradle
修改一下app下的build.gradle中代码,如下所示:
plugins {
id 'com.android.application'
}
android {
namespace 'com.llw.component'
compileSdk rootProject.ext.android.compileSdk
defaultConfig {
applicationId "com.llw.component"
minSdk rootProject.ext.android.minSdk
targetSdk rootProject.ext.android.targetSdk
versionCode rootProject.ext.android.versionCode
versionName rootProject.ext.android.versionName
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility rootProject.ext.compileOptions.sourceCompatibility
targetCompatibility rootProject.ext.compileOptions.targetCompatibility
}
}
dependencies {
implementation rootProject.ext.dependencies.publicImplementation
implementation rootProject.ext.dependencies.publicTestImplementation
implementation rootProject.ext.dependencies.publicAndroidTestImplementation
rootProject.ext.dependencies.other.each {
implementation project(it)
}
//不是组件时才依赖
if (!rootProject.ext.android.isApplication) {
implementation project(path: ':login')
implementation project(path: ':personal')
}
}
app模块就一直是application,不需要切换到library,然后修改compileSdk 的值,rootProject表示这个工程,这里引用ext,再引用android,最后找到compileSdk,实际的值就是32,其他的属性设置也是这个道理,这里面的dependencies 中需要引用basic模块,这样写的好处就是,有一天我的basic模块改成了network模块,我只需要修改config.gradle中的值就可以了,道理和其他全局属性一样。而最后这里的依赖其他的组件,需要在他们是library的时候才依赖,不是则不依赖。
(二)修改basic模块
basic作为其他组件依赖的基础模块,任何时候都是library模式,这一点和app模块恰恰相反,修改一下basic的build.gradle中代码,如下所示:
plugins {
id 'com.android.library'
}
android {
namespace 'com.llw.basic'
compileSdk rootProject.ext.android.compileSdk
defaultConfig {
minSdk rootProject.ext.android.minSdk
targetSdk rootProject.ext.android.targetSdk
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles "consumer-rules.pro"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
buildConfigField 'boolean', 'isApplication',
rootProject.ext.android.isApplication.toString()
}
advanced {
buildConfigField 'boolean', 'isApplication',
rootProject.ext.android.isApplication.toString()
}
debug {
buildConfigField 'boolean', 'isApplication',
rootProject.ext.android.isApplication.toString()
}
}
compileOptions {
sourceCompatibility rootProject.ext.compileOptions.sourceCompatibility
targetCompatibility rootProject.ext.compileOptions.targetCompatibility
}
}
dependencies {
implementation rootProject.ext.dependencies.publicImplementation
implementation rootProject.ext.dependencies.publicTestImplementation
implementation rootProject.ext.dependencies.publicAndroidTestImplementation
}
很多内容是相似的,不过这里有一点不一样,那就是定义了一个变量isApplication,这个变量的作用就是用来告知当前这个模块是application模式还是library模式,这很重要,因为app、login、personal在application模式下是各自独立的,而他们都需要继承basic,所以这个区分模块模式的工作就在basic完成,而basic需要做的任务还有别的,在com.llw.basic包下新建一个BaseActivity类,代码如下:
public class BaseActivity extends AppCompatActivity {
protected boolean isApplication;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
isApplication = BuildConfig.isApplication;
}
protected void showMsg(CharSequence msg) {
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
}
}
还有Application的处理,在com.llw.basic包下新建一个BaseApplication类,代码如下:
public class BaseApplication extends Application {
public static boolean isApplication;
@Override
public void onCreate() {
super.onCreate();
isApplication = BuildConfig.isApplication;
}
}
其他组件依赖了basic之后可以通过继承这两个类得知当前属于什么模式。如果出现BuildConfig报红,编译一下,如果还爆红,就重新导包,basic模块中的改动就完成了。
(三)修改login组件
修改这个login组件就有一些不一样了,因为它既有可能是application也有可能是library。当前我们的login还是application,那么对应的AndroidManifest.xml就是常规的,而如果变成了library模式时,这个AndroidManifest.xml也需要有变化,所以这里我们就需要两个AndroidManifest.xml,一个在application时用,一个在library时用。所以在login组件的main文件夹下新建一个manifest文件夹,然后再创建一个AndroidManifest.xml
这个AndroidManifest.xml的代码,如下所示:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
</application>
</manifest>
你再把application标签去掉就和basic中的AndroidManifest.xml完全一样了,这里改好之后,我们再去修改login组件的build.gradle,代码如下:
if (rootProject.ext.android.isApplication) {//修改插件的类型
apply plugin: 'com.android.application'
} else {
apply plugin: 'com.android.library'
}
android {
namespace 'com.llw.login'
compileSdk rootProject.ext.android.compileSdk
defaultConfig {
if (rootProject.ext.android.isApplication) {//application时需要id,版本号、版本名
applicationId "com.llw.login"
versionCode rootProject.ext.android.versionCode
versionName rootProject.ext.android.versionName
}
minSdk rootProject.ext.android.minSdk
targetSdk rootProject.ext.android.targetSdk
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
sourceSets {
main {
if (rootProject.ext.android.isApplication) {//修改使用的AndroidManifest
manifest.srcFile 'src/main/AndroidManifest.xml'
} else {
manifest.srcFile 'src/main/manifest/AndroidManifest.xml'
}
}
}
compileOptions {
sourceCompatibility rootProject.ext.compileOptions.sourceCompatibility
targetCompatibility rootProject.ext.compileOptions.targetCompatibility
}
}
dependencies {
implementation rootProject.ext.dependencies.publicImplementation
implementation rootProject.ext.dependencies.publicTestImplementation
implementation rootProject.ext.dependencies.publicAndroidTestImplementation
rootProject.ext.dependencies.other.each {
implementation project(it)
}
}
上面的修改代码主要就是组件是application时怎么做,library时怎么做,相信你能够看懂,最后我们修改一下activity_login.xml中TextView显示的文本内容,由Hello World!改成Login。
同时我们修改一下LoginActivity中的代码,让它继承自basic模块中的BaseActivity,使用父类的方法showMsg,弹出Toast。
(四)修改personal组件
这里的修改方式大致是一样的,我还是重复描述一遍吧,因为我怕你跟着操作到这里就不会了,首先同样需要一个AndroidManifest.xml,你可以直接把login组件中所写的复制过来。
然后修改personal的build.gradle,代码如下:
if (rootProject.ext.android.isApplication) {
apply plugin: 'com.android.application'
} else {
apply plugin: 'com.android.library'
}
android {
namespace 'com.llw.personal'
compileSdk rootProject.ext.android.compileSdk
defaultConfig {
if (rootProject.ext.android.isApplication) {
applicationId "com.llw.personal"
versionCode rootProject.ext.android.versionCode
versionName rootProject.ext.android.versionName
}
minSdk rootProject.ext.android.minSdk
targetSdk rootProject.ext.android.targetSdk
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
sourceSets {
main {
if (rootProject.ext.android.isApplication) {
manifest.srcFile 'src/main/AndroidManifest.xml'
} else {
manifest.srcFile 'src/main/manifest/AndroidManifest.xml'
}
}
}
compileOptions {
sourceCompatibility rootProject.ext.compileOptions.sourceCompatibility
targetCompatibility rootProject.ext.compileOptions.targetCompatibility
}
}
dependencies {
implementation rootProject.ext.dependencies.publicImplementation
implementation rootProject.ext.dependencies.publicTestImplementation
implementation rootProject.ext.dependencies.publicAndroidTestImplementation
rootProject.ext.dependencies.other.each {
implementation project(it)
}
}
除了有包名不一样以外,其他都和login组件的build.gralde一样,下面同样修改一下activity_personal.xml。
然后修改PersonalActivity的代码。
现在基本上就改完了。
④ 组件运行
目前app、login、personal都是组件,我们分别运行一下试试看。
通过这里切换需要运行的项目,下面我们依次运行一下app、login、personal,运行效果如下图所示。
<img src="https://ucc.alicdn.com/images/user-upload-01/e6cf0aad502745cfb4d5d65db6d5c686.jpeg" width="300"/><img src="https://ucc.alicdn.com/images/user-upload-01/e4ef8c0da21e40269e3b865d4f161af5.jpeg" width="300"/><img src="https://ucc.alicdn.com/images/user-upload-01/65bd277e168c4d7b9855167b52ef88a1.jpeg" width="300"/>
从这几个图来看,我们的组件依赖basic模块没有问题,其次就是组件各自为一个项目,你现在手机上应该有三个应用才对。
⑤ 切换模式
现在login和personal在application下可以正常运行,那如果在library时,app就依赖了login和personal,此时就只有app这一个组件了,那么这个时候app能不能正常运行呢?
下面我们把config.gradle中的isApplication的值从ture改成false,然后Sync Now。
就可以看到login和personal,现在不能够正常独立运行了,有一个 × ,然后注意看图标也变了,不是和app组件一样了,而是和basic一样了,这说明我们切换组件的项目模式是可行了,那么下面我们运行一下app组件,看能否正常运行。我这边是可以正常运行,不知道你那边怎么样。
三、源码
后面每次文章更新对应新的分支,源码地址指向分支地址,这样就不会让你看代码的时候和文章中所写的不一致了。
源码地址:StudyComponent
欢迎 Star 和 Fork