深入理解 Compose 在大型项目中的架构设计,掌握 Navigation、Hilt、性能优化等企业级开发技能。
前言
在前五篇中,我们掌握了 Compose 的核心开发技能。但在实际项目中,我们还需要考虑:
- 如何组织大型项目的代码结构?
- 如何实现页面导航?
- 如何管理依赖注入?
- 如何优化应用性能?
本篇文章将深入讲解 Compose 在企业级项目中的架构设计与工程化实践。
一、项目架构设计
1.1 推荐的项目结构
app/
├── src/main/java/com/example/app/
│ ├── App.kt # Application 类
│ ├── di/ # 依赖注入模块
│ │ ├── AppModule.kt
│ │ └── NetworkModule.kt
│ ├── data/ # 数据层
│ │ ├── local/ # 本地数据源
│ │ │ ├── dao/
│ │ │ ├── entity/
│ │ │ └── AppDatabase.kt
│ │ ├── remote/ # 远程数据源
│ │ │ ├── api/
│ │ │ └── dto/
│ │ ├── repository/ # 仓库层
│ │ └── model/ # 数据模型
│ ├── domain/ # 领域层(可选)
│ │ ├── usecase/
│ │ └── model/
│ ├── ui/ # UI 层
│ │ ├── theme/ # 主题配置
│ │ ├── component/ # 通用组件
│ │ ├── screen/ # 页面
│ │ │ ├── home/
│ │ │ │ ├── HomeScreen.kt
│ │ │ │ ├── HomeViewModel.kt
│ │ │ │ └── HomeUiState.kt
│ │ │ └── profile/
│ │ └── navigation/ # 导航配置
│ └── util/ # 工具类
└── src/main/res/ # 资源文件
1.2 分层架构原则
┌─────────────────────────────────────────────────────────────────┐
│ 分层架构图 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ UI Layer (Presentation) │ │
│ │ - Composable 函数 │ │
│ │ - ViewModel │ │
│ │ - UI State │ │
│ │ 职责:展示数据、处理用户交互 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Domain Layer (Optional) │ │
│ │ - UseCase │ │
│ │ - Domain Model │ │
│ │ 职责:业务逻辑、数据转换 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Data Layer │ │
│ │ - Repository │ │
│ │ - DataSource (Local/Remote) │ │
│ │ 职责:数据获取、缓存策略 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ 依赖方向:UI → Domain → Data │
│ │
└─────────────────────────────────────────────────────────────────┘
二、Navigation 组件
2.1 为什么需要 Navigation?
传统 Fragment 导航的问题:
- 事务管理复杂
- 返回栈难以控制
- 深度链接实现困难
- 参数传递类型不安全
Compose Navigation 的优势:
- 声明式导航
- 类型安全的参数传递
- 自动处理返回栈
- 支持深层链接
2.2 Navigation 基础
2.2.1 设置导航
// 1. 添加依赖
// implementation("androidx.navigation:navigation-compose:2.7.0")
// 2. 定义路由
sealed class Screen(val route: String) {
data object Home : Screen("home")
data object Profile : Screen("profile/{userId}") {
fun createRoute(userId: String) = "profile/$userId"
}
data object Settings : Screen("settings")
}
// 3. 创建 NavHost
@Composable
fun AppNavigation(
navController: NavHostController = rememberNavController()
) {
NavHost(
navController = navController,
startDestination = Screen.Home.route
) {
composable(Screen.Home.route) {
HomeScreen(
onNavigateToProfile = { userId ->
navController.navigate(Screen.Profile.createRoute(userId))
}
)
}
composable(
route = Screen.Profile.route,
arguments = listOf(
navArgument("userId") { type = NavType.StringType }
)
) { backStackEntry ->
val userId = backStackEntry.arguments?.getString("userId") ?: ""
ProfileScreen(
userId = userId,
onBack = { navController.popBackStack() }
)
}
composable(Screen.Settings.route) {
SettingsScreen()
}
}
}
2.2.2 导航操作
// 导航到目标页面
navController.navigate("profile/123")
// 返回上一页
navController.popBackStack()
// 返回指定页面
navController.popBackStack("home", inclusive = false)
// 清空返回栈并导航
navController.navigate("home") {
popUpTo("home") { inclusive = true }
}
// 启动单例页面(防止重复)
navController.navigate("detail") {
launchSingleTop = true
}
2.3 类型安全的导航(Navigation 2.8.0+)
// 1. 定义序列化路由类
@Serializable
object Home
@Serializable
data class Profile(val userId: String)
@Serializable
object Settings
// 2. 创建 NavHost
@Composable
fun AppNavigation(
navController: NavHostController = rememberNavController()
) {
NavHost(
navController = navController,
startDestination = Home
) {
composable<Home> {
HomeScreen(
onNavigateToProfile = { userId ->
navController.navigate(Profile(userId))
}
)
}
composable<Profile> { backStackEntry ->
val profile: Profile = backStackEntry.toRoute()
ProfileScreen(userId = profile.userId)
}
composable<Settings> {
SettingsScreen()
}
}
}
2.4 底部导航栏
@Composable
fun BottomNavApp() {
val navController = rememberNavController()
val items = listOf(
BottomNavItem.Home,
BottomNavItem.Search,
BottomNavItem.Profile
)
Scaffold(
bottomBar = {
NavigationBar {
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentDestination = navBackStackEntry?.destination
items.forEach { item ->
NavigationBarItem(
icon = { Icon(item.icon, contentDescription = item.label) },
label = { Text(item.label) },
selected = currentDestination?.hierarchy?.any {
it.route == item.route
} == true,
onClick = {
navController.navigate(item.route) {
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
launchSingleTop = true
restoreState = true
}
}
)
}
}
}
) { innerPadding ->
NavHost(
navController = navController,
startDestination = BottomNavItem.Home.route,
modifier = Modifier.padding(innerPadding)
) {
composable(BottomNavItem.Home.route) { HomeScreen() }
composable(BottomNavItem.Search.route) { SearchScreen() }
composable(BottomNavItem.Profile.route) { ProfileScreen() }
}
}
}
sealed class BottomNavItem(
val route: String,
val icon: ImageVector,
val label: String
) {
data object Home : BottomNavItem("home", Icons.Default.Home, "首页")
data object Search : BottomNavItem("search", Icons.Default.Search, "搜索")
data object Profile : BottomNavItem("profile", Icons.Default.Person, "我的")
}
2.5 深层链接
// AndroidManifest.xml 中配置
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" android:host="example.com" />
</intent-filter>
</activity>
// Navigation 中配置深层链接
composable(
route = "article/{articleId}",
deepLinks = listOf(
navDeepLink {
uriPattern = "https://example.com/article/{articleId}"
}
),
arguments = listOf(
navArgument("articleId") { type = NavType.StringType }
)
) { backStackEntry ->
val articleId = backStackEntry.arguments?.getString("articleId")
ArticleScreen(articleId = articleId)
}
三、Hilt 依赖注入
3.1 为什么需要依赖注入?
传统方式的痛点:
// ❌ 紧耦合,难以测试
class UserRepository {
private val api = RetrofitClient.api // 硬编码依赖
private val dao = AppDatabase.dao // 硬编码依赖
}
依赖注入的优势:
- 解耦组件
- 便于单元测试
- 生命周期自动管理
- 单例控制
3.2 Hilt 基础配置
// 1. 添加依赖
/*
plugins {
id("kotlin-kapt")
id("dagger.hilt.android.plugin")
}
dependencies {
implementation("com.google.dagger:hilt-android:2.48")
kapt("com.google.dagger:hilt-compiler:2.48")
implementation("androidx.hilt:hilt-navigation-compose:1.1.0")
}
*/
// 2. Application 类
@HiltAndroidApp
class MyApplication : Application()
// 3. Activity
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyApp()
}
}
}
3.3 定义和注入依赖
// 1. 创建 Module
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
@Provides
@Singleton
fun provideRetrofit(): Retrofit {
return Retrofit.Builder()
.baseUrl("https://api.example.com/")
.addConverterFactory(GsonConverterFactory.create())
.build()
}
@Provides
@Singleton
fun provideApiService(retrofit: Retrofit): ApiService {
return retrofit.create(ApiService::class.java)
}
}
@Module
@InstallIn(SingletonComponent::class)
object DatabaseModule {
@Provides
@Singleton
fun provideDatabase(@ApplicationContext context: Context): AppDatabase {
return Room.databaseBuilder(
context,
AppDatabase::class.java,
"app_database"
).build()
}
@Provides
fun provideUserDao(database: AppDatabase): UserDao {
return database.userDao()
}
}
// 2. Repository 注入依赖
class UserRepository @Inject constructor(
private val apiService: ApiService,
private val userDao: UserDao
) {
suspend fun getUsers(): List<User> {
return try {
val users = apiService.getUsers()
userDao.insertAll(users)
users
} catch (e: Exception) {
userDao.getAll()
}
}
}
// 3. ViewModel 注入 Repository
@HiltViewModel
class UserViewModel @Inject constructor(
private val userRepository: UserRepository
) : ViewModel() {
// ...
}
// 4. Composable 获取 ViewModel
@Composable
fun UserScreen(
viewModel: UserViewModel = hiltViewModel()
) {
// ...
}
3.4 限定符(Qualifiers)
// 定义限定符
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class AuthInterceptorOkHttpClient
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class OtherInterceptorOkHttpClient
// 提供不同实例
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
@AuthInterceptorOkHttpClient
@Provides
fun provideAuthOkHttpClient(authInterceptor: AuthInterceptor): OkHttpClient {
return OkHttpClient.Builder()
.addInterceptor(authInterceptor)
.build()
}
@OtherInterceptorOkHttpClient
@Provides
fun provideOtherOkHttpClient(otherInterceptor: OtherInterceptor): OkHttpClient {
return OkHttpClient.Builder()
.addInterceptor(otherInterceptor)
.build()
}
}
// 注入指定实例
class ApiService @Inject constructor(
@AuthInterceptorOkHttpClient private val client: OkHttpClient
)
四、性能优化
4.1 重组优化
4.1.1 避免不必要的重组
// ❌ 错误:整个列表都会重组
@Composable
fun UserList(users: List<User>) {
Column {
users.forEach { user ->
UserItem(user = user) // 所有 item 都会重组
}
}
}
// ✅ 正确:使用 LazyColumn,只重组可见项
@Composable
fun UserList(users: List<User>) {
LazyColumn {
items(users, key = { it.id }) { user ->
UserItem(user = user)
}
}
}
4.1.2 使用 @Stable 和 @Immutable
// ❌ 不稳定类,任何变化都会触发重组
class User {
var name: String = ""
}
// ✅ 稳定类
@Stable
class User {
var name: String = ""
}
// ✅ 不可变类,实例替换时才重组
@Immutable
data class User(
val name: String
)
4.1.3 使用 remember 缓存计算
@Composable
fun ExpensiveComponent(data: List<Int>) {
// ❌ 每次重组都计算
val sum = data.sum()
// ✅ 只在 data 变化时计算
val sum = remember(data) { data.sum() }
Text("Sum: $sum")
}
4.2 布局检查器使用
┌─────────────────────────────────────────────────────────────────┐
│ 使用布局检查器优化性能 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. 打开 Layout Inspector │
│ Tools → Layout Inspector │
│ │
│ 2. 查看重组次数 │
│ - 右侧面板显示每个 Composable 的重组次数 │
│ - 高重组次数的组件需要优化 │
│ │
│ 3. 常见优化方案 │
│ - 使用 key 优化列表 │
│ - 提取不依赖状态的部分 │
│ - 使用 derivedStateOf 减少重组 │
│ │
└─────────────────────────────────────────────────────────────────┘
4.3 图片加载优化
// 使用 Coil 加载图片
AsyncImage(
model = ImageRequest.Builder(LocalContext.current)
.data(imageUrl)
.crossfade(true)
.placeholder(R.drawable.placeholder)
.error(R.drawable.error)
.memoryCachePolicy(CachePolicy.ENABLED)
.diskCachePolicy(CachePolicy.ENABLED)
.build(),
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier.fillMaxWidth()
)
4.4 列表性能优化
@Composable
fun OptimizedList(items: List<Item>) {
LazyColumn {
items(
items = items,
key = { it.id }, // ✅ 使用 key
contentType = { it.type } // ✅ 使用 contentType
) { item ->
when (item.type) {
Type.HEADER -> HeaderItem(item)
Type.CONTENT -> ContentItem(item)
}
}
}
}
@Composable
fun ContentItem(item: Item) {
// ✅ 缓存计算结果
val formattedDate = remember(item.date) {
formatDate(item.date)
}
// ✅ 避免创建新的 lambda
val onClick = remember(item.id) {
{ viewModel.onItemClick(item.id) }
}
Card(onClick = onClick) {
Text(formattedDate)
}
}
五、测试
5.1 Compose UI 测试
// 1. 添加依赖
// androidTestImplementation("androidx.compose.ui:ui-test-junit4")
// 2. 编写测试
@RunWith(AndroidJUnit4::class)
class CounterTest {
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun counterIncrements_whenClicked() {
composeTestRule.setContent {
Counter()
}
// 查找并点击按钮
composeTestRule.onNodeWithText("Count: 0").assertExists()
composeTestRule.onNodeWithText("Increment").performClick()
// 验证结果
composeTestRule.onNodeWithText("Count: 1").assertExists()
}
}
5.2 ViewModel 测试
@ExperimentalCoroutinesApi
class UserViewModelTest {
@get:Rule
val instantExecutorRule = InstantTaskExecutorRule()
@get:Rule
val mainDispatcherRule = MainDispatcherRule()
private lateinit var viewModel: UserViewModel
private lateinit var repository: FakeUserRepository
@Before
fun setup() {
repository = FakeUserRepository()
viewModel = UserViewModel(repository)
}
@Test
fun `load users updates ui state`() = runTest {
// Given
val users = listOf(User("1", "Alice"), User("2", "Bob"))
repository.setUsers(users)
// When
viewModel.loadUsers()
// Then
assertEquals(UiState.Success(users), viewModel.uiState.value)
}
}
六、本篇小结
今天我们深入探讨了 Compose 的架构与工程化:
项目架构
- 理解了分层架构的设计原则
- 掌握了推荐的项目结构
Navigation
- 掌握了 Navigation 的基础用法
- 学会了类型安全的导航
- 理解了深层链接的实现
Hilt
- 理解了依赖注入的优势
- 掌握了 Hilt 的配置和使用
- 学会了限定符的使用
性能优化
- 掌握了重组优化的方法
- 学会了使用布局检查器
- 理解了列表性能优化
测试
- 掌握了 Compose UI 测试
- 学会了 ViewModel 测试
下篇预告
第七篇:高级特性与实战 将深入讲解:
- 自定义绘制与 Canvas
- 图形变换与 Shader 效果
- 窗口 Insets 与边缘到边缘适配
- 完整实战项目
敬请期待!
📌 系列文章导航
- 第一篇:初识 Compose ✅
- 第二篇:核心基石 ✅
- 第三篇:状态管理 ✅
- 第四篇:Material 组件与主题 ✅
- 第五篇:动画与交互 ✅
- 第六篇:架构与工程化(当前)✅
- 第七篇:高级特性与实战
如果这篇文章对你有帮助,欢迎 点赞、收藏、关注!有任何问题可以在评论区留言。