Android模块化开发和组件化开发

简介

单工程项目

  1. 单工程项目结构

image-20240508162916729

  1. 单工程项目的缺陷

随着应用的不断迭代更新,业务代码持续增加,随之而来的问题逐步呈现出来:

  • 各种业务代码混杂在同一个模块中,开发人员在开发、调试过程中的效率越来越低,比如定位某个业务问题,需要在多个业务代码混合的模块中寻找和跳转。
  • 工程师需要了解各个业务的功能,避免代码的改动影响其他业务的功能,导致开发和维护成本不断增加。
  • 由于功能工程越来越庞大,编译完整代码所花费的时间越来越长。
  • 多人协同开发时,开发风格不一,又难以将业务完全分割,大家互相影响,导致开发效率低下。
  • 代码复用性差,写过的代码又很难抽离出来再次使用。

模块化和组件化

  1. 什么是组件化
  • 组件(Component),对数据和方法的简单封装,功能单一,高内聚,并且是业务能划分的最小粒度。
  • 组件化是基于可重用的目的,将大型的软件系统按照分离关注点的形式,拆分成多个独立的组件,使得整个软件系统也能做到电路板一样,是单个或多个组件元件组装起来,哪个组件坏了,整个系统可继续运行,而不出现崩溃或不正常现象,做到更少的耦合和更高的内聚。
  1. 为什么需要使用组件化

组件化基于可重用的目的,将应用拆分成多个独立组件,以减少耦合,当然模块化也是可以解决缺陷的(模块化的概念可以看下面):

  • 通过关注分离的形式,将APP分离成多个模块,每个模块都是一个组件。解决了各种业务代码耦合在一起的导致的问题
  • 开发的过程中,让这些组件被其他组件依赖,但是在调试时也可以单独成为独立的工程并且运行,这样就解决了因为编译时过多降低开发效率的问题
  • 多人开发中,每个组件模块由单人负责,降低了开发之间沟通的成本,减少因代码风格不一致而产生的相互影响
  1. 模块化开发和组件化开发区别

image-20240508164106596

  1. 组件化要考虑的问题

在进行安卓应用程序的组件化设计时,有几个重要问题需要考虑:

  • 模块划分:

    需要合理划分应用程序的功能模块,确保每个模块具有清晰的职责和功能,避免功能重叠或耦合度过高的情况。

  • 组件间通信:

    设计有效的组件间通信机制,确保不同组件之间能够进行数据传递、事件触发等操作,常见的通信方式包括使用接口、广播、事件总线等。

  • 依赖管理:

    管理组件间的依赖关系,确保各个组件能够正确地引用和使用依赖的库、模块或服务,避免依赖冲突和版本不一致的问题。

  • 路由和导航:

    设计灵活的路由和导航方案,确保不同组件之间能够进行页面跳转和交互,包括页面路由、参数传递、页面堆栈管理等。

  • 组件生命周期管理:

    确保各个组件的生命周期能够正确地管理和调度,避免出现内存泄漏或资源泄漏等问题,尤其需要注意在组件销毁时释放资源。

  • UI组件的复用:

    考虑如何设计可复用的UI组件,以提高UI的一致性和开发效率,可以使用自定义View、Fragment等方式来实现UI组件的封装和复用。

  • 构建和打包优化:

    优化构建和打包过程,减少应用程序的体积和启动时间,包括资源的合理管理、代码的优化和混淆等方面。

  • 模块化团队协作:

    建立有效的团队协作机制和沟通渠道,确保各个团队能够协调一致地进行组件的开发和集成,可以采用版本控制、持续集成等方式来实现团队协作。

下面是大牛整理出来的组件分层思想(每个人都可以有自己的分层思想)

image-20240508165826150

(中间的功能组件层不一定要单独存在,可以之间在业务组件层实现)

组件化和插件化

  1. 组件化和插件化区别
  • 插件化也是基于模块化的思想,将应用拆分成多个模块,而这些模块都是一个APK,最终打包时将宿主APK和插件APK分开打包。在程序运行时,宿主APK可以动态地选择并加载想要的插件APK
  • 插件化与组件化存在很多相似之处,但是它们的根本区别在于:
    • 组件化的模块虽然在调试的时候可以切换到application独立运行,但是在最终打包时,每个模块始终都只是一个library,整个应用只有一个单独的APK。
    • 插件化只是拆分出多个APK,并且在运行时通过动态加载的技术方案,来加载这些插件APK。

组件化项目搭建

搭建各层

业务组件层创建,选择如图的模块样版,这样可以自动生成可单独运行调试的Application

image-20240508171510991

image-20240508171605942

基础组件层的创建,如果组件不需要进行application和library的切换就直接选择library

image-20240508172926684

image-20240508172901338

最后新建一个功能组件层,这一层不是必需的,这里还是创建出来

image-20240508173106489

对依赖进行统一管理

项目会依赖许多的第三方库,如果不对其进行管理,可能会出现依赖冲突的问题

旧版本的gradle在项目的build.gradle添加ext块进行管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
//这里是groovy语言,在项目的build.gradle添加ext块进行管理
ext {

//是否是debug模式,是模块就打包成application,不是就打包成library
isDebug = false

//项目版本和sdk版本等管理
//按照语法这其实就是数组
android = [
compileSdkVersion: 34,
minSdkVersion : 24,
targetSdkVersion : 34,
buildToolsVersion: "30.0.2",
versionCode : 1,
versionName : "1.0"
]

//使用编译jdk版本
compileOptions = [
sourceCompatibility: JavaVersion.VERSION_1_8,
targetCompatibility: JavaVersion.VERSION_1_8,
]

//如果模块打包成application就需要设置applicationId,这里配置项目中所有模块的applicationId
applicationId = [
"app" : "com.wuleizhenshang.componentstudyproject",
"main": "com.wuleizhenshang.mod_main"
]

//统一管理lib和arr的版本
versions = [
"appcompat" : "1.6.1",
"material" : "1.8.0",
"constraintlayout": "2.1.4",

"ARouter" : "1.5.2",
"ARouterCompiler" : "1.5.2",

"Gson" : "2.10.1"
]

//统一管理具体的lib
dependencies = [
"appcompat" : "androidx.appcompat:appcompat:$versions.appcompat",
"material" : "com.google.android.material:material:$versions.material",
"constraintlayout": "androidx.constraintlayout:constraintlayout:$versions.constraintlayout",

ARouter : "com.alibaba:arouter-api:$versions.ARouter",
ARouterCompiler : "com.alibaba:arouter-compiler:$versions.ARouterCompiler",

Gson : "com.google.code.gson:gson:$versions.Gson"
]
}

gradle7.4使用buildSrc统一管理依赖_buildsrc 目录-CSDN博客

gradle项目依赖统一管理常用配置_gradle 统一管理依赖的version-CSDN博客

Groovy语法介绍-CSDN博客

其他的mod可以如下使用即可

image-20240508213058904

image-20240508213459882

在base组件下依赖即可,在lib_base下使用api进行依赖,具有传递性,使用implementation是不具有传递性的,android部分一些配置可以直接复制,记住lib不需要切换application,就不需要进行配置了,其他配置同理,这里就不进行过多粘贴,可以下载demo直接看,最后就是app module的配置

切换application和library的module的AndroidManifest的配置

image-20240508215003759

新建debug文件夹后将原本的AndroidManifest复制到下面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" >

<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.ComponentStudyProject" >
<activity
android:name=".MainActivity"
android:exported="true" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

</manifest>

将原本的修改为如下

1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<application>
<activity android:name=".MainActivity" />
</application>

</manifest>

之后修改选择

image-20240508215657216

1
2
3
4
5
6
7
8
9
10
//修改AndroidManifest的选择
sourceSets{
main{
if (config.isDebug){
manifest.srcFile 'src/main/debug/AndroidManifest.xml'
}else {
manifest.srcFile 'src/main/AndroidManifest.xml'
}
}
}

ARouter的使用

具体使用可以看下面(一定要注意path的书写规则)

ARouter/README_CN.md at master · alibaba/ARouter (github.com)

一些需要注意的坑

注意在application进行初始化

导入依赖的时候在base使用api进行依赖导入,依赖注解器的导入是在需要使用到的module再进行导入,而不是在base导入依赖注解器就可以了,也就是annotationProcessor 'com.alibaba:arouter-compiler:x.x.x'

在gradle.properties下添加

1
2
# 启用Jetifier将support库转换成andoridX库
android.enableJetifier=true

ARouter实现功能组件之间的Activity跳转 - 不喜不悲1116 - 博客园 (cnblogs.com)

空指针异常看是不是写错路由了。。。(也要注意ARouter的path的书写规则)

项目使用

ComponentStudyProject.zip - 蓝奏云 (lanzout.com)