目录
  1. 1. 一、Jetpack 的架构哲学
  2. 2. 二、ViewModel:UI 状态的持有者
    1. 2.1. 2.1 ViewModel 的存储与恢复机制
    2. 2.2. 2.2 配置变更时保留 ViewModel 的原理
  3. 3. 三、LiveData/Flow:数据的观察者
    1. 3.1. 3.1 LiveData 的生命周期感知原理
    2. 3.2. 3.2 StateFlow:LiveData 的协程替代方案
  4. 4. 四、Navigation 组件:单 Activity 架构的支柱
  5. 5. 五、Paging 3:分页数据加载
  6. 6. 六、组件间的协同:一套完整的 MVVM 架构
  7. 7. 七、面试常问题目
解读开源框架系列-Jetpack组件架构设计

一、Jetpack 的架构哲学

Jetpack 是 Google 于 2018 年推出的 Android 开发组件套件,其核心理念是**”引导开发者走向正确的架构”。它不是一个单一框架,而是一系列相互配合的库,共同构成了一套有向无环图(DAG)的单向数据流架构**。

Google 推荐的架构分层:

┌──────────────────────────────────────────────────────┐
│ UI Layer (Activity/Fragment/Compose) │
│ - 显示数据,响应用户操作 │
│ - 持有 ViewModel 引用 │
├──────────────────────────────────────────────────────┤
│ ViewModel Layer │
│ - 持有 UI 状态(StateFlow / LiveData) │
│ - 暴露数据和操作给 UI │
│ - 不持有 Context(除了 AndroidViewModel 的 Application)│
├──────────────────────────────────────────────────────┤
│ Domain Layer (可选) │
│ - UseCase 用例 │
│ - 纯 Kotlin/Java,无 Android 依赖 │
├──────────────────────────────────────────────────────┤
│ Data Layer (Repository + DataSource) │
│ - Repository:单一数据源真相 │
│ - DataSource:Remote(网络)、Local(Room 数据库) │
└──────────────────────────────────────────────────────┘

核心设计原则:

  1. UDF(Unidirectional Data Flow):数据从 Data Layer 流向 UI Layer,事件从 UI Layer 向上传递。状态向下流动,事件向上冒泡。
  2. 生命周期感知:组件(LiveData, LifecycleObserver)自动响应 Lifecycle 的变化,避免内存泄漏和崩溃。
  3. 关注点分离:每层有自己的明确职责,通过接口松耦合。

二、ViewModel:UI 状态的持有者

ViewModel 是 Jetpack 架构中最核心的组件,负责管理与 UI 相关的数据。其关键设计是在配置变更(屏幕旋转)时保留数据

2.1 ViewModel 的存储与恢复机制

// AOSP 简化:androidx.lifecycle.ViewModelProvider
public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
this(owner.getViewModelStore(), ...);
}

public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
String canonicalName = modelClass.getCanonicalName();
return get("androidx.lifecycle.ViewModelProvider.DefaultKey:" + canonicalName, modelClass);
}

public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
ViewModel viewModel = mViewModelStore.get(key);
if (modelClass.isInstance(viewModel)) {
return (T) viewModel; // 缓存命中
}
// 通过 Factory 创建新的 ViewModel
viewModel = mFactory.create(modelClass);
mViewModelStore.put(key, viewModel);
return (T) viewModel;
}

ViewModelStore 是 ViewModel 的实际存储容器,它是一个简单的 HashMap 包装:

// androidx.lifecycle.ViewModelStore.java
public class ViewModelStore {
private final HashMap<String, ViewModel> mMap = new HashMap<>();

final void put(String key, ViewModel viewModel) {
ViewModel oldViewModel = mMap.put(key, viewModel);
if (oldViewModel != null) {
oldViewModel.onCleared();
}
}
}

2.2 配置变更时保留 ViewModel 的原理

ViewModelStore 被存储在 NonConfigurationInstances 中——这是 Activity 的一个内部机制,在 onRetainNonConfigurationInstance() 时保存,在 onCreate() 时恢复:

// ComponentActivity.java(简化逻辑)
@Override
public final Object onRetainNonConfigurationInstance() {
Object custom = onRetainCustomNonConfigurationInstance();
ViewModelStore viewModelStore = mViewModelStore;
if (viewModelStore == null) {
// 首次创建,从 lastNonConfigurationInstance 获取
NonConfigurationInstances nci = (NonConfigurationInstances) getLastNonConfigurationInstance();
if (nci != null) {
viewModelStore = nci.viewModelStore;
}
}
if (viewModelStore == null) {
viewModelStore = new ViewModelStore();
}
return new NonConfigurationInstances(custom, viewModelStore);
}

当 Activity 因配置变更(如旋转屏幕)被销毁重建时:

  1. 旧的 Activity 调用 onRetainNonConfigurationInstance(),将 ViewModelStore 保存。
  2. 新的 Activity 创建时,通过 getLastNonConfigurationInstance() 获取旧的 ViewModelStore
  3. ViewModelProvider.get() 发现 Store 中已有对应的 ViewModel,直接返回旧实例。

当 Activity 真正 finish(用户按返回键或调用 finish())时,ViewModelStore.clear() 被调用,所有 ViewModel 的 onCleared() 执行,释放资源。

三、LiveData/Flow:数据的观察者

3.1 LiveData 的生命周期感知原理

// androidx.lifecycle.LiveData.java
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
assertMainThread("observe");
if (owner.getLifecycle().getCurrentState() == DESTROYED) {
return; // 已销毁,忽略
}
LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
if (existing != null && !existing.isAttachedTo(owner)) {
throw new IllegalArgumentException("Cannot add the same observer"
+ " with different lifecycles");
}
owner.getLifecycle().addObserver(wrapper);
}

class LifecycleBoundObserver extends ObserverWrapper implements LifecycleEventObserver {
@Override
public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) {
Lifecycle.State currentState = mOwner.getLifecycle().getCurrentState();
if (currentState == DESTROYED) {
removeObserver(mObserver); // 自动解除订阅
return;
}
// 只在 STARTED 及以上状态(onStart 之后)通知更新
boolean shouldBeActive = currentState.isAtLeast(STARTED);
activeStateChanged(shouldBeActive);
}
}

LiveData 的生命周期感知表现在两个层面:

  • 自动取消:LifecycleOwner 进入 DESTROYED 时自动移除 Observer,防止内存泄漏。
  • 自动暂停/恢复:只有在 STARTED 及以上状态时才会将数据推送给 Observer。在 onStop 状态下(如应用切后台),LiveData 不会触发 UI 更新,避免不必要的渲染。

3.2 StateFlow:LiveData 的协程替代方案

Kotlin 的 StateFlow 逐渐成为 LiveData 的推荐替代方案:

// ViewModel 中使用 StateFlow
class NewsViewModel(private val repository: NewsRepository) : ViewModel() {
private val _uiState = MutableStateFlow(NewsUiState())
val uiState: StateFlow<NewsUiState> = _uiState.asStateFlow()

fun refresh() {
viewModelScope.launch {
try {
_uiState.update { it.copy(isLoading = true) }
val news = repository.getLatestNews()
_uiState.update { it.copy(isLoading = false, news = news) }
} catch (e: Exception) {
_uiState.update { it.copy(isLoading = false, error = e.message) }
}
}
}
}

// Activity/Fragment 中使用
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect { state ->
// 在 STARTED 及以上状态下收集,自动暂停/恢复
updateUI(state)
}
}
}

StateFlow 与 LiveData 的关键区别:

  • StateFlow 需要初始值(热流),LiveData 不强制初始值。
  • StateFlow 使用 ===(引用相等)去除重复值,LiveData 使用 equals()
  • StateFlow 依赖协程(Kotlin),LiveData 是 Java 实现,适合纯 Java 项目。

四、Navigation 组件:单 Activity 架构的支柱

Navigation 组件实现了单 Activity 多 Fragment的导航架构。其核心抽象是导航图(Navigation Graph):

<!-- res/navigation/nav_graph.xml -->
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/nav_graph"
app:startDestination="@id/homeFragment">

<fragment
android:id="@+id/homeFragment"
android:name="com.example.HomeFragment"
android:label="Home">
<action
android:id="@+id/action_home_to_detail"
app:destination="@id/detailFragment">
<argument
android:name="newsId"
app:argType="string" />
</action>
</fragment>

<fragment
android:id="@+id/detailFragment"
android:name="com.example.DetailFragment"
android:label="Detail" />
</navigation>

Navigation 内部栈管理的核心是 NavController

// androidx.navigation.NavController.java
public void navigate(@IdRes int resId, @Nullable Bundle args,
@Nullable NavOptions navOptions) {
NavDestination currentNode = mBackStack.isEmpty()
? mGraph
: mBackStack.peekLast().getDestination();
// 在导航图中查找目标
NavDestination node = currentNode.findNode(resId);
// 构建 NavBackStackEntry
// 执行 FragmentTransaction(对于 Fragment 目标)
// 将目标压入 backStack
}

Navigation 的 Safe Args 通过 Gradle 插件在编译期生成类型安全的参数类,避免字符串键值对的运行时错误。

五、Paging 3:分页数据加载

Paging 3 实现了增量数据加载的标准化方案,核心抽象是 PagingSourcePagingData

// 定义 PagingSource(数据源)
class NewsPagingSource(
private val newsApi: NewsApi,
private val query: String
) : PagingSource<Int, NewsItem>() {

override suspend fun load(params: LoadParams<Int>): LoadResult<Int, NewsItem> {
val page = params.key ?: 0
return try {
val response = newsApi.searchNews(query, page, params.loadSize)
LoadResult.Page(
data = response.items,
prevKey = if (page > 0) page - 1 else null,
nextKey = if (response.hasNext) page + 1 else null
)
} catch (e: Exception) {
LoadResult.Error(e)
}
}

override fun getRefreshKey(state: PagingState<Int, NewsItem>): Int? {
return state.anchorPosition?.let { anchorPosition ->
state.closestPageToPosition(anchorPosition)?.prevKey?.plus(1)
?: state.closestPageToPosition(anchorPosition)?.nextKey?.minus(1)
}
}
}

// Repository 层暴露 Flow<PagingData>
class NewsRepository(private val newsApi: NewsApi) {
fun searchNews(query: String): Flow<PagingData<NewsItem>> {
return Pager(
config = PagingConfig(pageSize = 20, enablePlaceholders = false),
pagingSourceFactory = { NewsPagingSource(newsApi, query) }
).flow
}
}

// ViewModel 中收集
class NewsViewModel(repository: NewsRepository) : ViewModel() {
val newsItems: Flow<PagingData<NewsItem>> = repository.searchNews("android")
.cachedIn(viewModelScope) // 缓存数据,配置变更后复用
}

Paging 3 的 RemoteMediator 支持网络+本地数据库的混合加载——先从 Room 数据库加载缓存数据,再从网络加载新数据并写入 Room,让 Room 自动通知 UI 更新。这实现了 Google 推荐的”单一数据源(SSOT,Single Source of Truth)”模式。

六、组件间的协同:一套完整的 MVVM 架构

将上述组件组合在一起的典型示例:

// Data Layer
@Dao
interface NewsDao {
@Query("SELECT * FROM news ORDER BY publishedAt DESC")
fun getAllNews(): PagingSource<Int, NewsEntity>
}

class NewsRepository(
private val newsApi: NewsApi,
private val newsDao: NewsDao
) {
fun getNews(): Flow<PagingData<NewsEntity>> {
return Pager(
config = PagingConfig(pageSize = 20),
remoteMediator = NewsRemoteMediator(newsApi, newsDao),
pagingSourceFactory = { newsDao.getAllNews() }
).flow
}
}

// ViewModel Layer
@HiltViewModel
class NewsViewModel @Inject constructor(
private val repository: NewsRepository
) : ViewModel() {
val newsPagingData = repository.getNews().cachedIn(viewModelScope)
}

// UI Layer
@AndroidEntryPoint
class NewsFragment : Fragment(R.layout.fragment_news) {
private val viewModel: NewsViewModel by viewModels()
private val adapter = NewsAdapter()

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.newsPagingData.collect { pagingData ->
adapter.submitData(pagingData)
}
}
}
}
}

这个架构中,每层的职责非常清晰:

  • Room(Data Layer) 是唯一的数据源。
  • Repository 协调 Remote(网络)和 Local(数据库)两个数据源。
  • ViewModel 持有 UI 状态,暴露 Flow<PagingData> 给 UI。
  • Fragment(UI Layer) 仅负责展示数据,通过 repeatOnLifecycle(STARTED) 安全收集流。
  • Hilt 负责所有对象的依赖注入。

七、面试常问题目

Q1: ViewModel 如何在屏幕旋转后保持数据?它会在 Activity 的 onDestroy 中销毁吗?

ViewModel 通过 onRetainNonConfigurationInstance() / getLastNonConfigurationInstance() 机制在配置变更时保留。ViewModel 存储在 ViewModelStore 中,ViewModelStore 被包裹在 NonConfigurationInstances 中。当 Activity 因屏幕旋转重建时,新的 Activity 通过 getLastNonConfigurationInstance() 获取旧的 ViewModelStore 及其中所有的 ViewModel。只有当 Activity 真正 finish(用户退出)时,系统才会调用 ViewModelStore.clear()ViewModel.onCleared()

Q2: LiveData 的 observe 和 Flow 的 collect 在日常使用中有什么本质区别?

LiveData 是生命周期感知的,observe() 自动在 DESTROYED 时解除订阅,在非 STARTED 时(如切后台)暂停分发。StateFlow 本身不是生命周期感知的,需要用 repeatOnLifecycle(STARTED) 配合 lifecycleScope 实现类似效果。LiveData 总是将最新值发送给新注册的 Observer(粘性事件),StateFlow 也是这样。LiveData 的 observe 回调在主线程执行,而 Flow 的 collect 在协程上下文中执行,不保证在主线程。

Q3: Paging 3 的 cachedIn 做了什么?为什么需要它?

cachedIn(viewModelScope) 将 Flow<PagingData> 缓存到指定 CoroutineScope 中。当配置变更(如旋转屏幕)时,ViewModel 存活但 Flow 的 collect 可能中断。如果没有 cachedIn,重新 collect 时会触发 PagingSource 的新一轮 load。cachedIn 确保 PagingData 在 ViewModel 的作用域内被缓存,配置变更后能立即恢复数据而无需重新请求网络。

Q4: Navigation 组件相比手动 FragmentTransaction 有什么优势?

(1) 声明式导航图——可视化导航结构,便于理解和维护。(2) 类型安全——Safe Args 编译期生成参数类。(3) 自动处理 FragmentTransaction——正确管理 back stack,包括深度链接(deep link)。(4) 统一的动画和过渡。(5) 内置支持 BottomNavigationView、NavigationDrawer 等常见 UI 模式。(6) 支持导航结果的传递(Fragment 之间的双向数据传递)。


参考源码路径:

  • ViewModel:androidx.lifecycle:lifecycle-viewmodel/src/main/java/androidx/lifecycle/ViewModelProvider.java
  • ViewModelStore:androidx.lifecycle:lifecycle-viewmodel/src/main/java/androidx/lifecycle/ViewModelStore.java
  • LiveData:androidx.lifecycle:lifecycle-livedata-core/src/main/java/androidx/lifecycle/LiveData.java
  • ComponentActivity:androidx.activity:activity/src/main/java/androidx/activity/ComponentActivity.java
  • Navigation:androidx.navigation:navigation-runtime/src/main/java/androidx/navigation/NavController.java
  • Paging 3:androidx.paging:paging-common/src/main/kotlin/androidx/paging/PagingSource.kt
  • Google 架构指南:https://developer.android.com/topic/architecture
打赏
  • 微信
  • 支付宝

评论