android jetpack第三篇:ViewModel原理和使用方式

一、前言

常规的客户端开发几乎都是数据驱动类型,也就是先获取数据,然后通过UI将数据信息展示给用户。所以数据的管理至关重要。android jetpack另一个重量级组件ViewModel就是为优雅的管理数据而生的。

下面考虑以下场景

1、当页面配置变更(如旋转屏幕),造成页面重建,如何能够更加便捷地恢复数据呢?

横竖屏切换数据保持

2、当数据在activity/fragment/application生命周期内有效,且需支持跨组件共享,如何实现?

数据共享

答案是:ViewModel。ViewModel是有生命周期感知能力数据持有共享方案,且能与UI解耦

那么ViewModel是怎么做到既能感知生命周期,又能保持共享数据,还能和UI解耦呢?

二、工作原理

介绍工作原理之前,先要了解构成ViewModel的关键类

1、ViewModelStore:从命名可以看出,这是ViewModel的仓库。从功能上也是如此,其通过一个HashMap保存ViewModel对象实例。

2、ViewModelStoreOwner:这个接口是ViewModelStore的持有者。实现该接口的主要是ComponentActivity、Fragment组件。

3、ViewModel:这是本文介绍的主角,是一个抽象类。业务方可继承该抽象类并在其内部管理数据。

4、ViewModelProvider:ViewModel对象的构建器,所有的ViewModel对象都需要使用ViewModelProvider构建,切记不可自行new一个ViewModel对象

5、ViewModelProvider.Factory:ViewModelProvider创建ViewModel对象时依赖的抽象工厂,业务方可以定制或使用默认工厂。

ViewModel框架只有以上这些类,下面看看如何使用ViewModelProvider创建一个ViewModel对象,主要有两种方式

方式一:

//自定义ViewModel类
public class UserInfoModel extends ViewModel{}

 //JetPackActivity.this是是页面类
UserInfoModel userInfoModel = new ViewModelProvider(JetPackActivity.this).get(UserInfoModel.class); 

方式二:

//自定义factory,由factory创建viewmodel实例,可以给viewmodel传参
public class UserInfoModelFactory implements ViewModelProvider.Factory {
    private final int data;

    public UserInfoModelFactory(int data) {
        this.data = data;
    }

    @NonNull
    @Override
    public <T extends ViewModel> T create(@NonNull Class<T> aClass) {
        if (aClass.isAssignableFrom(UserInfoModel.class)) {
            return (T) new UserInfoModel(data);
        }
        return null;
    }
}

//创建factory,并传给ViewModelProvider创建viewmodel实例。
ViewModelProvider.Factory factory = new UserInfoModelFactory(12);
UserInfoModel userInfoModel = new ViewModelProvider(JetPackActivity.this, factory).get(UserInfoModel.class);

说好的原理呢,怎么变成使用了?别着急,下面需要从创建对象为入口,分析ViewModel的创建过程:

1、ViewModelProvider 的构建需要传入一个ViewModelStoreOwner对象。常见的是Activity和Fragment。实际上是使用owner中的ViewModelStore。

public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {
       //使用ViewModelStore
        this(owner.getViewModelStore(), factory);
    }

    public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
        mFactory = factory;
        mViewModelStore = store;
    }

2、使用get(@NonNull Class modelClass)方法创建ViewModel实例,实际上是先在ViewModelStore的HashMap中查找实例是否存在,如果存在直接返回,如不存在,则使用factory创建一个并存入ViewModelStore中。

@NonNull
@MainThread
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");
        }
       //通过全限定名称,查询并创建viewmodel对象,存储在Map中也是使用的该KEY
        return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
}

public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
        //按照key从ViewModelStore中查找
        ViewModel viewModel = mViewModelStore.get(key);
        
        //如果找到且类型一致,则直接返回。所以同一个owner中,viewmodel实例只有一个
        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.
            }
        }
        
       //通过factory创建viewmodel实例,并存入ViewModelStore中
        if (mFactory instanceof KeyedFactory) {
            viewModel = ((KeyedFactory) mFactory).create(key, modelClass);
        } else {
            viewModel = mFactory.create(modelClass);
        }
        mViewModelStore.put(key, viewModel);
        return (T) viewModel;
}

通过上述源码知道

1、videmodel实现数据共享,实际上就是确保同一个owner中只有一个实例。因为唯一,所以数据相同且可以共享

2、videmodel存储在owner持有的ViewModelStore中,所以当owner销毁时,便能通知videmodel执行onCleared()

3、videmodel的低耦合主要是源自Lifecycle能力,当生命周期销毁时,会通过LifeCycleRegistry触发Videmodel的onCleared()。生命周期如下图。
ViewModel的生命周期

三、ViewModel应用

了解了viewmodel的工作原理,使用起来就很简单了。下面将创建一个activity和两个fragment,在activity和fragment中创建同一类viewmodel并且传入同样的owner(activity),实现viewmodel数据共享。

首先创建Activity类和fragment类

public class JetPackActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_jetpack);
        TextView mShowTv = findViewById(R.id.activity_text);
        UserInfoModel userInfoModel = new ViewModelProvider(this).get(UserInfoModel.class);
        mShowTv.setText(userInfoModel.getValue() + "");
    }
}

public class SecondFragment extends Fragment {
    public ComponentActivity activity;

    private UserInfoModel userInfoModel;

    @Override
    public void onAttach(@NonNull Context context) {
        super.onAttach(context);
        if (context instanceof ComponentActivity) {
            activity = (ComponentActivity) context;
        }
    }

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View containView = View.inflate(activity, R.layout.fragment_layout_test, null);
        initView(containView);
        return containView;
    }

    private void initView(View rootView) {
        TextView mShowTv = rootView.findViewById(R.id.fragment_text);
        userInfoModel = new ViewModelProvider(activity).get(UserInfoModel.class);
        mShowTv.setText(userInfoModel.getValue()+"");
    }
}

以下是activity布局代码activity_jetpack.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/activity_text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginLeft="25dp"
        android:layout_marginTop="25dp"
        android:layout_marginRight="25dp"
        android:gravity="center"
        android:textSize="17dp" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_below="@id/activity_text"
        android:orientation="horizontal">

        <fragment
            android:id="@+id/my_first_fragment"
            android:name=".SecondFragment"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1" />

        <fragment
            android:id="@+id/my_second_fragment"
            android:name=".SecondFragment"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1" />
    </LinearLayout>

</RelativeLayout>

接下来是fragment的布局fragment_layout_test.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="30dp"
        android:text="以下是一个fragment"
        android:layout_marginLeft="10dp"
        android:textSize="15dp" />

    <TextView
        android:id="@+id/fragment_text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginLeft="25dp"
        android:layout_marginRight="25dp"
        android:layout_marginTop="75dp"
        android:gravity="center"
        android:textSize="17dp" />
</RelativeLayout>

最后创建ViewModel类

public class UserInfoModel extends ViewModel {
    
    private int data = 5;

    public UserInfoModel() {
    }

    public UserInfoModel(int value) {
        setValue(value);
    }

    public void setValue(int value) {
        this.data = value;
    }

    public int getValue() {
        return data;
    }

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

运行上述代码便可以实现activity和两个fragment中的数据一致了,并且旋转屏幕后数据可以保持。当彻底销毁activity时会调用到ViewModel的onCleared方法。

至此ViewModel的原理和使用便分析结束了,但是demo还只是很简单的数据共享。下期介绍LiveData后将会持续完善demo,实现真正的工程级数据共享能力。

  • 17
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Android Jetpack是Google提供的一套用于加速Android应用开发的工具包,其中包括了许多架构组件,其中之一就是ViewModelViewModel是一种设计模式,用于保存和管理与UI相关的数据。在传统的Android开发中,当屏幕旋转或者因为其他原因导致Activity或Fragment重建时,之前保存的临时数据就会丢失。而ViewModel的出现解决了这个问题。 ViewModel的主要作用是将数据与UI组件分离。它的工作方式是创建一个ViewModel类,并在其中保存需要与UI组件交互的数据。这样,当屏幕旋转或重建时,ViewModel实例不会销毁,数据也会得到保留。然后,在Activity或Fragment中,通过获取ViewModel实例,可以轻松地访问这些数据。 使用ViewModel的好处有很多。首先,它可以避免内存泄漏,因为ViewModel的生命周期与Activity或Fragment无关。其次,它可以节省资源,因为当Activity或Fragment销毁时,ViewModel实例可以被系统缓存起来,下次再创建时可以直接返回该实例。另外,由于ViewModel保存了与UI相关的数据,可以减少因为屏幕旋转导致的数据重复加载的问题。 在使用ViewModel时,你可以选择使用Android Jetpack中的其他架构组件来进一步提高开发效率,比如通过LiveData实现数据的观察和通知,或者通过DataBinding来实现UI与数据的自动绑定。 总之,ViewModelAndroid Jetpack中非常重要的一个架构组件,它的出现实现了数据与UI的解耦,提高了开发效率,并且解决了数据丢失的问题。希望通过这篇文档的详解,你对ViewModel有了更深入的理解。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值