前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android Jetpack架构组件(三)之ViewModel

Android Jetpack架构组件(三)之ViewModel

原创
作者头像
xiangzhihong
修改2020-12-22 10:15:59
1.4K0
修改2020-12-22 10:15:59
举报
文章被收录于专栏:向治洪

我的博客即将同步至 OSCHINA 社区,这是我的 OSCHINA ID:xiangzhihong,邀请大家一同入驻:https://www.oschina.net/sharing-plan/apply

一、ViewModel简介

在早期的Android开发中,由于应用相对较小,页面相对简单,我们会将数据请求、页面UI处理和数据加载全部放在Activity或Fragment中进行,但是随着项目的迭代,这种开发方式显得越来越臃肿,并且也不易于项目的维护和扩展。

此时,借鉴后端的后端程序的开发思路,我们对Android项目进行了分层,典型的有MVC,MVP和MVVM等项目分层,然后每层负责自己的事情即可。以现在流行的MVVM模式为例。

  • Model层:数据层,主要负责数据实体和对数据实体的操作。
  • View层:视图层,对应Android的Activity、Fragment和View等,负责数据的显示以及与用户的交互。
  • ViewModel层:关联层,用于将Model和View进行绑定,当Model发生更改时,即时通知View进行刷新,当然,也可以反向通知。

在JetPack架构中,ViewModel组件是一个可以感知生命周期的形式来存储和管理视图相关的数据的组件,因此它适合以下场景。

  • 适合需要保存大量数据的场景。例如,对于需要保存小量数据的场景,我们可以使用Activity/ Fragment的onSaveInstanceState方法保存数据,然后在onCreate方法中利用onRestoreInstanceState进行还原。但是,onSaveInstanceState只适合用来存储数据量少且序列化或者反序列化不复杂的数据,如果被序列化的对象复杂的话,序列化会消耗大量的内存,进而造成丢帧和视觉卡顿等问题。而ViewModel不仅支持数据量大的情况,还不需要序列化、反序列化操作。
  • 在Android中,Activity/Fragment主要用于显示视图数据,如果它们也负责数据库或者网络加载数据等操作,那么势必造成代码臃肿,而将逻辑代码放到ViewModel之后,可以更有效的将视图数据相关逻辑和视图控制器分离开来。

除此之外,ViewModel的好处还有很多,但是最终的目的就是为了让代码可维护性更高,降低代码的冗余程度。

二、生命周期

我们知道,Android的Activity/Fragment是有生命周期的,我们可以在不同的生命周期函数中执行不同的操作来达到不同的目的。由于ViewModel是保存在内存中的,所以ViewModel的生命周期并不会随Activity/Fragment的生命周期发生变化 。

下图是官方给出的ViewModel与Activity的生命周期的对应关系示意图。

在这里插入图片描述
在这里插入图片描述

从上图可以看出,ViewModel会伴随着Activity/Fragment的整个生命周期,直到ViewModel绑定的Activity/Fragment执行onDestroy()方法之后才会被销毁。

三、基本使用

1,添加gradle以来,如下所示。

代码语言:txt
复制
dependencies {
   implementation 'androidx.lifecycle:lifecycle-viewmodel:2.2.0'
}

2,创建一个继承自ViewModel类的MyViewModel类,创建ViewModel类千万不能持有Context的引用,否则会引起内存泄漏,如果需要使用Context可以继承AndroidViewModel。

代码语言:txt
复制
    public class MyViewModel extends ViewModel {
    private MutableLiveData<String> user;
    public LiveData<String> getUsers() {
        if (user == null) {
            user = new MutableLiveData<String>();
            loadUsers();
        }
        return user;
    }

    private void loadUsers() {
        user.setValue("Android应用开发实战");
    }
}

3, 为了避免内存泄漏,我们可以在onCleared()方法中进行资源释放操作。然后,我们在Activity中就可以使用MyViewModel,如下所示。

代码语言:txt
复制
public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        MyViewModel model = new ViewModelProvider(this).get(MyViewModel.class);
        model.getUsers().observe(this, new Observer<String>() {
            @Override
            public void onChanged(@Nullable String s) {
                Log.d(TAG, "LiveData监听数据返回:"+s);
            }
        });
    }
}

四、 在 Fragment 之间共享数据

代码语言:txt
复制
    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 onViewCreated(@NonNull View view, Bundle savedInstanceState) {
            super.onViewCreated(view, savedInstanceState);
            model = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);
            itemSelector.setOnClickListener(item -> {
                model.select(item);
            });
        }
    }

    public class DetailFragment extends Fragment {

        public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
            super.onViewCreated(view, savedInstanceState);
            SharedViewModel model = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);
            model.getSelected().observe(getViewLifecycleOwner(), { item ->
               // Update the UI.
            });
        }
    }
    

五、源码分析

5.1 ViewModel源码

ViewModel类是一个抽象接口,其部分源码如下。

代码语言:txt
复制
public abstract class ViewModel {
    
    @Nullable
    private final Map<String, Object> mBagOfTags = new HashMap<>();
    private volatile boolean mCleared = false;

    @SuppressWarnings("WeakerAccess")
    protected void onCleared() {
    }

    @MainThread
    final void clear() {
        mCleared = true;
        if (mBagOfTags != null) {
            synchronized (mBagOfTags) {
                for (Object value : mBagOfTags.values()) {
                    // see comment for the similar call in setTagIfAbsent
                    closeWithRuntimeException(value);
                }
            }
        }
        onCleared();
    }

   
    @SuppressWarnings("unchecked")
    <T> T setTagIfAbsent(String key, T newValue) {
        T previous;
        synchronized (mBagOfTags) {
            previous = (T) mBagOfTags.get(key);
            if (previous == null) {
                mBagOfTags.put(key, newValue);
            }
        }
        T result = previous == null ? newValue : previous;
        if (mCleared) {
            closeWithRuntimeException(result);
        }
        return result;
    }

    
    @SuppressWarnings({"TypeParameterUnusedInFormals", "unchecked"})
    <T> T getTag(String key) {
        if (mBagOfTags == null) {
            return null;
        }
        synchronized (mBagOfTags) {
            return (T) mBagOfTags.get(key);
        }
    }

    private static void closeWithRuntimeException(Object obj) {
        if (obj instanceof Closeable) {
            try {
                ((Closeable) obj).close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

可以发现,ViewModel抽象类的主要作用就是使用HashMap存储数据。ViewModel 有一个子类AndroidViewModel,它的源码如下。

代码语言:txt
复制
public class AndroidViewModel extends ViewModel {
    @SuppressLint("StaticFieldLeak")
    private Application mApplication;

    public AndroidViewModel(@NonNull Application application) {
        mApplication = application;
    }

    @SuppressWarnings({"TypeParameterUnusedInFormals", "unchecked"})
    @NonNull
    public <T extends Application> T getApplication() {
        return (T) mApplication;
    }
}

与继承ViewModel不同,AndroidViewModel需要提供一个 Application 的 Context。

5.2 ViewModelProvider

在前面的示例代码中,我们在Activity中使用ViewModelProviders.of方法来获取ViewModel实例,如下所示。

代码语言:txt
复制
MyViewModel model = new ViewModelProvider(this).get(MyViewModel.class);

打开ViewModelProviders类的源码,可以发现ViewModelProviders一共有四个构造方法,都是用来创建ViewModelProvider对象,只不过参数不同而已。

代码语言:txt
复制
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 = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
        }
        return new ViewModelProvider(activity.getViewModelStore(), factory);
    }

在构建ViewModelProvider的时候需要用到ViewModelStore和Factory,下面我们来分别介绍一下它们。

5.3 ViewModelStore

ViewModelStore主要作用是存储ViewModel的容器,当我们打开ViewModelStore的源码时会发现ViewModelStore是通过HashMap来存储ViewModel的数据的。并且,ViewModelStore还提供了一个clear方法,用来清空Map集合里面的ViewModel,我们可以在Activity/Fragment的onDestroy方法执行clear方法执行ViewModel数据的清除。

代码语言:txt
复制
 protected void onDestroy() {
        super.onDestroy();
        if (mViewModelStore != null && !isChangingConfigurations()) {
            mViewModelStore.clear();
        }
    }

5.4 Factory

当我们使用ViewModelProvider获取ViewModel实例时,ViewModelProvider一共提供了4个构造函数,另一个比较重要的构造函数是

代码语言:txt
复制
public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory{
        mFactory = factory;
        mViewModelStore = store;
    }

ViewModelProvider的第二个参数是factory,它的子类有NewInstanceFactory和AndroidViewModelFactory两个,我们可以使用ViewModelProvider.AndroidViewModelFactory.getInstance获取单例的Factory对象,NewInstanceFactory源码如下。

代码语言:txt
复制
public static class NewInstanceFactory implements Factory {

        @SuppressWarnings("ClassNewInstance")
        @NonNull
        @Override
        public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
            //noinspection TryWithIdenticalCatches
            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的源代码如下。

代码语言:txt
复制
public static class AndroidViewModelFactory extends ViewModelProvider.NewInstanceFactory {

        private static AndroidViewModelFactory sInstance;

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

        private Application mApplication;

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

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

AndroidViewModelFactory实例化构造方法里面有个参数class,可以引用Context,其实是Appplication实例。在上面的代码中,如果是有application参数,则通过newInstance(application)实例化,否则调用父类的create方法然后通过newInstance()实例化。如果通过newInstance(application)实例化,就可以在ViewModel里面拿到Context,由于Application是APP全局的生命周期最长,所以就不存在内存泄露问题。

六、ViewModel是如何实现状态保留的

前面说过,ViewModel是不会随着Activity/Fragment的销毁而销毁的,因为ViewModel是将数据使用ViewModelStore 保存在HashMap 中,所以只要ViewModelStore不被销毁,则ViewModel的数据就不会被销毁。

众所周知,Android在横竖屏切换时会触发onSaveInstanceState,然后在还原时则会触发onRestoreInstanceState。除此之外,Android的Activity类还提供了onRetainNonConfigurationInstance和getLastNonConfigurationInstance两个方法,当设备状态发生改变时,上面的两个方法就会被系统调用。

其中,onRetainNonConfigurationInstance 方法用于处理配置发生改变时数据的保存,而getLastNonConfigurationInstance则用于恢复创建Activity时获取上次保存的数据。首先,我们来看一下onRetainNonConfigurationInstance方法,如下所示。

代码语言:txt
复制
public final Object onRetainNonConfigurationInstance() {
        Object custom = onRetainCustomNonConfigurationInstance();

        FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig();

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

        NonConfigurationInstances nci = new NonConfigurationInstances();
        nci.custom = custom;
        nci.viewModelStore = mViewModelStore;
        nci.fragments = fragments;
        return nci;
    }

可以发现,ViewModel会将数据存储在 NonConfigurationInstances 对象中,而NonConfigurationInstances是定义在Activity里面的一个类,如下所示。

代码语言:txt
复制
static final class NonConfigurationInstances {
        Object activity;
        HashMap<String, Object> children;
        FragmentManagerNonConfig fragments;
        ArrayMap<String, LoaderManager> loaders;
        VoiceInteractor voiceInteractor;
    }

再来看一下getLastCustomNonConfigurationInstance方法,

getLastNonConfigurationInstance方法返回的数据就是NonConfigurationInstances.activity属性,如下所示。

代码语言:txt
复制
@Nullable
    public Object getLastNonConfigurationInstance() {
        return mLastNonConfigurationInstances != null
                ? mLastNonConfigurationInstances.activity : null;
    }

现在,我们再看一下ComponentActivity 的 getViewModelStore方法,如下所示。

代码语言:txt
复制
@NonNull
@Override
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;
}

可以发现,在getViewModelStore方法中我们首先会获取NonConfigurationInstances对象,不为空则从其身上拿到ViewModelStore,也就是之前保存的ViewModelStore,然后当Activity被再次创建的时候恢复数据。

需要说明的是,onRetainNonConfigurationInstance方法会在onSaveInstanceState方法之后被调用,即调用顺序同样在onStop方法和 onDestroy方法之间。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、ViewModel简介
  • 二、生命周期
  • 三、基本使用
  • 四、 在 Fragment 之间共享数据
  • 五、源码分析
    • 5.1 ViewModel源码
      • 5.2 ViewModelProvider
        • 5.3 ViewModelStore
          • 5.4 Factory
          • 六、ViewModel是如何实现状态保留的
          相关产品与服务
          文件存储
          文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档