内容简介:在 Android 中,ViewModel 的作用就是在在屏幕旋转的时候,Activity 会重建,为了不让数据丢失,我们通常的做法是在另一方面,UI 控制器通常需要做一些耗时的异步调用操作,并且需要去管理这些调用。UI 控制器需要确保系统在销毁后去清理掉这些异步调用,以避免潜在的内存泄漏,这种管理方式需要大量的维护工作。而且,在配置更改后重建对象是很浪费资源的,因为该对象可能必须重新发出之前已经发出过的调用。
在 Android 中,ViewModel 的作用就是在 UI 控制器 ( 如 Activity、Fragment)的生命周期中保存和管理 UI 相关的数据。ViewModel 保存的数据在配置更改(如屏幕旋转)后会依然存在,不会丢失。
在屏幕旋转的时候,Activity 会重建,为了不让数据丢失,我们通常的做法是在 onSaveInstanceState()
方法中通过 bundle 保存数据,然后在 onCreate()
或 onRestoreInstanceState()
方法中取出 bundle 来恢复数据。然而,这种方式有一定的局限性,它只适用于 可序列化然后反序列化 的少量数据,对于 Bitmap 等比较大的数据就不适用了。
另一方面,UI 控制器通常需要做一些耗时的异步调用操作,并且需要去管理这些调用。UI 控制器需要确保系统在销毁后去清理掉这些异步调用,以避免潜在的内存泄漏,这种管理方式需要大量的维护工作。而且,在配置更改后重建对象是很浪费资源的,因为该对象可能必须重新发出之前已经发出过的调用。
UI 控制器一般只负责显示和处理用户操作,加载数据库数据或网络数据的工作应该委托给其它类,这样会让测试工作更加容易地进行。因此, 将视图数据相关操作从 UI 控制器逻辑中分离出来是很有必要。
ViewModel 使用
比如,一个 ViewModelActivity 需要展示一个 User 的列表数据,那么可以定义一个 UserViewModel 来获取数据,然后在 ViewModelActivity 中创建一个 UserViewModel 对象来获取到 User 的列表数据。
class UserViewModel : ViewModel() { private lateinit var users: MutableLiveData<List<User>> fun getUsers(): LiveData<List<User>> { if (!::users.isInitialized) { users = MutableLiveData() loadUsers() } return users } private fun loadUsers() { // Do an asynchronous operation to fetch users . Thread(Runnable { Thread.sleep(3000) // 在子线程发送值用 postValue , 否则用 setValue . users.postValue(listOf(User("1", "AA"), User("2", "BB"))) }).start() } } 复制代码
class ViewModelActivity : AppCompatActivity() { private val TAG = "ViewModelActivity" override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_view_model) // 就算配置更改(如屏幕旋转)了,获取到的 userViewModel 对象还会是上一次的 UserViewModel 对象 val userViewModel = ViewModelProviders.of(this).get(UserViewModel::class.java) // 这里的 this 需要用实现了 LifecycleOwner 的类的 this . 如 AppCompatActivity、FragmentActivity userViewModel.getUsers().observe(this, Observer { Log.e(TAG, it.toString()) // 打印结果:[User(id=1, name=AA), User(id=2, name=BB)] }) } } 复制代码
查看源码可知,ViewModelProviders.of(this) 获取了一个全新的 ViewModelProvider 对象,
public static ViewModelProvider of(@NonNull FragmentActivity activity, @Nullable Factory factory) { Application application = checkApplication(activity); if (factory == null) { factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application); } return new ViewModelProvider(ViewModelStores.of(activity), factory); } 复制代码
ViewModelProvider 对象调用 get() 方法获取到我们需要的 ViewModel 对象。追踪一下 get() 方法可以知道,ViewModel 对象是存储在一个 ViewModelStore 类的对象中的,该类里面使用 HashMap 来保存和获取 ViewModel .
ViewModel viewModel = mViewModelStore.get(key); 复制代码
获取 ViewModel 使用的 key 相对具体的 ViewModel 类是不会变化的,因此从 ViewModelStore 中取出的 ViewModel 对象也不会变。包括在配置更改后也可以获取到之前的 ViewModel .
当宿主 Activity 调用了 finish() 方法,系统会调用 ViewModel 对象的 onCleared() 方法来让它清理掉资源,到这里之后 ViewModel 才会被释放掉。
ViewModel 里面不要引用 View、或者任何持有 Activity 类的 context , 否则会引发内存泄漏问题。
当 ViewModel 需要 Application 类的 context 来获取资源、查找系统服务等,可以继承 AndroidViewModel 类。
class MyAndroidViewModel(application: Application) : AndroidViewModel(application) { private val app get() = getApplication<Application>() fun getStatus(code: Int): String { return when (code) { 1 -> app.resources.getString(R.string.be_late) // 迟到 2 -> app.resources.getString(R.string.leave_early) // 早退 else -> app.resources.getString(R.string.absenteeism) // 旷工 } } } 复制代码
val myAndroidViewModel = ViewModelProviders.of(this).get(MyAndroidViewModel::class.java) Log.e(TAG, myAndroidViewModel.getStatus(2)) // 打印结果:早退 复制代码
ViewModel 的生命周期
ViewModel 会一直保留在内存中,直到 Activity / Fragment 在以下情况下才会销毁:
- 宿主 Activity 被 finish 后调用 onDestroy 方法。
- 宿主 Fragment 被 detached 后调用 onDetach 方法。
下图展示了一个 Activity 经历了旋转然后调用 finish 的各种生命周期状态,同时展示了关联了该 Activity 的 ViewModel 的生命周期。(UI 控制器是 Fragment 的情况也类似。)
Fragment 之间共享数据
假设我们有这样的需求:在一个 MasterFragment 中有一个 User 列表,点击列表项后将点中的 User 对象传递给 DetailFragment 用于展示详细的 User 信息。
我们一般的做法是:在两个 Fragment 中定义一些通信接口,并且宿主 Activity 需要把它们绑定起来,这样做相当繁琐。并且两个 Fragment 还需要处理另外的 Fragment 尚未创建或者可见的场景。
为了避免以上繁琐的做法,我们可以通过两个 Fragment 之间共享一个 ViewModel 的方式来实现数据通信。
class SharedViewModel : ViewModel() { val selected = MutableLiveData<User>() fun select(user: User) { selected.value = user } } 复制代码
class MasterFragment : Fragment() { private val dataList = listOf(User("1", "张三"), User("2", "李四"), User("3", "王五")) override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { return inflater.inflate(R.layout.fragment_master, container, false) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) var model = activity?.run { ViewModelProviders.of(this).get(SharedViewModel::class.java) } ?: throw Exception("Invalid Activity") lvMaster.adapter = ArrayAdapter<User>( activity, android.R.layout.simple_expandable_list_item_1, dataList) lvMaster.setOnItemClickListener { _, _, position, _ -> model.select(dataList[position]) } } } 复制代码
class DetailFragment : Fragment() { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { return inflater.inflate(R.layout.fragment_detail, container, false) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) var model: SharedViewModel = activity?.run { ViewModelProviders.of(this).get(SharedViewModel::class.java) } ?: throw Exception("Invalid Activity") model.selected.observe(this, Observer<User> { item -> tvDetail.setText("${item?.id} : ${item?.name}") }) } } 复制代码
需要特别注意,两个 Fragment 都需要使用它们的宿主 Activty 的 this 来获取 ViewModelProviders , 这样才确保它们获取到的是同一个 ViewModel 对象。
这种数据通信的方式有以下几个好处:
- 宿主 Activity 不需要做任何的事情,也完全不知道 Fragment 之间的通信;
- 一个 Fragment 不需要知道另一个 Fragment 中除了 ViewModel 契约之外的其它事情,哪怕另一个 Fragment 消失了,它也继续保持正常工作;
- 每个 Fragment 都有自己的生命周期,它们之间互不影响,哪怕某一个 Fragment 被其它 Fragment 替换了,UI 还是会继续工作,没有任何问题。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Unix/Linux编程实践教程
Bruce Molay、杨宗源、黄海涛 / 杨宗源、黄海涛 / 清华大学出版社 / 2004-10-1 / 56.00元
操作系统是计算机最重要的系统软件。Unix操作系统历经了几十年,至今仍是主流的操作系统。本书通过解释Unix的工作原理,循序渐进地讲解实现Unix中系统命令的方法,让读者理解并逐步精通Unix系统编程,进而具有编制Unix应用程序的能力。书中采用启发式、举一反三、图示讲解等多种方法讲授,语言生动、结构合理、易于理解。每一章后均附有大量的习题和编程练习,以供参考。 本书适合作为高等院校计算机及......一起来看看 《Unix/Linux编程实践教程》 这本书的介绍吧!