模块化开发的优势
1。结构清晰,各个模块的代码实现分离,能快读定位到想要的功能模块。
2。方便协同开发与后期整合维护,一个大项目拆分成多个组件后,可以将一个组件作为一个独立维护的项目进行开发,通过壳工程的gradle来进行拆分整合。
3。基础组件的复用。可以将常用的封装好的一些基础类工具类封装到基础组件中。使用的时候直接导入就可以了。
4。低耦合,高内聚。
关于组件化 模块化 插件化的区别可以参考下面的链接:
Android 组件化、模块化、插件化区别详解_liuzhenyu0619的专栏-CSDN博客
下面开始从头创建一个模块化的项目:
1.新建一个壳工程。也就是整个项目的入口。
创建一个SplashActivity,作为整个项目的入口。
新建一个项目以后首先在根目录下新建一个root.gradle.(名字可以随便起)这个文件主要用来统一管理编辑项目中各个moudle的公共配置信息,以及依赖框架信息。
内容如下:
ext {
isMainMoudle = true
isM1Moudle = true
android = [
compileSdkVersion :31,
buildToolsVersion :"31.0.0",
applicationId :"com.lt.myapplication",
minSdkVersion :26,
targetSdkVersion :31,
versionCode :1,
versionName :"1.0",
testInstrumentationRunner :"androidx.test.runner.AndroidJUnitRunner"
]
version = [
appcompat_version :"1.3.1",
material_version :"1.4.0",
constraintlayout_version :"2.1.0",
navigation_fragment_version :"2.3.0",
navigation_ui_version :"2.3.0",
navigation_runtime_version :"2.3.0",
rxjava3_version :"3.1.1",
rxandroid3_version :"3.0.0",
okhttp3_version :"4.9.1",
retrofit_version :"2.9.0",
arouter_version :"1.5.2"
]
baseDen = [
"appcompat" :"androidx.appcompat:appcompat:${version.appcompat_version}",
"material" :"com.google.android.material:material:${version.material_version}",
"constraintlayout" :"androidx.constraintlayout:constraintlayout:${version.constraintlayout_version}",
"navigation_fragment" :"androidx.navigation:navigation-fragment:${version.navigation_fragment_version}",
"navigation_ui" :"androidx.navigation:navigation-ui:${version.navigation_ui_version}",
"navigation_runtime_version" :"androidx.navigation:navigation-runtime:${version.navigation_runtime_version}",
"arouter" :"com.alibaba:arouter-api:${version.arouter_version}"
]
netDen = [
"rxjava3" :"io.reactivex.rxjava3:rxjava:${version.rxjava3_version}",
"rxandroid3" :"io.reactivex.rxjava3:rxandroid:${version.rxandroid3_version}",
"okhttp3" :"com.squareup.okhttp3:logging-interceptor:${version.okhttp3_version}",
"retrofit2" :"com.squareup.retrofit2:retrofit:${version.retrofit_version}",
"converter_gson" :"com.squareup.retrofit2:converter-gson:${version.retrofit_version}",
"adapter" :"com.squareup.retrofit2:adapter-rxjava3:${version.retrofit_version}"
]
otherDeps = [
"arouter-compiler": "com.alibaba:arouter-compiler:${version.arouter_version}"
]
baseLibs = baseDen.values()
netLibs = netDen.values()
}
1. isMainMoudle = true isM1Moudle = true 2个标志位分别代表我要引用的2个项目是否以library的形式添加到当前项目中,还是以application形式进行编译调试。并根据不同的值设置不同的gradle的设置,后面会详细讲解。
2. android = [...] 各个项目moudle中的gradle文件的对应android代码块儿中的配置信息。(必须一致)
3. version = [...]导入的依赖库的插件版本号。
4. baseDen = [...] netDen = [...] otherDeps = [...]都是项目用到的依赖库。相信都能看明白。特别说明下otherDeps 设置的是Arouter的compiler的引用。因为需要用Arouter来进行各个moudle的跳转传参。所以每个项目中都需要单独引用以下Arouter的注解编译器。
5.baseLibs netLibs是为了方便导入设置的变量。
6.在项目的gradle文件中引入root.gradle。
apply from: "root.gradle" //添加刚建好的root.gradle的引用。
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath "com.android.tools.build:gradle:4.0.1"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
mavenCentral()
jcenter() // Warning: this repository is going to shut down soon
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
7.修改项目中app moudle的gradle文件:
//因为是主moudle所以不需要考虑application(独立开发模式)插件和library插件(集成开发模式)的切换问题。
//直接当application插件使用
plugins {
id 'com.android.application'
}
android {
compileSdkVersion rootProject.ext.android.compileSdkVersion
defaultConfig {
applicationId "com.lt.myapplication"
minSdkVersion rootProject.ext.android.minSdkVersion
targetSdkVersion rootProject.ext.android.targetSdkVersion
versionCode rootProject.ext.android.versionCode
versionName rootProject.ext.android.versionName
multiDexEnabled true
//添加Arouter注解器的设置
javaCompileOptions {
annotationProcessorOptions {
arguments = [ AROUTER_MODULE_NAME : project.getName() ]
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
buildFeatures {
viewBinding true
dataBinding true
}
lintOptions {
abortOnError false
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
//现在还没引入base moudle,main moudle,m1 moudle.暂时使用项目原有配置,此处可以不修改。在
//导入对应项目后可以按照下面代码进行处理。
//公共模块,不考虑单独作为application进行处理的话可以不用判断直接添加依赖
implementation project(':base')
if(rootProject.ext.isMainMoudle.toBoolean()){
implementation project(':main')
}
if(rootProject.ext.isM1Moudle.toBoolean()){
implementation project(':m1')
}
//引用Arouter的注解处理器的依赖
annotationProcessor rootProject.ext.otherDeps["arouter-compiler"]
}
2.创建三个项目(未来会成为我们主项目的lib)
三个项目中的moudle的gradle文件自动生成后先不进行修改,引入到主项目中在进行修改。创建项目时使用AndroidX的库,尽量不要使用support的库。
第一个项目base(导入到主项目后的moudle名,创建的时候随便取个符合规则的名字就可以,下面两个项目同理),里面包含了一些基础类 工具类。单纯的作为一个library(集成开发模式)来使用的项目。该项目是未来各个组件使用的基础library(集成开发模式)引用。
第二个项目main,随便写一个MainActivityActivity,来代表一个组件,项目启动后会从Splash页跳转到该Activity。控制切换project(独立开发模式)和library(集成开发模式)对应的标志位为 isMainMoudle。
第三个项目m1,随便写一个MainActivityActivity,来代表一个组件,从第二个项目的Activity可以跳转到该Activity。控制切换project(独立开发模式)和library(集成开发模式)对应的标志位为 isM1Moudle。
3.将三个项目导入到主项目中
File->New->Import Moudle。挨个导入刚才新建的三个项目。因为主项目中的主moudle已经叫app了,新创建的三个项目中的主moudle也叫app,所以需要改名,我这边分别改成base,main,m1(第1步的第7项中的gradle文件中导入的就是改名后的三个项目,第2步中直接用改后的名字来描述了三个项目)。
base下最关键的几个文件:BaseActivity,BaseFragment,BaseApplication。
BaseActivity:所有项目中使用的Activity都继承该Activity。保证所有的Activity都注册过Arouter。
public abstract class BaseActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable @org.jetbrains.annotations.Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(getLayout());
//向ARouter中注册当前Activity
ARouter.getInstance().inject(this);
}
public abstract int getLayout();
}
BaseFragment:与BaseActivity基本功能一致,保证所有Fragment都注册过Arouter.
BaseApplication: 主项目需要自定义的Application需要继承BaseApplication。
public class BaseApplication extends Application {
private static final String TAG = "BaseApplication";
@Override
public void onCreate() {
super.onCreate();
//集成开发时全部的moudle路由信息在这里初始化
initARouter();
}
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
}
private void initARouter() {
if (BuildConfig.DEBUG) {
ARouter.openLog();
ARouter.openDebug();
}
ARouter.init(this);
}
}
4.修改清单配置文件(AndroidManifest,xml)
所有需要以application进行单独编译调试的moudle需要有独立的library插件下的AndroidManifest和application插件下的AndroidManifest文件。
这部操作必须在Android Studio的Project的项目文件结构下进行操作。
1.在main和m1 moudle的src/main/项目包名 新建一个文件夹moudle。然后再该文件夹下复制一个当前moudle的清单文件。新清单文件位置不是固定一要在这个位置,因为这个新建的清单文件的使用是后面在修改moudle的build.gradle文件中指定的。
2.新的清单文件只保留最基础的相关设置。
新的main moudle清单如下:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.lt.main">
<application>
<activity
android:exported="true"
android:name=".MainActivity"/>
</application>
</manifest>
不要给<appliction>设置name属性。因为要用主项目中的自定义Application类。如果设置了当项目搭建完毕跑起来会报错:一个项目中有多个Appliction类。
原清单文件长这样:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.lt.main">
<application
android:name=".debug.MainApp"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:theme="@style/Theme.Main">
<activity
android:exported="true"
android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
android:name=".debug.MainApp"是在自己添加的用于独立开发模式中使用的自定义Application。
同样添加m1 moudle的library插件(集成开发模式)用的清单文件。
5.修改moudle下的build.gradle
建议切换到Android的项目文件结构下进行操作,因为所有的build.gradle都在一起找起来很方便。当然在Project下不闲麻烦也是可以的。
1.主项目的build.gradle和主项目下的主moudle app的build.gradle已经在第一步的第6和第7项中贴过了。
2.修改base moudle的build.gradle文件。如下
//作为library插件使用所以直接写死成library插件(集成开发模式)
apply plugin: 'com.android.library'
android {
compileSdkVersion rootProject.ext.android.compileSdkVersion
buildToolsVersion rootProject.ext.android.buildToolsVersion
defaultConfig {
minSdkVersion rootProject.ext.android.minSdkVersion
targetSdkVersion rootProject.ext.android.targetSdkVersion
versionCode rootProject.ext.android.versionCode
versionName rootProject.ext.android.versionName
multiDexEnabled true
//向Arouter注册当前moudle
javaCompileOptions {
annotationProcessorOptions {
arguments = [ AROUTER_MODULE_NAME : project.getName() ]
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
lintOptions {
abortOnError false
}
// lintOptions {
// disable('AllowBackup', 'GoogleAppIndexingWarning', 'MissingApplicationIcon')
// }
}
dependencies {
// 使用主项目中root.gradle中设置的变量引入依赖
// 包括封装网络通信层使用的一些框架(netLibs),以及一些基础框架(baseLibs).
// 如果需要添加新的通用依赖记的在root.gradle中添加,在这里引入依赖。
// 如果是某个moudle单独使用的基本上就别在root.gradle中添加了,直接在moudle中的
// build.gradle中引入就可以了
api rootProject.ext.netLibs
api rootProject.ext.baseLibs
// 使用主项目中root.gradle中设置引入Arouter的注解编译器
annotationProcessor rootProject.ext.otherDeps["arouter-compiler"]
}
该moudle作为基础library引入到各个组件中。在base中引入的依赖大部分可以在别的组件中直接使用,除了各种注解处理器(annotationProcessor)。在哪里使用了就要引用对应的依赖。
3.修改main moudle的build.gradle和m1的build.gradle。如下:
main下的build.gradle
//根据root.gradle中设置的标志位取值来决定当前项目是作为library(集成开发模式)给主项目中使用
//还是作为application独立进行修改编译
if (rootProject.ext.isMainMoudle.toBoolean()) {
apply plugin: 'com.android.library'
} else {
apply plugin: 'com.android.application'
}
android {
compileSdkVersion rootProject.ext.android.compileSdkVersion
buildToolsVersion rootProject.ext.android.buildToolsVersion
defaultConfig {
//集成开发模式下moudle不能有自己的applicationId
//独立开发模式下有
if (!rootProject.ext.isMainMoudle.toBoolean()) {
applicationId "com.lt.main"
}
minSdkVersion rootProject.ext.android.minSdkVersion
targetSdkVersion rootProject.ext.android.targetSdkVersion
versionCode rootProject.ext.android.versionCode
versionName rootProject.ext.android.versionName
multiDexEnabled true
javaCompileOptions {
annotationProcessorOptions {
arguments = [ AROUTER_MODULE_NAME : project.getName() ]
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
buildFeatures {
viewBinding true
}
// 为了防止moudle之间有同名的资源,需要建议给每个引入的moudle中给资源强行添加一个前缀。
// 注意自定义的drawabl文件和拷贝到项目中的图片名字也需要以main_开头。
// 比如MainActivity的layout文件原来叫activity_main,现在需要叫main_activity_main。
// 因为引入的moudle在作为library参与到主项目的资源文件ID都放在一个R文件中,所以需要去重。
resourcePrefix "main_"
//根据root.gradle中设置的标志位取值来决定引用哪个清单文件
sourceSets{
main{
if(rootProject.ext.isMainMoudle.toBoolean()){
//集成开发模式时使用的清单文件(第四步添加的)
manifest.srcFile 'src/main/AndroidManifest.xml'
//集成开发模式去除debug包中的java文件, 直接使用java路径中的java文件
java {
exclude 'debug/**'
}
}else{
//独立开发模式时使用的清单文件
manifest.srcFile 'src/main/moudle/AndroidManifest.xml'
}
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
//引用项目中的base moudle来使用外部依赖的组件
implementation project(':base')
// 使用主项目中root.gradle中设置引入Arouter的注解编译器
annotationProcessor rootProject.ext.otherDeps["arouter-compiler"]
}
m1下的build.gradle
//根据root.gradle中设置的标志位取值来决定当前项目是作为library(集成开发模式)给主项目中使用
//还是作为application独立进行修改编译
if (rootProject.ext.isM1Moudle.toBoolean()) {
apply plugin: 'com.android.library'
} else {
apply plugin: 'com.android.application'
}
android {
compileSdkVersion rootProject.ext.android.compileSdkVersion
buildToolsVersion rootProject.ext.android.buildToolsVersion
defaultConfig {
//集成开发模式下moudle不能有自己的applicationId
//独立开发模式下有
if (!rootProject.ext.isM1Moudle.toBoolean()) {
applicationId "com.lt.moudle1"
}
minSdkVersion rootProject.ext.android.minSdkVersion
targetSdkVersion rootProject.ext.android.targetSdkVersion
versionCode rootProject.ext.android.versionCode
versionName rootProject.ext.android.versionName
multiDexEnabled true
javaCompileOptions {
annotationProcessorOptions {
arguments = [ AROUTER_MODULE_NAME : project.getName() ]
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
buildFeatures {
viewBinding true
}
// 为了防止moudle之间有同名的资源,需要建议给每个引入的moudle中给资源强行添加一个前缀。
// 注意自定义的drawabl文件和拷贝到项目中的图片名字也需要以m1_开头。
// 比如MainActivity的layout文件原来叫activity_main,现在需要叫m1_activity_main。
// 因为引入的moudle在作为library参与到主项目的资源文件ID都放在一个R文件中,所以需要去重。
resourcePrefix "m1_"
//根据root.gradle中设置的标志位取值来决定引用哪个清单文件
sourceSets{
main{
if(rootProject.ext.isM1Moudle.toBoolean()){
//集成开发模式时使用的清单文件(第四步添加的)
manifest.srcFile 'src/main/AndroidManifest.xml'
//集成开发模式去除debug包中的java文件, 直接使用java路径中的java文件
java {
exclude 'debug/**'
}
}else{
//独立开发模式时使用的清单文件
manifest.srcFile 'src/main/moudle/AndroidManifest.xml'
}
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
//引用项目中的base moudle来使用外部依赖的组件
implementation project(':base')
// 使用主项目中root.gradle中设置引入Arouter的注解编译器
annotationProcessor rootProject.ext.otherDeps["arouter-compiler"]
}
千万记得moudle之间不要互相引用。否则会报循环引用的Error。
moudle之间页面的跳转传参是使用Arouter实现的。
到这里基本上框架就搭建完成了。
依赖的包里可能包含有重复的类。
解决方案:
1).找到重复引用依赖的包单独放到moudle的build.gradle中引用然后用exclued{}把重复的类给排除掉。
2).如果是AndroidX库和Support库中的类有重复的(引入的三方依赖可能会引用Support库),可以在主项目中的gradle.properties文件中添加以下代码
android.useAndroidX=true
android.enableJetifier=true
android.useAndroidX=true 表示“Android插件会使用对应的AndroidX库,而非Support库”;未设置时默认为false;
android.enableJetifier=true 表示Android插件会通过重写其二进制文件来自动迁移现有的第三方库,以使用AndroidX依赖项;未设置时默认为false;
当然,这两个属性对Android Sutio版本是有要求的:3.2
同时,由于Android Studio的版本对gradle、gradle plugin的最低版本也有限制,因此可以认为对gradle及其插件也是有要求的:
将 Android studio 升级到 3.2 及以上;
Gradle 插件版本改为 4.6 及以上;
compileSdkVersion 版本升级到 28 及以上;
buildToolsVersion 版本改为 28.0.2 及以上;
完成以上后,请再仔细检查以下位置。
1.需要独立开发模式的moudle中的build.gradle。
if (rootProject.ext.isM1Moudle.toBoolean()) {
apply plugin: 'com.android.library'
} else {
apply plugin: 'com.android.application'
}
判断是否写反。
2.需要独立开发模式的moudle中的build.gradle。
中android 下的 defaultConfig的applicationId是否是根据标志位添加的。
android {
compileSdkVersion 31
buildToolsVersion "31.0.0"
defaultConfig {
if (!rootProject.ext.isMainMoudle.toBoolean()) {
applicationId "com.lt.main"
}
minSdkVersion rootProject.ext.android.minSdkVersion
targetSdkVersion rootProject.ext.android.targetSdkVersion
versionCode rootProject.ext.android.versionCode
versionName rootProject.ext.android.versionName
multiDexEnabled true
javaCompileOptions {
annotationProcessorOptions {
arguments = [ AROUTER_MODULE_NAME : project.getName() ]
}
}
}
3.需要独立开发模式的moudle中的build.gradle。
sourceSets {
main {
if (rootProject.ext.isMainMoudle.toBoolean()) {
manifest.srcFile 'src/main/AndroidManifest.xml'
//集成开发模式去除debug包中的java文件, 直接使用java路径中的java文件
java {
exclude 'debug/**'
}
} else {
manifest.srcFile 'src/main/module/AndroidManifest.xml'
}
}
}
清单文件引用是否写反,以及对应路径是否正确。
最后可以参考一片文件写的不错