ViewModel实例什么时候被回收

2024-01-09  本文已影响0人  TechMix

一、ViewModel存在的意义?

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

二、怎么创建?

第一种方法(已弃用,不建议使用):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
}

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

网上很多关于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: ")
    }
}
// 按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也会新建一个实例。

// 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的生命周期

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();
    }
}
    @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;
    }
    @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 结论

<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>
上一篇下一篇

猜你喜欢

热点阅读