ViewModel

什么是 ViewModel

ViewModel 主要是用来存储 UI 中需要的数据,并且支持在配置发生变化之后依然存活。

生命周期

在 onCleared 可以对 ViewModel 清理

为什么需要 ViewModel

  1. 单一设计原则

    为了代码简洁,好维护,在 Activity 或者 Fragment 中,不应该存在与 UI 更新无关的代码,这个时候就需要把诸如数据加载等代码挪出,ViewModel 就是 UI 和 Model 之间的桥梁。

  2. 数据恢复

    当手机配置更改,系统重新创建 Activity 时,如果数据代码写在 Activity 中,那么相应的数据也会被销毁。为了解决这个,把数据放在 ViewModel 中就不会有这个问题了。

怎么使用 ViewModel

导入库

  • Support Library 26.1+
  • lifecycle:extensions
    implementation "android.arch.lifecycle:extensions:1.1.1"
    

注意

ViewModel 不能引用任何 View,Lifecycle,或者 Activity Context,不然很容易引起内存泄漏。

和 LiveData 一起使用

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

    private void loadUsers() {
        ...
        // 伪代码
        ...
        users.postValue(...)
    }
}

通过 ViewModelProviders.of(this).get(MyViewModel.class) 实例化 MyViewModel

public class MyActivity extends AppCompatActivity {
    public void onCreate(Bundle savedInstanceState) {
        ...
        MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class);
        model.getUsers().observe(this, users -> {
            // 更新 UI
        });
    }
}

ViewModelFactory

如果你的 ViewModel 需要在初始化的时候传入一些参数应该怎么办呢?

ViewModelFactory 可以帮到你

class ViewModelFactory(private val repository: MovieRepository) : ViewModelProvider.Factory {

    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        if (modelClass.isAssignableFrom(MainViewModel::class.java)) {
            return MainViewModel(repository) as T
        }
        throw IllegalArgumentException("Unknown ViewModel class")
    }
}


class MainViewModel(repository: MovieRepository) : ViewModel() {

}
ViewModelProviders.of(this, ViewModelFactory(repository)).get(MainViewModel::class.java)

在两个 Fragment 之间共享数据

如果两个 Fragment 都属于同一个 Activity 的话,可以通过 ViewModelProviders.of(getActivity()).get() 共享同一个 ViewModel

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 onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
        itemSelector.setOnClickListener(item -> {
            model.select(item);
        });
    }
}

public class DetailFragment extends Fragment {
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
        model.getSelected().observe(this, item -> {
           // Update the UI.
        });
    }
}

ViewModel 实现原理

类图

源码解析

基本概念

  • ViewModelProvider

    是一个用来实例化 ViewModel 的类

  • ViewModelStore

    是一个用来存 ViewModel 的类

  • ViewModelStoreOwner

    是一个接口,表示拥有 ViewModel 的类,对外暴露 getViewModelStore() 方法,Fragment 和 FragmentActivity 都实现了这个接口

是如何实现 两个 Fragment 之间共享数据 的?

因为 FragmentActivity 和 Fragment 都实现了 ViewModelStoreOwner,就表示他们都有 ViewModelStore,都可以用来存放 ViewModel。

当使用 ViewModelProviders.of(getActivity()) 时,用同一个 activity 获得的 ViewModelProvider,实际上就是用同一个 ViewModelStore,这样 get() 到的 ViewModel 也是同一个对象。

ViewModelProviders.of(getActivity()).get(SharedViewModel.class);


public class ViewModelProviders {
    ...
    public static ViewModelProvider of(@NonNull FragmentActivity activity,
            @Nullable Factory factory) {
        Application application = checkApplication(activity);
        if (factory == null) {
            factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
        }
        // 生成 ViewModelProvider 对象
        return new ViewModelProvider(ViewModelStores.of(activity), factory);
    }
    ...
}

public class ViewModelStores {
    ...
    @NonNull
    @MainThread
    public static ViewModelStore of(@NonNull FragmentActivity activity) {
        // 如果是 ViewModelStoreOwner 就用 ViewModelStoreOwner.getViewModelStore()
        if (activity instanceof ViewModelStoreOwner) {
            return ((ViewModelStoreOwner) activity).getViewModelStore();
        }
        // 如果不是就用 HolderFragment 实现
        return holderFragmentFor(activity).getViewModelStore();
    }
    ...
}

public class ViewModelProvider {
    public <T extends ViewModel> T get(@NonNull 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)) {
            return (T) viewModel;
        } else {
            if (viewModel != null) {
                // TODO: log a warning.
            }
        }
        viewModel = mFactory.create(modelClass);
        mViewModelStore.put(key, viewModel);
        return (T) viewModel;
    }

    public static class NewInstanceFactory implements Factory {
        @Override
        public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
            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);
            }
        }
    }
}

疑问?

实际上 ViewModel 是存在 ViewModelStore 中,ViewModelStore 又是放在 Activity 中,那么为啥当配置发生改变(旋转屏幕)后,Activity 重建,并不会让 ViewModel 的对象丢失,这是为啥呢?

那是因为当配置发生改变会触发 Activity#onRetainNonConfigurationInstance,然后缓存起来啦。

public final Object onRetainNonConfigurationInstance() {
    ...
    // 缓存起来了
    nci.viewModelStore = mViewModelStore;
    ...
    return nci;
}

protected void onCreate(@Nullable Bundle savedInstanceState) {
    ...
    // 如果有缓存,用缓存的 ViewModelStore
    NonConfigurationInstances nc =
            (NonConfigurationInstances) getLastNonConfigurationInstance();
    if (nc != null) {
        mViewModelStore = nc.viewModelStore;
    }
    ...
}

总结

至此关于 生命周期组件 三个组件都已经讲完了,内部原理,内部代码实现都是非常简单的。在实现的 ViewModel 的时候需要注意,不要和 View 等和 Activity 的 Context 有关的类有依赖,因为 ViewModel 生命周期比 Activity 生命周期长,如果持有引用会导致内存泄漏。