安卓開發(fā):ViewModel與AndroidViewModel實(shí)戰(zhàn)解析——讓數(shù)據(jù)管理不再“旋轉(zhuǎn)”失控

概述

在安卓開發(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不泄露


875b171f-d16c-4405-a4b6-09aaa9b98b0e.png

四、源碼解析:

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更“健壯”

  1. 職責(zé)單一原則
    每個(gè)ViewModel應(yīng)只負(fù)責(zé)一個(gè)屏幕或功能模塊的數(shù)據(jù)管理。例如,用戶列表ViewModel不應(yīng)同時(shí)處理登錄邏輯。
  2. 避免內(nèi)存泄漏
    禁止持有Activity/Fragment的引用(如通過接口回調(diào))。使用LiveData暴露數(shù)據(jù)時(shí),返回不可變類型(如LiveData<List<User>>而非MutableLiveData)。
  3. 結(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)求        
                 }    
         }
}
  1. 測(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)鍵工具。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容