小松的技术博客

六和敬

若今生迷局深陷,射影含沙。便许你来世袖手天下,一幕繁华。 你可愿转身落座,掌间朱砂,共我温酒煮茶。

Android 架构之美 - ViewModel

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 中才存在,这主要是解决 FragmentView 的生命周期与 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 中的 FragmentFragmentActivity 才实现了 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 的获取。那个时候, FragmentFragmentActivity 还没有实现 ViewModelStoreOwner。那我们又如何无感知的让生命周期融入框架中呢?

其实这里用了一个技巧,一个添加到 FragmentManagerFragment生命周期与 Activity 或者 parent fragment 的生命周期是一致的, 因此可以通过向 Activity 或者 Fragment 添加一个无 ViewFragment 来获取其生命周期,再将 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 的应用也是值得称赞的。

←微信← →支付宝 →