美文网首页
ViewModel实例什么时候被回收

ViewModel实例什么时候被回收

作者: TechMix | 来源:发表于2024-01-09 13:12 被阅读0次

    一、ViewModel存在的意义?

        ViewModel做为JetPack中重要的组件,翻译成中文就是“视图模型”,根据分离关注点原则,ViewModel的出现,主要是为了分担Activity中的职责,专门用于存放和界面相关的数据。只要是在界面上能看到的数据,它的相关变量都应该存放在ViewModel中,而不是Activity中。
        如果ViewModel作用只是将原本存放在Activity/Fragment中的数据拆分出去,那岂不是跟普通的Java类没啥两样?
        其实ViewModel更重要的是:

    • 实现数据共享和跨模块通信,ViewModel的存储是以Activity的class为key存放的,就可在有Activity上下文的不同子模块获取共享的ViewModel对象;
    • Configuration改变后Activity重建后将销毁前的数据再attach到视图中;
    • ViewModel中提供了生命周期感知(LiveData+Lifecycle)和协程(ViewModel本身定义了ViewModelScope协程作用域)的支持;

    二、怎么创建?

    第一种方法(已弃用,不建议使用):ViewModelProviders.of().get()

    val viewModel1 = ViewModelProviders.of(this).get(TestClearViewModel::class.java)
    

    该方式已经被官方弃用,不再建议使用,其实跟踪ViewModelProviders.of()方法实现,内部就是new了一个ViewModelProvider对象。


    ViewModelProvider.of方法截图

    第二种方法(第一种方案衍生出来的):自己创建ViewModelProvider实例,调用其get()方法,传入ViewModel的class对象

    获取代码
    val viewModel = ViewModelProvider(this).get(TestClearViewModel::class.java)
    
    弊端

    在同一个Activity的上下文,在不同的模块(类,即使同一个功能模块也会按单一职责拆分代码)中,想要获取共享的ViewModel实例会new多个ViewModelProvider()对象。


    image.png

    第三种方法(kt委托):

    val viewModel3 by viewModels<TestClearViewModel>()
    

    使用该方法获取ViewModel实例,需要添加以下依赖:

    // ps: 版本选取适合自己项目的
    implementation 'androidx.activity:activity-ktx:1.6.0'
    

    实现代码:

    @MainThread
    public inline fun <reified VM : ViewModel> ComponentActivity.viewModels(
        noinline extrasProducer: (() -> CreationExtras)? = null,
        noinline factoryProducer: (() -> Factory)? = null
    ): Lazy<VM> {
        val factoryPromise = factoryProducer ?: {
            defaultViewModelProviderFactory
        }
    
        return ViewModelLazy(
            VM::class,
            { viewModelStore },
            factoryPromise,
            { extrasProducer?.invoke() ?: this.defaultViewModelCreationExtras }
        )
    }
    

    最终实现还是通过new一个ViewModelProvider传入ViewModelProvider.Factory对象的方式创建的ViewModel对象,不同的是通过kt的Lazy机制,实现了一层缓存逻辑;

    public class ViewModelLazy<VM : ViewModel> @JvmOverloads constructor(
        private val viewModelClass: KClass<VM>,
        private val storeProducer: () -> ViewModelStore, // 由Activity、Fragment实现ViewModelStoreOwner接口后提供
        private val factoryProducer: () -> ViewModelProvider.Factory,
        private val extrasProducer: () -> CreationExtras = { CreationExtras.Empty }
    ) : Lazy<VM> {
        private var cached: VM? = null
    
        override val value: VM
            get() {
                // 其实在ViewModelStore中已经有一层HashMap实现的缓存了,这里又增加一层缓存。
                val viewModel = cached
                return if (viewModel == null) {
                    val factory = factoryProducer()
                    val store = storeProducer()
                    // 核心实现还是ViewModelProvider构造方法中传入ViewModelProvider.Factory对象
                    ViewModelProvider(
                        store,
                        factory,
                        extrasProducer()
                    ).get(viewModelClass.java).also {
                        cached = it
                    }
                } else {
                    viewModel
                }
            }
    
        override fun isInitialized(): Boolean = cached != null
    }
    
    • 从上面的代码中看到viewModels()方法是做为ComponentActivity的拓展方法实现的;
    • 懒加载按需创建ViewModel实例
    • ViewModelProvider.Factory使用的是ComponentActivity中的默认实现defaultViewModelProviderFactory

    三、生命周期?什么时候被销毁?

    网上很多关于ViewModel的说法,有类似“ViewModel的生命周期是长于Activity的”这样的说法,其实是容易让人产生误解的。
    下面进行测试验证一下,理解一下ViewModel的存活周期。
    测试代码很简单,在Activity中创建一个自定义的ViewModel

    // TestClearViewModel.kt的实现:
    class TestClearViewModel : ViewModel() {
        var userId = MutableLiveData<String>()
    
        override fun onCleared() {
            super.onCleared()
            Log.d(MainActivity2.TAG, "onCleared: $this")
        }
    }
    
    // MainActivity2.kt的实现代码:
    class MainActivity2 : AppCompatActivity() {
        companion object {
            const val TAG = "MainActivity_TAG"
        }
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main2)
            
            val viewModel by viewModels<TestClearViewModel>()
            Log.d(TAG, "onCreate viewModel: $viewModel")
        }
    
        override fun onConfigurationChanged(newConfig: Configuration) {
            super.onConfigurationChanged(newConfig)
            Log.d(TAG, "onConfigurationChanged: ")
        }
    
        override fun onDestroy() {
            super.onDestroy()
            Log.d(TAG, "onDestroy: ")
        }
    }
    
    • case1 : 正常的按back键finish掉Activity,再重建Activity:
    // 按back键之后的log:
    com.yanggui.mvvmdemo2 D/MainActivity_TAG: onCreate viewModel: com.yanggui.mvvmdemo2.TestClearViewModel@86439b1
    com.yanggui.mvvmdemo2 D/MainActivity_TAG: onCleared: com.yanggui.mvvmdemo2.TestClearViewModel@86439b1
    com.yanggui.mvvmdemo2 D/MainActivity_TAG: onDestroy: 
    
    // 重现点击桌面图标,重启Activity之后的log:
    com.yanggui.mvvmdemo2 D/MainActivity_TAG: onCreate viewModel: com.yanggui.mvvmdemo2.TestClearViewModel@8e96463
    

    从上面的log可以看到,ViewModel是跟随Activity的销毁而销毁的,Activity重启之后ViewModel也会新建一个实例。

    • case2 : 旋转屏幕之后finish掉Activity,引起的Activity重建:
    // Activity在点击桌面图标,应用启动时创建的ViewModel实例:
    com.yanggui.mvvmdemo2 D/MainActivity_TAG: onCreate viewModel: com.yanggui.mvvmdemo2.TestClearViewModel@c26ae96
    
    // 第一次旋转屏幕引起Activity重建:
    com.yanggui.mvvmdemo2 D/MainActivity_TAG: onDestroy: 
    com.yanggui.mvvmdemo2 D/MainActivity_TAG: onCreate viewModel: com.yanggui.mvvmdemo2.TestClearViewModel@c26ae96
    
    // 第二次旋转屏幕引起Activity重建:
    com.yanggui.mvvmdemo2 D/MainActivity_TAG: onDestroy: 
    com.yanggui.mvvmdemo2 D/MainActivity_TAG: onCreate viewModel: com.yanggui.mvvmdemo2.TestClearViewModel@c26ae96
    

    通过上述实际验证,旋转屏幕其实ViewModel不会重建。

    根据上述实际代码验证,也能说明google官方这张流程图:


    ViewModel的生命周期
      1. 正常情况的onDestroy,ViewModel会跟随Activity销毁而销毁;
      1. 异常情况下的onDestroy,ViewModel不会跟随Activity销毁而销毁,因为要在Activity异常销毁之后重建时用来根据ViewModel的数据恢复界面;

    3.1 旋转屏幕之后ViewModel为什么不会重建?

    ViewModel是由ViewModelStore类管理的,在其内部维护了一个HashMap,key是androidx.lifecycle.ViewModelProvider.DefaultKey:+ ViewModel的Class对象的canonicalName拼凑而成的,value就是ViewModel对象。

    // ViewModelStore.java:
    public class ViewModelStore {
        // ViewModelStore存储多个ViewModel实例,用HashMap存放
        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());
        }
    
        /**
         *  Clears internal storage and notifies ViewModels that they are no longer used.
         */
        public final void clear() {
            for (ViewModel vm : mMap.values()) {
                vm.clear();
            }
            mMap.clear();
        }
    }
    
    • ViewModelStore和ViewModelStoreOwner的关系
      ViewModelStoreOwner其实就是常见的Fragment、ComponentActivity(实现了ViewModelStoreOwner)


      image.png
    • ComponentActivity#getViewModelStore():

        @NonNull
        @Override
        public ViewModelStore getViewModelStore() {
            if (getApplication() == null) {
                throw new IllegalStateException("Your activity is not yet attached to the "
                        + "Application instance. You can't request ViewModel before onCreate call.");
            }
            if (mViewModelStore == null) {
                NonConfigurationInstances nc =
                        (NonConfigurationInstances) getLastNonConfigurationInstance();
                if (nc != null) {
                    // Restore the ViewModelStore from NonConfigurationInstances
                    mViewModelStore = nc.viewModelStore;
                }
                // 直接new了一个ViewModelStore用户管理一个ViewModel Map集合
                if (mViewModelStore == null) {
                    mViewModelStore = new ViewModelStore();
                }
            }
            return mViewModelStore;
        }
    
    • Fragment#getViewModelStore(): 转调FragmentManager#getViewModelStore(),最终在FragmentManagerViewModel中维护了一个Map<String, ViewModelStore>,用于存放多个Fragemnt对应的ViewModel实例,以who变量为key去获取ViweModel实例;
        @NonNull
        @Override
        public ViewModelStore getViewModelStore() {
            if (mFragmentManager == null) {
                throw new IllegalStateException("Can't access ViewModels from detached fragment");
            }
            return mFragmentManager.getViewModelStore(this);
        }
    

    当ViewModel被销毁时会回调其onCleared()方法,直接跟进调用点发现,在ComponentActivity的无参构造方法中有如下代码:

        public ComponentActivity() {
            // ... 
            getLifecycle().addObserver(new LifecycleEventObserver() {
                @Override
                public void onStateChanged(@NonNull LifecycleOwner source,
                        @NonNull Lifecycle.Event event) {
                    if (event == Lifecycle.Event.ON_DESTROY) {
                        // Clear out the available context
                        mContextAwareHelper.clearAvailableContext();
                        // 在这里做ViewModel的销毁逻辑
                        if (!isChangingConfigurations()) {
                            getViewModelStore().clear();
                        }
                    }
                }
            });
        // ...
    }
    

    从如上代码可以得出结论:
        非异常情况(改变Configuration等)下的Activity销毁,ViewModel是在Activity的onDestroy生命周期时销毁。

    3.2 isChangingConfigurations()的定义

    // 以下代码是在Activity.java中定义的:
    boolean mChangingConfigurations = false;
    public boolean isChangingConfigurations() {
        return mChangingConfigurations;
    }
    

    在Activity和Activity的子类中都没有找到有赋值的地方,因为修饰符是package的,于是在其相同包名下,通过Find in files查找到是在ActivityThread.java的handleRelaunchActivity()中赋值为true的。

        // ActivityThread.java,sdk 33
        @Override
        public void handleRelaunchActivity(ActivityClientRecord tmp, PendingTransactionActions pendingActions) {
            // ...
            r.activity.mConfigChangeFlags |= configChanges;
            r.mPreserveWindow = tmp.mPreserveWindow;
            // 这里会在屏幕旋转Activity重建时执行,将activity.mChangingConfigurations赋值为true
            r.activity.mChangingConfigurations = true;
            // ...
         }
    
    image.png

    3.3 结论

    • 1、手机旋转屏幕时Activity会被重建,但是ViewModel实例不会重新创建,由此可见ViewModel的生命周期是长于Activity的仅在非正常退出的情况成立
      ps:上述现象是清单文件中Activity节点不配置android:configChanges="orientation|screenSize"属性的情况下会走onDestroy->onCreate流程,Activity被重建。
    <activity
        android:name=".MainActivity2"
        android:exported="true"
        android:configChanges="orientation|screenSize"> // 注:两个属性都要写上才会不走重建Activity
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
    
    • 2、只有当Activity正常退出时才会跟着Activity一起销毁;
      正常退出:非ConfigurationChange造成的Activity重建。

    相关文章

      网友评论

          本文标题:ViewModel实例什么时候被回收

          本文链接:https://www.haomeiwen.com/subject/clupjdtx.html