IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Jetpack-ViewModel -> 正文阅读

[移动开发]Jetpack-ViewModel

ViewModel简介

ViewModel是Android Jetpack库中的一员,旨在以注重生命周期的方式存储和管理界面相关的数据。其与生命周期强相关。

ViewModel主要有几点需要关注的特点:

  1. 在组件(Activity/Fragment)的生命周期中ViewModel的数据会一直保存在内存中,即便在组件发生重建时(例如当Activity屏幕旋转或者设置改变等原因导致的页面重建)也会一直存在。
  2. ViewModel可以实现组件之间的数据共享,主要是通过使用相同的ViewModelStore来进行共享。Fragment可以通过Activity的ViewModelStore。或者?Fragment可以使?parentFragment的ViewModelStore来共享,或者也可以使?Activity的ViewModelStore共享;需要注意的是Fragment中如果是使用Activity进行数据共享的话ViewModel的释放就会跟随Activity的生命周期。

ViewModel使用

  • 正常情况下无需单独引入 ViewModel 相关库,因为androidx.appcompat:appcompat:1.4.1会自带 Lifecycle、LiveData、ViewModel 等依赖库。
  • 如果想单独引入 ViewModel 或者其其它相关扩展库可如下操作:
// 模块的 build.gradle
// ViewModel
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1'
// 用于 Compose 的 ViewModel 实用程序
implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.4.1'
// ViewModel 的已保存状态模块
implementation 'androidx.lifecycle:lifecycle-viewmodel-savedstate:2.4.1'

// 只有 Lifecycles(不带 ViewModel 或 LiveData)
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.1'
// 注释处理器
kapt 'androidx.lifecycle:lifecycle-compiler:2.4.1'
// 替换 - 如果使用 Java8,请使用此注释处理器,而不是 lifecycle-compiler 注释处理器
implementation 'androidx.lifecycle:lifecycle-common-java8:2.4.1'
// 可选 - 在 Service 中实现 LifecycleOwner 的助手
implementation 'androidx.lifecycle:lifecycle-service:2.4.1'
// 可选 - ProcessLifecycleOwner 给整个 App 前后台切换提供生命周期监听
implementation 'androidx.lifecycle:lifecycle-process:2.4.1'

// LiveData
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.4.1'
// 可选:对 LiveData 的 ReactiveStreams 支持
implementation 'androidx.lifecycle:lifecycle-reactivestreams-ktx:2.4.1'
// 可选 - LiveData 的测试助手
testImplementation 'androidx.arch.core:core-testing:2.1.0'

先从一个简单的使用代码来证实一下刚才说的特点1:ViewModel类让数据可在发生屏幕旋转等配置更改后继续留存

class MainViewModel : ViewModel() {
    var number: Int = 0
}

class MainActivity : BaseActivity<MainViewModel>() {

    private var btnPlus: Button? = null
    private var btnSub: Button? = null

    private var number = 0

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        // 获取一个MainViewModel
        ViewModelProvider(this).get(MainViewModel::class.java)
        initView()
    }

    private fun initView() {
        btnPlus = findViewById(R.id.btn_plus)
        btnSub = findViewById(R.id.btn_sub)

        btnPlus?.setOnClickListener {
            number = 100
            viewModel?.number = 100
        }

        btnSub?.setOnClickListener {
            LogUtils.d("MainActivity number : $number")
            LogUtils.d("MainViewModel number : ${viewModel?.number}")
        }
    }

    /**
     * 只有manifest文件中该Activity设置android:configChanges="orientation|screenSize"时才会调用此回调,否则都是销毁重建不会走到这个回调
     */
    override fun onConfigurationChanged(newConfig: Configuration) {
        LogUtils.d(" >>> onConfigurationChanged <<<")
        super.onConfigurationChanged(newConfig)
    }
    
}

这个例子中,因为我们并没有将MainActivity在manifest中设置android:configChanges="orientation|screenSize"属性,所以只要切换横竖屏就会发生Activity页面重建。

上述代码意思是:在MainActivity和MainActivity对应的ViewModel中都声明了一个number属性。点击+号时会将两个number值都设置为100,然后切换横竖屏发生页面重建,此时我们看执行结果:

首先启动应用然后点击+号

紧接着点击-号时输出为:
2022-09-01 18:35:33.137 2918-2918/com.lianwenhong.tradition_app D/JectpackFamily:  >>> MainActivity number : 100 <<<
2022-09-01 18:35:33.137 2918-2918/com.lianwenhong.tradition_app D/JectpackFamily:  >>> MainViewModel number : 100 <<<
将应用切换为横屏,再点击-号,此时输出为:
2022-09-01 18:35:42.139 2918-2918/com.lianwenhong.tradition_app D/JectpackFamily:  >>> MainActivity number : 0 <<<
2022-09-01 18:35:42.139 2918-2918/com.lianwenhong.tradition_app D/JectpackFamily:  >>> MainViewModel number : 100 <<<

可见页面发生重建时写在MainActivity中的变量时重新创建的,而在ViewModel中保存的变量则未被重新创建而是被保留了下来。具体为什么我们后面将原理的时候再说。

接下来举个ViewModel共享数据的例子:

class MainActivity : AppCompatActivity() {
    private var txtSeekValue: TextView? = null
    private var seekBar: SeekBar? = null
    private var gotoFragment: TextView? = null
    
    private var viewModel:MainViewModel? = null
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        viewModel = ViewModelProvider(this).get(MainViewModel::class.java)
        initView()
    }
    
    private fun initView() {

        txtSeekValue = findViewById(R.id.txt_seek_value)
        seekBar = findViewById(R.id.seek)
        gotoFragment = findViewById(R.id.btn_goto_fragment)

        demoShareData()
    }
    
    /**
     * Activity中有一个SeekBar,Fragment中也有一个SeekBar,实现两者的SeekBar值同步
     */
    private fun demoShareData() {
        seekBar?.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
            override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
                viewModel?.seekValue = progress
                txtSeekValue?.text = "${viewModel?.seekValue}"
            }

            override fun onStartTrackingTouch(seekBar: SeekBar?) {
            }

            override fun onStopTrackingTouch(seekBar: SeekBar?) {
            }
        })
        gotoFragment?.setOnClickListener {
            val mainFragment = MainFragment(true)
            if (!mainFragment.isAdded) {
                val transaction = supportFragmentManager.beginTransaction()
                transaction.add(R.id.f_me11, mainFragment)//动态添加
                transaction.addToBackStack("main_fragment")
                transaction.commit()//提交
            }
        }
    }
}

class MainViewModel : ViewModel() {

    var seekValue: Int = 0

}

class MainFragment(override val useParentViewModel: Boolean) :Fragment() {
    private var txtSeekValue: TextView? = null
    private var seekBar: SeekBar? = null
    private var fViewModel: ViewModel? = null
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // 如果传入的ViewModelStoreOwner是Activity则会从对应的Activity中获取MainViewModel名称对应的ViewModel,
        // 可以简单理解为此时fViewModel就是MainActivity中的MainViewModel
        activity?.let { fViewModel = ViewModelProvider(it)[MainViewModel::class.java] }
        // 如果如果传入的ViewModelStoreOwner是Fragment则表示用Fragment中的ViewModel,数据就和Activity中无关了,
        fViewModel = ViewModelProvider(this)[MainFragmentViewModel::class.java]
    }

    /**
     * 这里使用的ViewModel是Activity中的,所以就实现了Activity与Fragment的数据共享
     * 同样的,如果要实现Fragment和子Fragment之间的数据共享,只需要在子Fragment中使用父Fragment作为ViewModelStoreOwner
     * 代码:
     * parentFragment?.let { fViewModel = ViewModelProvider(it).get(MainViewModel::class.java) }
     * */
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val view = inflater.inflate(R.layout.fragment_main, container, false)
        txtSeekValue = view.findViewById(R.id.txt_seek_value)
        txtSeekValue?.text = "${(fViewModel as MainViewModel).seekValue}"
        seekBar = view.findViewById(R.id.seek)
        seekBar?.progress = (fViewModel as MainViewModel).seekValue
        seekBar?.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
            override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
                if (fromUser) {
                    (fViewModel as MainViewModel).seekValue = progress
                    txtSeekValue?.text = "${(fViewModel as MainViewModel).seekValue}"
                }
            }

            override fun onStartTrackingTouch(seekBar: SeekBar?) {
            }

            override fun onStopTrackingTouch(seekBar: SeekBar?) {
            }

        })
        return view
    }
}

ViewModel实现原理

用几个问题来切入源码阅读

  1. ViewModel是怎么创建的
  2. ViewModel为什么能实现Activity重建时保留数据
  3. ViewModel进行数据共享是怎么实现的

ViewModel是怎么创建的

首先看ViewModel的创建过程,我们通常使用这种方式来获取一个ViewModel:ViewModelProvider(this).get(MainViewModel::class.java),意思是从一个ViewModel提供者中get一个你想要的ViewModel类型的实例。

public open class ViewModelProvider // 这是ViewModelProvider的类定义

// 这是ViewModelProvider的构造方法,此时第二个参数传入的是defaultFactory(owner),从下面方法可以看出来其实真正的factory是一个SavedStateViewModelFactory
public constructor(
    owner: ViewModelStoreOwner
) : this(owner.viewModelStore, defaultFactory(owner), defaultCreationExtras(owner))

// 调用上一个构造方法只是准备了一些参数然后调用此构造,可见此时ViewModelProvider中有一个关键变量:store、factory、defaultCreationExtras
constructor(
    private val store: ViewModelStore,
    private val factory: Factory,
    private val defaultCreationExtras: CreationExtras = CreationExtras.Empty,
) {
    ...
    public open class AndroidViewModelFactory
    private constructor(
        private val application: Application?,
        @Suppress("UNUSED_PARAMETER") unused: Int,
    ) : NewInstanceFactory() {

        public constructor() : this(null, 0)

        public constructor(application: Application) : this(application, 0)

        override fun <T : ViewModel> create(modelClass: Class<T>, extras: CreationExtras): T {
            return if (application != null) {
                create(modelClass, application)
            } else {
                val application = extras[APPLICATION_KEY]
                if (application != null) {
                    create(modelClass, application)
                } else {
                    if (AndroidViewModel::class.java.isAssignableFrom(modelClass)) {
                        throw IllegalArgumentException(
                            "CreationExtras must have an application by `APPLICATION_KEY`"
                        )
                    }
                    super.create(modelClass)
                }
            }
        }

        override fun <T : ViewModel> create(modelClass: Class<T>): T {
            return if (application == null) {
                throw UnsupportedOperationException(
                    "AndroidViewModelFactory constructed " +
                        "with empty constructor works only with " +
                        "create(modelClass: Class<T>, extras: CreationExtras)."
                )
            } else {
                create(modelClass, application)
            }
        }

        @Suppress("DocumentExceptions")
        private fun <T : ViewModel> create(modelClass: Class<T>, app: Application): T {
            return if (AndroidViewModel::class.java.isAssignableFrom(modelClass)) {
                try {
                    modelClass.getConstructor(Application::class.java).newInstance(app)
                } catch (e: NoSuchMethodException) {
                    throw RuntimeException("Cannot create an instance of $modelClass", e)
                } catch (e: IllegalAccessException) {
                    throw RuntimeException("Cannot create an instance of $modelClass", e)
                } catch (e: InstantiationException) {
                    throw RuntimeException("Cannot create an instance of $modelClass", e)
                } catch (e: InvocationTargetException) {
                    throw RuntimeException("Cannot create an instance of $modelClass", e)
                }
            } else super.create(modelClass)
        }

        public companion object {
            internal fun defaultFactory(owner: ViewModelStoreOwner): Factory =
                if (owner is HasDefaultViewModelProviderFactory)
                    owner.defaultViewModelProviderFactory else instance

            internal const val DEFAULT_KEY = "androidx.lifecycle.ViewModelProvider.DefaultKey"

            private var sInstance: AndroidViewModelFactory? = null
            
            @JvmStatic
            public fun getInstance(application: Application): AndroidViewModelFactory {
                if (sInstance == null) {
                    sInstance = AndroidViewModelFactory(application)
                }
                return sInstance!!
            }

            private object ApplicationKeyImpl : Key<Application>

            @JvmField
            val APPLICATION_KEY: Key<Application> = ApplicationKeyImpl
        }
    }
    ...
}

先对上面ViewModelProvider的构建做一个简单解释:

  • 在构造ViewModelProvider时需要传入一个ViewModelStoreOwner,这是一个ViewModel容器的拥有者,后面再说。
  • 紧接着传入了一个ViewModelProvider.Factory,这是一个ViewModel的构造工厂类,此时用的是默认工厂,而针对Activity和Fragment而言这个默认工厂的真正类型是SavedStateViewModelFactory,这个可以通过传入的ViewModelStoreOwner中找到答案,比如传入的是Activity的话,ComponentActivity实现了ViewModelStoreOwnerHasDefaultViewModelProviderFactory两个接口,所以通过查看ComponentActivity的getDefaultViewModelProviderFactory()方法可知defaultFactory其实是SavedStateViewModelFactory类型
  • 最后一个参数是CreationExtras类型,和ViewModelProvider.Factory同理它也是在ComponentActivity中创建,我也没详细看这到底干嘛用的,跳过也没关系

什么是ViewModelStoreOwner?

// 这是一个ViewModel容器的持有者
public interface ViewModelStoreOwner {
    @NonNull
    ViewModelStore getViewModelStore();
}

所以实现这个接口的类必然需要实现getViewModelStore()方法并返回一个ViewModelStore,而ViewModelStore又是什么?

public class ViewModelStore {

    private final HashMap<String, ViewModel> mMap = new HashMap<>();

    final void put(String key, ViewModel viewModel) {
        ViewModel oldViewModel = mMap.put(key, viewModel);
        if (oldViewModel != null) {
            oldViewModel.onCleared();
        }
    }

    final ViewModel get(String key) {
        return mMap.get(key);
    }

    Set<String> keys() {
        return new HashSet<>(mMap.keySet());
    }

    public final void clear() {
        for (ViewModel vm : mMap.values()) {
            vm.clear();
        }
        mMap.clear();
    }
}

这就是一个ViewModel容器,其内部的数据结构是个Map用于根据key值存放ViewModel并提供了几个方法用于map的管理。例如当调用clear()时就会清空该容器并执行容器中每个ViewModel的clear()方法,插一句:ViewModel.clear()用于释放自身数据,它会回调一个onCleared()方法用于告知开发者数据清理完成。

分析到这里我们已经有了一个ViewModel的提供者ViewModelProvider,我们需要ViewModel的时候就可以找它。那我们现在去找它,ViewModelProvider(this).get(MainViewModel::class.java)这个get方法被重写了:

@MainThread
public open operator fun <T : ViewModel> get(modelClass: Class<T>): T {
    val canonicalName = modelClass.canonicalName
        ?: throw IllegalArgumentException("Local and anonymous classes can not be ViewModels")
    return get("$DEFAULT_KEY:$canonicalName", modelClass)
}

@MainThread
public open operator fun <T : ViewModel> get(key: String, modelClass: Class<T>): T {
    // 先尝试从ViewModelStore中获取需要的ViewModel实例
    val viewModel = store[key]
    if (modelClass.isInstance(viewModel)) {
        (factory as? OnRequeryFactory)?.onRequery(viewModel)
        // 如果实例存在直接返回,如果不存在走下面逻辑通过Factory去创建
        return viewModel as T
    } else {
        @Suppress("ControlFlowWithEmptyBody")
        if (viewModel != null) {
            // TODO: log a warning.
        }
    }
    val extras = MutableCreationExtras(defaultCreationExtras)
    extras[VIEW_MODEL_KEY] = key
    // ViewModel是第一次创建的话则通过ViewModelProvider.Factory去创建一个新的ViewModel并返回,返回之前也会把它加入ViewModelStore中进行缓存,下一次如果再需要获取同类型的ViewModel时就无需再创建并且数据也能得以保留
    return try {
        factory.create(modelClass, extras)
    } catch (e: AbstractMethodError) {
        factory.create(modelClass)
    }.also { store.put(key, it) }
}

这部分的主要意思是当开发者需要某种类型的ViewModel时,ViewModelProvider会先去ViewModelStore这个容器中获取,如果获取不到就通过ViewModelProvider.Factory.craete()去创建一个ViewModel实例然后将其缓存到ViewModelStore中,此处我们解析到的ViewModelProvider.Factory是SavedStateViewModelFactory类型。而create()方法内部是通过反射生成了所需的ViewModel实例,不详细进入了。

ViewModel为什么能实现Activity重建时保留数据

先用一张图说明一下ViewModel的生命周期
ViewModel生命周期

看下ComponentActivity:

public class ComponentActivity extends androidx.core.app.ComponentActivity implements
        ContextAware,
        LifecycleOwner,
        ViewModelStoreOwner,
        HasDefaultViewModelProviderFactory,
        SavedStateRegistryOwner,
        OnBackPressedDispatcherOwner,
        ActivityResultRegistryOwner,
        ActivityResultCaller,
        OnConfigurationChangedProvider,
        OnTrimMemoryProvider,
        OnNewIntentProvider,
        OnMultiWindowModeChangedProvider,
        OnPictureInPictureModeChangedProvider,
        MenuHost {
    
    public ComponentActivity() {
        ...
        getLifecycle().addObserver(new LifecycleEventObserver() {
            @Override
            public void onStateChanged(@NonNull LifecycleOwner source,
                    @NonNull Lifecycle.Event event) {
                // 当Activity走到onDestroy时会走到这个回调中
                if (event == Lifecycle.Event.ON_DESTROY) {
                    // Clear out the available context
                    mContextAwareHelper.clearAvailableContext();
                    // And clear the ViewModelStore
                    // 在执行Activity的销毁过程中判断是因为配置改变导致的重建还是因为Activity被关闭导致的销毁,如果是配置改变导致的重建则不会清空ViewModel数据
                    if (!isChangingConfigurations()) {
                        getViewModelStore().clear();
                    }
                }
            }
        });
        ...
    }
}

当Activity因为某些原因比如在设置页面做了某些改动或者切换横竖屏等导致Activity需要销毁重建时,isChangingConfigurations()会返回true,因此().clear();就不会执行,数据就能得以保留。

那Fragment中的ViewModel是在什么时候释放的呢?
因为代码牵扯太多就不贴了,特地画了个流程图。流程图分为init、get、destroy三个过程:
Fragment中使用ViewModel工作原理

结论就是当Fragment执行销毁过程时执行到Fragment.performDestroyView()代码中处理了ViewModel的数据清理逻辑,最终具体清理的代码是:

final class FragmentManagerViewModel extends ViewModel {
    ...
    void clearNonConfigState(@NonNull Fragment f) {
        if (FragmentManager.isLoggingEnabled(Log.DEBUG)) {
            Log.d(TAG, "Clearing non-config state for " + f);
        }
        // Clear and remove the Fragment's child non config state
        FragmentManagerViewModel childNonConfig = mChildNonConfigs.get(f.mWho);
        if (childNonConfig != null) {
            childNonConfig.onCleared();
            mChildNonConfigs.remove(f.mWho);
        }
        // Clear and remove the Fragment's ViewModelStore
        ViewModelStore viewModelStore = mViewModelStores.get(f.mWho);
        if (viewModelStore != null) {
            viewModelStore.clear();
            mViewModelStores.remove(f.mWho);
        }
    }
    ...
}

所以在Fragment中使用ViewModel时需要注意,ViewModel在onDestroyView这个生命周期节点中已经被清除了,所以不要在后续的onDestroy和onDetach中使用

ViewModel进行数据共享是怎么实现的

如果理解了上面的原理那这个问题就很好回答,以Activity共享数据给Fragment为例,其实进行数据共享的本质就是在Activity和Fragment中使用同一个ViewModel以此达到共享的效果。而我们知道每个ViewModel都会被添加到一个ViewModelStore(也就是容器)中,每个ViewModelStore又从属于一个ViewModelStoreOwner,这是背景。
ViewModel存储关系
我们在获取ViewModel都会使用这样的代码,这样对于ViewModel的创建、缓存、生命周期绑定都由系统替我们完成:

val viewModel = ViewModelProvider(this).get(MainViewModel::class.java)

不建议自己这样去创建val viewModel = ViewModel(),这样创建出来的ViewModel不具备数据保留、生命周期绑定等特性,也不建议自行获取ViewModelStore等方式来使用ViewModel,因为ViewModelStore的方法除了clear其他都是默认修饰符,除非反射否则你不能操作ViewModelStore,而且也一样得自己去绑定生命周期等操作。

那Fragment想要获取Activity共享的数据只需要给ViewModelProvider构造传入Activity作为ViewModelStoreOwner即可,这样Fragment得到的ViewModel是从Activity的ViewModelStore这个容器中取的ViewModel,就能达到Activity和Fragment使用的是同一份数据的效果,父Fragment和子Fragment之间共享数据也同理。

还有一点值得注意的是,Activity的销毁和Fragment的替换都会导致当前Fragment的ViewModel执行clear()。(例如Activity A中有FragmentA,Activity销毁和Activity执行replace FragmentB都会导致FragmentA中的ViewModel执行clear() )

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-09-13 11:27:25  更:2022-09-13 11:29:51 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 4:51:43-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码