快速上手
相关源文件
本页面内容基于以下源文件生成:
- README.md
- build.gradle
- settings.gradle
- gradle.properties
- app/build.gradle
- app/src/main/java/com/zj/wanandroid/MyApp.kt
- app/src/main/java/com/zj/wanandroid/MainActivity.kt
- app/src/test/java/com/zj/wanandroid/ExampleUnitTest.kt
- app/src/androidTest/java/com/zj/wanandroid/ExampleInstrumentedTest.kt
- app/src/main/java/com/zj/wanandroid/theme/Type.kt
本项目是一个基于 Jetpack Compose 实现的 WanAndroid 客户端,采用 MVI 架构模式和单 Activity 架构设计。该项目在 Google 发布 Compose 1.0 稳定版后开发,旨在探索 Compose 在生产环境中的最佳实践(README.md:1-3)。
项目简介与架构概览
MVI 架构设计
项目采用 MVI(Model-View-Intent)架构,该架构借鉴了前端框架的思想,强调数据的单向流动和唯一数据源。MVI 架构主要分为三部分(README.md:17-24):
| 组件 | 职责 | 说明 |
|---|---|---|
| Model | UI 状态管理 | 与 MVVM 不同,MVI 的 Model 主要指 UI 状态(State),如页面加载状态、控件位置等 |
| View | 界面展示 | 通过订阅 Model 的变化实现界面刷新,可能是 Activity 或任意 UI 承载单元 |
| Intent | 用户操作封装 | 用户的任何操作都被包装成 Intent 后发送给 Model 层进行数据请求 |
MVI 与 MVVM 的主要区别在于:MVI 架构下 ViewModel 的实现对 View 层屏蔽,只能通过发送 Intent 来驱动事件;同时使用 ViewState 对 State 集中管理,只需订阅一个 ViewState 便可获取页面的所有状态。
单 Activity 架构优势
项目采用单 Activity + Navigation + 多 Compose 架构。这种设计的主要优势在于:Activity 与 Compose 通过 AndroidComposeView 中转,Activity 越多需要创建的 AndroidComposeView 越多,对性能有影响;而使用单 Activity 架构,所有页面跳转都在 Compose 内部完成(README.md:69-73)。
正在加载图表渲染器...
环境配置与依赖
系统要求
| 项目 | 要求 |
|---|---|
| Android SDK | minSdk 21, targetSdk 31, compileSdk 31 |
| JDK 版本 | Java 8 (1.8) |
| Kotlin | 1.5.31 |
| Compose | 1.0.5 |
Gradle 版本配置
项目根目录的 build.gradle 定义了核心版本信息(build.gradle:1-16):
groovy1ext { 2 compose_version = '1.0.5' 3 kotlin_version = '1.5.31' 4 accompanistVersion = "0.18.0" 5 hiltVersion = "2.38.1" 6 hiltCompilerVersion = "1.0.0" 7 hiltComposeVersion = "1.0.0-alpha03" 8 retrofitVersion = "2.9.0" 9 gsonVersion = "2.8.6" 10 datastoreVersion = "1.0.0-rc02" 11 pagingVersion = "3.1.0-rc01" 12 pagingComposeVersion = "1.0.0-alpha14" 13 constraintComposeVersion = "1.0.0-rc01" 14}
核心依赖库
应用层 app/build.gradle 包含了项目所需的核心依赖(app/build.gradle:54-93):
| 依赖类别 | 库名 | 版本 | 用途 |
|---|---|---|---|
| Compose 核心 | androidx.compose.ui | 1.0.5 | UI 基础组件 |
| Compose Material | androidx.compose.material | 1.0.5 | Material Design 组件 |
| Navigation | navigation-compose | 2.4.0-beta02 | 页面导航 |
| Hilt | hilt-android | 2.38.1 | 依赖注入 |
| Hilt Navigation | hilt-navigation-compose | 1.0.0-alpha03 | Compose 与 Hilt 集成 |
| Retrofit | retrofit2 | 2.9.0 | 网络请求 |
| Paging | paging-compose | 1.0.0-alpha14 | 分页加载 |
| DataStore | datastore-preferences | 1.0.0-rc02 | 数据存储 |
| Accompanist | accompanist-* | 0.18.0 | Compose 辅助库 |
应用入口配置
Application 类配置
项目使用 Hilt 进行依赖注入,所有使用 Hilt 的 App 必须包含一个使用 @HiltAndroidApp 注解的 Application 类。@HiltAndroidApp 将会触发 Hilt 代码的生成,包括用作应用程序依赖项容器的基类(app/src/main/java/com/zj/wanandroid/MyApp.kt:1-29):
kotlin1@HiltAndroidApp 2class MyApp : Application() { 3 companion object { 4 @SuppressLint("StaticFieldLeak") 5 lateinit var CONTEXT: Context 6 } 7 8 override fun onCreate() { 9 super.onCreate() 10 CONTEXT = this 11 DataStoreUtils.init(this) 12 } 13}
生成的 Hilt 组件依附于 Application 的生命周期,它是 App 的父组件,提供其他组件访问的依赖。在 Application 中设置好 @HiltAndroidApp 之后,就可以使用 Hilt 提供的组件了。
MainActivity 入口
MainActivity 作为应用的唯一 Activity,使用 @AndroidEntryPoint 注解以支持 Hilt 依赖注入。在 onCreate 方法中通过 setContent 设置 Compose 内容(app/src/main/java/com/zj/wanandroid/MainActivity.kt:1-23):
kotlin1@AndroidEntryPoint 2class MainActivity : ComponentActivity() { 3 @ExperimentalFoundationApi 4 @ExperimentalComposeUiApi 5 @ExperimentalPagerApi 6 override fun onCreate(savedInstanceState: Bundle?) { 7 super.onCreate(savedInstanceState) 8 setContent { 9 HomeEntry() 10 } 11 } 12}
安装步骤
克隆项目
bash1git clone https://github.com/RicardoJiang/wanandroid-compose.git 2cd wanandroid-compose
环境准备
- 确保已安装 Android Studio Arctic Fox 或更高版本
- 确保 JDK 版本为 1.8
- 配置 Android SDK,最低 API Level 21
构建与运行
推荐路径(通过 Android Studio):
- 使用 Android Studio 打开项目根目录
- 等待 Gradle Sync 完成
- 点击 Run 按钮或使用快捷键运行应用
命令行构建(建议/常见做法,未在仓库证据中出现):
bash1# Linux/macOS 2./gradlew assembleDebug 3 4# Windows 5gradlew.bat assembleDebug
安装 APK 到设备(需要确认具体命令,未在仓库证据中出现):
bash1# Linux/macOS 2./gradlew installDebug 3 4# Windows 5gradlew.bat installDebug
运行验证
验证构建成功
构建成功后,可在 app/build/outputs/apk/debug/ 目录下找到生成的 APK 文件。预期输出应包含 BUILD SUCCESSFUL 信息(需要确认,未在仓库证据中出现)。
验证应用启动
应用启动后,应显示 WanAndroid 首页内容,包含首页、广场、问答、体系、我的等底部导航栏。应用包名为 com.zj.wanandroid(app/build.gradle:14)。
验证核心功能
| 功能模块 | 验证方法 |
|---|---|
| 首页列表 | 滑动查看文章列表,点击文章进入详情 |
| 登录功能 | 进入"我的"页面,点击登录,输入账号密码 |
| 广场页面 | 查看广场文章列表,支持下拉刷新 |
| 页面导航 | 使用底部导航栏切换页面,验证返回栈 |
ViewModel 与生命周期管理
ViewModel 获取方式
在 Compose 中一般可以通过两种方式获取 ViewModel(README.md:80-103):
kotlin1// 方式1: viewModel() - 与 ViewModelStoreOwner 绑定 2@Composable 3fun LoginPage( 4 loginViewModel: LoginViewModel = viewModel() 5) { 6 // ... 7} 8 9// 方式2: hiltViewModel() - 推荐,支持 NavGraph/Destination Scope 10@Composable 11fun LoginPage( 12 loginViewModel: LoginViewModel = hiltViewModel() 13) { 14 // ... 15}
方式1返回的 ViewModel 与 Activity 生命周期一致,在单 Activity 架构中将一直存在,不会释放。方式2通过 Hilt 实现,可以获取 NavGraph Scope 或 Destination Scope 的 ViewModel,Destination Scope 的 ViewModel 会跟随 BackStack 的弹出自动 Clear,避免泄露。
Compose 生命周期
Composable 的生命周期分为三个阶段(README.md:105-118):
| 阶段 | 触发时机 |
|---|---|
| onActive (onEnter) | Composable 首次进入组件树时 |
| onCommit (onUpdate) | UI 随着 recomposition 发生更新时 |
| onDispose (onLeave) | Composable 从组件树移除时 |
生命周期监听实现
通过 DisposableEffect 可以在 Compose 中监听 Activity 生命周期。在 onActive 时监听 Activity 的生命周期,在 onDispose 时取消监听(README.md:120-146):
kotlin1@Composable 2fun LoginPage( 3 loginViewModel: LoginViewModel = hiltViewModel() 4) { 5 val lifecycleOwner = LocalLifecycleOwner.current 6 DisposableEffect(key1 = Unit) { 7 val observer = object : LifecycleObserver { 8 @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) 9 fun onResume() { 10 viewModel.dispatch(Action.Resume) 11 } 12 13 @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) 14 fun onPause() { 15 viewModel.dispatch(Action.Pause) 16 } 17 } 18 lifecycleOwner.lifecycle.addObserver(observer) 19 onDispose { 20 lifecycleOwner.lifecycle.removeObserver(observer) 21 } 22 } 23}
列表状态保存
滚动位置丢失问题
使用 Paging3 加载分页数据并显示到 LazyColumn 时,页面跳转后返回可能导致列表滚动位置重置到顶部。这是因为 rememberLazyListState 会在列表中至少有一项时 restore 滚动位置,而 Paging 通过 Flow 获取数据,当返回页面重组时并不能马上获取到 Paging 数据,第一帧时 Paging 的 itemCount 为 0(README.md:168-178)。
解决方案
将 LazyListState 保存在 ViewModel 中,并且在 Paging 中有数据时再还原 listState(README.md:180-207):
kotlin1@HiltViewModel 2class SquareViewModel @Inject constructor( 3 private var service: HttpService, 4) : ViewModel() { 5 private val pager by lazy { 6 simplePager { service.getSquareData(it) }.cachedIn(viewModelScope) 7 } 8 val listState: LazyListState = LazyListState() 9} 10 11@Composable 12fun SquarePage( 13 navCtrl: NavHostController, 14 scaffoldState: ScaffoldState, 15 viewModel: SquareViewModel = hiltViewModel() 16) { 17 val squareData = viewStates.pagingData.collectAsLazyPagingItems() 18 // 当 Paging 有数据时,返回 ViewModel 中的 listState 19 val listState = if (squareData.itemCount > 0) viewStates.listState else LazyListState() 20 21 RefreshList(squareData, listState = listState) { 22 itemsIndexed(squareData) { _, item -> 23 // ... 24 } 25 } 26}
常见问题与排错
问题1:Gradle Sync 失败
症状:Android Studio 提示 Gradle Sync 失败,无法下载依赖。
解决方案:
- 检查网络连接,确保能访问 Google Maven 仓库
- 项目配置了
google()和mavenCentral()仓库(settings.gradle:3-6) - 如在国内开发,建议配置镜像源或使用 VPN
问题2:Hilt 依赖注入失败
症状:运行时抛出 IllegalStateException,提示找不到依赖。
解决方案:
- 确保 Application 类使用了
@HiltAndroidApp注解(app/src/main/java/com/zj/wanandroid/MyApp.kt:17) - 确保 Activity 使用了
@AndroidEntryPoint注解(app/src/main/java/com/zj/wanandroid/MainActivity.kt:12) - 检查 ViewModel 是否使用了
@HiltViewModel注解 - 执行 Clean Project 后重新构建
问题3:Compose 预览不可用
症状:Android Studio 中 Compose Preview 面板显示错误或空白。
解决方案:
- 确保使用 Android Studio Arctic Fox 或更高版本
- 检查
build.gradle中composeOptions配置(app/build.gradle:43-46) - 确保 Kotlin 编译器版本与 Compose 版本匹配
- 重新执行 Gradle Sync
问题4:LazyColumn 滚动位置丢失
症状:从详情页返回列表页时,列表滚动位置重置到顶部。
解决方案:
- 对于普通页面,使用
rememberLazyListState即可 - 对于带 Header/Footer 的 Paging 页面,将
LazyListState保存在 ViewModel 中 - 在 Paging 数据加载完成后再应用保存的状态
下一步建议
完成快速上手后,建议继续探索以下内容:
- 深入 MVI 架构:了解 ViewState、ViewEvent、ViewAction 的完整定义和使用方式
- Navigation 导航:学习 Compose Navigation 的路由配置和参数传递
- 网络请求:研究 Retrofit 与 Compose 的集成方式
- 分页加载:深入 Paging3 在 Compose 中的应用
更多实现细节可参考项目源码和 README 中的架构说明部分(README.md:14-67)。
