价格

快速上手

相关源文件

本页面内容基于以下源文件生成:

本项目是一个基于 Jetpack Compose 实现的 WanAndroid 客户端,采用 MVI 架构模式和单 Activity 架构设计。该项目在 Google 发布 Compose 1.0 稳定版后开发,旨在探索 Compose 在生产环境中的最佳实践(README.md:1-3)。

项目简介与架构概览

MVI 架构设计

项目采用 MVI(Model-View-Intent)架构,该架构借鉴了前端框架的思想,强调数据的单向流动和唯一数据源。MVI 架构主要分为三部分(README.md:17-24):

组件职责说明
ModelUI 状态管理与 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 SDKminSdk 21, targetSdk 31, compileSdk 31
JDK 版本Java 8 (1.8)
Kotlin1.5.31
Compose1.0.5

Gradle 版本配置

项目根目录的 build.gradle 定义了核心版本信息(build.gradle:1-16):

groovy
1ext {
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.ui1.0.5UI 基础组件
Compose Materialandroidx.compose.material1.0.5Material Design 组件
Navigationnavigation-compose2.4.0-beta02页面导航
Hilthilt-android2.38.1依赖注入
Hilt Navigationhilt-navigation-compose1.0.0-alpha03Compose 与 Hilt 集成
Retrofitretrofit22.9.0网络请求
Pagingpaging-compose1.0.0-alpha14分页加载
DataStoredatastore-preferences1.0.0-rc02数据存储
Accompanistaccompanist-*0.18.0Compose 辅助库

应用入口配置

Application 类配置

项目使用 Hilt 进行依赖注入,所有使用 Hilt 的 App 必须包含一个使用 @HiltAndroidApp 注解的 Application 类。@HiltAndroidApp 将会触发 Hilt 代码的生成,包括用作应用程序依赖项容器的基类(app/src/main/java/com/zj/wanandroid/MyApp.kt:1-29):

kotlin
1@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):

kotlin
1@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}

安装步骤

克隆项目

bash
1git clone https://github.com/RicardoJiang/wanandroid-compose.git
2cd wanandroid-compose

环境准备

  1. 确保已安装 Android Studio Arctic Fox 或更高版本
  2. 确保 JDK 版本为 1.8
  3. 配置 Android SDK,最低 API Level 21

构建与运行

推荐路径(通过 Android Studio)

  1. 使用 Android Studio 打开项目根目录
  2. 等待 Gradle Sync 完成
  3. 点击 Run 按钮或使用快捷键运行应用

命令行构建(建议/常见做法,未在仓库证据中出现)

bash
1# Linux/macOS
2./gradlew assembleDebug
3
4# Windows
5gradlew.bat assembleDebug

安装 APK 到设备(需要确认具体命令,未在仓库证据中出现):

bash
1# Linux/macOS
2./gradlew installDebug
3
4# Windows
5gradlew.bat installDebug

运行验证

验证构建成功

构建成功后,可在 app/build/outputs/apk/debug/ 目录下找到生成的 APK 文件。预期输出应包含 BUILD SUCCESSFUL 信息(需要确认,未在仓库证据中出现)。

验证应用启动

应用启动后,应显示 WanAndroid 首页内容,包含首页、广场、问答、体系、我的等底部导航栏。应用包名为 com.zj.wanandroidapp/build.gradle:14)。

验证核心功能

功能模块验证方法
首页列表滑动查看文章列表,点击文章进入详情
登录功能进入"我的"页面,点击登录,输入账号密码
广场页面查看广场文章列表,支持下拉刷新
页面导航使用底部导航栏切换页面,验证返回栈

ViewModel 与生命周期管理

ViewModel 获取方式

在 Compose 中一般可以通过两种方式获取 ViewModel(README.md:80-103):

kotlin
1// 方式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):

kotlin
1@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):

kotlin
1@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 失败,无法下载依赖。

解决方案

  1. 检查网络连接,确保能访问 Google Maven 仓库
  2. 项目配置了 google()mavenCentral() 仓库(settings.gradle:3-6
  3. 如在国内开发,建议配置镜像源或使用 VPN

问题2:Hilt 依赖注入失败

症状:运行时抛出 IllegalStateException,提示找不到依赖。

解决方案

  1. 确保 Application 类使用了 @HiltAndroidApp 注解(app/src/main/java/com/zj/wanandroid/MyApp.kt:17
  2. 确保 Activity 使用了 @AndroidEntryPoint 注解(app/src/main/java/com/zj/wanandroid/MainActivity.kt:12
  3. 检查 ViewModel 是否使用了 @HiltViewModel 注解
  4. 执行 Clean Project 后重新构建

问题3:Compose 预览不可用

症状:Android Studio 中 Compose Preview 面板显示错误或空白。

解决方案

  1. 确保使用 Android Studio Arctic Fox 或更高版本
  2. 检查 build.gradlecomposeOptions 配置(app/build.gradle:43-46
  3. 确保 Kotlin 编译器版本与 Compose 版本匹配
  4. 重新执行 Gradle Sync

问题4:LazyColumn 滚动位置丢失

症状:从详情页返回列表页时,列表滚动位置重置到顶部。

解决方案

  1. 对于普通页面,使用 rememberLazyListState 即可
  2. 对于带 Header/Footer 的 Paging 页面,将 LazyListState 保存在 ViewModel 中
  3. 在 Paging 数据加载完成后再应用保存的状态

下一步建议

完成快速上手后,建议继续探索以下内容:

  1. 深入 MVI 架构:了解 ViewState、ViewEvent、ViewAction 的完整定义和使用方式
  2. Navigation 导航:学习 Compose Navigation 的路由配置和参数传递
  3. 网络请求:研究 Retrofit 与 Compose 的集成方式
  4. 分页加载:深入 Paging3 在 Compose 中的应用

更多实现细节可参考项目源码和 README 中的架构说明部分(README.md:14-67)。