安卓开发:ViewModel与AndroidViewModel实战解析——让数据管理不再“旋转”失控

概述

在安卓开发中,屏幕旋转堪称“数据杀手”——Activity重建时,临时数据瞬间蒸发,仿佛程序员对着黑屏哀叹:“我的用户列表呢?!”这时,ViewModel与AndroidViewModel如同超级英雄登场,专治各种配置变更引发的数据丢失症。本文将以严谨的技术解析搭配幽默的实战案例,带你玩转这两个架构组件

正文

一、为什么需要ViewModel?——解决安卓开发的“旋转焦虑”

1. 传统方案的痛点

当Activity因屏幕旋转重建时,onSaveInstanceState()只能保存少量简单数据(如字符串、整型),而复杂对象(如用户列表、网络请求结果)会直接丢失。若在onCreate()中重新加载数据,不仅效率低下,还可能引发内存泄漏——UI控制器(Activity/Fragment)成了“数据保姆”,既要处理用户交互,又要管理异步请求,最终累成“过劳死”。

2. ViewModel的救赎

ViewModel的核心使命是管理UI相关数据,并在配置变更时自动保留数据。它的生命周期比Activity更长(Activity销毁时才清理),且绝不持有Context或View引用,避免内存泄漏。例如,一个显示用户列表的Activity可以将数据加载逻辑交给ViewModel,旋转屏幕后,ViewModel依然存活,数据完好无损

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("李四")) // 假设从网络或数据库加载    
    }
}

二、ViewModel实战:从入门到“旋转不晕”

1. 基本用法:三步构建数据保险箱步骤1:创建ViewModel类

继承ViewModel,定义数据和方法。例如,一个计数器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中获取实例

使用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:观察数据变化

通过LiveData监听数据更新,自动刷新UI

2. 高级场景:

跨Fragment共享数据当两个Fragment需要同步数据时(如主-从列表详情),可通过Activity作用域共享ViewModel:

// FragmentA
class  FragmentA : Fragment() {    
      private val sharedViewModel: SharedViewModel by activityViewModels()  
           override fun onViewCreated(view: View, savedInstanceState: Bundle?) {   
              sharedViewModel.selectItem(Item("数据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. 带参数初始化:Factory模式解耦若

ViewModel需要依赖注入(如Repository),可通过ViewModelProvider.Factory实现:

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:当ViewModel需要“上下文”

1. 何时使用AndroidViewModel?

若ViewModel需要访问系统服务(如Context.getSystemService()),可继承AndroidViewModel,通过构造函数传入Application上下文(Application是全局单例,不会引发内存泄漏)。

class LocationViewModel(application: Application) : AndroidViewModel(application) {  
  private val locationManager = application.getSystemService(Context.LOCATION_SERVICE) as LocationManager  
      fun getLocation() {        
           // 使用locationManager获取位置   
      }
}

2. 与普通ViewModel的区别

特性ViewModelAndroidViewModel上下文持有禁止持有Context通过Application持有上下文适用场景纯数据管理需要系统服务的场景内存泄漏风险无需确保Application不泄露


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

四、源码解析:

ViewModel的“黑科技”1. 生命周期管理:ViewModelStore的魔法

Activity通过ViewModelStoreOwner接口管理ViewModel存储。配置变更时,ViewModelStore被保留,新Activity直接复用原ViewModel

// ComponentActivity.java
 public ViewModelStore getViewModelStore() {    
      if (mViewModelStore == null) {        
         NonConfigurationInstances nc = getLastNonConfigurationInstance();        
         if (nc != null) {            
             mViewModelStore = nc.viewModelStore; // 从上次配置变更恢复        
          }        
        if (mViewModelStore == null) {            
            mViewModelStore = new ViewModelStore(); // 首次创建        
        }    
    }    
   return mViewModelStore;
}

2. 线程安全:双重校验锁保证单例

ViewModel的创建通过putIfAbsent保证原子性,避免多线程重复创建:

// 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 {        
          // 双重校验锁        
                synchronized (mMap) {            
                viewModel = mMap.get(key);            
                 if (viewModel == null) {                
                     viewModel = factory.create(modelClass);        
                     ViewModel existing = mMap.putIfAbsent(key, viewModel);                
                      if (existing != null) {                    
                              viewModel = existing; // 复用已存在实例                
                      }            
                 }       
      }        
       return (T) viewModel;    
     }
}

五、最佳实践:让ViewModel更“健壮”

  1. 职责单一原则
    每个ViewModel应只负责一个屏幕或功能模块的数据管理。例如,用户列表ViewModel不应同时处理登录逻辑。
  2. 避免内存泄漏
    禁止持有Activity/Fragment的引用(如通过接口回调)。使用LiveData暴露数据时,返回不可变类型(如LiveData<List<User>>而非MutableLiveData)。
  3. 结合Repository模式
    将数据操作(网络/数据库)委托给Repository层,ViewModel仅负责协调:
class UserViewModel(private val repository: UserRepository) : ViewModel() {    
         val users = repository.getUsers() 
         // 从Repository获取数据    
          fun refreshUsers() {        
                 viewModelScope.launch {            
                     repository.fetchUsers() // 触发网络请求        
                 }    
         }
}
  1. 测试友好
    ViewModel应易于单元测试,模拟依赖对象(如Repository):
@Test
fun `test load users`() {   
      val mockRepository = mock(UserRepository::class.java)    
       val viewModel = UserViewModel(mockRepository)    
       // 模拟Repository返回数据    
       `when`(mockRepository.getUsers()).
          thenReturn(MutableLiveData(listOf(User("测试用户"))))    
  // 验证数据是否正确    
 assertEquals(1, viewModel.users.value?.size)}

六、幽默彩蛋:

ViewModel的“人生哲学”ViewModel的座右铭:“旋转吧,屏幕!我的数据永不丢失!”AndroidViewModel的独白:“我需要Context?不,我只需要Application——毕竟,全局单例才是真爱。”Fragment的吐槽:“以前我要自己管数据,现在有了ViewModel,终于可以专心当‘花瓶’了!”

七、总结:

ViewModel——安卓开发的“数据保镖”ViewModel与AndroidViewModel通过生命周期感知能力和数据持久化特性,彻底解决了配置变更导致的数据丢失问题。结合LiveData、Repository等组件,开发者可构建出高内聚、低耦合的架构。无论是简单计数器还是复杂数据驱动界面,它们都是实现清晰代码的关键工具。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容