【Jetpack】ViewModel视图与数据的桥梁

QuibblerQuibbler 2020-12-10 约 32 分钟 2101 次阅读

ViewModel视图与数据的桥梁

学习Jetpack库第二步,ViewMode是必经之路。它是Google提供的类,专门用来存放数据。Android开发中通常会将业务逻辑和UI界面 拆分,不然的话会导致页面代码臃肿复杂。

1、ViewModel

听到ViewModel就想到MVVM,ViewModel就是介于View和Model之间的对象。将数据从View中脱离出来。

1.1、ViewModel介绍

ViewModel是抽象类,但是并没有抽象方法需要开发者去实现。

public abstract class ViewModel {
        // Can't use ConcurrentHashMap, because it can lose values on old apis (see b/37042460)
        @Nullable
        private final Map<String, Object> mBagOfTags = new HashMap<>();
        private volatile boolean mCleared = false;
        /**
         * This method will be called when this ViewModel is no longer used and will be destroyed.
         * <p>
         * It is useful when ViewModel observes some data and you need to clear this subscription to
         * prevent a leak of this ViewModel.
         */
        protected void onCleared() {
        }
        @MainThread
        final void clear() {
            mCleared = true;
            ...
            onCleared();
        }
        ...
    }

其中有一个onCleared()空方法,当Activity销毁对应的ViewModel不再使用时,该方法会被回调。开发者在这里释放回收自定义ViewModel中的资源,以免造成内存泄漏,或者对数据进行持久化的保存处理。

1.2、AndroidViewModel

因为ViewModel的生命周期远比Activity的长,如果ViewModel中持有了任何Activity、Context即有可能造成内存泄漏,所以不要将Activity传递给ViewModel持有!

如果要使用Context也最好使用Application全局Context,Google在ViewModel中又提供了另一个类AndroidViewModel,继承自ViewModel,只不过持有了全局Application你作为Context。获取全局Context还有一个大家熟知的方法:如何获取全局Context?

public class AndroidViewModel extends ViewModel {
    
        private Application mApplication;
        public AndroidViewModel(Application application) {
            mApplication = application;
        }
        
        public <T extends Application> T getApplication() {
            return (T) mApplication;
        }
    }

1.3、引入ViewModel

使用ViewModel需要在项目的build.gradle文件中添加如下依赖,更多Lifecycle的类库详见 Libraries > Lifecycle">Jetpack > Libraries > Lifecycle

dependencies {
    ...
    def lifecycle_version = "2.2.0"
    // ViewModel
    implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version"
    ...
}

2、ViewModel简单使用

学会初步的ViewModel使用,更多用法还得在项目上通过实战磨炼。

2.1、自定义ViewModel

前面说到ViewModel是抽象类,开发者不能直接创建ViewModel的实例,需要继承ViewModel实现自定义的ViewModel,并且别忘了重写onCleared()方法释放资源。

例:

public class BitmapViewModel extends ViewModel {
        private Bitmap mBitmap;
        
        public Bitmap getBitmap() {
            return mBitmap;
        }
        
        public void loadBitmap() {
            //加载Bitmap
        }
        
        @Override
        protected void onCleared() {
            super.onCleared();
            if (null != mBitmap) {
                mBitmap.recycle();
                mBitmap = null;
            }
        }
    }

2.2、获取ViewModel

在UI中获取ViewModel对象,借助ViewModelProvider对象获取,源码第三节中分析。

TimerViewModel timerViewModel = new ViewModelProvider(this).get(TimerViewModel.class);

通过ViewModel实例就可以拿到其中的数据,将数据和UI层分析。后面还会继续学习LiveData,结合LiveData将完美实现数据的更新同步,当ViewModel中的数据加载完成准备好的时候,通过LiveData通知更新主界面UI。

2.3、获取AndroidViewModel

方法同上,只不过需要注意的是使用的是AndroidViewModel,继承的自定义类必须提供带Application作为参数的构造函数,否则会抛出异常。


这里我多此一举,定义的是无参的构造函数,调用super父类构造方法传入的是全局Application参数

public class StudentViewModel extends AndroidViewModel {
        //
        public StudentViewModel() {
            super(MyApplication.getApplication());
        }
        
    }

查看源码才会发现,因为AndroidViewModel设计就是持有一个Application作为Context,避免开发者开发引起内存泄漏。所以内部的AndroidViewModelFactory反射创建AndroidViewModel的时候会默认调用AndroidViewModel的带参数的构造函数。

public <T extends ViewModel> T create(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);
    }

所以使用AndroidViewModel需要提供一个带参数的构造函数:

public class StudentViewModel extends AndroidViewModel {
        //需要带参数,给反射调用
        public StudentViewModel(Application context) {
            super(context);
        }
        
    }

2.4、更多用法

ViewModel这么好用,可以用来解耦View和数据。更多的坑点和用法还得在项目实战中开发摸索。

①数据持久化

②fragment间通信

③结合LiveData解耦View和Model数据

3、ViewModel原理

AndroidX中的一些新特性很方便,仔细阅读源码,了解其中的设计思想。

3.1、构造ViewModelProvider

从ViewModelProvider类创建开始,它的构造函数如下,需要一个ViewModelStoreOwner类型的构造参数:

public ViewModelProvider(ViewModelStoreOwner owner) {
        this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
                ? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
                : NewInstanceFactory.getInstance());
    }

AndroidX中的ComponentActivity就实现了ViewModelStoreOwner接口。

public interface ViewModelStoreOwner {
        /**
         * Returns owned {@link ViewModelStore}
         *
         */
        ViewModelStore getViewModelStore();
    }

ComponentActivity中的getViewModelStore()方法具体实现如下,可以看到当Activity还未完全初始化是不能使用ViewModel的。必须在Activity的onCreate后(super.onCreate()后)才能获取ViewModel实例。

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.");
        }
        ...
        return mViewModelStore;
    }

同时,在ComponentActivity的构造函数中可以发现,添加了Lifecycle监控Activity的生命周期,关于Lifecycle详见Lifecycle生命周期感知组件。当Activity销毁(注意如果是横竖屏切换除外)时会回调ViewModelStore中的clear()方法,下一小节会看到这个方法遍历回调ViewModel的clear()方法清空释放资源(内部会调用onClear()方法)。

public ComponentActivity() {
        Lifecycle lifecycle = getLifecycle();
        ...
        getLifecycle().addObserver(new LifecycleEventObserver() {
            @Override
            public void onStateChanged(LifecycleOwner source,
                    @NonNull Lifecycle.Event event) {
                if (event == Lifecycle.Event.ON_DESTROY) {
                    if (!isChangingConfigurations()) {
                        getViewModelStore().clear();
                    }
                }
            }
        });
        ...
    }

3.2、ViewModelStore

ViewModelStoreOwner接口中的getViewModelStore()方法返回ViewModelStore对象,类的源码不过30行,不难看出内部通过HashMap<String,ViewModel>来存储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);
        }
        
        Set<String> keys() {
            return new HashSet<>(mMap.keySet());
        }
        
        /**
         *  Clears internal storage and notifies ViewModels that they are no longer used.
         */
        public final void clear() {
            for (ViewModel vm : mMap.values()) {
                vm.clear();
            }
            mMap.clear();
        }
    }

3.3、ViewModelProvider#get

ViewModelProvider实例构造好之后,紧接着调用它的get()泛型方法,传入自定义的ViewModel的Class

private static final String DEFAULT_KEY = "androidx.lifecycle.ViewModelProvider.DefaultKey";
    
    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);
    }

原来ViewModelStore中存储ViewModel的HashMap的键是"androidx.lifecycle.ViewModelProvider.DefaultKey" + 自定义ViewModel类名。

public <T extends ViewModel> T get(String key,Class<T> modelClass) {
        ViewModel viewModel = mViewModelStore.get(key);
        if (modelClass.isInstance(viewModel)) {
            if (mFactory instanceof OnRequeryFactory) {
                ((OnRequeryFactory) mFactory).onRequery(viewModel);
            }
            return (T) viewModel;
        } else {
            //noinspection StatementWithEmptyBody
            if (viewModel != null) {
                // TODO: log a warning.
            }
        }
        if (mFactory instanceof KeyedFactory) {
            viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
        } else {
            viewModel = (mFactory).create(modelClass);
        }
        mViewModelStore.put(key, viewModel);
        return (T) viewModel;
    }

3.4、ViewModel缓存

从上面的get(String key,Class<T> modelClass)方法最终获取到ViewModel。

先从ViewModelStore中获取,如果有缓存且和modelClass类型一样就直接返回

ViewModel viewModel = mViewModelStore.get(key);
        if (modelClass.isInstance(viewModel)) {
            if (mFactory instanceof OnRequeryFactory) {
                ((OnRequeryFactory) mFactory).onRequery(viewModel);
            }
            return (T) viewModel;
        }

如果有结果但是类型和需要的modelClass不一样,啥也不干顶多输出一条warning日志,说明缓存的ViewModel类型和key不一致。这里没有直接抛出异常,很不Google!

else {
            //noinspection StatementWithEmptyBody
            if (viewModel != null) {
                // TODO: log a warning.
            }
        }

接下来,是ViewModelStore也没有需要的modelClass类型的ViewModel缓存,就需要通过工厂模式创建新的modelClass类型的ViewModel返回,并且将其缓存在ViewModelStore中。

if (mFactory instanceof KeyedFactory) {
            viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
        } else {
            viewModel = (mFactory).create(modelClass);
        }
        mViewModelStore.put(key, viewModel);
        return (T) viewModel;

3.5、Factory创建ViewModel

mFactory是Factory接口类型,定义了基础的create()接口方法

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

它的实现类之一是SavedStateViewModelFactory类,实现上面的方法,注意这里通过源码才发现自定义的ViewModel不可以是匿名类,必须定义为继承自ViewModel的具体类,否则因为没有类名无法组合有效的key而抛出异常。

public <T extends ViewModel> T create(Class<T> modelClass) {
        String canonicalName = modelClass.getCanonicalName();
        if (canonicalName == null) {
            throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
        }
        return create(canonicalName, modelClass);
    }

接下来就是通过反射创建指定modelClass类型的ViewModel实例了:

public <T extends ViewModel> T create(String key,Class<T> modelClass) {
        boolean isAndroidViewModel = AndroidViewModel.class.isAssignableFrom(modelClass);
        Constructor<T> constructor;
        if (isAndroidViewModel) {
            constructor = findMatchingConstructor(modelClass, ANDROID_VIEWMODEL_SIGNATURE);
        } else {
            constructor = findMatchingConstructor(modelClass, VIEWMODEL_SIGNATURE);
        }
        // doesn't need SavedStateHandle
        if (constructor == null) {
            return mFactory.create(modelClass);
        }

4、ViewModel坑点

Google一直都很坑,不断的迭代更新难免有各种问题。接入SDK约等于接入Bug!

4.1、AndroidViewModel创建失败

自定义AndroidViewModel的时候,标准流程操作,获取实例,一气呵成

final UserViewModel userViewModel = new ViewModelProvider(this).get(UserViewModel.class);

结果运行时奔溃了


分析追踪了一波代码,发现从创建ViewModelProvider的时候内部就出错了,使用错误的Factory工厂,当然无法创建AndroidViewModel类型的实例。

public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
        this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
                ? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
                : NewInstanceFactory.getInstance());
    }

问题就在第二个参数的位置owner instanceof HasDefaultViewModelProviderFactory,说明当前的Activity并不是HasDefaultViewModelProviderFactory!难道还不一样,以前运行好着呢,查看源码发现果然不一样。一个版本的ComponentActivity实现了HasDefaultViewModelProviderFactory接口


另一个项目使用的ComponentActivity却没有实现这个接口,因为使用的是1.0.0低版本AndroidX库中的activity。


所以导致了后面出现的乌龙问题,Android开发,重要的一点就是做好版本的兼容处理,因为Google真的很坑,说不定哪天把接口又变了!

def activity_version = "1.1.0"
    // Java language implementation
    implementation "androidx.activity:activity:$activity_version"

相关资料:

         Libraries > Lifecycle">Jetpack > Libraries > Lifecycle

        ViewModel的基本使用

        深入了解架构组件之ViewModel

        ViewModel的用法

        

相关推荐

精选
ViewPager和PagerAdapter、FragmentPagerAdapter、FragmentStatePager
Android

ViewPager和PagerAdapter、FragmentPagerAdapter、FragmentStatePager

ViewPager1、ViewPagerandroidx.viewpager.widget.ViewPager,Android中使用非常广泛的控件,可以说是APP必备:首次打开引导页、页面Banner广告等。常用方法:setAdapter() 设置适配器setOffscreenPageLimit() 设置缓存的页面个数,默认是 1setCurrentItem() 跳转到特定的页面setOnPage

1.1k
获取Android内置WebView内核版本
Android

获取Android内置WebView内核版本

获取Android内置WebView内核版本竟然能遇到这样奇葩的事情,网页用的技术过于新颖,以至于只支持高版本Chromium内核的,低版本安卓系统中内置的内核版本较低,无法加载前端页面。1、设置查看在系统设置里 > 应用 > 应用管理 > 显示系统应用,查看WebView组件:2、页面查看通过WebView发起的网络请求,都会带上浏览器的UA,通常页面都可以通过UA判断浏览器的内核版本。这里有两

1.1w
Android 14适配总结
Android

Android 14适配总结

Android 14适配总结毕业工作至今已经适配了三个Android大版本,从Android 11到Android 12、再到Android 13。2023年,Google即将推出的Android 14,上半年已经开始第一批适配。现在,第四个Android版本已经适配完,总结记录一下。1、Android 14计划Google一般会在2月份对外发布预告,同时放出开发者预览版。“拉通”各大平台、厂商以

1.1w