概述
在安卓開發(fā)中,屏幕旋轉(zhuǎn)堪稱“數(shù)據(jù)殺手”——Activity重建時(shí),臨時(shí)數(shù)據(jù)瞬間蒸發(fā),仿佛程序員對(duì)著黑屏哀嘆:“我的用戶列表呢?!”這時(shí),ViewModel與AndroidViewModel如同超級(jí)英雄登場(chǎng),專治各種配置變更引發(fā)的數(shù)據(jù)丟失癥。本文將以嚴(yán)謹(jǐn)?shù)募夹g(shù)解析搭配幽默的實(shí)戰(zhàn)案例,帶你玩轉(zhuǎn)這兩個(gè)架構(gòu)組件
正文
一、為什么需要ViewModel?——解決安卓開發(fā)的“旋轉(zhuǎn)焦慮”
1. 傳統(tǒng)方案的痛點(diǎn)
當(dāng)Activity因屏幕旋轉(zhuǎn)重建時(shí),onSaveInstanceState()只能保存少量簡(jiǎn)單數(shù)據(jù)(如字符串、整型),而復(fù)雜對(duì)象(如用戶列表、網(wǎng)絡(luò)請(qǐng)求結(jié)果)會(huì)直接丟失。若在onCreate()中重新加載數(shù)據(jù),不僅效率低下,還可能引發(fā)內(nèi)存泄漏——UI控制器(Activity/Fragment)成了“數(shù)據(jù)保姆”,既要處理用戶交互,又要管理異步請(qǐng)求,最終累成“過勞死”。
2. ViewModel的救贖
ViewModel的核心使命是管理UI相關(guān)數(shù)據(jù),并在配置變更時(shí)自動(dòng)保留數(shù)據(jù)。它的生命周期比Activity更長(zhǎng)(Activity銷毀時(shí)才清理),且絕不持有Context或View引用,避免內(nèi)存泄漏。例如,一個(gè)顯示用戶列表的Activity可以將數(shù)據(jù)加載邏輯交給ViewModel,旋轉(zhuǎn)屏幕后,ViewModel依然存活,數(shù)據(jù)完好無損
class UserViewModel : ViewModel() {
private val _users = MutableLiveData<List<User>>()
val users: LiveData<List<User>> = _users
init {
loadUsers()
// 模擬異步加載
}
private fun loadUsers() {
_users.value = listOf(User("張三"),
User("李四")) // 假設(shè)從網(wǎng)絡(luò)或數(shù)據(jù)庫加載
}
}
二、ViewModel實(shí)戰(zhàn):從入門到“旋轉(zhuǎn)不暈”
1. 基本用法:三步構(gòu)建數(shù)據(jù)保險(xiǎn)箱步驟1:創(chuàng)建ViewModel類
繼承ViewModel,定義數(shù)據(jù)和方法。例如,一個(gè)計(jì)數(shù)器ViewModel:
class CounterViewModel : ViewModel() {
private val _count = MutableLiveData(0)
val count: LiveData<Int> = _count
fun increment() {
_count.value = _count.value?.plus(1)
}
}
步驟2:在Activity/Fragment中獲取實(shí)例
使用by viewModels()委托(Kotlin)或ViewModelProvider(Java)
class MainActivity : AppCompatActivity() {
private val viewModel: CounterViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel.count.observe(this) {
count -> tvCounter.text = "Count: $count"
}
btnIncrement.setOnClickListener {
viewModel.increment()
}
}
}
步驟3:觀察數(shù)據(jù)變化
通過LiveData監(jiān)聽數(shù)據(jù)更新,自動(dòng)刷新UI
2. 高級(jí)場(chǎng)景:
跨Fragment共享數(shù)據(jù)當(dāng)兩個(gè)Fragment需要同步數(shù)據(jù)時(shí)(如主-從列表詳情),可通過Activity作用域共享ViewModel:
// FragmentA
class FragmentA : Fragment() {
private val sharedViewModel: SharedViewModel by activityViewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
sharedViewModel.selectItem(Item("數(shù)據(jù)1"))
}
}
// FragmentB
class FragmentB : Fragment() {
private val sharedViewModel: SharedViewModel by activityViewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
sharedViewModel.selectedItem.observe(viewLifecycleOwner) {
item -> tvItem.text = item.name
}
}
}
// SharedViewModelclass
SharedViewModel : ViewModel() {
val selectedItem = MutableLiveData<Item>() fun selectItem(item: Item) {
selectedItem.value = item
}
}
3. 帶參數(shù)初始化:Factory模式解耦若
ViewModel需要依賴注入(如Repository),可通過ViewModelProvider.Factory實(shí)現(xiàn):
class UserViewModel(
private val userRepository: UserRepository) : ViewModel() {
val users =. userRepository.getUsers()
}
class UserViewModelFactory(private val repository: UserRepository) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return UserViewModel(repository) as T
}
}// 使用
val factory = UserViewModelFactory(provideUserRepository())
val viewModel = ViewModelProvider(this, factory).get(UserViewModel::class.java)
三、AndroidViewModel:當(dāng)ViewModel需要“上下文”
1. 何時(shí)使用AndroidViewModel?
若ViewModel需要訪問系統(tǒng)服務(wù)(如Context.getSystemService()),可繼承AndroidViewModel,通過構(gòu)造函數(shù)傳入Application上下文(Application是全局單例,不會(huì)引發(fā)內(nèi)存泄漏)。
class LocationViewModel(application: Application) : AndroidViewModel(application) {
private val locationManager = application.getSystemService(Context.LOCATION_SERVICE) as LocationManager
fun getLocation() {
// 使用locationManager獲取位置
}
}
2. 與普通ViewModel的區(qū)別
特性ViewModelAndroidViewModel上下文持有禁止持有Context通過Application持有上下文適用場(chǎng)景純數(shù)據(jù)管理需要系統(tǒng)服務(wù)的場(chǎng)景內(nèi)存泄漏風(fēng)險(xiǎn)無需確保Application不泄露

四、源碼解析:
ViewModel的“黑科技”1. 生命周期管理:ViewModelStore的魔法
Activity通過ViewModelStoreOwner接口管理ViewModel存儲(chǔ)。配置變更時(shí),ViewModelStore被保留,新Activity直接復(fù)用原ViewModel
// ComponentActivity.java
public ViewModelStore getViewModelStore() {
if (mViewModelStore == null) {
NonConfigurationInstances nc = getLastNonConfigurationInstance();
if (nc != null) {
mViewModelStore = nc.viewModelStore; // 從上次配置變更恢復(fù)
}
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore(); // 首次創(chuàng)建
}
}
return mViewModelStore;
}
2. 線程安全:雙重校驗(yàn)鎖保證單例
ViewModel的創(chuàng)建通過putIfAbsent保證原子性,避免多線程重復(fù)創(chuàng)建:
// ViewModelStore.java
public <T extends ViewModel> T get(String key, Class<T> modelClass) {
ViewModel viewModel = mMap.get(key);
if (viewModel != null && modelClass.isAssignableFrom(viewModel.getClass())) {
return (T) viewModel;
} else {
// 雙重校驗(yàn)鎖
synchronized (mMap) {
viewModel = mMap.get(key);
if (viewModel == null) {
viewModel = factory.create(modelClass);
ViewModel existing = mMap.putIfAbsent(key, viewModel);
if (existing != null) {
viewModel = existing; // 復(fù)用已存在實(shí)例
}
}
}
return (T) viewModel;
}
}
五、最佳實(shí)踐:讓ViewModel更“健壯”
- 職責(zé)單一原則
每個(gè)ViewModel應(yīng)只負(fù)責(zé)一個(gè)屏幕或功能模塊的數(shù)據(jù)管理。例如,用戶列表ViewModel不應(yīng)同時(shí)處理登錄邏輯。 - 避免內(nèi)存泄漏
禁止持有Activity/Fragment的引用(如通過接口回調(diào))。使用LiveData暴露數(shù)據(jù)時(shí),返回不可變類型(如LiveData<List<User>>而非MutableLiveData)。 - 結(jié)合Repository模式
將數(shù)據(jù)操作(網(wǎng)絡(luò)/數(shù)據(jù)庫)委托給Repository層,ViewModel僅負(fù)責(zé)協(xié)調(diào):
class UserViewModel(private val repository: UserRepository) : ViewModel() {
val users = repository.getUsers()
// 從Repository獲取數(shù)據(jù)
fun refreshUsers() {
viewModelScope.launch {
repository.fetchUsers() // 觸發(fā)網(wǎng)絡(luò)請(qǐng)求
}
}
}
- 測(cè)試友好
ViewModel應(yīng)易于單元測(cè)試,模擬依賴對(duì)象(如Repository):
@Test
fun `test load users`() {
val mockRepository = mock(UserRepository::class.java)
val viewModel = UserViewModel(mockRepository)
// 模擬Repository返回?cái)?shù)據(jù)
`when`(mockRepository.getUsers()).
thenReturn(MutableLiveData(listOf(User("測(cè)試用戶"))))
// 驗(yàn)證數(shù)據(jù)是否正確
assertEquals(1, viewModel.users.value?.size)}
六、幽默彩蛋:
ViewModel的“人生哲學(xué)”ViewModel的座右銘:“旋轉(zhuǎn)吧,屏幕!我的數(shù)據(jù)永不丟失!”AndroidViewModel的獨(dú)白:“我需要Context?不,我只需要Application——畢竟,全局單例才是真愛?!盕ragment的吐槽:“以前我要自己管數(shù)據(jù),現(xiàn)在有了ViewModel,終于可以專心當(dāng)‘花瓶’了!”
七、總結(jié):
ViewModel——安卓開發(fā)的“數(shù)據(jù)保鏢”ViewModel與AndroidViewModel通過生命周期感知能力和數(shù)據(jù)持久化特性,徹底解決了配置變更導(dǎo)致的數(shù)據(jù)丟失問題。結(jié)合LiveData、Repository等組件,開發(fā)者可構(gòu)建出高內(nèi)聚、低耦合的架構(gòu)。無論是簡(jiǎn)單計(jì)數(shù)器還是復(fù)雜數(shù)據(jù)驅(qū)動(dòng)界面,它們都是實(shí)現(xiàn)清晰代碼的關(guān)鍵工具。