Android Jetpack架构组件(三)之ViewModel

ViewModel简介

在早期的Android开发中,因为应用相对较小,页面相对简单,咱们会将数据请求、页面UI处理和数据加载所有放在Activity或Fragment中进行,可是随着项目的迭代,这种开发方式显得愈来愈臃肿,而且也不易于项目的维护和扩展。android

此时,借鉴后端的后端程序的开发思路,咱们对Android项目进行了分层,典型的有MVC,MVP和MVVM等项目分层,而后每层负责本身的事情便可。以如今流行的MVVM模式为例。数据库

  • Model层:数据层,主要负责数据实体和对数据实体的操做。
  • View层:视图层,对应Android的Activity、Fragment和View等,负责数据的显示以及与用户的交互。
  • ViewModel层:关联层,用于将Model和View进行绑定,当Model发生更改时,即时通知View进行刷新,固然,也能够反向通知。

在JetPack架构中,ViewModel组件是一个能够感知生命周期的形式来存储和管理视图相关的数据的组件,所以它适合如下场景。后端

  • 适合须要保存大量数据的场景。例如,对于须要保存小量数据的场景,咱们可使用Activity/ Fragment的onSaveInstanceState方法保存数据,而后在onCreate方法中利用onRestoreInstanceState进行还原。可是,onSaveInstanceState只适合用来存储数据量少且序列化或者反序列化不复杂的数据,若是被序列化的对象复杂的话,序列化会消耗大量的内存,进而形成丢帧和视觉卡顿等问题。而ViewModel不只支持数据量大的状况,还不须要序列化、反序列化操做。
  • 在Android中,Activity/Fragment主要用于显示视图数据,若是它们也负责数据库或者网络加载数据等操做,那么势必形成代码臃肿,而将逻辑代码放到ViewModel以后,能够更有效的将视图数据相关逻辑和视图控制器分离开来。

除此以外,ViewModel的好处还有不少,可是最终的目的就是为了让代码可维护性更高,下降代码的冗余程度。网络

生命周期

咱们知道,Android的Activity/Fragment是有生命周期的,咱们能够在不一样的生命周期函数中执行不一样的操做来达到不一样的目的。因为ViewModel是保存在内存中的,因此ViewModel的生命周期并不会随Activity/Fragment的生命周期发生变化 。架构

下图是官方给出的ViewModel与Activity的生命周期的对应关系示意图。
在这里插入图片描述
从上图能够看出,ViewModel会伴随着Activity/Fragment的整个生命周期,直到ViewModel绑定的Activity/Fragment执行onDestroy()方法以后才会被销毁。app

基本使用

1,添加gradle以来,以下所示。ide

dependencies {
   implementation 'androidx.lifecycle:lifecycle-viewmodel:2.2.0'
}

2,建立一个继承自ViewModel类的MyViewModel类,建立ViewModel类千万不能持有Context的引用,不然会引发内存泄漏,若是须要使用Context能够继承AndroidViewModel。函数

public class MyViewModel extends ViewModel {
    private MutableLiveData<String> user;
    public LiveData<String> getUsers() {
        if (user == null) {
            user = new MutableLiveData<String>();
            loadUsers();
        }
        return user;
    }

    private void loadUsers() {
        user.setValue("Android应用开发实战");
    }
}

3, 为了不内存泄漏,咱们能够在onCleared()方法中进行资源释放操做。而后,咱们在Activity中就可使用MyViewModel,以下所示。源码分析

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        MyViewModel model = new ViewModelProvider(this).get(MyViewModel.class);
        model.getUsers().observe(this, new Observer<String>() {
            @Override
            public void onChanged(@Nullable String s) {
                Log.d(TAG, "LiveData监听数据返回:"+s);
            }
        });
    }
}

在 Fragment 之间共享数据

public class SharedViewModel extends ViewModel {
        private final MutableLiveData<Item> selected = new MutableLiveData<Item>();

        public void select(Item item) {
            selected.setValue(item);
        }

        public LiveData<Item> getSelected() {
            return selected;
        }
    }

    public class MasterFragment extends Fragment {
        private SharedViewModel model;

        public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
            super.onViewCreated(view, savedInstanceState);
            model = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);
            itemSelector.setOnClickListener(item -> {
                model.select(item);
            });
        }
    }

    public class DetailFragment extends Fragment {

        public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
            super.onViewCreated(view, savedInstanceState);
            SharedViewModel model = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);
            model.getSelected().observe(getViewLifecycleOwner(), { item ->
               // Update the UI.
            });
        }
    }

源码分析

ViewModel源码

ViewModel类是一个抽象接口,其部分源码以下。gradle

public abstract class ViewModel {
    
    @Nullable
    private final Map<String, Object> mBagOfTags = new HashMap<>();
    private volatile boolean mCleared = false;

    @SuppressWarnings("WeakerAccess")
    protected void onCleared() {
    }

    @MainThread
    final void clear() {
        mCleared = true;
        if (mBagOfTags != null) {
            synchronized (mBagOfTags) {
                for (Object value : mBagOfTags.values()) {
                    // see comment for the similar call in setTagIfAbsent
                    closeWithRuntimeException(value);
                }
            }
        }
        onCleared();
    }

   
    @SuppressWarnings("unchecked")
    <T> T setTagIfAbsent(String key, T newValue) {
        T previous;
        synchronized (mBagOfTags) {
            previous = (T) mBagOfTags.get(key);
            if (previous == null) {
                mBagOfTags.put(key, newValue);
            }
        }
        T result = previous == null ? newValue : previous;
        if (mCleared) {
            closeWithRuntimeException(result);
        }
        return result;
    }

    
    @SuppressWarnings({"TypeParameterUnusedInFormals", "unchecked"})
    <T> T getTag(String key) {
        if (mBagOfTags == null) {
            return null;
        }
        synchronized (mBagOfTags) {
            return (T) mBagOfTags.get(key);
        }
    }

    private static void closeWithRuntimeException(Object obj) {
        if (obj instanceof Closeable) {
            try {
                ((Closeable) obj).close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

能够发现,ViewModel抽象类的主要做用就是使用HashMap存储数据。ViewModel 有一个子类AndroidViewModel,它的源码以下。

public class AndroidViewModel extends ViewModel {
    @SuppressLint("StaticFieldLeak")
    private Application mApplication;

    public AndroidViewModel(@NonNull Application application) {
        mApplication = application;
    }

    @SuppressWarnings({"TypeParameterUnusedInFormals", "unchecked"})
    @NonNull
    public <T extends Application> T getApplication() {
        return (T) mApplication;
    }
}

与继承ViewModel不一样,AndroidViewModel须要提供一个 Application 的 Context。

ViewModelProvider

在前面的示例代码中,咱们在Activity中使用ViewModelProviders.of方法来获取ViewModel实例,以下所示。

MyViewModel model = new ViewModelProvider(this).get(MyViewModel.class);

打开ViewModelProviders类的源码,能够发现ViewModelProviders一共有四个构造方法,都是用来建立ViewModelProvider对象,只不过参数不一样而已。

public static ViewModelProvider of(@NonNull Fragment fragment) {
        return of(fragment, null);
    }
    
public static ViewModelProvider of(@NonNull FragmentActivity activity) {
        return of(activity, null);
    }
    
public static ViewModelProvider of(@NonNull Fragment fragment, @Nullable Factory factory) {
        Application application = checkApplication(checkActivity(fragment));
        if (factory == null) {
            factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
        }
        return new ViewModelProvider(fragment.getViewModelStore(), factory);
    }
    
 public static ViewModelProvider of(@NonNull FragmentActivity activity,
            @Nullable Factory factory) {
        Application application = checkApplication(activity);
        if (factory == null) {
            factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
        }
        return new ViewModelProvider(activity.getViewModelStore(), factory);
    }

在构建ViewModelProvider的时候须要用到ViewModelStore和Factory,下面咱们来分别介绍一下它们。

ViewModelStore

ViewModelStore主要做用是存储ViewModel的容器,当咱们打开ViewModelStore的源码时会发现ViewModelStore是经过HashMap来存储ViewModel的数据的。而且,ViewModelStore还提供了一个clear方法,用来清空Map集合里面的ViewModel,咱们能够在Activity/Fragment的onDestroy方法执行clear方法执行ViewModel数据的清除。

protected void onDestroy() {
        super.onDestroy();
        if (mViewModelStore != null && !isChangingConfigurations()) {
            mViewModelStore.clear();
        }
    }

Factory

当咱们使用ViewModelProvider获取ViewModel实例时,ViewModelProvider一共提供了4个构造函数,另外一个比较重要的构造函数是

public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory{
        mFactory = factory;
        mViewModelStore = store;
    }

ViewModelProvider的第二个参数是factory,它的子类有NewInstanceFactory和AndroidViewModelFactory两个,咱们可使用ViewModelProvider.AndroidViewModelFactory.getInstance获取单例的Factory对象,NewInstanceFactory源码以下。

public static class NewInstanceFactory implements Factory {

        @SuppressWarnings("ClassNewInstance")
        @NonNull
        @Override
        public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
            //noinspection TryWithIdenticalCatches
            try {
                return modelClass.newInstance();
            } catch (InstantiationException e) {
                throw new RuntimeException("Cannot create an instance of " + modelClass, e);
            } catch (IllegalAccessException e) {
                throw new RuntimeException("Cannot create an instance of " + modelClass, e);
            }
        }
    }

而AndroidViewModelFactory的源代码以下。

public static class AndroidViewModelFactory extends ViewModelProvider.NewInstanceFactory {

        private static AndroidViewModelFactory sInstance;

       
        @NonNull
        public static AndroidViewModelFactory getInstance(@NonNull Application application) {
            if (sInstance == null) {
                sInstance = new AndroidViewModelFactory(application);
            }
            return sInstance;
        }

        private Application mApplication;

       
        public AndroidViewModelFactory(@NonNull Application application) {
            mApplication = application;
        }

        @NonNull
        @Override
        public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
            if (AndroidViewModel.class.isAssignableFrom(modelClass)) {
                //noinspection TryWithIdenticalCatches
                try {
                    return modelClass.getConstructor(Application.class).newInstance(mApplication);
                } catch (NoSuchMethodException e) {
                    throw new RuntimeException("Cannot create an instance of " + modelClass, e);
                } catch (IllegalAccessException e) {
                    throw new RuntimeException("Cannot create an instance of " + modelClass, e);
                } catch (InstantiationException e) {
                    throw new RuntimeException("Cannot create an instance of " + modelClass, e);
                } catch (InvocationTargetException e) {
                    throw new RuntimeException("Cannot create an instance of " + modelClass, e);
                }
            }
            return super.create(modelClass);
        }
    }

AndroidViewModelFactory实例化构造方法里面有个参数class,能够引用Context,实际上是Appplication实例。在上面的代码中,若是是有application参数,则经过newInstance(application)实例化,不然调用父类的create方法而后经过newInstance()实例化。若是经过newInstance(application)实例化,就能够在ViewModel里面拿到Context,因为Application是APP全局的生命周期最长,因此就不存在内存泄露问题。

ViewModel是如何实现状态保留的

前面说过,ViewModel是不会随着Activity/Fragment的销毁而销毁的,由于ViewModel是将数据使用ViewModelStore 保存在HashMap 中,因此只要ViewModelStore不被销毁,则ViewModel的数据就不会被销毁。

众所周知,Android在横竖屏切换时会触发onSaveInstanceState,而后在还原时则会触发onRestoreInstanceState。除此以外,Android的Activity类还提供了onRetainNonConfigurationInstance和getLastNonConfigurationInstance两个方法,当设备状态发生改变时,上面的两个方法就会被系统调用。

其中,onRetainNonConfigurationInstance 方法用于处理配置发生改变时数据的保存,而getLastNonConfigurationInstance则用于恢复建立Activity时获取上次保存的数据。首先,咱们来看一下onRetainNonConfigurationInstance方法,以下所示。

public final Object onRetainNonConfigurationInstance() {
        Object custom = onRetainCustomNonConfigurationInstance();

        FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig();

        ... 
        
        if (fragments == null && mViewModelStore == null && custom == null) {
            return null;
        }

        NonConfigurationInstances nci = new NonConfigurationInstances();
        nci.custom = custom;
        nci.viewModelStore = mViewModelStore;
        nci.fragments = fragments;
        return nci;
    }

能够发现,ViewModel会将数据存储在 NonConfigurationInstances 对象中,而NonConfigurationInstances是定义在Activity里面的一个类,以下所示。

static final class NonConfigurationInstances {
        Object activity;
        HashMap<String, Object> children;
        FragmentManagerNonConfig fragments;
        ArrayMap<String, LoaderManager> loaders;
        VoiceInteractor voiceInteractor;
    }

再来看一下getLastCustomNonConfigurationInstance方法,
getLastNonConfigurationInstance方法返回的数据就是NonConfigurationInstances.activity属性,以下所示。

@Nullable
    public Object getLastNonConfigurationInstance() {
        return mLastNonConfigurationInstances != null
                ? mLastNonConfigurationInstances.activity : null;
    }

如今,咱们再看一下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;
        }
        if (mViewModelStore == null) {
            mViewModelStore = new ViewModelStore();
        }
    }
    return mViewModelStore;
}

能够发现,在getViewModelStore方法中咱们首先会获取NonConfigurationInstances对象,不为空则从其身上拿到ViewModelStore,也就是以前保存的ViewModelStore,而后当Activity被再次建立的时候恢复数据。

须要说明的是,onRetainNonConfigurationInstance方法会在onSaveInstanceState方法以后被调用,即调用顺序一样在onStop方法和 onDestroy方法之间。

相关文章
相关标签/搜索