Android组件化
今日核心目标
- 掌握组件化核心概念、架构优势及基础拆分原则,完成简单的组件化项目初始化(单工程多模块搭建),规避组件化入门常见踩坑点,建立架构思维。
- 掌握组件间3种基础通信方式(Intent、接口、热流),完成user组件与壳工程、组件与组件间的简单通信实战,强化“高内聚、低耦合”的架构思维,提升组件化实操能力。
组件化入门
围绕以下几点
- 组件化概念
- 组件化优势
- 组件拆分原则
- 组件化职场案例
- 组件化踩坑点规避
- 组件化工程搭建
组件化概念
组件化
将一个完整的App,拆分成多个可复用、可独立编译运行的组件(例:首页组件、搜索组件、个人中心组件)。
- 每个组件可独立开发、测试、迭代,最终通过组件融合,组装成完整App
- 核心:独立可复用
与模块化区别
- 模块化侧重功能拆分(例:网络模块、数据库模块)。模块不可独立运行,依赖主工程。
- 组件化侧重业务拆分(例:用户业务组件、订单业务组件)。组件可独立运行、独立部署,是模块化的进阶版。
- 实际开发中组件化更适合大型项目的开发。
组件化优势
- 提升开发效率:多团队并行开发,互不干扰,减少代码冲突,缩短开发周期。
- 降低维护成本:组件独立,某一组件迭代、修复bug时,不影响其他组件,后期维护更高效,减少“牵一发而动全身”的问题。
- 提升代码复用性:公共组件可在多个项目中复用,核心业务组件可在不同App中复用,降低重复开发成本。
- 公共组件:工具类、基础UI组件。
- 便于测试部署:单个组件可独立编译、独立测试,无需编译整个工程,测试效率提升。后续可实现组件热更新,提升用户体验。
组件拆分原则
组件拆分直接决定架构设计的合理性,是组件化入门的核心。须遵循以下原则,规避拆分混乱问题。
- 高内聚:一个组件内的功能高度相关,不掺杂其他无关业务。
- 例:个人中心组件,仅包含登录、注册、个人信息管理等与用户相关的功能。
- 低耦合:组件间减少依赖,若必须依赖,通过
接口实现,避免直接依赖具体实现。- 例:个人中心组件不可直接调用首页组件的方法,通过
接口通信。- 接口:定义在公共组件中,各业务组件通过依赖公共模块,实现
接口通信(接口具体实现在各业务组件中)。 - 接口通信需依托base基础模块,将接口下沉至公共模块,实现组件间解耦。
- 接口:定义在公共组件中,各业务组件通过依赖公共模块,实现
- 例:个人中心组件不可直接调用首页组件的方法,通过
- 单一职责:一个组件只负责一个核心业务或功能,避免
大而全。- 例:搜索组件仅负责搜索相关功能,不包括数据缓存、用户信息展示等无关功能。
- 可复用性:拆分时考虑组件的复用场景,避免一次性组件。
- 例:公共工具组件、基础UI组件,需设计成可复用状态。
组件化职场案例
电商App拆分:各组件独立开发、通过接口通信
- 首页组件
- 商品组件
- 购物车组件
- 订单组件
- 用户组件
- 支付组件
工具类App拆分
- 核心功能组件(例:文件管理组件、图片处理组件)。
- 基础组件:工具类组件、UI组件
- 壳工程组件:负责各组件组装融合
组件化踩坑点规避
踩坑点1:组件拆分混乱,出现跨组件依赖、功能重叠
- 修复:严格遵循
高内聚、低耦合、单一职责原则,拆分前先梳理业务模块,明确各组件的核心功能,避免越界。
踩坑点2:模块依赖冲突(不同组件依赖不同版本的三方库OkHttp、Coroutine等)
- 修复:统一工程根目录的build.gradle统一管理依赖,避免组件引入不同版本依赖。
坑点3:组件无法独立运行
- 修复:通过gradle配置,实现
组件模式和集成模式的切换。- 组件模式:配置为application
- 集成模式:配置为library
坑点4:组件间通信方式选择错误(业务组件间存在直接依赖,导致耦合过高)
- 修复:分析业务选择合适的通信方式,降低耦合。
接口通信、Intent通信、热流SharedFlow/StateFlow通信
坑点5:工程搭建后报错(资源冲突、清单文件合并失败)
- 修复:统一资源命名规范,配置清单文件合并规则,避免资源重复。
- 组件资源通过前缀区分
组件化工程搭建
基础版本先实现“壳工程+基础模块+业务组件”的基础架构。
- 壳工程:不包含具体业务逻辑,仅负责组件融合、初始化,是整个App入口。
- 初始化:AppContext初始化、路由初始化、核心库初始化等。
- base模块:包含公共工具类、基础UI模块、网络封装、数据库封装等,供所有业务组件复用。
- 业务组件(以个人中心组件为例):包含用户相关业务逻辑(登录、注册、个人信息等)。
- 可独立运行、独立测试,依赖base模块。
组件化工程创建步骤
- 1.Android Studio新建项目ComponentDemo
- 2.创建base模块(新建Module)
- 项目右键 -> New -> Module -> 选Android Library(命名base,输入包名com.xxx.base) -> Finish完成模块创建
- base模块的
build.gradle.kts三方库依赖使用统一版本管理工具,统一管理依赖版本号。 - 添加公共工具类、基础UI、网络封装、数据库封装等
- 3.创建个人中心
user组件(新建Module)- 项目右键 -> New -> Module -> 选Android Library(命名user,输入包名com.xxx.user) -> Finish完成模块创建
- user组件的
build.gradle.kts三方库依赖使用统一版本管理工具,统一管理依赖版本号。 - 配置user模块,支持独立运行(切换组件模式/集成模式),在build.gradle.kts中直接写死配置实现,按需手动切换
- 注意
- 在项目根目录的gradle.properties中添加开关,在build.gradle.kts的
plugin {}中是无法使用的。 - 在build.gradle.kts的
plugin {}前面是无法定义变量的。
- 在项目根目录的gradle.properties中添加开关,在build.gradle.kts的
- 注意
// user/build.gradle.kts
plugins {
if (true) {
id("com.android.application")
} else {
id("com.android.library")
}
...
}
android {
...
defaultConfig {
...
// 组件模式配置
if (true) {
applicationId = "com.xxx.user"
}
}
}
dependencies {
implementation(project(":base"))
...
}
- 4.配置壳工程
...
// 按需修改该值
val isUserModule = true
dependencies {
implementation(project(":base"))
// 集成模式依赖user组件
if (!isUserModule) {
implementation(project(":user"))
}
...
}
- 5.壳工程初始化
class AppApplication : Application() {
override fun onCreate() {
super.onCreate()
// 初始化AppContext
AppContext.init(this)
// 其他初始化操作
}
}
- 6.验证
- 切换集成模式,同步工程,打包验证是否有报错
- 运行壳工程,确保App能正常启动,无crash
- 切换成组件模式,运行user组件,确保user组件能独立运行
组件间通信
围绕以下几点
- 组件间通信认知
- 3种基础通信方式:Intent、接口、热流
- 组件通信踩坑点规避
组件间通信认知
先明确组件通信的核心原则和选型逻辑,避免盲目选择,贴合实际开发场景。
核心原则
- 始终遵循高内聚、低耦合、单一职责,组件间不直接引用、不直接调用方法。
- 通过统一的通信媒介实现数据传递和功能调用,降低维护成本。
选型逻辑
- Intent:简单页面跳转+少量数据传递。
- 接口:组件间功能调用。依托base模块,将接口下沉至公共模块,实现组件间解耦。
- 热流(SharedFlow/StateFlow):组件间状态共享、事件传递。
3种基础通信方式:Intent、接口、热流
每种通信方式按原理-适用场景-实战讲述。
Intent通信:页面跳转+少量数据传递
原理
- 基于Android原生Intent机制,通过隐式Intent(指定组件包名+类名)实现组件间页面跳转,附带少量数据。
- 适用于简单页面跳转场景,无需额外依赖库,原生支持。
适用场景:仅传递少量简单数据(页面标题、搜索id)
- 壳工程跳转user组件的页面
- user页面内部页面跳转
- user组件与home组件间跳转
实战
try {
// 指定目标页面
val intent = Intent()
intent.setComponent(ComponentName(packageName, "com.xxx.user.UserActivity"))
intent.putExtra("userId", "userId")
startActivity(intent)
} catch (e: ActivityNotFoundException) {
Log.e("Xxx", e.message ?: "ActivityNotFoundException")
}
// 目标页面
class UserActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_user)
// 接收Intent传递的数据
val userId = intent.getStringExtra("userId") ?: "userId"
}
...
}
接口通信:组件间功能调用、业务能力复用
原理
- 采用
接口下沉+服务实现的方式,将组件间需要调用的接口定义在base模块,由具体业务组件实现接口,其他组件通过接口调用功能。 - 不依赖具体实现,实现高内聚、低耦合,是实际开发中组件间功能调用的主流方式。
适用场景
- 组件间功能调用(例:user组件提供获取用户信息功能,供其他组件调用)。
- 业务能力复用(例:支付组件提供支付功能,供订单组件调用)。
实战
- 1.base模块定义接口
// 定义在base模块,由user组件实现的接口
interface IUserService {
// 获取用户信息
fun getUserInfo(): UserInfo
// 异步方法:结合协程+Flow
suspend fun getAsyncUserInfo(): UserInfo
}
- 2.在user组件中实现接口
// user组件实现
class UserServiceImpl: IUserService {
override fun getUserInfo(): UserInfo {
val userInfo = ""
...
return userInfo
}
override suspend fun getAsyncUserInfo(): UserInfo {
val userInfo = ""
...
return userInfo
}
}
- 3.base模块中,创建服务管理类(用于注册获取接口实现,避免组件间直接依赖)。
// 服务管理类
object ServiceManager {
// 存储接口实现,简单实现,后续可结合依赖注入框架优化
private val serviceMap = HashMap<Class<out Any>, Any>()
// 注册接口实现,由具体组件初始化时注册
fun <T: Any> registerService(clazz: Class<T>, service: T) {
serviceMap[clazz] = service
}
// 获取接口实现
@Suppress("UNCHECKED_CAST")
fun <T: Any> getService(clazz: Class<T>): T? = serviceMap[clazz] as? T
}
- 4.初始化时注册user组件接口的实现
ServiceManager.registerService(IUserService::class.java, UserServiceImpl())
- 5.其他组件中验证调试
val service = ServiceManager.getService(IUserService::class.java)
val userInfo = service?.getUserInfo()
热流通信:组件间状态共享、事件传递
原理
基于Flow热流(StateFlow/SharedFlow),在base模块中创建热流实例,组件通过发送热流、收集热流实现状态共享和事件传递(例:用户登录状态变化、全局通知)。
- StateFlow:适用于UI状态管理(保存最新状态)。
- SharedFlow:适用于离散事件传递。
适用场景:结合协程实现异步通信,避免内存泄漏
- 组件间状态共享(例:用户登录状态、主题切换)。
- 离散事件传递(例:点击事件、通知提示)。
实战
- 1.在base模块中创建热流管理类(统一管理热流,供其他组件调用)
// 热流管理类
object FlowBusManager {
// StateFlow:用于状态共享,保存最新状态,例登录状态
private val _userStateFlow = MutableStateFlow<UserInfo?>(null)
val userStateFlow: StateFlow<UserInfo?> = _userStateFlow.asStateFlow()
// SharedFlow:用于离散事件传递,例通知、点击事件,缓冲值个数为1
private val _userEventFlow = MutableSharedFlow<String>(extraBufferCapacity = 1)
private val userEventFlow: SharedFlow<String> = _userEventFlow.asSharedFlow()
// 发送用户状态:用户登录,信息更新时调用
fun sendUserState(userInfo: UserInfo?) {
_userStateFlow.value = userInfo
}
// 发送用户事件:用户退出,操作成功时调用
suspend fun sendUserEvent(event: String) {
_userEventFlow.emit(event)
}
}
- 2.在user组件中,发送热流,模拟用户登录、事件触发
// 用户登陆成功时调用
FlowBusManager.sendUserState(userInfo)
// 需在协程环境下调用
FlowBusManager.sendUserEvent("用户登陆成功")
- 3.在壳工程中,收集热流,接收用户状态和事件
// 收集用户登录状态
CoroutineHelper.getPageScope(lifecycle).launch {
FlowBusManager.userStateFlow
.bindLifecycle(this@MainActivity)
.collect { userInfo ->
Log.i("XxxActivity", "userInfo: $userInfo")
}
}
// 收集用户事件
CoroutineHelper.getPageScope(lifecycle).launch {
FlowBusManager.userEventFlow
.bindLifecycle(this@MainActivity)
.collect { event ->
Toast.makeText(this@MainActivity, event, Toast.LENGTH_SHORT).show()
}
}
/* ------- 附:工具方法,避免内存泄漏 ------- */
// 页面Scope,跟随页面生命周期(Activity、Fragment),避免内存泄漏
fun getPageScope(
lifecycle: Lifecycle,
): CoroutineScope {
val scope = CoroutineScope(Job() + mainDispatcher + coroutineHandler)
lifecycle.addObserver(object : DefaultLifecycleObserver {
override fun onDestroy(owner: LifecycleOwner) {
// 页面销毁,取消所有关联的协程任务
if (scope.isActive) {
scope.cancel()
Log.w(TAG, "page destroy, cancel task")
}
}
})
return scope
}
// Flow与协程联动:页面绑定生命周期,避免泄漏
fun <T> Flow<T>.bindLifecycle(activity: Activity): Flow<T> = this
.onStart {
// 页面启动时收集
}
.onCompletion {
// 页面销毁时取消收集
if (activity.isDestroyed) {
currentCoroutineContext().cancel(CancellationException("$activity isDestroyed"))
}
}
- 4.验证
- 集成模式下,打开App,跳转登录页,登录,看壳工程能否正常接收状态和事件。
- 页面销毁时热流自动取消收集,无内存泄漏,状态能实时同步,事件能正常接收。。
组件通信踩坑点规避
坑点1:Intent跳转报错,类找不到
- 原因
- 1.类名反射时包名错误
- 2.Activity未注册
- 3.组件模式/集成模式配置错误导致library未直接或间接被壳工程依赖打包至apk中。
- 修复
- 1.核对包名+类名
- 2.确保Activity注册
- 3.确保以library模式被壳工程直接或间接依赖打包至apk中。
- 4.兜底加Activity检查、try-catch规避App直接崩溃。
坑点2:接口通信时,接口实例获取不到,为null
- 原因:接口未注册或注册时机晚于调用时机。
- 修复:组件初始化时,注册接口实现,确保调用前完成注册(可在Application#onCreate()完成注册)。
坑点3:热流收集导致内存泄漏
- 原因:热流收集未绑定页面生命周期,页面销毁后仍在收集。
- 修复:绑定生命周期或在页面销毁时取消协程,确保热流及时取消。
坑点4:组件间直接依赖,违背低耦合原则
- 原因:直接引用其他组件的类或方法,未通过接口、热流等媒介通信。
- 修复:删除直接依赖,通过接口、热流、Intent实现通信,接口下沉至base模块。
坑点5:热流发送后无法接收
- 原因:SharedFlow未设置extraBufferCapacity,或收集时机晚于发送时机。
- 修复:SharedFlow设置extraBufferCapacity=1,确保发送的事件能被后续收集者收集。
SharedFlow配置规则:未设置extraBufferCapacity(=0)+ 发射在前、收集在后 → 收集者绝对收不到数据。而且发射方会一直卡住、挂起,直到有人来收集。
- emit () 必须等有人 collect 才能发送成功
- 没人接收 → emit 挂起、卡住
- 发射在前,收集在后 → 数据发不出去,一直等
- 后来的收集者收不到之前发射的数据
坑点6:依赖冲突,各组件依赖协程、流、OkHttp3等版本不一致
- 原因:不同组件引入不同版本依赖,导致打包合并时依赖冲突。
- 修复:使用依赖管理,统一管理版本依赖,避免组件单独引入依赖。