Android(安卓)组件化
1.概述
早期用 Eclipse 进行 Android 开发,创建一个工程,对于引入开源框架时候,采用的是用库的形式进行引入;到后来有 Android Studio 的出现,在 Project 下可以存在多个 module,除了要运行的 module 是 application 外,其他 module 都是 library。在每个 module 的 build.gradle 文件中区分:
应用:
apply plugin: 'com.android.application'
库:
apply plugin: 'com.android.library'
2. 模块化和组件化
2.1 模块化
把常用的功能、控件、基础类、第三方库、权限等公共部分抽离封装,把业务拆分成N个模块进行独立(module)的管理;而所有的业务组件都依赖于封装的基础库,业务组件之间不做依赖,这样的目的是为了让每个业务模块能单独运行。模块化的特点是:模块之间解耦,可以独立管理。
如下项目采用模块化,只有一个模块是 application,其他的都是 libary:
优缺点:
如上图所示,将自定义的软键盘、自定义控件等分为不同的模块,彼此之间降低耦合;而且模块可以复用,甚至不同的项目都可以复用。随着业务越来越多,模块也越来越多,当模块原来越多,每次进行编译都要长时间的等待。
2.2 组件化
将一个app的代码拆分成几份独立的组件,组件之间是低耦合的,可以独立编译打包;也可以将组件打包到一个apk中。组件化的核心是角色的转换。 在打包时, 是library; 在调试时, 是application。组件化后的优点:每个模块可独立编译,提高了编译速度;开发只负责自己的模块,还可以再做的隔离一些,每个业务线只可见自己业务模块的代码,避免了误修改和版本管理问题;公共的Lib依然是个独立模块。
组件化与模块化的区别:是每个模块的角色的转换,一个组件可以独立编译打包,也可以作为lib集成到整个apk中。
3.组件化的实践
最近瑞幸咖啡正处于风口浪尖,就以瑞幸咖啡的APP为模板:
分为五个模块:首页、菜单、潮品、购物车、我的。
3.1 创建模块,并实现组件模式和集成模式的切换
创建五个module,但是五个 module 都是application,可以独立运行,这里对 application 模块名进行统一格式进行命名。对于application 模块以 module_X 的格式进行命名,而对于 library 模块以 lib_X 的格式进行命名。
创建了三个不同的module,如何做到在调试阶段,每个module都能独立运行,在发布时候集成编译成一个APP呢?Android Studio中的Module主要有两种属性,分别为:application属性和library属性;在项目的根目录下的文件 gradle.properties 中定义相关常量,所有的 module 都能读取该常量。用这种办法来切换module为应用还是库。
gradle.properties 文件中定义五个参数用来控制五个不同的模块是否是独立编译:
# isBuildModuleXX 用于控制是否单独编译,true:单独编译,false:library形式#首页模块isBuildModuleHome=true#菜单模块isBuildModuleMenu=true#潮品模块isBuildModuleFashions=true#购物车模块isBuildModuleShopping=true#我的模块isBuildModuleUser=true
在五个 module 的 build.gradle 中读取 gradle.properties中的常量,决定 module 为应用还是库:
if (isBuildModuleHome.toBoolean()) { apply plugin: 'com.android.application'} else { apply plugin: 'com.android.library'}
3.2 不同的模块,确保 SDK 和引入库版本号一致
不同模块的依赖 SDK 版本不一致或者引入的库版本不一致会导致编译问题和兼容性问题。解决办法是在主项目最外层用一个文件来统一管理引入库和 SDK 的版本。
在工程的根目录创建文件 dependency.gradle:
ext { minSdkVersion = 16 targetSdkVersion = 29 compileSdkVersion = 29 buildToolsVersion = "29.0.3" versionCode = 1 versionName = "1.0" versions = [ ... ... ] dependencies = [ ... ... ]}
在工程的 build.gradle 中引入文件 dependency.gradle:
apply from: "dependency.gradle"
在模块中的 build.gradle 实现对文件 dependency.gradle 的使用:
android { compileSdkVersion rootProject.ext.compileSdkVersion buildToolsVersion rootProject.ext.buildToolsVersion defaultConfig { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion versionCode rootProject.ext.versionCode versionName rootProject.ext.versionName testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles 'consumer-rules.pro' } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } }}
3.3 AndroidManifest 管理问题
在创建模块的时候,是以 application 的形式进行创建的,所以每个模块都有程序的启动入口。但是一旦整合起来后,如果继续保持多个启动入口,明显是有问题的。
在 main 目录下新增文件夹 compound,用于模块以 library 形式存在时候提供 AndroidManifest.xml ;而Android studio 产生的原路径下的 AndroidManifest.xml 文件保留,用于存储单独编译时候的 AndroidManifest.xml 文件。
在 library 状态下的 AndroidManifest.xml 文件:
<?xml version="1.0" encoding="utf-8"?>
单独编译状态下的 AndroidManifest.xml 文件:
<?xml version="1.0" encoding="utf-8"?>
不同状态下定义好了不同的 AndroidManifest.xml 文件了,而 AndroidStudio 该如何识别呢?
在组件的 build.gradle 文件的 android 中进行 manifest 的管理:
android { ... ... sourceSets { main { if (isBuildModuleHome.toBoolean()) { manifest.srcFile 'src/main/AndroidManifest.xml' }else{ manifest.srcFile 'src/main/compound/AndroidManifest.xml' } } }}
3.4 基础公共组件
以 library 形式创建模块 lib_base 和 lib_common,其中 lib_base用于第三方库的统一依赖,lib_common 用于封装公共部分,如对于网络请求等的二次封装等。
lib_base 的 build.gradle文件如下所示:
apply plugin: 'com.android.library'apply plugin: 'kotlin-android'apply plugin: 'kotlin-android-extensions'def root_dependencies = rootProject.ext.dependenciesandroid { compileSdkVersion rootProject.ext.compileSdkVersion buildToolsVersion rootProject.ext.buildToolsVersion defaultConfig { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion versionCode rootProject.ext.versionCode versionName rootProject.ext.versionName testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles 'consumer-rules.pro' } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } }}dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) api root_dependencies.kotlin api root_dependencies.appcompat api root_dependencies.androidx_core api root_dependencies.glide annotationProcessor root_dependencies.glide_compiler api root_dependencies.retrofit api root_dependencies.converter_gson api root_dependencies.retrofit_adapter_rxjava api root_dependencies.rxjava api root_dependencies.constraintlayout api root_dependencies.okhttp api root_dependencies.eventbus api root_dependencies.easypermissions api root_dependencies.coroutines_core}
其中对于依赖的第三方库版本的定义在工程根目录下的文件 dependency.gradle 中:
ext { minSdkVersion = 16 targetSdkVersion = 29 compileSdkVersion = 29 buildToolsVersion = "29.0.3" versionCode = 1 versionName = "1.0" versions = [ appcompat : "1.1.0", androidx_core : "1.2.0", kotlin : "1.3.70", glide : "4.11.0", glide_compiler : "4.11.0", retrofit : "2.7.2", converter_gson : "2.7.2", retrofit_adapter_rxjava: "2.7.2", rxjava : "3.0.0", constraintlayout : "1.1.3", okhttp : "4.0.0", eventbus : "3.2.0", easypermissions : "3.0.0", coroutines_core : "1.3.5" ] dependencies = [ kotlin : "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$versions.kotlin", androidx_core : "androidx.core:core-ktx:$versions.androidx_core", appcompat : "androidx.appcompat:appcompat:$versions.appcompat", glide : "com.github.bumptech.glide:glide:$versions.glide", glide_compiler : "com.github.bumptech.glide:compiler:$versions.glide_compiler", retrofit : "com.squareup.retrofit2:retrofit:$versions.retrofit", converter_gson : "com.squareup.retrofit2:converter-gson:$versions.converter_gson", retrofit_adapter_rxjava: "com.squareup.retrofit2:adapter-rxjava2:$versions.retrofit_adapter_rxjava", rxjava : "io.reactivex.rxjava3:rxjava:$versions.rxjava", constraintlayout : "androidx.constraintlayout:constraintlayout:$versions.constraintlayout", okhttp : "com.squareup.okhttp3:okhttp:$versions.okhttp", eventbus : "org.greenrobot:eventbus:$versions.eventbus", easypermissions : "pub.devrel:easypermissions:$versions.easypermissions", coroutines_core : "org.jetbrains.kotlinx:kotlinx-coroutines-core:$versions.coroutines_core" ]}
在 lib_commom 中定义对 lib_base的依赖,build.gradle 文件如下:
......dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) api project(path: ':lib_base')}
业务模块实现对 lib_commom 的依赖:
dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation project(':lib_common')}
3.5 使用 ARouter 进行组件之间的调用和通信
由于业务组件间不存在依赖关系,不可以通过 Intent 进行跳转。组件化的目的是为了解决模块间的强依赖问题,这时候就需要借助于路由的,我使用的是阿里巴巴的开源框架 ARouter。
ARouter 的地址:https://github.com/alibaba/ARouter
3.5.1 ARouter 的导入
根据官方的文档,需要添加的依赖有:
dependencies { // Replace with the latest version compile 'com.alibaba:arouter-api:?' annotationProcessor 'com.alibaba:arouter-compiler:?' ...}
在这里,我将 arouter-api 添加在 lib_base 中,arouter-compiler 必须添加在每个组件模块中。由于此项目采用的语言为 kotlin,配置如下:
需要在每个组件模块中添加:
android { ... ... defaultConfig { ... ... kapt { arguments { arg("AROUTER_MODULE_NAME", project.getName()) } } } ... ...}dependencies { ... ... kapt root_dependencies.arouter_compiler ... ...}
而且还需要添加:
apply plugin: 'kotlin-kapt'
3.5.2 页面间的简单跳转
在目标页面 Activity/Fragment 类上写上 Route Path 的注解,这里的路径需要注意的是至少需要有两级,/xx/xx。
@Route(path = "/home/HomeActivity")class HomeActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_home) } override fun onDestroy() { super.onDestroy() }}
在需要进行跳转的页面进行触发跳转:
fun click(v: View) { when (v.id) { R.id.btn_home -> { ARouter.getInstance().build("/home/HomeActivity").navigation() } R.id.btn_menu -> { ARouter.getInstance().build("/menu/MenuActivity").navigation() } R.id.btn_fashions -> { ARouter.getInstance().build("/fashions/FashionsActivity").navigation() } R.id.btn_shopping -> { ARouter.getInstance().build("/shopping/ShoppingActivity").navigation() } R.id.btn_user -> { ARouter.getInstance().build("/user/UserActivity").navigation() } } }
效果如下:
这里只是列举了页面的简单跳转,如果需要在跳转的时候携带参数等,可以参照 ARouter 的介绍:https://github.com/alibaba/ARouter
4.总结
此篇文章只是针对组件化进行简单的实现,其中还有很多问题没有描述具体的解决办法,比如如何在每个模块获取登录信息、如何避免资源名字冲突等问题。
更多相关文章
- React Native开发指南
- Android对ListView控件增删改查
- Android(安卓)开源组件PagerBottomTabStrip 快速构建底部导航栏
- Android(安卓)帧动画 的实现
- 在Android中借助TensorFlow使用机器学习(译)
- android MediaCodec ACodec OMX tips
- Android(安卓)aar与 jar
- 将第三方apk变成系统apk
- Master OpenCV with Pratical Computer Vision Projects——如何