Jetpack ViewModel 基本使用

简介

ViewModel,直接翻译过来就是"视图模型",再换个说法,其实就是"界面模型"。界面,在Android系统中通常采用Activity和Fragment来承载。那么,“界面模型”,我理解就是用于处理界面数据,界面逻辑等内容的载体,便于分担传统MVC架构中Controller角色的职责。由此,可总结ViewModel的基本作用:

  • 数据持久化

    如果系统销毁或重新创建界面控制器,则存储在其中的任何临时性界面相关数据都会丢失。例如,应用的某个 Activity 中可能包含用户列表。因配置更改而重新创建 Activity 后,新 Activity 必须重新提取用户列表。对于简单的数据,Activity 可以使用 onSaveInstanceState() 方法从 onCreate() 中的捆绑包恢复其数据,但此方法仅适合可以序列化再反序列化的少量数据,而不适合数量可能较大的数据,如用户列表或位图

  • 避免异步回调内存泄漏

    界面控制器经常需要进行异步调用,这些调用可能需要一些时间才能返回结果。界面控制器需要管理这些调用,并确保系统在其销毁后清理这些调用以避免潜在的内存泄露。此项管理需要大量的维护工作,并且在因配置更改而重新创建对象的情况下,会造成资源的浪费,因为对象可能需要重新发出已经发出过的调用。

  • 分担Activity/Fragment工作

    诸如 Activity 和 Fragment 之类的界面控制器主要用于显示界面数据、对用户操作做出响应或处理操作系统通信(如权限请求)。如果要求界面控制器也负责从数据库或网络加载数据,那么会使类越发膨胀。为界面控制器分配过多的责任可能会导致单个类尝试自己处理应用的所有工作,而不是将工作委托给其他类。以这种方式为界面控制器分配过多的责任也会大大增加测试的难度。

ViewModel生命周期

ViewModel 对象存在的时间范围是获取 ViewModel 时传递给 ViewModelProvider 的 Lifecycle。ViewModel 将一直留在内存中,直到限定其存在时间范围的 Lifecycle 永久消失.

  • 对于 Activity,是在 Activity 完成时;
  • 对于 Fragment,是在 Fragment 分离时。

ViewModel生命周期

上图说明的是Activity 经历屏幕旋转而后结束的过程中所处的各种生命周期状态。通常在系统首次调用 Activity 对象的 onCreate() 方法时请求 ViewModel。系统可能会在 Activity 的整个生命周期内多次调用 onCreate(),如在旋转设备屏幕时。ViewModel 存在的时间范围是从您首次请求 ViewModel 直到 Activity 完成并销毁。

基本使用

ViewModel一般和LiveData搭配使用,ViewModel通过LiveData持久化界面数据,然后界面通过LiveData观察数据变化进行相应的界面调整。

创建ViewModel

class RefreshViewModel : ViewModel() {
    val bannerLiveData = MutableLiveData<List<Banner>>()
    val moshi = Moshi.Builder().build()

    fun getBanner() {
        viewModelScope.launch(Dispatchers.IO) {
            ……
        }
    }
}

构建界面对应ViewModel直接继承ViewModel抽象类即可,这里还有一类特殊的ViewModel:AndroidViewModel 。可以查看下源码,发现其保存了Application对象,所以在创建过程中需要传入对应的Application实例。那么它有何作用呢?其实它的作用就是Application的作用,拥有了Application,就可以拥有全局资源。

/**
 * Application context aware {@link ViewModel}.
 * <p>
 * Subclasses must have a constructor which accepts {@link Application} as the only parameter.
 * <p>
 */
public class AndroidViewModel extends ViewModel {
    @SuppressLint("StaticFieldLeak")
    private Application mApplication;

    public AndroidViewModel(@NonNull Application application) {
        mApplication = application;
    }

    /**
     * Return the application.
     */
    @SuppressWarnings({"TypeParameterUnusedInFormals", "unchecked"})
    @NonNull
    public <T extends Application> T getApplication() {
        return (T) mApplication;
    }
}

获取ViewModel

配合Fragmen KTX模块使用,获取ViewModel相当容易。添加fragment-ktx依赖,然后调用快捷代理方法。

dependencies {
    implementation "androidx.fragment:fragment-ktx:1.2.5"
}
// Get a reference to the ViewModel scoped to this Fragment
val viewModel by viewModels<MyViewModel>()

// Get a reference to the ViewModel scoped to its Activity
val viewModel by activityViewModels<MyViewModel>()

若ViewModel构造方法需要传参,则需按照如下步骤操作:

  • 构建工厂类,实现ViewModelProvider.Factory接口
  • 实现ViewModelProvider.Factory接口create方法,构建ViewModel
  • 传入工厂类实例至viewmodels()代理方法
class RefreshVMFactory : ViewModelProvider.Factory {
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        if (modelClass.isAssignableFrom(RefreshViewModel::class.java)) {
            @Suppress("UNCHECKED_CAST")
            return RefreshViewModel("I am a param") as T
        }
        throw IllegalArgumentException("Unknown ViewModel class")
    }
}
private val viewModel: RefreshViewModel by viewModels { RefreshVMFactory() }

Activity、Fragment内观察数据变化

viewModel.bannerLiveData.observe(this, Observer { banner ->
    mBinding.refresh.isRefreshing = false
    adapter.submitList(banner)
})

更喜欢的一种用法

其实我更喜欢ViewModel的另一个使用场景,就是在 Fragment 之间共享数据。以前此类问题只能通过接口或者EventBus等来处理,并且需要注意生命周期的问题。通过使用 Activity 范围共享ViewModel,可以很好的解决这个痛点。如下示例,两个ViewPager中的Fragment,都可以进行计数加一的操作,计数结果在Activity范围内共享,也就是两个Fragment下的计数显示相同,并非每个Fragment只处理自己的计数操作。

class Vp2ViewModel : ViewModel() {
    val sample = MutableLiveData(0)

    fun addOne() {
        sample.postValue(sample.value?.plus(1))
    }
}
class FirstFragment : Fragment() {

    companion object {
        fun newInstance() = FirstFragment()
    }

    private lateinit var mBinding: FragmentFirstBinding
    private val viewModel: Vp2ViewModel by activityViewModels()
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {

        if (!this::mBinding.isInitialized) {
            mBinding = FragmentFirstBinding.inflate(inflater, container, false)
            mBinding.viewModel = viewModel
            viewModel.sample.observe(viewLifecycleOwner, Observer {
                mBinding.viewModel = viewModel
            })
        }
        return mBinding.root
    }
}

SecondFragment和FirstFragment基本相同。

class TabVp2Activity : AppCompatActivity() {

    private lateinit var mBinding: ActivityTabVp2Binding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        mBinding = DataBindingUtil.setContentView(this, R.layout.activity_tab_vp2)
        val adapter = Vp2Adapter(this)
        mBinding.vp2.adapter = adapter

        val tabConfigurationStrategy =
            TabLayoutMediator.TabConfigurationStrategy { tab, position ->
                when (position) {
                    0 -> tab.text = "first"
                    1 -> tab.text = "second"
                }
            }
        val tabLayoutMediator =
            TabLayoutMediator(mBinding.tab, mBinding.vp2, tabConfigurationStrategy)
        tabLayoutMediator.attach()
    }
}

效果基本如下:
Fragments间个共享数据

示例源码

https://github.com/onlyloveyd/AndroidPractice

扫码关注,持续更新

回复【计算机视觉】【Android】【Flutter】【数字图像处理】获取对应学习资料。
欢迎留言交流。

onlyloveyd CSDN认证博客专家 Android Kotlin OpenCV
个人公众号【OpenCV or Android】,热爱Android、Kotlin、Flutter和OpenCV。毕业于华中科技大学计算机专业,曾就职于华为武汉研究所。目前在三线小城市生活,专注技术与研发。
©️2020 CSDN 皮肤主题: 数字20 设计师:CSDN官方博客 返回首页