今天看啥  ›  专栏  ›  Android开发实践

Android 架构之美 - ViewModel

Android开发实践  · 掘金  ·  · 2018-10-31 08:56

ViewModel 是数据与 UI 分离的中间层,提供了一个将数据转换为 UI 友好型数据的场所。其次,它也提供了多 Fragment 复用相同 ViewModel 的机制。

简单使用

class UserViewModel(): ViewModel() {  
    val userLiveData = LiveData<User>()

    override fun onCleared(){
       // clear 工作,例如 Rxjava 里取消订阅
    }
}

class UserFragment(): Fragment() {  
    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        val viewModel = ViewModelProviders.of(this).get(UserViewModel::class.java)
        viewModel.userLiveData.observe(viewLifecycleOwner, Observer {
            // update ui
        })
    }
}

如果你想在多个 Fragment (同一个 Activity )里复用这个 UserViewModel , 那么只需要将 ViewModelProviders.of(this) 更换为 ViewModelProviders.of(activity) 即可, 这样 UserViewModel 的生存时间就会和 activity 一致。

注意:这里我使用了 viewLifecycleOwner 而没用使用 lifecycleOwner 。 而 viewLifecycleOwner 是在 androidx 中才存在,这主要是解决 Fragment 中 View 的生命周期与 Fragment 的生命周期不同步的问题。 假设从 FragmentA 跳转到 FragmentB 时, FragmentA 会经历下列生命周期:

onPause -> onStop -> onDestroyView

从 FragmentB 返回到 FragmentA 时, FragmentA 又会重新经历下列生命周期:

onCreateView -> onActivityCreated -> onStart -> onResume

如果用 Fragment 的生命周期 lifecycleOwner , 那么将会在FragmentB 返回到 FragmentA 时产生新的 observe 订阅,而旧的订阅并没有被销毁。在之前的版本,可能需要开发者自己去处理重复订阅的问题,而新版 androidx 则提供了 View 的生命周期 viewLifecycleOwner , 开发者可以视需求而采用不同的生命周期。

源码解析

ViewModel 并不是由用户直接通过构造器生成的,而是通过 ViewModelProvider 来获取,这使得使用者不用关心 ViewModel 的生命周期,全部交给框架内部解决。 框架提供了 ViewModelProviders 这个工具方法来提供各种场景下的 ViewModelProvider

// 提供一个将 viewModel 存储 在 Fragment 的 ViewModelProvider
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);
}

// 提供一个将 viewModel 存储 在 Activity 的 ViewModelProvider
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);
}

我们可以看出,其关键根据传参是 Fragment 还是 FragmentActivity 而提供不同的 ViewModelStore :

  • 如果是 Fragment , 则 ViewModelStore 依附在 Fragment 上,它提供的 ViewModel 可以存活至 Fragment 销毁。
  • 如果是 FragmentActivity ,则 ViewModelStore 依附在 FragmentActivity 上,它提供的 ViewModel 可以存活至 FragmentActivity 销毁。 因此这些 ViewModel 可以在多个 Fragment 中共享。

另外值得一提的是:只有 androidx 中的 Fragment 和 FragmentActivity 才实现了 ViewModelStoreOwner 接口。 所以 androidx 和这之前 support-v4 里的实现并不一样。 旧版本的实现也值得一提,这个我稍后再详细介绍。

public interface ViewModelStoreOwner {  
    /**
     * Returns owned {@link ViewModelStore}
     *
     * @return a {@code ViewModelStore}
     */
    @NonNull
    ViewModelStore getViewModelStore();
}

// Fragment 关于 ViewModelStoreOwner 的实现
public class Fragment implements ViewModelStoreOwner, ... {  
    // 省略其它源码...
    // ViewModelStore for storing ViewModels associated with this Fragment
    ViewModelStore mViewModelStore;

    public ViewModelStore getViewModelStore() {
        if (getContext() == null) {
            throw new IllegalStateException("Can't access ViewModels from detached fragment");
        }
        if (mViewModelStore == null) {
            mViewModelStore = new ViewModelStore();
        }
        return mViewModelStore;
    }

    public void onDestroy() {
        mCalled = true;
        FragmentActivity activity = getActivity();
        boolean isChangingConfigurations = activity != null && activity.isChangingConfigurations();
        if (mViewModelStore != null && !isChangingConfigurations) {
            mViewModelStore.clear();
        }
    }
}

// FragmentActivity 关于 ViewModelStoreOwner 的实现
public class FragmentActivity extends ComponentActivity implements ViewModelStoreOwner, ... {  
    private ViewModelStore mViewModelStore;

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

    protected void onCreate(@Nullable Bundle savedInstanceState) {
        //...
        NonConfigurationInstances nc =
                (NonConfigurationInstances) getLastNonConfigurationInstance();
        if (nc != null && nc.viewModelStore != null && mViewModelStore == null) {
            mViewModelStore = nc.viewModelStore;
        }
        // ...
    }

    protected void onDestroy() {
        super.onDestroy();

        if (mViewModelStore != null && !isChangingConfigurations()) {
            mViewModelStore.clear();
        }

        mFragments.dispatchDestroy();
    }
}

而 ViewModelStore 本身的实现比较容易,基本就是一个 HashMap 来保存 ViewModel 实例:

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

    /**
     *  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();
    }
}

ViewModelStore 的问题解决了,我们就可以看看 ViewModelProvider 是如何构造 ViewModel 实例并存入 ViewModelFactory 的了。首先看看 ViewModelProvider 里提供的 Factory 接口:

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
     */
    @NonNull
    <T extends ViewModel> T create(@NonNull Class<T> modelClass);
}

通过名字我们就可以看出,这是一个工厂模式的应用,我们可以通过实例出不同的 Factory 来以不同的方式构造 ViewModel ,当然,如果我们没有特殊需求,可以直接使用框架提供的默认 Factory 来构造无参数的 ViewModel 。 如果我们需要传参,则我们需要自己实现 Factory 接口了。

public class ViewModelProvider {

    private static final String DEFAULT_KEY = "androidx.lifecycle.ViewModelProvider.DefaultKey";

    private final Factory mFactory;
    private final ViewModelStore mViewModelStore;

    public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
        String canonicalName = modelClass.getCanonicalName();
        //...
        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.
            }
        }

        // 如果不存在,则通过 Factory 创建,并放入 mViewModelStore 中缓存
        viewModel = mFactory.create(modelClass);
        mViewModelStore.put(key, viewModel);
        //noinspection unchecked
        return (T) viewModel;
    }
}

至此,整个 ViewModel 就说得差不多了,使用简单,原理也不是很难,但是功能确实强大。接下来会详细介绍下 ViewModel 1.x 版本时 ViewModelStore 的获取。那个时候, Fragment 和 FragmentActivity 还没有实现 ViewModelStoreOwner 。那我们又如何无感知的让生命周期融入框架中呢?

其实这里用了一个技巧,一个添加到 FragmentManager 的 Fragment 生命周期与 Activity 或者 parent fragment 的生命周期是一致的, 因此可以通过向 Activity 或者 Fragment 添加一个无 View 的 Fragment 来获取其生命周期,再将 ViewModelStore 依附在 这个 Fragment 上。 如果有看过 glide 源码的话,就可以发现其实它也是这么做的,非常成熟的套路,可以学习学习。

ViewModel 1.x 中 ViewModelStore 的获取

public static ViewModelProvider of(@NonNull Fragment fragment) {  
        //...
        return new ViewModelProvider(ViewModelStores.of(fragment), sDefaultFactory);
    }

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

我们可以看到,其主要其将核心逻辑抽取在了 ViewModelStores 中,接下来我以 FragmentActivity 为例继续追踪源码, Fragment 的逻辑大体相同,只是依赖于 childFragment 而已。

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

public static HolderFragment holderFragmentFor(FragmentActivity activity) {  
    // 移交给 HolderFragmentManager 处理
    return sHolderFragmentManager.holderFragmentFor(activity);
}

HolderFragment holderFragmentFor(FragmentActivity activity) {  
    FragmentManager fm = activity.getSupportFragmentManager();

    // 查找 FragmentManager 是否已经存在 HolderFragment
    HolderFragment holder = findHolderFragment(fm);
    if (holder != null) {
        // 已经存在,直接返回
        return holder;
    }

    // 因为 fragment commit 是个异步的过程,所以 FragmentManager 不存在可能是因为 commit 还没执行完成,因此需要在 mNotCommittedActivityHolders 里也存放一份。
    holder = mNotCommittedActivityHolders.get(activity);
    if (holder != null) {
        return holder;
    }

    if (!mActivityCallbacksIsAdded) {
        mActivityCallbacksIsAdded = true;
        // 添加 callback, 在 activity 销毁时移除 mNotCommittedActivityHolders 里的缓存, 一个应用只需添加一个 callback 足矣。
        activity.getApplication().registerActivityLifecycleCallbacks(mActivityCallbacks);
    }

    // 不存在,则创建,并存入 mNotCommittedActivityHolders 中
    holder = createHolderFragment(fm);
    mNotCommittedActivityHolders.put(activity, holder);
    return holder;
}

private static HolderFragment createHolderFragment(FragmentManager fragmentManager) {  
    HolderFragment holder = new HolderFragment();
    // 添加 HOLDER_TAG, 便于寻找
    fragmentManager.beginTransaction().add(holder, HOLDER_TAG).commitAllowingStateLoss();
    return holder;
}

private static HolderFragment findHolderFragment(FragmentManager manager) {  
    //...
    // 通过 HOLDER_TAG 来寻找
    Fragment fragmentByTag = manager.findFragmentByTag(HOLDER_TAG);
    if (fragmentByTag != null && !(fragmentByTag instanceof HolderFragment)) {
        throw new IllegalStateException("Unexpected "
                + "fragment instance was returned by HOLDER_TAG");
    }
    return (HolderFragment) fragmentByTag;
}

这就是之前版本的做法,不过其实也可以通过 lifecycle 去做。有了 lifecycle 之后,这种做法就显得有些浪费了。但不管如何,这种对 Fragment 的应用也是值得称赞的。

查看原文: Android 架构之美 - ViewModel




原文地址:访问原文地址
快照地址: 访问文章快照