安卓组件化

3 篇文章 0 订阅
1 篇文章 0 订阅
本文详细介绍了Android模块化开发的优势,如结构清晰、便于协同开发和维护、基础组件复用及低耦合高内聚。通过创建一个壳工程,设置不同标志位区分应用和库,统一管理依赖,并对各模块的Gradle文件进行配置,实现模块化项目搭建。同时,文中还涉及组件化、模块化和插件化的区别,以及解决依赖冲突的方法。最后,提供了一份详细的步骤指南,帮助开发者实践Android模块化开发。
摘要由CSDN通过智能技术生成

模块化开发的优势

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'
            }
        }

    }

清单文件引用是否写反,以及对应路径是否正确。

最后可以参考一片文件写的不错

重温经典,不留遗憾!Android 组件化最佳实践 - 简书Demo地址 : https://github.com/renxuelong/ComponentDemo[https://github.com/renxuelong/Comp...https://www.jianshu.com/p/4989fb0d92cc

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值