Android开发经验谈Android开发Android技术知识

给广大 Android 开发者的一份组件化架构指南:从理论到实战

2022-07-22  本文已影响0人  程序老秃子

为什么需要组件化?

什么是组件化?

所谓的组件化就是把需求拆成一个一个的小模块,最后组装需要的模块组成一个app

模块化架构

创建一个 Project 后可以创建多个 Module,这个 Module 就是所谓的模块。一个的例子,在写代码的时候我们会把每个模块拆开,每个 tab 所包含的内容就是一个模块,这样可以减少 module 的代码量,但是每个模块之间的肯定是有页面的跳转,数据传递等。

组件化架构

组件化带来的优势

加快编译速度:每个业务组件都可以单独运行调试,速度提升好几倍

功能重用:一次编码处处复用,再也不需要复制代码了。基础组件和业务基础组件,调用者可以根据文档就可以一键集成和使用。

提高协作效率:每个组件都有专人维护,只需要重点测试修改的组件即可。

组件化配置

1、全局配置

全局配置 :

isModuleMode 是最终要的配置 , 通过该配置的 true / false 设置当前是否开启组件化

集成模式 true ( 默认模式 , 模块化 )

组件模式 false ( 组件化 )

androidConfig 用于统一管理各个 Module 中的版本号 , 如编译版本号 , 最小版本号 , 目标版本号 ;applicationId 用于保存各个模块的包名 , 尤其是 module 依赖库的包名 , 组件化的状态下 , 该 module 需要独立运行 , 必须配置一个 applicationId 包名 。

dependencies 用于统一管理各个模块之间的依赖库 , 避免管理分散 ;

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="Java" cid="n151" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">// ext 是 extension 扩展的含义
// ext 后的 {} 花括号 , 是闭包 ,
ext{

 // 是否是模块化模式
 // 集成模式 true ( 默认模式 , 模块化 )
 // 组件模式 false ( 组件化 )
 isModuleMode = true

 // 定义 android 变量 , 类型是字典 Map 集合
 // 其中定义了若干键值对集合
 androidConfig = [
 compileSdkVersion : 30,
 minSdkVersion : 18,
 targetSdkVersion : 30,
 versionCode : 1,
 versionName : "1.0"
 ]

 applicationId = [
 "app" : "kim.hsl.component",
 "module1" : "kim.hsl.module1",
 "module2" : "kim.hsl.module2",
 ]

 // androidx 版本号
 androidxVersion = "1.3.0"
 constraintlayoutVersion = "2.0.4"
 materialVersion = "1.3.0"

 // 统一管理依赖库
 dependencies = [
 // ${} 表示引用之前定义的变量
 "appcompat" : "androidx.appcompat:appcompat:${androidxVersion}",
 "constraintlayout" : "androidx.constraintlayout:constraintlayout:${constraintlayoutVersion}",
 "material" : "com.google.android.material:material:${materialVersion}"
 ]
}</pre>

2、工程下的 build.gradle 配置

在总的 build.gradle 配置中 , 引入上述全局配置 , 其作用就相当于将上述全局配置原封不动拷贝过来 ;

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="Java" cid="n154" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">apply from: "component.gradle"</pre>

完整配置 :

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="Java" cid="n156" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">// Top-level build file where you can add configuration options common to all sub-projects/modules.
// 将 component.gradle 配置文件中的内容导入到该位置
// 相当于引入头文件
apply from: "component.gradle"

buildscript {
 repositories {
 google()
 jcenter()
 }
 dependencies {
 classpath "com.android.tools.build:gradle:4.1.0"

 // NOTE: Do not place your application dependencies here; they belong
 // in the individual module build.gradle files
 }
}

allprojects {
 repositories {
 google()
 jcenter()
 }
}

task clean(type: Delete) {
 delete rootProject.buildDir
}</pre>

3、app 模块下的 build.gradle 配置

在 app 模块中重点关注 , 在组件模式下 , 一定不能引入依赖库 , 否则会报错 , 因为组件模式下这两个依赖库是两个可运行的独立应用 ;

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="Java" cid="n159" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">dependencies {
 if (isModuleMode){
 // 集成模式下才能引用这两个 Library Module
 implementation project(path: ':module1')
 implementation project(path: ':module2')
 }
}</pre>

版本号 , applicationId , 依赖库 统一管理 :

从 Project 级别的配置中获取变量 :

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="Java" cid="n162" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">def androidConfig = rootProject.ext.androidConfig
def appId = rootProject.ext.applicationId
def dep = rootProject.ext.dependencies</pre>

版本号 和 applicationId 统一管理 :

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" cid="n164" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">android {
 compileSdkVersion androidConfig.compileSdkVersion

 defaultConfig {
 applicationId appId["app"]
 minSdkVersion androidConfig.minSdkVersion
 targetSdkVersion androidConfig.targetSdkVersion
 versionCode androidConfig.versionCode
 versionName androidConfig.versionName
 }</pre>

依赖库统一管理 :

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" cid="n166" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">dependencies {

 //implementation 'androidx.appcompat:appcompat:1.3.0'
 //implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
 //implementation 'com.google.android.material:material:1.3.0'

 implementation dep.appcompat
 implementation dep.constraintlayout
 implementation dep.material
}</pre>

完整配置 :

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" cid="n168" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">plugins {
 id 'com.android.application'
}

def androidConfig = rootProject.ext.androidConfig
def appId = rootProject.ext.applicationId
def dep = rootProject.ext.dependencies

android {
 compileSdkVersion androidConfig.compileSdkVersion
 buildToolsVersion "30.0.3"

 defaultConfig {
 applicationId appId["app"]
 minSdkVersion androidConfig.minSdkVersion
 targetSdkVersion androidConfig.targetSdkVersion
 versionCode androidConfig.versionCode
 versionName androidConfig.versionName

 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

 // ARoute 需要的配置
 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
 }
}

dependencies {

 //implementation 'androidx.appcompat:appcompat:1.3.0'
 //implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
 //implementation 'com.google.android.material:material:1.3.0'

 implementation dep.appcompat
 implementation dep.constraintlayout
 implementation dep.material

 testImplementation 'junit:junit:4.+'
 androidTestImplementation 'androidx.test.ext:junit:1.1.2'
 androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'

 // 替换成最新版本, 需要注意的是api
 // 要与compiler匹配使用,均使用最新版可以保证兼容
 api 'com.alibaba:arouter-api:1.5.1'
 annotationProcessor 'com.alibaba:arouter-compiler:1.5.1'

 if (isModuleMode){
 // 集成模式下才能引用这两个 Library Module
 implementation project(path: ':module1')
 implementation project(path: ':module2')
 }

}</pre>

组件化需解决的问题

业务组件间没有依赖,如何实现数据通信?

业务组件如何实现单独调试?

壳工程Application生命周期如何下发?

业务组件间没有依赖,如何实现页面跳转?

独立调试

单工程方案

所谓的单工程方案就是把所有组件都放到一个工程下

单工程利弊分析:

首先在 gradle.properties 文件内声明一个变量:

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" cid="n184" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">// gradle.properties
isModule = true</pre>

isModule 为 true 时表示组件可以作为 apk 运行起来,false 表示组件只能作为 library。我们根据需要改变这个值后同步下gradle即可。

然后在某个 module 的 build.gradle 文件内用这个变量做三个地方的判断:

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" cid="n187" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">// build.gradle

// 区分是应用还是库
if(isModule.toBoolean()) {
 apply plugin: 'com.android.application'
}else {
 apply plugin: 'com.android.library'
}

android {
 defaultConfig {
 // 如果是应用需要指定application
 if(isModule.toBoolean()) {
 applicationId "com.xxx.xxx"
 }
 }
 sourceSets {
 main {
 // 应用和库的AndroidManifest文件区分
 if(isModule.toBoolean()) {
 manifest.srcFile 'src/main/debug/AndroidManifest.xml'
 }else {
 manifest.srcFile 'src/main/AndroidManifest.xml' 
 }
 }
 }
}</pre>

由于library是不需要 Application 和启动Activity页,所以我们要区分这个文件,应用manifest指定的路径没有特定,随意找个路径创建即可。在应用AndroidManifest.xml里我们要设置启动页:

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" cid="n189" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"><manifest xmlns:android="http://schemas.android.com/apk/res/android"
 package="com.sun.biz_home">
 <application
 android:allowBackup="true"
 android:label="@string/home_app_name"
 android:supportsRtl="true"
 android:theme="@style/home_AppTheme">
 <activity android:name=".debug.HomeActivity">
 <intent-filter>
 <action android:name="android.intent.action.MAIN" />
 <category android:name="android.intent.category.LAUNCHER" />
 </intent-filter>
 </activity>
 </application>
</manifest></pre>

library 的 AndroidManifest.xml 不需要这些:

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" cid="n191" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"><manifest xmlns:android="http://schemas.android.com/apk/res/android"
 package="com.sun.biz_home">
</manifest></pre>

gradle 依赖 module 的方式主要有两种:

一般来说我们只需要使用 implementation 即可,api 是会造成项目编译时间变长,而且会引入该模块不需要的功能,代码之间耦合变得严重了。不过 module_common 是统一了基础组件版本的公共库,所有组件都应需要依赖它并拥有基础组件的能力,所以基本每个业务组件和业务基础组件都应该依赖公共库:

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="" cid="n199" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">dependencies {
 implementation project(':module_common')
}</pre>

而 common 组件依赖基础组件应该是用 api,因为把基础组件的能力传递给上层业务组件:

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="" cid="n201" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">dependencies {
 api project(':module_base')
 api project(':module_util')
}</pre>

多工程方案

多工程就是每个组件都是一个工程,例如创建一个工程后 app 作为壳组件,它依赖 biz_home 运行,因此不需要 isModule 来控制独立调试,它本身就是一个工程可以独立调试。

多工程的利弊就是和单工程相反的:

多工程组件依赖需要用到maven仓库。把每个组件的aar上传到公司内网的maven仓库,然后像这样去依赖:

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="" cid="n211" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">implementation 'com.xxx.xxx:module_common:1.0.0'
</pre>

我们把三方库统一放到 config.gradle 内管理:

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" cid="n213" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">ext {
 dependencies = [
 "glide": "com.github.bumptech.glide:glide:4.12.0",
 "glide-compiler": "com.github.bumptech.glide:compiler:4.12.0",
 "okhttp3": "com.squareup.okhttp3:okhttp:4.9.0",
 "retrofit": "com.squareup.retrofit2:retrofit:2.9.0",
 "retrofit-converter-gson"  : "com.squareup.retrofit2:converter-gson:2.9.0",
 "retrofit-adapter-rxjava2" : "com.squareup.retrofit2:adapter-rxjava2:2.9.0",
 "rxjava2": "io.reactivex.rxjava2:rxjava:2.2.21",
 "arouter": "com.alibaba:arouter-api:1.5.1",
 "arouter-compiler": "com.alibaba:arouter-compiler:1.5.1",
 // our lib
 "module_util": "com.sun.module:module_util:1.0.0",
 "module_common": "com.sun.module:module_common:1.0.0",
 "module_base": "com.sun.module:module_base:1.0.0",
 "fun_splash": "com.sun.fun:fun_splash:1.0.0",
 "fun_share": "com.sun.fun:fun_share:1.0.0",
 "export_biz_home": "com.sun.export:export_biz_home:1.0.0",
 "export_biz_me": "com.sun.export:export_biz_me:1.0.0",
 "export_biz_msg": "com.sun.export:export_biz_msg:1.0.0",
 "biz_home": "com.sun.biz:biz_home:1.0.0",
 "biz_me": "com.sun.biz:biz_me:1.0.0",
 "biz_msg": "com.sun.biz:biz_msg:1.0.0"
 ]
}</pre>

这样方便版本统一管理, 然后在根目录的 build.gradle 内导入:

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" cid="n215" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">apply from: 'config.gradle'</pre>

最后在各自的模块引入依赖,比如在 module_common 中这么引入依赖即可。

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" cid="n217" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">dependencies {
 api rootProject.ext.dependencies["arouter"]
 kapt rootProject.ext.dependencies["arouter-compiler"]
 api rootProject.ext.dependencies["glide"]
 api rootProject.ext.dependencies["okhttp3"]
 api rootProject.ext.dependencies["retrofit"]
 api rootProject.ext.dependencies["retrofit-converter-gson"]
 api rootProject.ext.dependencies["retrofit-adapter-rxjava2"]
 api rootProject.ext.dependencies["rxjava2"]
 api rootProject.ext.dependencies["module_util"]
 api rootProject.ext.dependencies["module_base"]
}</pre>

页面跳转

做完组件之间的隔离后,暴露出来最明显的问题就是页面跳转和数据通信的问题。一般来说,页面跳转都是显示startActivity跳转,在组件化项目内就不适用了,隐式跳转可以用,但每个Activity都要写 intent-filter 就显得有点麻烦,所以最好的方式还是用路由框架。

实际上市面已经有比较成熟的路由框架专门就是为了组件化而生的,比如美团的WMRouter,阿里的ARouter等,本例使用 ARouter 框架,看下ARouter页面跳转的基本操作。

首先肯定是引入依赖,以 module_common 引入ARouter举例,build.gradle 应该添加:

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" cid="n222" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">android {
 defaultConfig {
 javaCompileOptions {
 annotationProcessorOptions {
 arguments = [AROUTER_MODULE_NAME: project.getName()]
 }
 }
 }
 compileOptions {
 sourceCompatibility JavaVersion.VERSION_1_8
 targetCompatibility JavaVersion.VERSION_1_8
 }
}
dependencies {
 api rootProject.ext.dependencies["arouter"]
 kapt rootProject.ext.dependencies["arouter-compiler"]
}</pre>

kapt注解依赖没有办法传递,所以我们不可避免得需要在每个模块都声明这些配置,除了 api rootProject.ext.dependencies["arouter"] 这行。然后需要全局注册 ARouter,我是在 module_common 统一注册的。

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" cid="n224" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">class AppCommon: BaseApp{
 override fun onCreate(application: Application) {
 MLog.d(TAG, "BaseApp AppCommon init")
 initARouter(application)
 }

 private fun initARouter(application: Application) {
 if(BuildConfig.DEBUG) {
 ARouter.openLog()
 ARouter.openDebug()
 }
 ARouter.init(application)
 }
}</pre>

接着我们在 module_common 模块内声明一个路由表用作统一管理路径。

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" cid="n226" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">// RouterPath.kt
class RouterPath {
 companion object {
 const val APP_MAIN = "/app/MainActivity"
 const val HOME_FRAGMENT = "/home/HomeFragment"
 const val MSG_FRAGMENT = "/msg/MsgFragment"
 const val ME_FRAGMENT = "/me/MeFragment"
 const val MSG_PROVIDER = "/msg/MsgProviderImpl"
 }
}</pre>

然后在MainActivity类文件上进行注解:

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" cid="n228" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">@Route(path = RouterPath.APP_MAIN)
class MainActivity : AppCompatActivity() {
}</pre>

任意模块只需要调用 ARouter.getInstance().build(RouterPath.APP_MAIN).navigation() 即可实现跳转。如果我们要加上数据传递也很方便:

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" cid="n230" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">ARouter.getInstance().build(RouterPath.APP_MAIN)
 .withString("key", "value")
 .withObject("key1", obj)
 .navigation()
</pre>

然后在MainActivity使用依赖注入接受数据:

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" cid="n232" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">class MainActivity : AppCompatActivity() {
 @Autowired
 String key = ""
}</pre>

Arouter方案

在 export_biz_msg 组件下声明 IMsgProvider,此接口必须实现 IProvider 接口:

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" cid="n235" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">interface IMsgProvider: IProvider {
 fun onCountFromHome(count: Int = 1)
}
</pre>

然后在 biz_msg 组件里实现这个接口:

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" cid="n237" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">@Route(path = RouterPath.MSG_PROVIDER)
class MsgProviderImpl: IMsgProvider {
 override fun onCountFromHome(count: Int) {
 // 这里只是对数据进行分发,有监听计数的对象会收到
 MsgCount.instance.addCount(count)
 }
 override fun init(context: Context?) {
 // 对象被初始化时调用
 }
}
</pre>

在 biz_home 首页组件中发送计数:

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" cid="n239" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">val provider = ARouter.getInstance().build(RouterPath.MSG_PROVIDER).navigation() as IMsgProvider
provider.onCountFromHome(count)</pre>

可以看到其实和页面跳转的方式基本雷同,包括获取 Fragment 实例的方式也是这种。ARouter把所有通信的方式都用一种api实现,让使用者上手非常容易。

Application生命周期分发

当 app 壳工程启动Application初始化时要通知到其他组件初始化一些功能。这里提供一个简单的方式。

首先我们在 module_common 公共库内声明一个接口 BaseApp:

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" cid="n244" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">interface BaseApp {
 fun onCreate(application: Application)
}</pre>

然后每个组件都要创建一个 App 类实现此接口,比如biz_home组件:

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" cid="n246" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">class HomeApp: BaseApp {
 override fun onCreate(application: Application) {
 // 初始化都放在这里
 MLog.d(TAG, "BaseApp HomeApp init")
 }
}</pre>

剩下最后一步就是从 app 壳工程分发 application 的生命周期了,这里用到反射技术:

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" cid="n248" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">val moduleInitArr = arrayOf(
 "com.sun.module_common.AppCommon",
 "com.sun.biz_home.HomeApp",
 "com.sun.biz_msg.MsgApp",
 "com.sun.biz_me.MeApp"
)
class App: Application() {
 override fun onCreate() {
 super.onCreate()
 initModuleApp(this)
 }
 private fun initModuleApp(application: Application) {
 try {
 for(appName in moduleInitArr) {
 val clazz = Class.forName(appName)
 val module = clazz.getConstructor().newInstance() as BaseApp
 module.onCreate(application)
 }
 }catch (e: Exception) {
 e.printStackTrace()
 }
 }
}</pre>

总结

以上就是今天的所有内容,相信大家看完之后都会有着自己的收获,但是盲目的学习并不能让你的技术稳定的增长

我自荐一套 《完整的Android学习资料,以及一些视频课讲解 现在点击此链接即可免费获取

最后我想说:

对于程序员来说,要学习的知识内容、技术有太多太多,要想不被环境淘汰就只有不断提升自己,从来都是我们去适应环境,而不是环境来适应我们

技术是无止境的,你需要对自己提交的每一行代码、使用的每一个工具负责,不断挖掘其底层原理,才能使自己的技术升华到更高的层面

Android 架构师之路还很漫长,与君共勉

上一篇下一篇

猜你喜欢

热点阅读