ViewModel简单使用与解析

在开始说ViewModel之前我们先来一些我们经常要考虑问题:
1.Activity屏幕旋转怎么处理保留和处理数据?通过onSaveInstanceState?那如果数据比较大呢?
2.如果Acntivity/Fragment持有的后台线程在未结束之前我们按了返回,任务结束后操作UI是不是各种NullPointerException?
3.如果我们正在执行一个很耗时的任务,我们怎样能做到在旋转屏幕的时候不中断这个任务而后能返回正解的结果呢?

以上种种跟生命周期相关的问题都是比较常见又不得不面对的,那有没有很优雅的处理方式呢?
有,就是ViewModel

ViewModel官方介绍:

ViewModel类旨在以生命周期意识的方式存储和管理与UI相关的数据。
ViewModel类允许数据在配置更改(例如屏幕旋转)后继续存在。

我们先来看下应用场景

我们在竖屏状态下启动了一个耗时任务,然后翻转手机到横屏状态

横竖屏切换

通过图片我们看到在切换到横屏时耗时任务并没有中断,在Activity重新创建后还能收到它返回的数据。我们来看下这过程Activity生命周期的变化:


image.png

通过图片我们看到Activity确实已经Destroy然后又重新Cread了。然后如果我们直接按返回键退出这个Activity


image.png

你会看到Activity被Destroy的同时,ViewModel也跟着Clear了。
说明我们的ViewModel不仅仅能保存数据,能还保留任务,而且对生命周期还是可感知的。

那ViewModel是怎么做到这些的呢?我们先从简单的使用开始:

导入ViewModel:

//androidx的版本,包含ViewModel和LiveData
implementation "androidx.lifecycle:lifecycle-extensions:2.0.0"
//非androidx版本
implementation "android.arch.lifecycle:extensions:1.1.1"

创建一个继承自ViewModel的类:

public class MViewModel extends ViewModel

获取一个ViewModel实例:

//在Activity中
mViewModel = ViewModelProviders.of(this).get(MViewModel.class);
//在Fragment中,传this是获取和此Fragment绑定的ViewModel,
//传getActivity则获取到的是跟activity中的是同一个ViewModel
mViewModel = ViewModelProviders.of(this).get(MViewModel.class);
mViewModel = ViewModelProviders.of(getActivity).get(MViewModel.class);

我们看下完整的代码

自定义MViewModel(提前用到了LiveData可以先不用管,后面会讲到)

public class MViewModel extends ViewModel {

    MutableLiveData<String> mString;
    MutableLiveData<String> msgString;


    public MutableLiveData<String> getString(){
        if(mString==null){
            mString=new MutableLiveData<>();
        }
        return mString;
    }

    public MutableLiveData<String> getMsgString(){
        if(msgString==null){
            msgString=new MutableLiveData<>();
        }
        return msgString;
    }

    public void startTask(){
        new Thread(){
            @Override
            public void run() {
                //请求网络数据、数据库、加载大图等。
                //如果在Activity转屏的时候取消这些任务,那恢复的时候就要重新加载,势必浪费资源
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //此处用的是LiveData,如果我们不用LiveData可能是通过EventBus之类的把数据传递出去的
                mString.postValue("我是来自3秒后的数据");
                super.run();
            }
        }.start();
    }


    @SuppressWarnings("WeakerAccess")
    protected void onCleared() {
        //做一些数据清理工作
        Log.e("MViewModel","onCleared");
    }

}

MainActivity中获取了一个ViewModel

public class MainActivity extends AppCompatActivity {

    private MViewModel mViewModel;
    private TextView mTextView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTextView=findViewById(R.id.desc);
        mViewModel = ViewModelProviders.of(this).get(MViewModel.class);
        
       //订阅数据变化,可以暂时理解为EventBus的消息订阅
        mViewModel.getString().observe(this, new Observer<String>() {
            @Override
            public void onChanged(String s) {
                Log.e("MainActivity", "耗时任务结束返回数据");
                mTextView.setText(s);
            }
        });

        mViewModel.getMsgString().observe(this, new Observer<String>() {
            @Override
            public void onChanged(String s) {
                Log.e("MainActivity", "Fragment1发过来的数据");
                mTextView.setText(s);
            }
        });

        getSupportFragmentManager().beginTransaction().replace(R.id.container, new TestFragment(),
                TestFragment.class.getName()).commit();
        getSupportFragmentManager().beginTransaction().replace(R.id.container2, new TestFragment2(),
                TestFragment.class.getName()).commit();

        findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.e("MainActivity", "开始耗时任务");
                mViewModel.startTask();
            }
        });


    }
}

为了说明ViewModel的作用域,我们再建一个TestFragment

public class TestFragment extends Fragment {

    private static final String TAG = "TestFragment";

    private View view;
    private MViewModel mMViewModel;
    private Button mBtn;
    private TextView mDesc;

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
        view = inflater.inflate(R.layout.fragment_test, container, false);
        mBtn = view.findViewById(R.id.btn);
        mDesc = view.findViewById(R.id.desc);
        return view;

    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        //这里传入了getActivity(),返回的是跟Activity同一个ViewModel.
        mMViewModel = ViewModelProviders.of(getActivity()).get(MViewModel.class);
        mMViewModel.getString().observe(this, new Observer<String>() {
            @Override
            public void onChanged(String s) {
                mDesc.setText(s);
            }
        });

        mBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mMViewModel.getMsgString().setValue("Fragment1 发送的数据");
            }
        });
    }

}

通过上面的代码你会发现ViewModel使用起来非常的简单,只需要继承ViewModel即可,其余的生命周期之类的就完全不用我们考虑,它自己就能处理。除此之外同一Activity内的Fragment还能不通过Activity直接进行数据交互和共享,实现了真正意义上的解耦。

原理解析

1创建过程解析

1.1 ViewModelProviders.of()

ViewModelProviders 类提供了4个静态工厂方法 of() 创建新的 ViewModelProvider 对象。

    public static ViewModelProvider of(@NonNull Fragment fragment) {
        return of(fragment, null);
    }    

    public static ViewModelProvider of(@NonNull FragmentActivity activity) {
        return of(activity, null);
    }

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

    public static ViewModelProvider of(@NonNull FragmentActivity activity,
            @Nullable Factory factory) {
        Application application = checkApplication(activity);
        if (factory == null) {
            //默认的factory
            factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
        }
        return new ViewModelProvider(activity.getViewModelStore(), factory);
    }

这里主要是ViewModelProvider、Fragemtn/Activity下的ViewModelStrore、Factory(从上你可以看出默认都是ViewModelProvider.AndroidViewModelFactory)

1.2 activity.getViewModelStore()和fragment.getViewModelStore()

      //ViewModelStoreOwner
      public interface ViewModelStoreOwner {
          ViewModelStore getViewModelStore();
      }

      //activity实现ViewModelStoreOwner
     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) {
                //需要注意的是这里,优先从NonConfigurationInstances中取出,这就是为什么Activity在转屏重建之后还能拿到原来的ViewModel的原因,后面我们看是怎么存的
                // Restore the ViewModelStore from NonConfigurationInstances
                mViewModelStore = nc.viewModelStore;
            }
            if (mViewModelStore == null) {
                mViewModelStore = new ViewModelStore();
            }
        }
        return mViewModelStore;
    }
    //fragment实现ViewModelStoreOwner
    public ViewModelStore getViewModelStore() {
        if (getContext() == null) {
            throw new IllegalStateException("Can't access ViewModels from detached fragment");
        }
        if (mViewModelStore == null) {
            mViewModelStore = new ViewModelStore();
        }
        return mViewModelStore;
    }

1.3 ViewModelStrore

我们看到上面的方法返回的都是一个ViewModelStore 对象,并且是Fragemnt/Activity下的一个成员变量。ViewModelStore 类中维护一个 Map<String, ViewModel> 对象存储已创建的 ViewModel 对象,说明一个Activity或者Fragment下可以保存多个不同的ViewModel,并提供 put() 和 get() 方法。

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

1.4 ViewModelProvider.Factory

Factory 接口定义了一个创建 ViewModel 的接口 create(),ViewModelProvider 在需要时调用该方法新建 ViewModel 对象。

    public interface Factory {
        <T extends ViewModel> T create(@NonNull Class<T> modelClass);
    }

Android 已经内置了2个 Factory 实现类,分别是:

  • AndroidViewModelFactory 实现类,可以创建 ViewModel 和 AndroidViewModel 子类对象。
  • NewInstanceFactory 类,只可以创建 ViewModel 子类对象。
    它们的实现都是通过反射机制调用 ViewModel 子类的构造方法创建对象。
public static class NewInstanceFactory implements Factory {
    @Override
    public <T extends ViewModel> T create(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);
        }
    }
}

AndroidViewModelFactory 继承 NewInstanceFactory 类,是个单例,支持创建 AndroidViewModel 子类对象。

public static class AndroidViewModelFactory extends ViewModelProvider.NewInstanceFactory {

    private static AndroidViewModelFactory sInstance;

    public static AndroidViewModelFactory getInstance(Application application) {
        if (sInstance == null) {
            sInstance = new AndroidViewModelFactory(application);
        }
        return sInstance;
    }

    private Application mApplication;

    public AndroidViewModelFactory(Application application) {
        mApplication = application;
    }

    @Override
    public <T extends ViewModel> T create(Class<T> modelClass) {
        if (AndroidViewModel.class.isAssignableFrom(modelClass)) {
            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);
    }
}

1.5 ViewModelProvider

ViewModelProviders.of()返回的是一个ViewModelProvider对象,这个类下面就两个字段

    private final Factory mFactory;
    private final ViewModelStore mViewModelStore;

通过上面的解析我们知道Factory是创建ViewModel的工厂,ViewModelStore是用来存储ViewModel的,所以我们来看下其中的get方法

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

至此,一个ViewModel便通过ViewModelProviders.of(activity/fragment).get()创建完成。

2生命周期和保存活过程解析

ViewModel生命周期.png

从上图可知ViewModel跟实例化时传入的Activity/Fragment的生命周期是保持一致的,但是从文章一开始我们就说到在屏幕旋转的时候Acticity已经被Destroy掉的情况下ViewModel却依然存活并正常执行其内部的任务。原因就在于Activity的Configuration Changes。

2.1 Configuration Change概述

Configuration 这个类描述了设备的所有配置信息,这些配置信息会影响到应用程序检索的资源。包括了用户指定的选项(locale和scaling)也包括设备本身配置(例如input modes,screen size and screen orientation).可以在该类里查看所有影响Configuration Change 的属性。

横竖屏切换是我们最常见的影响配置变化的因素,还有很多其他影响配置的因素有语言的更改(例如中英文切换)、键盘的可用性(这个没理解)等

常见的引发Configuration Change的属性:
横竖屏切换:android:configChanges="orientation"
键盘可用性:android:configChanges="keyboardHidden"
屏幕大小变化:android:configChanges="screenSize"
语言的更改:android:configChanges="locale"

在程序运行时,如果发生Configuration Change会导致当前的Activity被销毁并重新创建 ,即先调用onDestroy紧接着调用onCreate()方法。 重建的目的是为了让应用程序通过自动加载可替代资源来适应新的配置。

2.2 ViewModelStore的存取

当设备发生Configuration Change之后,Activity在Destroy之前会调用onRetainNonConfigurationInstance方法,FragmentActivity#onRetainNonConfigurationInstance()源码如下

   public final Object onRetainNonConfigurationInstance() {
        Object custom = onRetainCustomNonConfigurationInstance();
        //这地方是调用Fragment的处理方法,进去之后也是类似的处理方式
        FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig();

        if (fragments == null && mViewModelStore == null && custom == null) {
            return null;
        }

        NonConfigurationInstances nci = new NonConfigurationInstances();
        nci.custom = custom;
        //mViewModelStore将ViewModelStrore保存
        nci.viewModelStore = mViewModelStore;
        nci.fragments = fragments;
        return nci;
    }

我们可以看到此方法将ViewModelStore对象保存了下来,然后在onCreate的时候取回

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

此外回到我们之前说的FragmentActivity#getViewModelStore你会发现也做了相应的取出操作

public ViewModelStore getViewModelStore() {
       //省略 ...
        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;
    }

2.3 销毁过程

Configuration Change的过程不销毁,但是从文章开始我们发现当我们按返回键主动结束这个Activity的时候ViewModel也跟着执行onClear()了。我们来看下系统是如何区分的:
FragmentActivity#onDestroy

    protected void onDestroy() {
        super.onDestroy();
        if (mViewModelStore != null && !isChangingConfigurations()) {
            mViewModelStore.clear();
        }
        mFragments.dispatchDestroy();
    }

FragmentActivity#onDestroy

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

由此我们可以看出FragmentActivity跟Fragment的onDestroy方法中都对Configuration Change做了相应的判断。

最后的总结

  • 通过 ViewModelProviders 创建 ViewModelProvider 对象,调用该对象的 get() 方法获取 ViewModel 对象。 当 ViewModelStore 里不存在想要的对象,ViewModelProvider 会使用 Factory 新建一个对象并存放到 ViewModelStore 里。
  • 当发生 发生 Configuration Changes 时,FragmentActivity 利用 getLastNonConfigurationInstance()、onRetainNonConfigurationInstance() 方法实现 -ViewModelStore 的保留与恢复,进而实现 ViewModel 对象的保活。
  • 当 FragmentActivity 和 Fragment 被销毁时,会根据是否发生 Configuration Changes 来决定是否销毁 ViewModel。
  • 最重要的一点是在发生 Configuration Changes时,ViewModel的存活时间是会比Activity/Fragment的存活时间要长,所以我们不能在ViewModel中持有Activity/Fragment引用,否则会产生泄漏。所以我们不能通过接口的形式(MVP)将数据返回给Activity/Fragment使用,为此官方给出的方案便是结合LiveData使用,我们将在下一篇文章中讲到。
  • 如果ViewModel中要使用到Application可以通过继承AndroidViewModel,也是通过ViewModelProviders.of(this).get(MViewModel.class)方式实例化

示例源码地址

下一篇文章LiveData使用与解析

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,287评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,346评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,277评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,132评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,147评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,106评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,019评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,862评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,301评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,521评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,682评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,405评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,996评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,651评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,803评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,674评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,563评论 2 352

推荐阅读更多精彩内容