一、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 数据库) │ └──────────────────────────────────────────────────────┘
|
核心设计原则:
- UDF(Unidirectional Data Flow):数据从 Data Layer 流向 UI Layer,事件从 UI Layer 向上传递。状态向下流动,事件向上冒泡。
- 生命周期感知:组件(LiveData, LifecycleObserver)自动响应 Lifecycle 的变化,避免内存泄漏和崩溃。
- 关注点分离:每层有自己的明确职责,通过接口松耦合。
二、ViewModel:UI 状态的持有者
ViewModel 是 Jetpack 架构中最核心的组件,负责管理与 UI 相关的数据。其关键设计是在配置变更(屏幕旋转)时保留数据。
2.1 ViewModel 的存储与恢复机制
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; } viewModel = mFactory.create(modelClass); mViewModelStore.put(key, viewModel); return (T) viewModel; }
|
ViewModelStore 是 ViewModel 的实际存储容器,它是一个简单的 HashMap 包装:
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() 时恢复:
@Override public final Object onRetainNonConfigurationInstance() { Object custom = onRetainCustomNonConfigurationInstance(); ViewModelStore viewModelStore = mViewModelStore; if (viewModelStore == null) { NonConfigurationInstances nci = (NonConfigurationInstances) getLastNonConfigurationInstance(); if (nci != null) { viewModelStore = nci.viewModelStore; } } if (viewModelStore == null) { viewModelStore = new ViewModelStore(); } return new NonConfigurationInstances(custom, viewModelStore); }
|
当 Activity 因配置变更(如旋转屏幕)被销毁重建时:
- 旧的 Activity 调用
onRetainNonConfigurationInstance(),将 ViewModelStore 保存。
- 新的 Activity 创建时,通过
getLastNonConfigurationInstance() 获取旧的 ViewModelStore。
ViewModelProvider.get() 发现 Store 中已有对应的 ViewModel,直接返回旧实例。
当 Activity 真正 finish(用户按返回键或调用 finish())时,ViewModelStore.clear() 被调用,所有 ViewModel 的 onCleared() 执行,释放资源。
三、LiveData/Flow:数据的观察者
3.1 LiveData 的生命周期感知原理
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; } boolean shouldBeActive = currentState.isAtLeast(STARTED); activeStateChanged(shouldBeActive); } }
|
LiveData 的生命周期感知表现在两个层面:
- 自动取消:LifecycleOwner 进入 DESTROYED 时自动移除 Observer,防止内存泄漏。
- 自动暂停/恢复:只有在 STARTED 及以上状态时才会将数据推送给 Observer。在 onStop 状态下(如应用切后台),LiveData 不会触发 UI 更新,避免不必要的渲染。
3.2 StateFlow:LiveData 的协程替代方案
Kotlin 的 StateFlow 逐渐成为 LiveData 的推荐替代方案:
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) } } } } }
lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.uiState.collect { state -> updateUI(state) } } }
|
StateFlow 与 LiveData 的关键区别:
- StateFlow 需要初始值(热流),LiveData 不强制初始值。
- StateFlow 使用
===(引用相等)去除重复值,LiveData 使用 equals()。
- StateFlow 依赖协程(Kotlin),LiveData 是 Java 实现,适合纯 Java 项目。
四、Navigation 组件:单 Activity 架构的支柱
Navigation 组件实现了单 Activity 多 Fragment的导航架构。其核心抽象是导航图(Navigation Graph):
<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:
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); }
|
Navigation 的 Safe Args 通过 Gradle 插件在编译期生成类型安全的参数类,避免字符串键值对的运行时错误。
五、Paging 3:分页数据加载
Paging 3 实现了增量数据加载的标准化方案,核心抽象是 PagingSource 和 PagingData:
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) } } }
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 } }
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 架构
将上述组件组合在一起的典型示例:
@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 } }
@HiltViewModel class NewsViewModel @Inject constructor( private val repository: NewsRepository ) : ViewModel() { val newsPagingData = repository.getNews().cachedIn(viewModelScope) }
@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