当前位置:   article > 正文

【Jetpack】学穿:ViewModel → 视图模型_jetpack recycleview和viewmodel

jetpack recycleview和viewmodel

0x1、引言

来来来,继续学穿Jetpack,本节带来组件 → ViewModel 视图模型的解读!叫 视图数据 可能更贴切,有人也叫 视图状态,都一个意思,怎么称呼看你自己喜欢~

ViewModel 一言以蔽之

ViewModel 将 视图数据视图控制器 中分离,并实现了 数据管理 的:一致性数据共享(跨页面通信)作用域可控

① 视图数据与控制器分离

视图控制器

一般代指Activity和Fragment,它们通过在屏幕上绘制View,捕获用户事件,处理用户与互动界面相关的操作来 控制界面

视图数据

就是你用来对控件setXxx()的数据源,它和与它相关的决策逻辑 (或者说管理) 不应该放到视图控制器中。

ViewModel所做的事,就是用 模版方法模式 进行封装,隐藏一些具体细节,提供简洁的API供我们使用。给了我们一种它们好像真的分离了的错觉,实际上还是与视图控制器紧密相连,ViewModel依旧被对应的Activity、Fragment所持有

最直观的体现::页面配置变更,引起页面销毁重建,ViewModel中的数据不会因此而丢失

写个简单的例子更直观,先不用ViewModel:

class TestActivity : AppCompatActivity() {
    private var mCount: Int = 0
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_test)
        tv_content.text = "${mCount}" 
    }
    bt_test.setOnClickListener { tv_content.text = "${++mCount}" }
} 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

操作:点击按钮mCount会自增1,旋转手机触发屏幕翻转,发现数字又从0开始了:

接着用上ViewModel,定义一个类继承ViewModel,把mCount丢到里面:

class TestViewModel: ViewModel() { var mCount = 0 } 
  • 1

改动下原代码:

class TestActivity : AppCompatActivity() {
    private lateinit var testViewModel: TestViewModel
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_test)
        testViewModel = ViewModelProvider(this).get(TestViewModel::class.java)
        tv_content.text = "${testViewModel.mCount}"
    }
    bt_test.setOnClickListener { tv_content.text = "${++testViewModel.mCount}" }
} 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

执行同样的操作,无论怎么翻转,数字都不会因页面重建而丢失(表现为从0开始)。

结论:把视图数据放到ViewModel里,就不会受页面配置变化销毁重建的影响。


② 一致性

对了,上面说到的配置变更,除了横竖屏切换外,还有这些:

分辨率调整、权限变更、系统字体样式变更、系统语言切换、多窗口设定、系统导航方式变更等。

在以前,为了避免这种 页面配置变更引起的页面销毁重建 导致的 视图数据丢失 问题,需要我们在 onSaveInstanceState()onRestoreInstanceState() 手动编写数据保存和恢复的语句。

页面一多、要保存恢复的数据一多、加之多人协作,就很容易出现 结果不一致的问题,比如:某人在编写存数据相关的代码,漏掉了某个数据,导致拿时没拿到正确的数据。

结论:使用ViewModel,你只管把数据丢里面就行,无需关心具体如何存取,间接保证了结果一致性。


扩展一下:有了ViewModel就不需要onSaveInstanceState()了?

答:非也非也,具体用哪个还得权衡数据复杂度、访问速度及生命周期,建议 混合使用,分而治之

怎么说?除了这两种存储恢复数据的方式外,还有一种 持久化存储,官方文档 《保存界面状态》 提供了一个维度参考表:

简要说下笔者的看法:

ViewModel

  • 数据存内存中,读取更快,和Activity(或其他生命周期所有者)关联,配置更改期间保留在内存中,系统会自动将ViewModel与发生配置更改后产生的新Activity实例关联;
  • 关联组件 (Activity或Fragment) 退出时,系统会自动销毁ViewModel,进程终止也会销毁;
  • 适用于:配置更改后数据需要继续存在的场景,支持复杂对象

onSaveInstanceState()

  • 用Bundle存储数据以便于跨进程传递,存储上限受限于Binder(1M),请勿用于存储大量数据(如Bitmap),也不要存需要冗长序列化和反序列化操作的复杂数据结构;
  • onSaveInstanceState()会将数据序列化到磁盘,如果序列化对象很复制,序列化时会占用大量内存,可能丢帧和视觉卡顿;
  • 适用于:配置更改后少量数据、Activity异常关闭,进程被终止后重新打开 需要恢复的场景。

持久性存储

如果 数据的恢复非常重要、存储数据非常大、数据需要长期存储 的场景,可以考虑持久化存储,比如存数据库中。建议策略:间歇性提前自动把临时数据从内存中备份到硬盘中。当然,持久性存储不局限于本地,网络亦可。


③ 数据共享 (跨页面通信)

日常开发中,Activity和Fragment通信,Fragment与Fragment通信的场景非常常见,常见的做法下述几种:

    1. 依托于宿主Activity,定义一堆公共的访问Fragment的方法,setArguments() 或者 目标Fragment()预留回调;
    1. Fragment中调 getActivity() 获得宿主Activity,强转后获取FragmentManager实例,通过findFragmentById()或者ByTag()拿到目标Fragment实例,调用setArguments()传参;
    1. 利用Fragment Result的API,使用公共FragmentManager,充当传递数据的中心存储,setFragmentResult() 和 setFragmentResultListener();
    1. EventBus、广播等

各有利弊,而采用ViewModel,只需 指定作用域,即可轻松实现跨页面通信。写个烂大街的经典例子:点击列表Fragment,更新右侧内容Fragment,预期效果如下:

接着写代码实现一波,先是左侧列表项的布局 (item_list.xml):

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/tv_choose"
    android:layout_width="match_parent"
    android:layout_height="60dp"
    android:background="@android:color/holo_green_light"
    android:gravity="center" /> 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

接着到列表适配器类 (ListAdapter.kt)

class ListAdapter(data: ArrayList<String>): RecyclerView.Adapter<ListAdapter.ViewHolder>() {
    private var mData = data
    private var mClickListener: ItemClickListener? = null

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        return ViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_list, parent ,false))
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.chooseTv?.let {
            it.text = mData[position]
            it.setOnClickListener { mClickListener?.onItemClick(mData[position]) }
        }
    }

    override fun getItemCount() = mData.size

    fun setOnItemClickListener(listener: ItemClickListener) {
        this.mClickListener = listener
    }

    inner class ViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) {
        var chooseTv: TextView? = null
        init { chooseTv = itemView.findViewById(R.id.tv_choose) }
    }

    interface ItemClickListener { fun onItemClick(choose: String) }
} 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

设置文本,预留点击接口而已,再接着到列表Fragment的布局,直接一个RecyclerView (fragment_list.xml)

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="150dp"
    android:layout_height="match_parent">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rv_list"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</FrameLayout> 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

接着自定义ViewHolder,维护一个选中的值,配置LiveData:

class SharedViewModel: ViewModel() {
    private val mSelectData = MutableLiveData<String>()
    fun select(data: String) { mSelectData.value = data }
    fun getSelected() = mSelectData
} 
  • 1
  • 2
  • 3
  • 4
  • 5

再接着把ListFragment也写出来,获取宿主Activity作用域的SharedViewModel实例,点击时更新值:

class ListFragment(data: ArrayList<String>): Fragment(R.layout.fragment_list) {
    private var mData = data
    // 定义SharedViewModel变量
    private var mModel: SharedViewModel? = null

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        
        // 获得宿主Activity作用域内的SharedViewModel实例
        mModel = ViewModelProvider(requireActivity()).get(SharedViewModel::class.java)
        
        view.findViewById<RecyclerView>(R.id.rv_list).apply {
            adapter = ListAdapter(mData).also {
                it.setOnItemClickListener(object : ListAdapter.ItemClickListener {
                    override fun onItemClick(choose: String) {
                        // 更新ViewModel中的mSelectData
                        mModel?.select(choose)
                    }
                })
            }
            layoutManager = LinearLayoutManager(activity)
        }
    }
} 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

在接着到右侧Fragment,xml里就一个简单的TextView:(fragment_content.xml):

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/holo_blue_light">

    <TextView
        android:id="@+id/tv_content"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"/>

</LinearLayout> 

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

补齐ContentFragment:

class ContentFragment : Fragment(R.layout.fragment_content) {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        // 获取宿主Activity作用域的SharedViewModel实例,然后监听数据变化
        ViewModelProvider(requireActivity()).get(SharedViewModel::class.java).getSelected()
            .observe(viewLifecycleOwner) {
                view.findViewById<TextView>(R.id.tv_content).text = "您翻牌了:${it}"
            }
    }
} 

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

紧着是测试Activity的xml (activity_vm_test.xml):

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal">

    <FrameLayout
        android:id="@+id/fly_choose"
        android:layout_width="wrap_content"
        android:layout_height="match_parent" />

    <FrameLayout
        android:id="@+id/fly_content"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"/>

</LinearLayout> 

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

最后上测试Activity:

class VMTestActivity: AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_vm_test)
        val mData = arrayListOf("XX","XX","XX","XX","XXX", "XX", "XX", "XX")
        supportFragmentManager.apply {
            beginTransaction().replace(R.id.fly_choose, ListFragment(mData)).commit()
            beginTransaction().replace(R.id.fly_content, ContentFragment()).commit()
        }
    }
} 

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

别看这里代码好像很多的样子,只是为了便于读者copy运行体验而已,核心就这四点:

  • ① 自定义定义ViewModel,里面放通信的数据,提供set()和get()方法;
  • ② ViewModelProvider(requireActivity()).get(SharedViewModel::class.java) 获取一个 作用域为宿主Activity 的ViewModel实例;
  • ③ 发消息:调用ViewModel实例的set()方法更新数据;
  • ④ 收消息:调用ViewModel实例的get()方法获得一个LiveData,然后observe() 监听数据变化

通过这样的方式,间接实现了跨页面通信,宿主Activity还蒙在鼓里 (无代码入侵),两个Fragment就偷偷完成了py交易,妙啊!!!


④ 作用域可控

上面两个Fragment轻松实现数据共享的例子,得益于ViewModel的 作用域可控,在创建 ViewModelProvider 时注入不同的 ViewModelStoreOwner 来反映作用域。

如果换成传入 Fragment实例本身,作用域就 仅限于此Fragment,当此Fragment销毁时,对应的ViewModel实例也会被销毁。


扩展一下:ViewModel是如何实现作用域可控的?

看一波源码探探原理,先跟下 ViewModelProvider 的构造方法:

初始化了一个Factory和ViewModelStore,先看第一个参数,调用了 owner.getViewModelStore()

此方法返回一个 ViewModelStore,跟下:

吼,内部 维护一个ViewModel的集合,还提供一个clear()方法,遍历回调ViewModel的clear()方法,并清空集合

所以包含关系是:ViewModelProviderViewModelStoreViewModel

接着看第二个参数,根据传入的ViewModelStoreOwner不同,使用不同的工厂实例:

  • 判断条件:owner是否为 HasDefaultViewModelProviderFactory 类型?
  • 是:强转后调用 getDefaultViewModelProviderFactory() 获取一个 ViewModelProvider.Factory 实例;
  • 否:调用 NewInstanceFactory.getInstance() 获取一个 NewInstanceFactory 实例;

构造方法中初始化了Factory和ViewModelStore实例,继续往下走,跟下 get() 方法:

在此拼接了一个 key,继续跟另一个 get() 方法:

这里执行的操作非常简单明了:

  • 拼接key 区分不同作用域的ViewModel实例,规则:固定字符串+ViewModel完整类名;
  • ② 尝试从 缓存Map 中根据key获取ViewModel实例;
  • ③ 拿到:直接返回
  • ④ 拿不到:往下走,通过工厂创建新ViewModel实例,保存到缓存Map中,然后返回;

到此好像还没get√到具体怎么实现作用域可控?

把关注点拉回 owner.getViewModelStore() 上,这个owner是 ViewModelStoreOwner 类型的,而我们上面传入的是 Activity实例,可以推测Actitivty绝壁实现了这个接口。定位一波:

跟下 getViewModelStore()

2333,原来是 ComponentActivity 内部自己维护了一个ViewModelStore。

啧啧,再来看看Fragment又是怎么玩的,跟下 Fragment.getViewModelStore()

跟下 FragmentManager.getViewModelStore()

Fragment 内部持有一个 FragmentManagerViewModel 实例,点进去它的 getViewModelStore() 方法:

好家伙,我悟了:Fragment → FragmentManager → FragmentManagerViewModel → mViewModelStores集合

所以:ViewModel的作用域可控 = 工厂模式 + 缓存集合(特定key规则)

关于ViewModel的特点大概了解到这,接着过下基本用法~


0x2、ViewModel 基本用法

老规矩,官方文档双手奉上:《ViewModel 概览》,以官方文档和源码为准~

① 依赖组件

ViewModel基本配合LiveData使用,更多依赖可以选择可参见:Lifecycle

def lifecycle_version = "2.4.1"

// Java项目
implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version"

// Kotlin项目
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" 

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

咳,如果你启用了DataBinding,可以不用另外依赖,不然会发现两个版本的ViewModel:

不信的话,自己命令行键入:gradlew :app:dependencies > dependencies.txt 扫一波就知道了

② 实现ViewModel

上面例子已经写得很明显了,就不重复了,这里提两点:

  1. 如果依赖了 activity-ktx 模块,可以使用 by viewModels() 委托初始化ViewModel。
implementation 'androidx.activity:activity-ktx:1.4.0'
val model: MyViewModel by viewModels()

// Tips:如果依赖了fragment-ktx模块,可以在Fragment中商用activityViewModels() 委托初始化
// 宿主Activity的ViewModel
implementation 'androidx.fragment:fragment-ktx:1.4.1'
private val model: SharedViewModel by activityViewModels() 

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

当所有者Activity关闭时,会调用ViewModel对象的onCleared() 方法,以便它可以清理资源。

  1. ViewModel绝不能引用View、Lifecycle或可能存储对Activity上下文的引用的类内存泄露警告!!!

③ ViewModel的生命周期

ViewModel将一直存在与内存中,直到限定其时间访问的Lifecycle永久消失:对于Activity,是在Activity finish时,对于Fragment,是在Fragment移除时。基本用法就这些,协程搭配Jetpack组件使用,后面会专门讲~


0x3、面试题:ViewModel自动保存和恢复的原理

ViewModel好像就这个能问了,简单的探一探,使用 ViewModelProvider 实例化ViewModel时,传入 ViewModelStoreOwner 对象作为参数,Activity、Fragment自然实现了这个接口。跟下:ComponentActivity.getViewModelStore()

跟下 ensureViewModelStore

点开 NonConfigurationInstances,可以看到 ViewModelStore 对象被缓存在这里:

跟下 getLastNonConfigurationInstance()

就是Activity除了提供 onSaveInstanceState()onRestoreInstanceState() 外,还另外提供了两个方法 onRetainNonConfigurationInstance()getLastNonConfigurationInstance() 专门处理配置更改。

跟下 onRetainNonConfigurationInstance()

就是在配置更改销毁重建过程中,先调用 onRetainNonConfigurationInstance() 保存 旧Activity中的ViewModelStore实例,重建后通过 getLastNonConfigurationInstance() 获取到之前保存的ViewModelStore实例。

知道怎么保存和恢复,接着就是确定 调用时机 ,跟下 ActivityThread.performDestroyActivity() 它是 Activity销毁 调用的核心实现:

跟下 Activity.retainNonConfigurationInstances()

知道保存数据的方法是在这里调用的,接着看获取数据的方法又是在哪调用的,跟下 Activity.handleLaunchActivity(),它是 Activity启动 的重要步骤:

还记得销毁处的代码吗:

销毁时,先存到 ActivityClientRecord.lastNonConfigurationInstances 中,然后在Activity启动时,通过 attach() 方法传递给新Activity。

到此就一清二楚了,onSaveInstanceState() 相关的也可以在 ActivityThread() 中找到踪迹,如:

就不去跟了,除了前面说的存数据的颗粒度大小不同外,两者还存在下述区别:

  • onSaveInstanceState() 的数据最终存储到 ActivityManagerServiceActivityRecord 中,即 系统进程,所以APP被杀后还能恢复;
  • onRetainNonConfigurationInstance() 数据是存储到 ActivityClientRecord 中,即 应用自身进程中 ,所以APP被杀后无法恢复。

另外,再送一个问题:ViewModelStore的onCleard()何时会被调用

关于原理相关的就只了解到这吧,Activity销毁重建完整逻辑可是个大块头,就不展开讲了~


0x4、加餐:ViewModel-State组件

上面说过ViewModel仅对页面变更,Activity销毁后打开重建只能用onSaveInstanceState(),写个简单例子验证下:

class VMFirstActivity: AppCompatActivity() {
    companion object {
        const val COUNT_TAG = "count"
    }
    private var mSaveInstanceCount = 0
    private val mModel: VMViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_wm_first)
        // 判断是否有保存的数据,有取值
        savedInstanceState?.let { mSaveInstanceCount = it.getInt(COUNT_TAG) }
        tv_vm_content.text = "ViewModel保存的数据:${mModel.mCount}"
        tv_on_save_content.text = "onSaveInstanceState()保存的数据:${mSaveInstanceCount}"
        bt_test.setOnClickListener {
            tv_vm_content.text = "ViewModel保存的数据:${++mModel.mCount}"
            tv_on_save_content.text = "onSaveInstanceState()保存的数据:${++mSaveInstanceCount}"
        }
    }

    override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState)
        outState.putInt(COUNT_TAG, mSaveInstanceCount)
    }
}

class VMViewModel: ViewModel() { var mCount = 0 } 

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

运行后点击按钮自增多次,然后旋转手机引起Activity销毁重建,效果一致:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EM3EipRg-1654522034819)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1bc8e194153e4abeb9d7e47c03b2067e~tplv-k3u1fbpfcp-zoom-in-crop-mark:1956:0:0:0.image)]

接着试下杀掉进程,home键退到后台,键入下述命令:

adb shell am kill 应用包名 

  • 1
  • 2

点击桌面图标重新打开:

果然,ViewModel中的数据丢失了,如果数据比较重要,而且量不大,可以在onCreate()拿到savedInstanceState时也重置一下值。

savedInstanceState?.let {
    mSaveInstanceCount = it.getInt(COUNT_TAG)
    mModel.mCount = it.getInt(COUNT_TAG)
} 

  • 1
  • 2
  • 3
  • 4
  • 5

然后就可以了,当然,这样搞法有点冗余,如果能判断是配置变更引起的重建,还是异常销毁引起的重建就好了,笔者暂时没找到判定的API,只能这样了。有知道的小伙伴欢迎在评论区告知~

对于这种场景,Jetpack其实还给我们提供了一个模块:SaveState,activity库内部默认引入了这个组件,不需要另外依赖,当然你要依赖特定版本也是可以的:

implementation "androidx.savedstate:savedstate:1.0.0" 

  • 1
  • 2

用法非常简单,ViewModel的构造方法,传入一个SavedStateHandle参数,然后用这个参数读取数据即可:

class VMViewModel(private val state: SavedStateHandle): ViewModel() {
    private val countTag = "count"
    fun setValue(value: Int) = state.set(countTag, value)
    fun getValue() = state.get<Int>(countTag)
} 

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

把原先onSaveInstanceState相关的代码干掉后:

class VMFirstActivity: AppCompatActivity() {
    private val mModel: VMViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_wm_first)
        // 如果为空,要先设置一个初始值,不然一直都是null
        if(mModel.getValue() == null) mModel.setValue(0)
        tv_vm_content.text = "ViewModel保存的数据:${mModel.getValue()}"
        bt_test.setOnClickListener {
            // 需要判空,然后在更新值
            mModel.getValue()?.plus(1)?.let { mModel.setValue(it) }
            tv_vm_content.text = "ViewModel保存的数据:${mModel.getValue()}"
        }
    }
} 

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

清爽多了,除了可以调用 get() 方法外,还可以调用 getLiveData() 来获取LiveData类型的值。代码就更精简了:

非常简单,不过要注意下:

存数据跟Bundle一样,存对象要序列化,然后也适合保存轻量级的数据!!!

原理的话,还是利用的 onSaveInstanceState(),每个ViewModel的数据单独存在一个Bundle中,再合并成一个整体,放到outBundle,所以它同样不能存超过1M的数据。


0x5、小结

本节过了下ViewModel的用法,对它的特点:视图数据与控制器、数据管理的一致性、数据共享、作用域可控进行了详解的解读,并配以简单例子帮助理解,还从源码层面讲解了ViewModel自动保存和恢复的原理,最后还提了一嘴ViewModel-State组件的使用。基本上算是面面俱到了,相信看完的读者用起ViewModel来也是水到渠成了。

有问题或者建议欢迎在评论区提出,肝文不易,如果本文有帮到你的话,可以给个三连,谢谢~


总结

要想成为架构师,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
在这里插入图片描述
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

一、架构师筑基必备技能

1、深入理解Java泛型
2、注解深入浅出
3、并发编程
4、数据传输与序列化
5、Java虚拟机原理
6、高效IO
……

在这里插入图片描述

二、Android百大框架源码解析

1.Retrofit 2.0源码解析
2.Okhttp3源码解析
3.ButterKnife源码解析
4.MPAndroidChart 源码解析
5.Glide源码解析
6.Leakcanary 源码解析
7.Universal-lmage-Loader源码解析
8.EventBus 3.0源码解析
9.zxing源码分析
10.Picasso源码解析
11.LottieAndroid使用详解及源码解析
12.Fresco 源码分析——图片加载流程

在这里插入图片描述

三、Android性能优化实战解析

  • 腾讯Bugly:对字符串匹配算法的一点理解
  • 爱奇艺:安卓APP崩溃捕获方案——xCrash
  • 字节跳动:深入理解Gradle框架之一:Plugin, Extension, buildSrc
  • 百度APP技术:Android H5首屏优化实践
  • 支付宝客户端架构解析:Android 客户端启动速度优化之「垃圾回收」
  • 携程:从智行 Android 项目看组件化架构实践
  • 网易新闻构建优化:如何让你的构建速度“势如闪电”?

在这里插入图片描述

四、高级kotlin强化实战

1、Kotlin入门教程
2、Kotlin 实战避坑指南
3、项目实战《Kotlin Jetpack 实战》

  • 从一个膜拜大神的 Demo 开始

  • Kotlin 写 Gradle 脚本是一种什么体验?

  • Kotlin 编程的三重境界

  • Kotlin 高阶函数

  • Kotlin 泛型

  • Kotlin 扩展

  • Kotlin 委托

  • 协程“不为人知”的调试技巧

  • 图解协程:suspend

在这里插入图片描述

五、Android高级UI开源框架进阶解密

1.SmartRefreshLayout的使用
2.Android之PullToRefresh控件源码解析
3.Android-PullToRefresh下拉刷新库基本用法
4.LoadSir-高效易用的加载反馈页管理框架
5.Android通用LoadingView加载框架详解
6.MPAndroidChart实现LineChart(折线图)
7.hellocharts-android使用指南
8.SmartTable使用指南
9.开源项目android-uitableview介绍
10.ExcelPanel 使用指南
11.Android开源项目SlidingMenu深切解析
12.MaterialDrawer使用指南
在这里插入图片描述

六、NDK模块开发

1、NDK 模块开发
2、JNI 模块
3、Native 开发工具
4、Linux 编程
5、底层图片处理
6、音视频开发
7、机器学习

在这里插入图片描述

七、Flutter技术进阶

1、Flutter跨平台开发概述
2、Windows中Flutter开发环境搭建
3、编写你的第一个Flutter APP
4、Flutter开发环境搭建和调试
5、Dart语法篇之基础语法(一)
6、Dart语法篇之集合的使用与源码解析(二)
7、Dart语法篇之集合操作符函数与源码分析(三)

在这里插入图片描述

八、微信小程序开发

1、小程序概述及入门
2、小程序UI开发
3、API操作
4、购物商场项目实战……

在这里插入图片描述

全套视频资料:

一、面试合集
在这里插入图片描述
二、源码解析合集

在这里插入图片描述
三、开源框架合集

在这里插入图片描述
欢迎大家一键三连支持,若需要文中资料,直接点击文末CSDN官方认证微信卡片免费领取【保证100%免费】↓↓↓

本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号