一、组件化
作为一个单工程撸到底的开发人员,想试着将项目进行组件化改造,说动就动。毕竟技术都是写出来的,看看感觉懂了,但是实际开发中还是能遇到各种各样的问题,开始搞起来。
1.1 为什么使用组件化
一直使用单工程操作,项目越来越大导致出现了不少的问题:
- 查找问题慢:定位问题,需要在多个代码混合的模块中寻找和跳转。
- 开发维护成本增加:避免代码的改动影响其它业务的功能,导致开发和维护成本不断增加。
- 编译时间长:项目工程越大,编译完整代码所花费的时间越长。
- 开发效率低:多人协作开发时,开发风格不一,又很难将业务完全分割,大家互相影响,导致开发效率低下。
- 代码复用性差:写过的代码很难抽离出来再次利用。
1.2 模块化与组件化
1.2.1 模块
将一个程序按照其功能做拆分,分成相互独立的模块,以便于每个模块只包含与其功能相关的内容,比如登录模块、首页模块等等。
1.2.2 组件
组件指的是单一的功能组件,如登录组件、视频组件、支付组件 等,每个组件都可以以一个单独的 module 开发,并且可以单独抽出来作为 SDK 对外发布使用。可以说往往一个模块包含了一个或多个组件。
1.3 组件化的优势
组件化基于可重用的目的,将应用拆分成多个独立组件,以减少耦合:
- 加快编译速度:每个业务功能都是一个单独的工程,可独立编译运行,拆分后代码量较少,编译自然变快。
- 解耦:通过关注点分离的形式,将App分离成多个模块,每个模块都是一个组件。
- 提高开发效率:多人开发中,每个组件模块由单人负责,降低了开发之间沟通的成本,减少因代码风格不一而产生的相互影响。
- 代码复用:类似我们引用的第三方库,可以将基础组件或功能组件剥离。在新项目微调或直接使用。
1.4 组件化需要解决的问题
- 组件分层:怎么将一个项目分成多个组件、组件间的依赖关系是怎么样的?
- 组件单独运行和集成调试:组件是如何独立运行和集成调试的?
- 组件间通信:主项目与组件、组件与组件之间如何通信就变成关键?
二、组件分层
组件依赖关系是上层依赖下层,修改频率是上层高于下层。先上一张图:
2.1 基础组件
基础公共模块,最底层的库:
- 封装公用的基础组件;
- 网络访问框架、图片加载框架等主流的第三方库;
- 各种第三方SDK。
2.2 common组件(lib_common)
- 支撑业务组件、功能组件的基础(BaseActivity/BaseFragment等基础能力;
- 依赖基础组件层;
- 业务组件、功能组件所需的基础能力只需要依赖common组件即可获得。
2.3 功能组件
- 依赖基础组件层;
- 对一些公用的功能业务进行封装与实现;
- 业务组件可以在library和application之间切换,但是最后打包时必须是library ;
2.4 业务组件
- 可直接依赖基础组件层;同时也能依赖公用的一些功能组件;
- 各组件之间不存在依赖关系,通过路由进行通信;
- 业务组件可以在library和application之间切换,但是最后打包时必须是library ;
2.5 主工程(app)
- 只依赖各业务组件;
- 除了一些全局的配置和主Activity之外,不包含任何业务代码,是应用的入口;
2.6 完成后项目
这只是个大概,并不是说必须这样,可以按照自己的方式来。比如:你觉得基础组件比较多导致project里面的项目太多,那么你可以创建一个lib_base,然在lib_base里面再创建其他基础组件即可。
三、组件单独调试
3.1 创建组件(收藏)
- library和application之间切换:选择第一项。
- 始终是library:选择第二项
这样尽可能的减少变动项,当然这仅仅是个建议,看个人习惯吧。
因为咱们创建的是一个module,所以在AndridManifest中添加android:exported="true"属性可直接构建一个APK。下面咱们看看如何生成不同的工程类型。
3.2 动态配置组件的工程类型
在 AndroidStudio 开发 Android 项目时,使用的是 Gradle 来构建,具体来说使用的是 Android Gradle 插件来构建,Android Gradle 中提供了三种插件,在开发中可以通过配置不同的插件来构建不同的工程。
3.2.1 build.gradle(module)
//构建后输出一个 APK 安装包 apply plugin: 'com.android.application' //构建后输出 ARR 包 apply plugin: 'com.android.library' //配置一个 Android Test 工程 apply plugin: 'com.android.test'
独立调试:设置为 Application 插件。
集成调试:设置为 Library 插件。
3.2.2 设置gradle.properties
isDebug = true 独立调试
3.2.3 动态配制插件(build.gradle)
//注意gradle.properties中的数据类型都是String类型,使用其他数据类型需要自行转换 if(isDebug.toBoolean()){ //构建后输出一个 APK 安装包 apply plugin: 'com.android.application' }else{ //构建后输出 ARR 包 apply plugin: 'com.android.library' }
3.3 动态配置组件的 ApplicationId 和 AndroidManifest 文件
- 一个 APP 是只有一个 ApplicationId ,所以在单独调试和集成调试组件的 ApplicationId 应该是不同的。
- 单独调试时也是需要有一个启动页,当集成调试时主工程和组件的AndroidManifest文件合并会产生多个启动页。
根据上面动态配制插件的经验,我们也需要在build.gradle中动态配制ApplicationId 和 AndroidManifest 文件。
3.3.1 准备两个不同路径的 AndroidManifest 文件
有什么不同?咱们一起看看具体内容。
3.3.2 src/main/debug/AndroidManifest
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.scc.module.collect"> <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.SccMall"> <activity android:name=".CollectActivity" android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
3.3.3 src/main/debug/AndroidManifest
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.scc.module.collect"> <application android:allowBackup="true" android:supportsRtl="true" > <activity android:name=".CollectActivity"/> </application> </manifest>
3.3.4 动态配制(build.gradle)
defaultConfig { if(isDebug.toBoolean()){ //独立调试的时候才能设置applicationId applicationId "com.scc.module.collect" } } sourceSets { main { if (isDebug.toBoolean()) { //独立调试 manifest.srcFile 'src/main/debug/AndroidManifest.xml' } else { //集成调试 manifest.srcFile 'src/main/AndroidManifest.xml' } } }
3.4 实现效果
3.4.1 独立调试
isDebug = true
3.4.2 集成调试
isDebug = false
四、Gradle配置统一管理
4.1 config.gradle
当我们需要进行插件版本、依赖库版本升级时,项目多的话改起来很麻烦,这时就需要我们对Gradle配置统一管理。如下:
具体内容
ext{ //组件独立调试开关, 每次更改值后要同步工程 isDebug = true android = [ // 编译 SDK 版本 compileSdkVersion: 31, // 最低兼容 Android 版本 minSdkVersion : 21, // 最高兼容 Android 版本 targetSdkVersion : 31, // 当前版本编号 versionCode : 1, // 当前版本信息 versionName : "1.0.0" ] applicationid = [ app:"com.scc.sccmall", main:"com.scc.module.main", webview:"com.scc.module.webview", login:"com.scc.module.login", collect:"com.scc.module.collect" ] dependencies = [ "appcompat" :'androidx.appcompat:appcompat:1.2.0', "material" :'com.google.android.material:material:1.3.0', "constraintlayout" :'androidx.constraintlayout:constraintlayout:2.0.1', "livedata" :'androidx.lifecycle:lifecycle-livedata:2.4.0', "viewmodel" :'androidx.lifecycle:lifecycle-viewmodel:2.4.0', "legacyv4" :'androidx.legacy:legacy-support-v4:1.0.0', "splashscreen" :'androidx.core:core-splashscreen:1.0.0-alpha01' ] libARouter= 'com.alibaba:arouter-api:1.5.2' libARouterCompiler = 'com.alibaba:arouter-compiler:1.5.2' libGson = 'com.google.code.gson:gson:2.8.9' }
4.2 添加配制文件build.gradle(project)
apply from:"config.gradle"
4.3 其他组件使用
//build.gradle //注意gradle.properties中的数据类型都是String类型,使用其他数据类型需要自行转换 if(isDebug.toBoolean()){ //构建后输出一个 APK 安装包 apply plugin: 'com.android.application' }else{ //构建后输出 ARR 包 apply plugin: 'com.android.library' } android { compileSdkVersion 31 defaultConfig { if(isDebug.toBoolean()){ //独立调试的时候才能设置applicationId applicationId "com.scc.module.collect" } minSdkVersion 21 targetSdkVersion 31 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } sourceSets { main { if (isDebug.toBoolean()) { //独立调试 manifest.srcFile 'src/main/debug/AndroidManifest.xml' } else { //集成调试 manifest.srcFile 'src/main/AndroidManifest.xml' } } } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } } dependencies { // implementation root.dependencies.appcompat // implementation root.dependencies.material // implementation root.dependencies.constraintlayout // implementation root.dependencies.livedata // implementation root.dependencies.viewmodel // implementation root.dependencies.legacyv4 // implementation root.dependencies.splashscreen // implementation root.libARouter //上面内容在lib_common中已经添加咱们直接依赖lib_common implementation project(':lib_common') testImplementation 'junit:junit:4.+' androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' }