Android Architecture Components -- ViewModel

栏目: 后端 · 发布时间: 6年前

内容简介:ViewModel 我们在 MVC、MVP 和 MVVM 架构中经常见到这个概念。本文来介绍 LifeCycle 库中的 ViewModel 库。ViewModel 库是用来保存应用UI数据的类,它可以在配置变更(比如屏幕选择导致的Activit重建)后继续存在,可以避免因数据Activit重建需要数据保存和恢复问题而导致的各种错误。

ViewModel 我们在 MVC、MVP 和 MVVM 架构中经常见到这个概念。

本文来介绍 LifeCycle 库中的 ViewModel 库。

ViewModel 库是用来保存应用UI数据的类,它可以在配置变更(比如屏幕选择导致的Activit重建)后继续存在,可以避免因数据Activit重建需要数据保存和恢复问题而导致的各种错误。

因此,Android 推荐使用这样的架构设计,将应用中所有的UI数据保存在ViewModel中,而不是Activity中,这样能保证数据不受比如configuration change 带来的Activity重建而带来的影响。

Android 开发中一个常见的坑是很多的变量、逻辑和数据放在Activity和Fragment中,这样的代码比较混乱和难以维护,这种开发模式违反了单一职责的原则,ViewModel可以有效地划分责任,具体地,它可以用来保存 Activity 的所有UI数据,然后Activity仅负责了解如何在屏幕上显示该数据和接受用户互动,但是它不会处理这些互动,如果应用要加载和存储数据,建议创建一个 Repository 的存储区类。

另外还应该确保 ViewModel 不会因为承担过多的责任而变的臃肿,要避免这种情况,可以创建 Presenter 类,或者实现一种更成熟的架构。

使用

要创建一个 ViewModel,首先要扩展ViewModel类,然后将Activity中之前与UI相关的实例变量摆放在这个ViewModel中,然后在 Activity 中的 onCreate 中从 ViewModel Provider 的框架实用类再获取 ViewModel。

请注意 ViewModel Provider 将获取一个 Activity 的实例,它可以确保Activity重建后获取一个新的Activity实例,不过,请确保它始终和同一个ViewModel关联。

对于ViewModel实例,你可以通过getter函数在Activity中直接获取数据。

ViewModel可以单独使用,当然可以和 LiveData 相结合,ViewModel 返回一个 LiveData 对象,然后在 Activity中创建监听,然后更新数据。

使用 ViewModel 和 LiveData 可以创建反应式界面。也就是说当底层数据更新时,UI界面会相应自动更新。

当然还可以和 Data Binding 结合使用。

public class LiveDataTimerViewModel extends ViewModel {

    private static final int ONE_SECOND = 1000;

    private MutableLiveData<Long> mElapsedTime = new MutableLiveData<>();

    private long mInitialTime;

    public LiveDataTimerViewModel() {
        mInitialTime = SystemClock.elapsedRealtime();
        Timer timer = new Timer();

        // Update the elapsed time every second.
        timer.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                final long newValue = (SystemClock.elapsedRealtime() - mInitialTime) / 1000;
                mElapsedTime.postValue(newValue);
            }
        }, ONE_SECOND, ONE_SECOND);

    }

    public LiveData<Long> getElapsedTime() {
        return mElapsedTime;
    }
}
private LiveDataTimerViewModel mLiveDataTimerViewModel;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    setContentView(R.layout.chrono_activity_3);

    mLiveDataTimerViewModel = ViewModelProviders.of(this).get(LiveDataTimerViewModel.class);

    final Observer<Long> elapsedTimeObserver = new Observer<Long>() {
        @Override
        public void onChanged(@Nullable final Long aLong) {
            String newText = ChronoActivity3.this.getResources().getString(
                    R.string.seconds, aLong);
            ((TextView) findViewById(R.id.timer_textview)).setText(newText);
        }
    };

    mLiveDataTimerViewModel.getElapsedTime().observe(this, elapsedTimeObserver);
}

ViewModel的默认构造函数是没有任何参数的,如果你想要修改,可以使用ViewModelFactory类创建一个自定义的构造函数,

ViewModel 的生命周期

ViewModel 的生命周期是由传递给 ViewModelProviders.of(this) 的生命周期决定的,ViewModel 初始化后保留在内存中,直到 Activity 永久消失,比如退出并销毁。

下图展示了 ViewModel 的生命周期:

Android Architecture Components -- ViewModel

在系统第一次调用 Activity 的 onCreate() 方法时初始化 ViewModel,系统可能会在Activity的整个生命周期内多次调用 onCreate(),例如当设备屏幕旋转时。 ViewModel 的生命周期从第一次请求 ViewModel 开始,直到 Activity 被 FINISHED 并销毁。

Fragment 之间共享数据

我们经常遇到在一个Activity中有多个 Fragment 需要互相通信的情况,一般的做法可能就是两个 Fragment 都需要定义一些接口,而它们的 Activity 必须将两者联系在一起。 此外,Fragment 必须处理另一个 Fragment 尚未创建或可见的情况。

使用 ViewModel 可以解决这个问题, Fragment 可以通过一个在 Activity 范围内共享的 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时,两个Fragment都要使用getActivity()。这样,这两个Fragment会收到同一个SharedViewModel实例,该实例的作用域为Activity。

这样做有以下好处:

  • 这个 Activity 不需要做任何事情,也不需要知道 Fragment 之间的交流。
  • 除了 SharedViewModel 的接口之外,Fragment不需要了解彼此。 如果其中一个 Fragment 消失,另一个 Fragment 继续照常工作。
  • 每个 Fragment 都有自己的生命周期,不受其他生命周期的影响。 一个 Fragment 替换成另一个 Fragment,UI继续工作也没有任何问题。

用 ViewModel 来代替 Loaders

在之前我们会经常使用 CursorLoader 来保持应用程序中的数据与数据库同步。那么现在就可以用 ViewModel 来代替 Loader 的使用了。使用 ViewModel 可以将 UI controller 与数据加载操作分开,这意味着在类之间的强引用减少了。

Loader 通用的做法是使用 CursorLoader 来观察数据库中的数据,当数据库的数据变化时,Loader 会自动触发一次数据加载并更新UI,如下图:

Android Architecture Components -- ViewModel

现在我们可以使用 ViewModel + LiveData + Room 的组合来代替 Loader,ViewModel 确保设备配置改变后数据仍然存在,当数据发生变化时,Room 会通知 LiveData,而 LiveData 可以来更新 UI。如下图:

Android Architecture Components -- ViewModel

注意事项

不要将 Context 传入 ViewModel

也就是说 Activity、Fragment和View都不能被传入,正如之前介绍,ViewModel 可以比相联的 Activity 和Fragment 的生命周期都要长。假设你在 ViewModel 中存储了一个Activity,当旋转屏幕导致Activity销毁时,但是 ViewModel 还保存着已经被销毁的 Activity 的引用,这样就会造成内存泄漏。

如果你需要比ViewModel生命周期长的Application类,可以使用 AndroidViewModel的子类,通过这个子类就可以直接使用 Application 的引用了。

ViewModel 和 onSaveInstanceState 并用

ViewModel 不应该取代 onSaveInstanceState 的使用,他们两者是相辅相成的。当进程被关闭时,ViewModel将被销毁,但是 onSaveInstanceState 将不会受到影响。

另外,ViewModel可以用来存储大量数据,而 onSaveInstanceState 就只可以用来存储有限的数据了。我们尽可能把多一点的UI数据存储到ViewModel中。在Activity重新创建是不需要重新加载或者生成数据。

如果进程被关闭,我们应该用 onSaveInstanceState 来存储足够还原 UI 状态的最少数据。

源码分析

照例来看看源码实现。

创建实例

ViewModelProviders.of(this).get(TimerViewModel.class) 这个方法会返回一个 TimerViewModel 实例对象。

先来看一下 ViewModelProvider 的 get 方法:

public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
    // 首先从 ViewModelStore 根据key中获取缓存的实例
    // key 的形式 android.arch.lifecycle.ViewModelProvider.DefaultKey:com.example.heqiang.testsomething.jetpack.TimerViewModel
    ViewModel viewModel = mViewModelStore.get(key);

    if (modelClass.isInstance(viewModel)) {
        // 如果有缓存,那么返回缓存对象
        return (T) viewModel;
    } else {
        //noinspection StatementWithEmptyBody
        if (viewModel != null) {
            // TODO: log a warning.
        }
    }
    // 如果没有,生成一个对象,并放入到 ViewModelStore
    viewModel = mFactory.create(modelClass);
    mViewModelStore.put(key, viewModel);
    //noinspection unchecked
    return (T) viewModel;
}

ViewModelStore 其实就是内部维护了一个 HashMap 来存放 ViewModel 对象的。

ViewModelStore 是在 ViewModelProviders.of 方法中创建 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(ViewModelStores.of(activity), factory);
}
public static ViewModelStore of(@NonNull FragmentActivity activity) {
    // 如果实现了 ViewModelStoreOwner 接口,那么直接调用个方法获取
    if (activity instanceof ViewModelStoreOwner) {
        return ((ViewModelStoreOwner) activity).getViewModelStore();
    }
    // 获取 HolderFragment 实例来获取 ViewModelStore
    return holderFragmentFor(activity).getViewModelStore();
}

HolderFragment.holderFragmentFor()

HolderFragment holderFragmentFor(FragmentActivity activity) {
    FragmentManager fm = activity.getSupportFragmentManager();
    // 获取 HolderFragment
    HolderFragment holder = findHolderFragment(fm);
    // 如果已经创建,返回
    if (holder != null) {
        return holder;
    }
    // 从缓存列表中获取
    holder = mNotCommittedActivityHolders.get(activity);
    if (holder != null) {
        return holder;
    }

    if (!mActivityCallbacksIsAdded) {
        mActivityCallbacksIsAdded = true;
        // 注册生命周期回调,当生命周期结束时remove缓存的 HolderFragment
        activity.getApplication().registerActivityLifecycleCallbacks(mActivityCallbacks);
    }
    // 创建 HolderFragment 并添加到 HolderFragment
    holder = createHolderFragment(fm);
    mNotCommittedActivityHolders.put(activity, holder);
    return holder;
}

从上面这系列分析可以看到,ViewModel 生命周期和 ViewModelStore 相关,而 ViewModelStore 又和 HolderFragment 相关。

那么 HolderFragment 是什么呢?接下来分析。

HolderFragment

public class HolderFragment extends Fragment implements ViewModelStoreOwner {
    private static final HolderFragmentManager sHolderFragmentManager = new HolderFragmentManager();

    private ViewModelStore mViewModelStore = new ViewModelStore();

    public HolderFragment() {
        setRetainInstance(true);
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        sHolderFragmentManager.holderFragmentCreated(this);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        mViewModelStore.clear();
    }

    @NonNull
    @Override
    public ViewModelStore getViewModelStore() {
        return mViewModelStore;
    }
    ......
}

HolderFragment 继承自 Fragment 实现了 ViewModelStoreOwner 接口,在生命周期结束的 onDestroy() 执行 ViewModelStore.clear(),然后这个方法会调用 ViewModel 的 onCleared() 方法。

那么 HolderFragment 是怎么做到在 Activity 配置变更导致 Activity 重建的情况下而不销毁呢?

玄机就在 setRetainInstance(true) 这里。

我们称之为 Fragment 的非中断保存,设置了这个属性的 Fragment 当它依附的 Activity 由于配置变更导致的重建时,该 Fragment 不会销毁。

ViewModel 维持它的生命周期正式巧用了 Fragment 的这一特性。

总结

综合上面的源码分析,我们可以得到下面的结论:

  • ViewModel 的生命周期和 HolderFragment 保持同步。
  • HolderFragment 持有 ViewModelStore,ViewModelStore 持有 ViewModel,ViewModel 以键值对缓存在 ViewModelStore 的 HashMap 中。
  • 一个 Activity 或者 Fragment 只会有一个HolderFragment。
  • 一个 Activity 或者 Fragment 可以有多个 ViewModel。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Linux从入门到精通

Linux从入门到精通

刘忆智、等 / 清华大学出版社 / 2010-1-1 / 59.00元

linux是目前增长最迅速的操作系统。本书由浅入深、循序渐进地向读者介绍linux的基本使用和系统管理。全书内容包括linux概述、linux安装、linux基本配置、桌面环境基本操作、shell基本命令、文件和目录管理、软件包管理、磁盘管理、用户与用户组管理、进程管理、网络配置、浏览网页、收发邮件、文件传输和共享、远程登录、多媒体应用、图像浏览和处理、打印机配置、办公软件的使用、linux编程工......一起来看看 《Linux从入门到精通》 这本书的介绍吧!

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具