本指南涵盖 Android 应用开发的最佳实践和推荐架构,助力开发者构建健壮高效的应用程序。。
前置要求
本文假设您已具备 Android 框架基础知识。若需系统学习 Android 开发,建议先完成《Android 基础知识》
版本号 | 修订日期 | 修订内容 | 修订人 |
---|---|---|---|
v1.0 | 2025-02-02 | 初始版本发布 | 小李子 |
需要权威资料的请移步 官方社区文档。参考文献
Android 应用面临独特挑战:
传统架构痛点:
新架构核心目标:
典型 Android 应用由以下核心组件构成:
组件类型 | 角色定位 | 核心职责 | 生命周期 | 声明要求 |
---|---|---|---|---|
Activity |
界面容器主界面 | 用户交互主入口 | 低 系统托管 |
✅ |
Fragment |
模块化界面单元 | 界面复用与组合 | 中 宿主控制 |
❌ |
Service |
后台服务 | 无界面长时任务处理 | 低 系统托管 |
✅ |
ContentProvider |
数据共享桥梁 | 跨应用数据访问接口 | 低 系统托管 |
✅ |
BroadcastReceiver |
全局事件监听器 | 系统 / 应用级广播处理 | 低 瞬时存活 |
✅ |
组件需在应用清单 AndroidManifest.xml
中声明,示例:
<manifest>
<application>
<activity android:name=".MainActivity" />
<service android:name=".DownloadService" />
<provider android:name=".DataProvider" />
<receiver android:name=".NetworkReceiver" />
application>
manifest>
黄金法则
应用组件应 作为轻量级通道,禁止存储核心业务数据与状态
界面组件职责边界:
// 正例:Activity 仅处理 UI 逻辑
class MainActivity : AppCompatActivity() {
private val viewModel: MainViewModel by viewModels()
override fun onCreate() {
viewModel.uiState.observe(this) { state ->
updateUI(state) // UI 更新逻辑
}
}
private fun onRefreshClicked() = viewModel.loadData()
}
// 反例:Activity 包含数据加载逻辑
class BadActivity : AppCompatActivity() {
override fun onCreate() {
// 错误:混合网络请求与UI逻辑
CoroutineScope(Dispatchers.IO).launch {
val data = fetchData()
runOnUiThread { updateUI(data) }
}
}
}
数据模型是最好是持久性模型,数据模型代表应用的数据
,独立
于界面元素和其他组件,与界面和应用组件的生命周期无关,但
在操作系统销毁应用进程时会被移除
在应用中定义新数据类型时,应为其指定单一数据源 SSOT
, 指的是该数据的持有者
,只有持有者可以修改或转换数据。
为实现这一点,SSOT 会以不可变类型
公开数据,并提供可供其他类型调用的函数或事件来修改数据
在离线优先应用中,数据库通常作为应用数据的单一数据源;在其他情况下,单一数据源也可以是 ViewModel
甚至界面
实施要点:
单一数据源原常与单向数据流 UDF 模式结合使用。在 UDF 模式下,状态仅沿一个方向流动
,而修改数据的事件则朝相反方向流动
在 Android 中,状态或数据通常从分区层次结构中较高的分区类型流向较低的分区类型,事件则通常在较低分区类型触发,直至到达 SSOT 对应的相关数据类型。
例如,应用数据一般从数据源流向界面,而用户事件(如按钮点击)则从界面流向 SSOT,在 SSOT 中修改应用数据并以不可变类型公开。
该模式能更好地保证数据一致性
,减少错误
,便于调试
,同时具备 SSOT 模式的所有优势
典型数据流:
单向数据流优势:
基于上述常见架构原则,每个应用至少应包含两个层:
推荐分层结构:
app/
├─ ui/ # 界面层
├─ domain/ # 领域层(可选)
└─ data/ # 数据层
现代应用架构倡导采用以下方法及其他相关方法:
界面层(或呈现层)的 主要 功能是在屏幕上显示应用数据。
无论数据变化是由用户交互(如按钮点击)还是外部输入(如网络响应)引起,界面都应及时更新以反映这些变化。
界面层组成部分:
组件 | 职责 | 推荐技术 |
---|---|---|
Activity / Fragment |
生命周期管理与界面容器 | ViewBinding |
ViewModel |
状态管理与业务逻辑代理 | ViewModel |
UI Elements | 具体界面元素渲染 | Compose / XML Layout |
代码规范:
class MainViewModel : ViewModel() {
private val _uiState = MutableStateFlow<UiState>(Loading)
val uiState: StateFlow<UiState> = _uiState.asStateFlow()
fun loadData() = viewModelScope.launch {
_uiState.value = Loading
try {
val data = repository.fetchData()
_uiState.value = Success(data)
} catch (e: Exception) {
_uiState.value = Error(e)
}
}
}
详情参考:Jetpack 界面层 官方文档
应用的数据层 包含业务逻辑,这些业务逻辑决定了应用的价值,包括应用创建
、存储
和更改数据
的规则。
数据层由多个仓库组成,每个仓库可包含零到多个数据源。针对应用中处理的每种不同类型的数据,应分别创建一个存储库类。例如,为电影相关数据创建
MoviesRepository 类,为付款相关数据创建 PaymentsRepository 类。
在典型架构中,数据层的仓库向应用的其他部分提供数据,同时依赖于数据源。
典型结构:
data/
├─ repository/
│ └─ UserRepository.kt
├─ model/
│ └─ User.kt
└─ datasource/
├─ local/
└─ remote/
存储库类承担以下任务:
Repository 模式:
class UserRepository(
private val localSource: UserLocalDataSource,
private val remoteSource: UserRemoteDataSource
) {
suspend fun getUsers(): List<User> {
return try {
val remoteData = remoteSource.fetchUsers()
localSource.cacheUsers(remoteData)
remoteData
} catch (e: IOException) {
localSource.getCachedUsers()
}
}
}
数据源实现:
class UserRemoteDataSource(
private val api: UserApi
) {
suspend fun fetchUsers(): List<User> {
return api.getUsers().mapToDomain()
}
}
每个数据源类仅负责处理一个数据源,数据源可以是文件、网络来源或本地数据库。数据源类是应用与数据操作系统之间的桥梁
详情参考:Jetpack 数据层 官方文档
使用场景:
领域层是位于界面层与数据层之间的可选层。它负责封装复杂的业务逻辑,或供多个 ViewModel
复用的简单业务逻辑。
若添加了此层,领域层会向界面层提供依赖项,同时自身依赖于数据层。此层中的类通常称为“用例”或“交互方”,每个用例应仅负责单一功能。
例如,若多个ViewModel
依赖时区在屏幕上显示适当消息,
应用可能包含 GetTimeZoneUseCase 类
用例示例:
class GetUserProfileUseCase(
private val userRepository: UserRepository
) {
suspend operator fun invoke(userId: String): UserProfile {
return userRepository.getProfile(userId)
.validate()
.transform()
}
}
详情参考:Jetpack 领域层 官方文档
应用中的类需要依赖其他类才能正常工作,方案对比:
方案 | 优点 | 缺点 |
---|---|---|
手动注入 | 简单直接 | 难以维护大型项目 |
Hilt | 类型安全、自动装配 | 需要学习曲线 |
服务定位器 | 灵活配置 | 运行时错误风险 |
借助这些模式可扩展代码,它们提供了清晰的依赖项管理方式,避免了代码复制和复杂性增加。此外,还能在测试和生产实现之间快速切换
建议在 Android 应用中采用依赖项注入模式,并使用 Hilt 库。它可自动遍历依赖项树构造对象,为依赖项提供编译时保证,并为 Android 框架类创建依赖项容器。
Hilt 最佳实践:
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
@Provides
fun provideRetrofit(): Retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(MoshiConverterFactory.create())
.build()
@Provides
fun provideUserApi(retrofit: Retrofit): UserApi = retrofit.create(UserApi::class.java)
}
编程具有创造性,构建 Android 应用也不例外。在多个 Activity
或 Fragment
之间传递数据、检索远程数据并本地存储以支持离线使用,或处理复杂应用中的其他常见情况时,往往有多种解决方案。
虽然以下建议并非强制要求,但在大多数情况下,遵循这些建议可使代码库更强大、更具可测试性且更易维护:
Activity
、Service
和 Broadcast Receivers
Context
或 Toast
)的类。将其他类与这些类分离,有助于提高可测试性,降低应用耦合度。十大黄金准则:
Activity
中保留重要数据,使用StateFlow
+ViewModel
持久化界面状态Coroutine
+Dispatchers
、CoroutineScope
明确线程边界Flow
处理异步数据流,LiveData
代替直接回调feature
模块Espresso
,ViewModel
用JUnit
onDestroy
中释放协程作用域Crashlytics
集成)Room
+Paging
、Paging 3
实现分页加载质量指标对比:
指标 | 传统架构 | Jetpack 架构 | 提升幅度 |
---|---|---|---|
崩溃率 | 2.1% | 0.6% | 71%↓ |
冷启动时间 | 1200ms | 650ms | 46%↓ |
代码复用率 | 38% | 75% | 97%↑ |
单元测试覆盖率 | 40% | 85% | 113%↑ |
CI/CD 通过率 | 82% | 96% | 17%↑ |
数据来源:Google 2023 Android 开发者调研报告
Android Jetpack 使用入门