Jetpack-ViewModel

153 阅读4分钟

一.ViewModel的作用:

activity重建可能需要在销毁前保存变量数据,等重建完后将数据恢复; 现在可通过使用ViewModel替代此功能

override fun onSaveInstanceState(outState: Bundle) {
    super.onSaveInstanceState(outState)
    //从bundle中获取变量
}
​
override fun onRestoreInstanceState(savedInstanceState: Bundle?) {
    super.onRestoreInstanceState(savedInstanceState)
    //将变量值存储到bundle中
}

二.ViewModel的使用 (旋转后ui上显示的值大小不发生改变)

步骤一: 创建一个类继承ViewModel, 定一个一个变量,用来测试横竖屏切换,变量值是否会发生改变

class RotateViewModel:ViewModel() {
    var num = 0 //初始值
}

步骤二:创建ViewModel对象,点击改变num值,观察横竖屏的变量变化和 activity的hashcode和 viewModel对象的hashcode

class RotateActivity : AppCompatActivity() {
​
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_my_activity)
​
        var model = ViewModelProvider(this, ViewModelProvider.NewInstanceFactory())
            .get(RotateViewModel::class.java)
​
        var btn = findViewById<Button>(R.id.btn_center)
​
        btn.text = "${model.num}"
        btn.setOnClickListener {
            btn.text = "${++model.num}"
        }
​
        Log.i(TAG, "onCreate--- activity hashcode=${this.hashCode()}")
        Log.i(TAG, "onCreate--- model hashcode=${model.hashCode()}")
    }
​
​
    override fun onRetainCustomNonConfigurationInstance(): Any? {
        val cf = resources.configuration; //获取设置的配置信息
        val ori = cf.orientation //获取屏幕方向
        if(ori == Configuration.ORIENTATION_LANDSCAPE){
            //横屏
            Log.i(TAG, "横屏--- onRetainCustomNonConfigurationInstance-- ")
        }else if(ori == Configuration.ORIENTATION_PORTRAIT){
            //竖屏
            Log.i(TAG, "竖屏--- onRetainCustomNonConfigurationInstance-- ")
        }
        return super.onRetainCustomNonConfigurationInstance()
    }
  
    override fun onDestroy() {
        Log.i(TAG, "onDestroy--- activity hashcode=${this.hashCode()}")
        super.onDestroy()
    }
​
    companion object {
        const val TAG = "RotateViewModel"
    }
​
}

运行后自由旋转,点击按钮num自增, 横竖屏ui显示的num值一样, 证明了即使activity发生重建, ViewModel中变量的值没有改变。观察切换屏幕的打印

//第一次打印 (竖屏)

RotateViewModel: onCreate--- activity hashcode=134750604

RotateViewModel: onCreate--- model hashcode=5104856

//第二次打印(切换到横屏)

竖屏--- onRetainCustomNonConfigurationInstance--

onDestroy--- activity hashcode=134750604

onCreate--- activity hashcode=254609558

onCreate--- model hashcode=5104856

//第三次打印(切换到竖屏)

横屏--- onRetainCustomNonConfigurationInstance--

onDestroy--- activity hashcode=254609558

onCreate--- activity hashcode=214362739

onCreate--- model hashcode=5104856

说明了ViewModel的生命周期要比Activity长, 即使Activity onDestory,ViewModel对象还是当初创建的ViewModel对象。有2个疑问: ViewModel生命周期为什么那么长? ViewModel何时被销毁? img

三.ViewModel的原理

ViewModel自始至终离不开ViewModelStore, 分两种情况: ViewModel实例初始化和从ViewModelStore中获取Vie wModel

3.1 ViewModel实例初始化, 作为value存入ViewModelStore的缓存中

ViewModelProvider(this, ViewModelProvider.NewInstanceFactory())
    .get(RotateViewModel::class.java)

这里的this 是ViewModelStoreOwner,ComponentActivity 实现 ViewModelStoreOwner并重写了getViewModelStore方法, 目的是为了得到ViewModelStore实例

public ViewModelStore getViewModelStore() {
    if (mViewModelStore == null) {
        NonConfigurationInstances nc =
                (NonConfigurationInstances) getLastNonConfigurationInstance();
        if (nc != null) {
            // Restore the ViewModelStore from NonConfigurationInstances
            mViewModelStore = nc.viewModelStore;
        }
        if (mViewModelStore == null) {
            //ViewModel还未创建时走这里
            mViewModelStore = new ViewModelStore();
        }
    }
    return mViewModelStore;
}

通过工厂模式创建ViewModel实例, 并将该实例存入mViewModelStore中

public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
    ViewModel viewModel = mViewModelStore.get(key);
​
    if (modelClass.isInstance(viewModel)) {
        //noinspection unchecked
        return (T) viewModel;
    } else {
        //noinspection StatementWithEmptyBody
        if (viewModel != null) {
            // TODO: log a warning.
        }
    }
    if (mFactory instanceof KeyedFactory) {
        viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
    } else {
        viewModel = (mFactory).create(modelClass);
    }
    mViewModelStore.put(key, viewModel);
    //noinspection unchecked
    return (T) viewModel;
}

这样我们就可以使用ViewModel了, 当我们切换横竖屏之后

3.2 从ViewModelStore的缓存中获取ViewModel实例 切换横竖屏, ActivityThread 会调用performDestroyActivity方法

ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing,
        int configChanges, boolean getNonConfigInstance, String reason) {
    ActivityClientRecord r = mActivities.get(token);
    /////////////////////省略/////////////////////
    if (getNonConfigInstance) {
      try {
        //横竖屏切换
        r.lastNonConfigurationInstances
          = r.activity.retainNonConfigurationInstances();
    } catch (Exception e) {
      if (!mInstrumentation.onException(r.activity, e)) {
        throw new RuntimeException(
          "Unable to retain activity "
          + r.intent.getComponent().toShortString()
          + ": " + e.toString(), e);
      }
    }
    /////////////////////省略/////////////////////
    return r;    
}

->Activity retainNonConfigurationInstances()

->ComponentActivity onRetainNonConfigurationInstance()

public final Object onRetainNonConfigurationInstance() {
    Object custom = onRetainCustomNonConfigurationInstance();
​
    ViewModelStore viewModelStore = mViewModelStore;
    if (viewModelStore == null) {
        // No one called getViewModelStore(), so see if there was an existing
        // ViewModelStore from our last NonConfigurationInstance
        NonConfigurationInstances nc =
                (NonConfigurationInstances) getLastNonConfigurationInstance();
        if (nc != null) {
            viewModelStore = nc.viewModelStore;
        }
    }
​
    if (viewModelStore == null && custom == null) {
        return null;
    }
    //在Activity销毁前将viewModelStore保存到nci中
    NonConfigurationInstances nci = new NonConfigurationInstances();
    nci.custom = custom;
    nci.viewModelStore = viewModelStore;
    return nci;
}

这一步的目的很明显了,在Activity销毁前将viewModelStore保存到nci中,nci会赋值给上一步Activity retainNonConfigurationInstances() 的局部变量activity

NonConfigurationInstances retainNonConfigurationInstances() {
    Object activity = onRetainNonConfigurationInstance();
    /////////////////////省略/////////////////////
    NonConfigurationInstances nci = new NonConfigurationInstances();
    nci.activity = activity;
    nci.children = children;
    nci.fragments = fragments;
    nci.loaders = loaders;
    if (mVoiceInteractor != null) {
        mVoiceInteractor.retainInstance();
        nci.voiceInteractor = mVoiceInteractor;
    }
    return nci;
}

最终会返回给 ActivityClientRecord的lastNonConfigurationInstances,这么做其实很明显了,即使activity 被销毁了(销毁的时候进行了横竖屏切换),ActivityClientRecord 始终保存着ViewModel实例

当我们从竖屏切换到横屏后,Activity重建,会重新执行生命周期的onCreate方法

var model = ViewModelProvider(this, ViewModelProvider.NewInstanceFactory())
    .get(RotateViewModel::class.java)

会再次得到viewModel实例,此时的实例依然是当初的ViewModel

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.");
    }
    //由于Activity重建了,此时ComponentActivity中的成员变量mViewModelStore默认是null
    if (mViewModelStore == null) {
        NonConfigurationInstances nc =
                (NonConfigurationInstances) getLastNonConfigurationInstance();
        if (nc != null) {
            // Restore the ViewModelStore from NonConfigurationInstances
            //从nc中获取到获取mViewModelStore,走这里
            mViewModelStore = nc.viewModelStore;
        }
        if (mViewModelStore == null) {
            mViewModelStore = new ViewModelStore();
        }
    }
    return mViewModelStore;
}

getLastNonConfigurationInstance() 是从哪得到的, activity重建时,ActivityThread会执行performLaunchActivity

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    /////////////////////省略///////////////////// 
    activity.attach(appContext, this, getInstrumentation(), r.token,
                  r.ident, app, r.intent, r.activityInfo, title, r.parent,
                  r.embeddedID, r.lastNonConfigurationInstances, config,
                  r.referrer, r.voiceInteractor, window, r.configCallback,
                  r.assistToken);
​
    /////////////////////省略///////////////////// 
    return activity;
}

r.lastNonConfigurationInstances,这里的ActivityClientRecord是当初保持nc时的ActivityClientRecord实例, 因此此时的ViewModel是从mViewModelStore获取到的, 所以ViewModel被赋予了保存数据和恢复数据的功能

ViewModel何时被清理?

getLifecycle().addObserver(new LifecycleEventObserver() {
    @Override
    public void onStateChanged(@NonNull LifecycleOwner source,
            @NonNull Lifecycle.Event event) {
        if (event == Lifecycle.Event.ON_DESTROY) {
            if (!isChangingConfigurations()) {
                getViewModelStore().clear();
            }
        }
    }
});

宿主Activity/Fragment 被销毁,但不是屏幕横竖屏切换的情况下,ViewModel会被销毁