Android Jetpack组件(三)ViewModel

news/2024/7/7 21:03:38 标签: ViewModel, Jetpack, Android Jetpack, 数据恢复

Android Jetpack组件系列文章:
Android Jetpack组件(一)LifeCycle
Android Jetpack组件(二)Navigation
Android Jetpack组件(三)ViewModel
Android Jetpack组件(四)LiveData
Android Jetpack组件(五)Room
Android JetPack组件(六)DataBinding
Android Jetpack组件(七)Paging
Android Jetpack组件(八)WorkManager
Android Jetpack组件(九)DataStore

首语

对于支持横竖屏切换的应用程序,我们切换横竖屏时,Activity会被重新创建,我们需要考虑数据的存储和恢复。Jetpack为我们提供了ViewModel组件帮我们解决这个问题,ViewModel以注重生命周期的方式存储和管理界面相关的数据。ViewModel独立于配置变化,就算Activity重建,也不会影响ViewModel的生命周期。
<a class=ViewModel生命周期" />
在应用开发中,通常将UI交互、数据获取等业务逻辑全部写在页面中,当项目需求不断增加,页面功能复杂时,页面类会显得尤为臃肿,且不宜维护。这样做也违背了“单一功能原则”,页面只应该负责处理用户与UI控件的交互及数据展示,获取数据的业务逻辑应该单独处理。
Android提供了ViewModel类专门用于存放应用程序页面所需的数据,它可以理解为视图与数据模型的桥梁,使视图与数据分离开同时也保持通信。

ViewModelonSaveInstanceState_15">ViewModel与onSaveInstanceState()

通常我们使用onSaveInstanceState()来解决屏幕旋转带来的数据丢失问题,但是它只能保存少量的支持序列化的数据,Viewmodel支持页面中的所有数据。需要注意的是,ViewModel不支持数据的持久化,当界面彻底销毁时,ViewModel及所持有的数据就不存在了,onSaveInstanceState()没有这个限制,可以持久化页面的数据,两者用途不一。

依赖

 	//包含了 viewmodel 和 livedata,lifecycle-extensions 中的 API 已弃用
    //implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
    //或者指明使用viewmodel
    implementation "androidx.lifecycle:lifecycle-viewmodel:2.2.0"

使用

首先自定义一个ViewModel,继承ViewModel类。

public class HomeViewModel extends ViewModel {

    @Override
    protected void onCleared() {
        super.onCleared();
    }
}

ViewModel是一个抽象类,只有一个onClear(),当ViewModel与之关联的Activity都被销毁时,该方法就会被调用,在方法里可以执行一些资源释放相关的工作,注意屏幕旋转导致的Activity重建不会调用该方法。
我们通过一个计数器的例子来展示ViewModel的使用,将视图与数据分离。

	private int value;

    public String gtValue() {
        return String.valueOf(value);
    }

    public void addValue() {
        value+=1;
        if (onChangedListener != null) {
            onChangedListener.onChanged(String.valueOf(value));
        }
    }

    /**
     * 通过接口的形式将数据传递出去,更好的方式是通过LiveData,
     */
    public interface onChangedListener {
        void onChanged(String value);
    }

    private onChangedListener onChangedListener;

    public void setOnChangeListener(onChangedListener onChangeListener) {
        this.onChangedListener = onChangeListener;
    }

然后在Activity中,每点击一次,计数器+1,ViewModel的实例化是通过ViewModelProvider来完成的,它会判断ViewModel是否存在,若存在直接返回,不存在则创建。

		HomeViewModel homeViewModel = new ViewModelProvider(this).get(HomeViewModel.class);
        textView.setText(homeViewModel.gtValue());

        button.setOnClickListener(view -> homeViewModel.addValue());
        homeViewModel.setOnChangeListener(textView::setText);

运行代码可以发现点击计数器都会+1,同时旋转屏幕导致Activity重建时,数据不会消失,代表ViewModel并没有被销毁,持有的数据一直存在。

Fragment间共享数据

Activity中的两个或更多 Fragment 经常需要相互通信,这种情况处理比较复杂,ViewModel能将数据从Activity中剥离处理,只要Activity不销毁,ViewModel就一直存在,基于这些特性,多个Fragment 可以使用其 Activity 范围共享 ViewModel 来处理此类通信。

 //ViewModelProvider的范围必须是所在activity
 HomeViewModel homeViewModel = new ViewModelProvider(getActivity()).get(HomeViewModel.class);

在另一个Fragment中同样实例化ViewModel。当切换Fragment时,会提示计数器当前值,达到了Fragment之间的通信。

 homeViewModel = new ViewModelProvider(getActivity()).get(HomeViewModel.class);
 @Override
    public void onHiddenChanged(boolean hidden) {
        super.onHiddenChanged(hidden);
        Toast.makeText(getActivity(), homeViewModel.gtValue(),Toast.LENGTH_SHORT).show();
    }

Fragment中使用ViewModelActivity中使用类似。

ViewModel_89">ViewModel的原理

ViewModel接收一个ViewModelStoreOwner对象作为参数,我们传递this,这是因为Activity继承ComponentActivity,它默认实现了ViewModelStoreOwner接口。源码如下。

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

实现此接口的职责是在配置更改期间保留拥有的ViewModelStore ,并在该作用域将要被销毁时调用。

public class ComponentActivity extends androidx.core.app.ComponentActivity implements
        LifecycleOwner,
        ViewModelStoreOwner,
        HasDefaultViewModelProviderFactory,
        SavedStateRegistryOwner,
        OnBackPressedDispatcherOwner {
        
 	public ComponentActivity() {
  		getLifecycle().addObserver(new LifecycleEventObserver() {
            @Override
            public void onStateChanged(@NonNull LifecycleOwner source,
                    @NonNull Lifecycle.Event event) {
                if (event == Lifecycle.Event.ON_DESTROY) {
                    if (!isChangingConfigurations()) {
                        getViewModelStore().clear();
                    }
                }
            }
        });
 	}
 	
    @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()返回的类型是ViewModelStore,从源码可以看出,ViewModel是以HashMap<String,ViewModel>的形式缓存起来了,就像之前说的,页面需要ViewModel时,先判断缓存中是否存在,若存在,直接返回,不存在则创建。
Fragment也默认实现了ViewModelStoreOwner接口,原理和Activity类似。
需要注意的是,实例化ViewModel时不要传入任何类型的Context或带有Context引用的对象,将引发内存泄漏。

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

ViewModel_182">AndroidViewModel

如果实例化ViewModel要传递Context对象,可以使用AndroidViewModel类,它继承自ViewModel,并接收Application作为Context,因此它的生命周期和应用Application的生命周期一样,不会导致内存泄漏,同时可以处理特定场景的数据问题。


http://www.niftyadmin.cn/n/533573.html

相关文章

[转]nopcommerce商城系统--如何编写一个插件

本文转自&#xff1a;http://www.cnblogs.com/ganqiyin/p/3680771.html 原址&#xff1a;http://www.nopcommerce.com/docs/77/how-to-write-a-nopcommerce-plugin.aspx plug-in (或 plugin)是一个为更大的软件应用程序添加特定的能力的组件(Wikipedia) 插件是用来扩展nopComme…

VIM安装/卸载/升级8

卸载老版本的vim $ dpkg -l | grep vim #查看已经安装的旧版 $ sudo dpkg -P vim vim-tiny vim-common #这里不限于这几个&#xff0c;看上一步命令列出来的内容&#xff0c;如果报依赖问题&#xff0c;不能卸载&#xff0c;用下面命令 sudo apt-get autoremove --purge vim …

Android Jetpack组件(四)LiveData

Android Jetpack组件系列文章&#xff1a; Android Jetpack组件&#xff08;一&#xff09;LifeCycle Android Jetpack组件&#xff08;二&#xff09;Navigation Android Jetpack组件&#xff08;三&#xff09;ViewModel Android Jetpack组件&#xff08;四&#xff09;LiveD…

使用win10远程控制ubuntu16.04

使用win10远程控制ubuntu16.04&#xff0c;网上很多需要安装xfce桌面的。今天介绍一下&#xff0c;不需要安装其他桌面&#xff0c;使用Ubuntu16.04自带桌面&#xff0c;漂亮美观。 Ubuntu16.04端&#xff1a; 1、打开终端&#xff0c;安装xrdp,vncserver sudo apt-get install…

git clone的速度慢到难以忍受问题的解决方法~

方法一&#xff1a; 先把 github项目导入到码云&#xff0c;再clone到本地 方法二&#xff1a; 一般在github上面看到一个好的开源项目&#xff0c;想要使用它&#xff0c;或者想要研究一下它的源码&#xff0c;这时我们需要使用git clone命令。 git clone就是仓库克隆&#…

Android Jetpack组件(五)Room

Android Jetpack组件系列文章&#xff1a; Android Jetpack组件&#xff08;一&#xff09;LifeCycle Android Jetpack组件&#xff08;二&#xff09;Navigation Android Jetpack组件&#xff08;三&#xff09;ViewModel Android Jetpack组件&#xff08;四&#xff09;LiveD…

dfs判断连通图(无向)

在图论中&#xff0c;连通图基于连通的概念。在一个无向图 G 中&#xff0c;若从顶点vi到顶点vj有路径相连&#xff08;当然从vj到vi也一定有路径&#xff09;&#xff0c;则称vi和vj是连通的。如果 G 是有向图&#xff0c;那么连接vi和vj的路径中所有的边都必须同向。如果图中…

vim无法完美支持Python2和Python 3

import sys; print(sys.version) 如果你要在VIM中增加诸如YouCompleteMe这一类的插件的话&#xff0c;那么你需要VIM有Python的支持&#xff0c;这个是在一开始编译的选项里指定的。 我一开始怀着美好的愿望&#xff0c;希望vim能完美支持Python2和Python 3&#xff0c;所以&a…