Evil Mouth's Blog

Android Architecture Components分析记录(三)

August 18, 2017

记录分析AAC第三篇---ViewModel,官方地址 https://developer.android.com/topic/libraries/architecture/viewmodel.html?hl=zh-cn

前言

由于UI组件可能会因为某些原因(比如旋转屏幕等)导致被系统销毁或者重建从而导致所持数据丢失,通知针对这种状况采取的做法是通过onSaveInstanceState保存数据,并在界面重建后取出来,例如EditText编辑框中的内容,然而这种做法只适应于少量数据甚至最好只存储基本类型的数据。

官方针对此问题设计了ViewModel(当然不止这个原因),例如一个列表页,以往的做法下旋转屏幕后还需要重新请求数据,使用ViewModel后可以将减少这一次请求,而且这一切恢复操作完全不需要我们管理,这样又能减轻界面的负担。

下面先看看ViewModel的使用方法

使用

先创建ViewModel

public class UserInfoModel extends ViewModel {

    private MutableLiveData<UserInfo> mUserInfoMutableLiveData;

    public LiveData<UserInfo> getUserInfoLiveData() {
        if (mUserInfoMutableLiveData == null) {
            mUserInfoMutableLiveData = new MutableLiveData<>();
            loadUserInfo();
        }
        return mUserInfoMutableLiveData;
    }

    private void loadUserInfo() {
        //例如网络加载操作
    }
}

然后就可以在Activity使用

UserInfoModel userInfoModel = ViewModelProviders.of(this).get(UserInfoModel.class);
        userInfoModel.getUserInfoLiveData().observe(this, new Observer<UserInfo>() {
            @Override
            public void onChanged(@Nullable UserInfo userInfo) {
                //更新UI
            }
        });

注意是通过ViewModelProviders而不是new拿到ViewModel实例

分析

在上门的例子可以看到实例ViewModel的方法不是new而是通过ViewModelProviders取得

of()

@MainThread
    public static ViewModelProvider of(@NonNull FragmentActivity activity) {
        initializeFactoryIfNeeded(activity.getApplication());
        return new ViewModelProvider(ViewModelStores.of(activity), sDefaultFactory);
    }

    private static DefaultFactory sDefaultFactory;

    private static void initializeFactoryIfNeeded(Application application) {
        if (sDefaultFactory == null) {
            sDefaultFactory = new DefaultFactory(application);
        }
    }

of()方法其实有四个,分别是

  • of(@NonNull Fragment fragment)
  • of(@NonNull FragmentActivity activity)
  • of(@NonNull Fragment fragment, @NonNull Factory factory)
  • of(@NonNull FragmentActivity activity,@NonNull Factory factory)

第二个参数是支持自定义Factory,由于ViewModel是通过反射实例化的,所以默认的构造函数的无参的,如果需要操作系统服务,可以选择继承AndroidViewModel,这也是第一句initializeFactoryIfNeeded(activity.getApplication())初始化sDefaultFactory的原因,为了在后面反射时传入application

of()返回的是一个ViewModelProvider

public ViewModelProvider(ViewModelStore store, Factory factory) {
        mFactory = factory;
        this.mViewModelStore = store;
    }

只是赋值了下ViewModelStoreFactory,具体实例化是在get()中。

ViewModelStore

先看看ViewModelStore怎么来的

ViewModelStores.of(activity)

public static ViewModelStore of(FragmentActivity activity) {
        return holderFragmentFor(activity).getViewModelStore();
    }

public static HolderFragment holderFragmentFor(FragmentActivity activity) {
        return sHolderFragmentManager.holderFragmentFor(activity);
    }

HolderFragment holderFragmentFor(FragmentActivity activity) {
            FragmentManager fm = activity.getSupportFragmentManager();
            HolderFragment holder = findHolderFragment(fm);
            if (holder != null) {
                return holder;
            }
            holder = mNotCommittedActivityHolders.get(activity);
            if (holder != null) {
                return holder;
            }

            if (!mActivityCallbacksIsAdded) {
                mActivityCallbacksIsAdded = true;
                activity.getApplication().registerActivityLifecycleCallbacks(mActivityCallbacks);
            }
            holder = createHolderFragment(fm);
            mNotCommittedActivityHolders.put(activity, holder);
            return holder;
        }

private static HolderFragment createHolderFragment(FragmentManager fragmentManager) {
            HolderFragment holder = new HolderFragment();
            fragmentManager.beginTransaction().add(holder, HOLDER_TAG).commitAllowingStateLoss();
            return holder;
        }

ViewModelStore是通过一个HolderFragment.getViewModelStore()获得,这个HolderFragment是一个透明无界面的FragmentViewModelStores就是保存在HolderFragment中。随后通过FragmentManagerHolderFragment添加到Activity,这也是为什么这个库要依赖于FragmentActivity的原因。

接着看getViewModelStore()

public ViewModelStore getViewModelStore() {
        return mViewModelStore;
    }

private ViewModelStore mViewModelStore = new ViewModelStore();

public class ViewModelStore {

    private final HashMap<String, ViewModel> mMap = new HashMap<>();

    final void put(String key, ViewModel viewModel) {
        ViewModel oldViewModel = mMap.get(key);
        if (oldViewModel != null) {
            oldViewModel.onCleared();
        }
        mMap.put(key, viewModel);
    }

    final ViewModel get(String key) {
        return mMap.get(key);
    }

    /**
     *  Clears internal storage and notifies ViewModels that they are no longer used.
     */
    public final void clear() {
        for (ViewModel vm : mMap.values()) {
            vm.onCleared();
        }
        mMap.clear();
    }
}

所以ViewModel是被保存在这里,通过一个HashMap进行存取。 咦,等一下,这个HashMap以及这个ViewModelStore都是局部变量,旋转屏幕也会销毁呀,明明不能保存数据。

setRetainInstance

这时候看到了HolderFragment的构造函数

public HolderFragment() {
        setRetainInstance(true);
    }

查看API

/**
     * Control whether a fragment instance is retained across Activity
     * re-creation (such as from a configuration change).  This can only
     * be used with fragments not in the back stack.  If set, the fragment
     * lifecycle will be slightly different when an activity is recreated:
     * <ul>
     * <li> {@link #onDestroy()} will not be called (but {@link #onDetach()} still
     * will be, because the fragment is being detached from its current activity).
     * <li> {@link #onCreate(Bundle)} will not be called since the fragment
     * is not being re-created.
     * <li> {@link #onAttach(Activity)} and {@link #onActivityCreated(Bundle)} <b>will</b>
     * still be called.
     * </ul>
     */
    public void setRetainInstance(boolean retain) {
        mRetainInstance = retain;
    }

设定了retain=true之后,Fragment的生命周期将会改变,不会因为旋转屏幕等操作重建,但是会有以下几点后果

  • onDestroy()将不会回调
  • onDetach仍会回调,因为Activity重建了,所以Fragment暂时分离
  • onCreate(Bundle)也不会回调,因为Fragment并没有销毁
  • onAttach()onActivityCreated(Bundle)仍会回调

通过setRetainInstance(true),改变了HolderFragment的部分生命周期流程,从而保持ViewModelStore从而保持ViewModel,所以保持了ViewModel中的数据。

get()

最后看ViewModelProvider.get()

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

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.
            }
        }

        viewModel = mFactory.create(modelClass);
        mViewModelStore.put(key, viewModel);
        //noinspection unchecked
        return (T) viewModel;
    }

先在ViewModelStoreviewModel,拿到直接返回,拿不到就通过Factory.create()反射实例化

@Override
        public <T extends ViewModel> T create(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);
        }

@Override
        public <T extends ViewModel> T create(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);
            }
        }

如果是AndroidViewModel的话,则会带多个application参数,所以官方提供自定义Factory自行操作

/**
     * Implementations of {@code Factory} interface are responsible to instantiate ViewModels.
     */
    public interface Factory {
        /**
         * Creates a new instance of the given {@code Class}.
         * <p>
         *
         * @param modelClass a {@code Class} whose instance is requested
         * @param <T>        The type parameter for the ViewModel.
         * @return a newly created ViewModel
         */
        <T extends ViewModel> T create(Class<T> modelClass);
    }

总结

ViewModel负责帮UI准备数据,减轻 UI 负担,并能够自动保存数据防止某些场景丢失、快速读取更新 UI。 由于LiveData的观察者特性,所以ViewModel还可以实现数据共享,例如最常见的ViewPager绑定着多个Fragment,某些场景会涉及到Fragment之间的通信,以往的做法要不是通过Activity进行中转通信,要不是使用事件总线。使用ViewModel可以很方便解决这个问题,通过of(getActivity())绑定到Activity,这样的ViewModel将会是同一个实例,也就能共同使用同一份数据从而进行通信等操作。

ViewModel的生命周期会持续到Activity.onDestroyFragment.onDetach,并会回调onCleared,所以有些大型操作依旧得进行解除。

— Evil Mouth