Android Jatpack--ViewModel

1.ViewModel

ViewModel是Jetpack的一部分。 ViewModel类旨在以注重生命周期的方式存储和管理界面相关的数据。ViewModel类让数据可在发生屏幕旋转等配置更改后继续留存。

ViewModel出现的背景:

①职责分离

Android开发中,在页面较为简单的情况下,通常会将UI交互、数据获取等相关的业务逻辑全部写在页面中。但是在页面功能复杂的情况下,这样做就是不合适的。因为这样不符合“单一功能原则”。页面只应该负责处理用户与UI控件的交互,应该将UI与数据相关的业务逻辑隔离开。

为了能够更好地将职能划分,Android提供了ViewModel类,专门用于存放应用程序页面所需的数据。

②数据保留不丢失

在Android中如果Activity、Fragment销毁或者重建,存储在其中的数据会丢失。对于简单的数据,Activity可以使用onSaveInstanceState()方法来从onCreate()中恢复数据,但这个方法只适合可以序列化再反序列化的少量数据,而不适合较大的数据。

③防止异步操作时内存泄露

UI界面经常需要异步操作,比如网络请求等,当界面销毁时往往需要手动维护异步取消的动作,这样显得特别繁琐,并且把所有代码都写在界面中,会变得特别臃肿。

于是就需要将视图数据与界面分离,让层次清晰且高效。ViewModel作为视图数据和界面的桥梁,用来存储与UI相关的数据,它通过lifecycle感知的方式存储和管理UI相关数据。它可以维护自己的生命周期,不需要手动操作,这无疑大大降低开发难度。

ViewModel的生命周期:

be68ea840e704c3c90bde8fe1c56db09.png

这张图是在没有任何设置的情况下,旋转屏幕时Activity的生命周期变化和ViewModel的生命周期。可以看到Activity重建的时候,ViewModel中的数据是不会被清理的。即ViewModel类使得数据在配置更改(如屏幕旋转)时保活。

从这张图可以看出:

①ViewModel的生命周期比创建它的Activity、Fragment的生命周期都要长,即ViewModel中的数据会一直存活在Activity/Fragment中。因此ViewModel不能持有Context的对象,否则会出现内存泄露。

也就是说ViewModel一直保留在内存中,直到它的作用域永久消失:在activity的情况下,当它finishes时;而在fragment的情况下,当它被detached时。

②Activity在生命周期中可能会触发多次onCreate(),而ViewModel只会在第一次onCreate()时创建,然后直到最后Activity销毁。

ViewModel的应用:

屏幕旋转后,使用户操作数据仍然存在。

2.ViewModel的使用

①写一个类继承自ViewModel

class MyViewModel : ViewModel() {

    private var number:Int = 0

    fun getNumber(): Int {

        return number

    }

    fun setNumber() {

        number++

    }

    override fun onCleared() {

        super.onCleared()

        //viewModel销毁时调用,可以做一些释放资源的操作,防止内存泄露

    }

}

②使用自定义的ViewModel

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {

        super.onCreate(savedInstanceState)

        setContentView(R.layout.activity_main)

        val myViewModel = ViewModelProvider( this).get(MyViewModel::class.java)

        btn_add.setOnClickListener {

            myViewModel.setNumber()

            tv_text.setText("" + myViewModel.getNumber())

        }

    }

}

点击Button可以使TextView上的数字一直增加,而且旋转屏幕时数据也会保持,不会丢失(如果不使用ViewModel,旋转屏幕时TextView上的数据会变为0)。

ViewModel是一个抽象类,只有一个onCleared()方法。当ViewModel不再被需要,即与之相关的Activity/Fragment被销毁时,该方法会被系统调用。因此可以在该方法中执行一些资源释放的相关操作,防止造成内存泄露。注意: 当屏幕旋转而导致的Activity重建,并不会调用该方法。

注意:

①如果Activity重新创建,它接收的ViewModel实例与第一个Activity创建的实例相同。

②当Activity完成(不能简单理解为destroy)时,框架会调用ViewModel的onCleared()方法,以便它可以清理资源。

③ViewModel的生命周期比视图的生命周期长,所以ViewModel绝不能持有视图、Lifecycle或Activity的上下文等引用,否则就会造成内存泄漏。

④构造器有参数的ViewModel

当自定义的ViewModel的构造方法需要传参数的时候,就不能像上面一样进行实例化了。而需要借助于ViewModelProvider的Fatory。

下对上面的MyViewModel进行修改:

class MyViewModel : ViewModel() {

    private var number:Int = 0

    fun getNumber(): Int {

        return number

    }

    fun setNumber() {

        number++

    }

    override fun onCleared() {

        super.onCleared()

        //viewModel销毁时调用,可以做一些释放资源的操作,防止内存泄露

    }

    class MyViewModelFactory(val name:String) : ViewModelProvider.Factory {

        override fun <T : ViewModel?> create( modeClass : Class<T>) : T {

             return MyViewModel(name) as T

        }

    }

}

可以看到在MyViewModel有一个内部类继承自ViewModelProvider.Factory接口,并重写了create方法,这个方法返回一个ViewModel,这里就是要实例化MyViewModel对象,因此这里返回一个MyViewModel对象,可以看到参数这时候就通过MyViewModelFactory传给MyViewModel了。

这时候不仅仅是MyViewModel要进行修改,在进行MyViewModel实例化的时候也需要进行相应的修改。

获取SharedViewModel部分的代码要改成如下代码:

val myViewModel = ViewModelProviders(this, MyViewModel.MyViewModelFactory("Lucy")).get(MyViewModel::class.java)

其实就是在获取ViewModelProvider对象时有修改,这里用的是带有Factory的方法,传入刚才在MyViewmodel中写的Factory的实例。

3.源码分析

①ViewModel的实例缓存在哪里

ViewModel的创建方法是

val myViewModel = ViewModelProvider( this).get(MyViewModel::class.java)

创建ViewModelProvider对象并传入this参数,然后通过ViewModelProvider的get方法,传入MyViewModel的class类型,就得到了 myViewModel实例。

看一下ViewModelProvider的构造方法:

public ViewModelProvider( ViewModelStoreOwner owner) {

    // 获取owner对象的ViewModelStore对象

    this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory ? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory() : NewInstanceFactory.getInstance());

}

ViewModelProvider构造方法的参数类型是 ViewModelStoreOwner。而使用时传入的是this,这是因为Activity实现了ViewModelStoreOwner接口。

public class ComponentActivity extends androidx.core.app.ComponentActivity implements ViewModelStoreOwner ... {

    private ViewModelStore mViewModelStore;

    // 重写了ViewModelStoreOwner接口的唯一的方法getViewModelStore()

    @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.");

        }

        ensureViewModelStore();

        return mViewModelStore;

    }

}

再看刚刚的ViewModelProvider构造方法里调用了this(ViewModelStore, Factory),将 ComponentActivity#getViewModelStore返回的ViewModelStore实例传了进去,并缓存到ViewModelProvider中。

public ViewModelProvider(ViewModelStore store, Factory factory) {

    mFactory = factory;

    // 缓存ViewModelStore对象

    mViewModelStore = store;

}

接着看ViewModelProvider#get方法做了什么。

@MainThread

public <T extends ViewModel> T get( Class<T> modelClass) {

    String canonicalName = modelClass.getCanonicalName();

    if (canonicalName == null) {

        throw new IllegalArgumentException( "Local and anonymous classes can not be ViewModels");

    }

    return get(DEFAULT_KEY + ":" + canonicalName, modelClass);

}

获取ViewModel的CanonicalName, 然后调用了另一个get方法。

@MainThread

public <T extends ViewModel> T get(String key, Class<T> modelClass) {

     //从mViewModelStore缓存中尝试获取

    ViewModel viewModel = mViewModelStore.get(key);

    // 命中缓存

    if (modelClass.isInstance(viewModel)) {

        if (mFactory instanceof OnRequeryFactory) {

            ((OnRequeryFactory) mFactory).onRequery(viewModel);

        }

        // 返回缓存的 ViewModel 对象

        return (T) viewModel;

    }

    // 使用工厂模式创建 ViewModel 实例

    if (mFactory instanceof KeyedFactory) {

        viewModel = ((KeyedFactory) mFactory).create(key, modelClass);

    } else {

        viewModel = mFactory.create( modelClass);

    }

    // 将创建的ViewModel实例放进mViewModelStore缓存中

    mViewModelStore.put(key, viewModel);

    // 返回新创建的 ViewModel 实例

    return (T) viewModel;

}

通过ViewModelProvider的构造方法可以知道mViewModelStore其实是Activity里的mViewModelStore对象,它在 ComponentActivity中被声明。 看到了put方法,不难猜它内部用了Map结构。

public class ViewModelStore {

   //内部有一个 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();

        }

    }

 // 通过key获取ViewModel对象

    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的创建流程看完了,简单总结:ViewModel对象存在于ComponentActivity的mViewModelStore对象中。

②为什么Activity旋转屏幕后ViewModel可以恢复数据

在ViewModelProvider的构造方法中,获取ViewModelStore对象时,实际调用了 MainActivity#getViewModelStore(),而 getViewModelStore()实现在MainActivity的父类 ComponentActivity中。

 ComponentActivity.java:

@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.");

    }

    ensureViewModelStore();

    return mViewModelStore;

}

在返回mViewModelStore对象之前调用了 ensureViewModelStore()方法。

void ensureViewModelStore() {

    if (mViewModelStore == null) {

        NonConfigurationInstances nc = (NonConfigurationInstances) getLastNonConfigurationInstance();

       if (nc != null) {

            // Restore the ViewModelStore from NonConfigurationInstances

            mViewModelStore = nc.viewModelStore;

        }

        if (mViewModelStore == null) {

            mViewModelStore = new ViewModelStore();

        }

    }

}

当mViewModelStore == null调用了getLastNonConfigurationInstance()获取 NonConfigurationInstances对象nc,当nc != null时将mViewModelStore赋值为 nc.viewModelStore,最终viewModelStore == null时,才会创建ViewModelStore实例。

不难发现,之前创建的viewModelStore对象被缓存在NonConfigurationInstances中。

 // 它是 ComponentActivity 的静态内部类

static final class NonConfigurationInstances {

    Object custom;

    // 果然在这儿

    ViewModelStore viewModelStore;

}

NonConfigurationInstances对象是通过 getLastNonConfigurationInstance()来获取的。

public Object getLastNonConfigurationInstance() {

    return mLastNonConfigurationInstances != null ?mLastNonConfigurationInstances.activity : null;

}

注意:

①onRetainNonConfigurationInstance方法和getLastNonConfigurationInstance是成对出现的,跟onSaveInstanceState机制类似,只不过它是仅用作处理配置更改的优化。

②返回的是onRetainNonConfigurationInstance 返回的对象。

看看onRetainNonConfigurationInstance 方法

//保留所有适当的非配置状态

@Override

public final Object onRetainNonConfigurationInstance() {

    Object custom = onRetainCustomNonConfigurationInstance();

    ViewModelStore viewModelStore = mViewModelStore;

    // 若 viewModelStore 为空,则尝试从 getLastNonConfigurationInstance() 中获取

    if (viewModelStore == null) {

        NonConfigurationInstances nc =(NonConfigurationInstances) getLastNonConfigurationInstance();

        if (nc != null) {

            viewModelStore = nc.viewModelStore;

        }

    }

   //依然为空,说明没有需要缓存的,则返回null

    if (viewModelStore == null && custom == null) {

        return null;

    } 

    // 创建 NonConfigurationInstances 对象,并赋值viewModelStore

    NonConfigurationInstances nci = new NonConfigurationInstances();

    nci.custom = custom;

    nci.viewModelStore = viewModelStore;

    return nci;

}

到这儿大概明白了,Activity在因配置更改而销毁重建过程中会先调用onRetainNonConfigurationInstance保存viewModelStore实例。 在重建后可以通过getLastNonConfigurationInstance方法获取之前的viewModelStore实例。因此Activity旋转屏幕后ViewModel可以恢复数据。

③什么时候ViewModel#onCleared()会被调用

public abstract class ViewModel {

    protected void onCleared() {

    }

    @MainThread

    final void clear() {

        mCleared = true;

        if (mBagOfTags != null) {

            synchronized (mBagOfTags) {

                for (Object value : mBagOfTags.values()) {

                    closeWithRuntimeException( value);

                }

            }

        }

        onCleared();

    }

}

onCleared()方法被clear()调用了。 刚才看 ViewModelStore源码时好像是调用了clear() ,回顾一下:

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();

    }

}

在ViewModelStore的clear()中,遍历mMap并调用ViewModel对象的clear(),再看ViewModelStore的clear()什么时候被调用的:

// ComponentActivity的构造方法

public ComponentActivity() {

    ... 

    getLifecycle().addObserver(new LifecycleEventObserver() {

        @Override

        public void onStateChanged( LifecycleOwner source,Lifecycle.Event event) {

            if (event == Lifecycle.Event.ON_DESTROY) {

                mContextAwareHelper.clearAvailabl eContext();

                if (!isChangingConfigurations()) {

                    getViewModelStore().clear();

                }

            }

        }

    });

    ...

}

观察当前activity生命周期,当Lifecycle.Event == Lifecycle.Event.ON_DESTROY,并且 isChangingConfigurations()返回false时才会调用ViewModelStore#clear 。

 // Activity#isChangingConfigurations()

public boolean isChangingConfigurations() {

    return mChangingConfigurations;

}

isChangingConfigurations用来检测当前的Activity是否因为Configuration的改变被销毁了, 配置改变返回true,非配置改变返回false。

总结:在activity销毁时,判断如果是非配置改变导致的销毁,getViewModelStore().clear()才会被调用。

4.ViewModel使用注意

①Fragment间共享数据

因为ViewModel只会在Activity存活时会创建一次,因此在同一个Activity中可以在多个Fragment中共享ViewModel中数据。

public class FragmentA extends Fragment{

    ViewModelProviders.of(getActivity()).get( MyViewModel.class).getDatas().observe(this, new Observer<User>() {

        @Override

        public void onChanged(User user) {

            //获取Activity中数据变化

       }

    });

}

public class FragmentB extends Fragment{

    ViewModelProviders.of(getActivity()).get( MyViewModel.class).getDatas().observe(this, new Observer<User>() {

        @Override

        public void onChanged(User user) {

        }

    });

}

//在Activity中更新数据

ViewModelProviders.of(this).get(MyViewModel.class). updateUser();

②使用ViewModel的时候,要注意ViewModel不能够持有View、Lifecycle、Acitivity引用,而且不能够包含任何包含前面内容的类。因为ViewModel的生命周期比它们长,这样很有可能会造成内存泄漏。

③ViewModel中使用Context

如果ViewModel需要Applicaiton的Context(为了获取系统服务),可以使用AndroidViewModel。

普通的ViewModel生命周期都很短,随着Activity 销毁而销毁。如果要创建一个长生命周期的ViewModel可以使用AndroidViewModel。

AndroidViewModel 持有了一个Application,所以它的生命周期会很长。具体使用如下:

class MyViewModel(application: Application) : AndroidViewModel(application) {

    override fun onCleared() {

        super.onCleared()

        //viewModel销毁时调用,可以做一些释放资源的操作

    }

}

可以在AndroidViewModel中存储一些全局数据。

猜你喜欢

转载自blog.csdn.net/zenmela2011/article/details/130927490
今日推荐