Android开发

Android Jetpack之AAC最佳拍档 LiveData+ViewModel(二)

本文主要是介绍Android Jetpack之AAC最佳拍档 LiveData+ViewModel(二),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

Jetpack AAC 系列文章

  • Android Jetpack之AAC Lifecycle你用对了吗(一)
  • Android Jetpack之AAC 最佳拍档之LiveData+ViewModel(二)

上一篇文章我们从使用案例和源码角度分析了Jetpack AAC Lifecycle组件。

Lifecycle在Jetpack AAC组件的金字塔结构中可以说是底层建筑的存在,将各个组件串联在一起,其重要性不言而喻,所以还是非常有必要了解Lifecycle在AAC架构组件中所扮演的角色。

没有看过的童鞋可以点击上面链接进行查看哦!

今天我们继续来了解LiveData和ViewModel,相信很多人看到LiveData和ViewModel首先联想到的就是MVVM架构。 那么LiveData和ViewModel在MVVM架构中是扮演怎样的角色呢?而MVVM中VM又是用于解决哪些问题?ViewModel又是如何搭配LiveData使用的呢?

本文将从上述几个问题出发,对LiveData和ViewModel进行分析讲解。

简介

我们先来看看官方是怎样描述的。

关于LiveData和ViewModel,我们先看看官方对它的是怎样描述的。

LiveData

 LiveData is a data holder class that can be observed within a given lifecycle.
 * This means that an {@link Observer} can be added in a pair with a {@link LifecycleOwner}, and
 * this observer will be notified about modifications of the wrapped data only if the paired
 * LifecycleOwner is in active state
复制代码

大致的意思是LiveData是一个带有生命周期且可被观察的数据持有类,这意味着它可以搭配Observer以及LifecycleOwner使用,只有当LifecycleOwner处于活跃状态时,观察者的才会被触发。

ViewModel

ViewModel is a class that is responsible for preparing and managing the data for an 
{@link android.app.Activity Activity} or a {@link androidx.fragment.app.Fragment Fragment}.
It also handles the communication of the Activity /Fragment with the rest of the application
(e.g. calling the business logic classes).
复制代码

从官方描述的可以看出ViewModel在Activity/Fragment中负责准备和管理数据,还可可以用来进行Activity/Fragment之间的通讯。

 A ViewModel is always created in association with a scope (an fragment or an activity) and will
 * be retained as long as the scope is alive. E.g. if it is an Activity, until it is
 * finished.
复制代码

ViewModel一般是在Activity/Fragment中进行创建,只要Activity一直存在,ViewModel也会一直保留。当我们在Activity中使用ViewModel时,直至Activity被标记或者真实关闭后,ViewModel才会被销毁。

MVVM

上图是官方在Jetpack应用架构指南中给出的MVVM架构中各个模块交互的工作流程图,每个组件仅依赖于其下一级的组件。 而VM在MVVM中的职责就是负责视图模型层与M层/V层进行交互。Respostitory在这里可以理解为M层,为数据提供层,当M层数据有变更时,由VM层来通知V层Activity/Fragment中数据内容变更,UI层在自行根据内容进行UI刷新。

注:ViewModel只负责数据管理和业务逻辑相关的工作,不涉任何和UI相关的操作。ViewModel只专注于处理业务数据以及与M层的数据请求逻辑,UI刷新操作则交给自己的上一级去操作。 每一层都有每一层所关注的内容和责任,这样才能达到真正解耦,也是名副其实的"分手大师"。

案例

假设我们有一个用户页面,需要拉取用户的信息进行填充展示,看看用LiveData和ViewModel如何实现。

ViewModel

public class UserViewModel extends ViewModel {

    private MutableLiveData<User> userMutableLiveData;

    public MutableLiveData<User> getUser() {
        if (userMutableLiveData == null) {
            userMutableLiveData = new MutableLiveData<>();
            userMutableLiveData.setValue(null);
        }
        return userMutableLiveData;
    }

    public void setUserMutableLiveData(User user) {
        userMutableLiveData.setValue(user);
    }

    public void loadUser() {
        User user = new UserModel().getUserById("10001");
        setUserMutableLiveData(user);
    }
}
复制代码

V层

public class UserActivity extends AppCompatActivity {
    private static final String TAG = "UserActivity";

    TextView tvUserName;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_user);
        tvUserName = findViewById(R.id.tv_user_name);
        UserViewModel userViewModel = new ViewModelProvider(UserActivity.this).get(UserViewModel.class);
        userViewModel.getUser().observe(this, new Observer<User>() {
            @Override
            public void onChanged(User user) {
                Log.e(TAG, "onChanged: user.name" + user.name);
                tvUserName.setText(user.name);
            }
        });
        userViewModel.loadUser("1003");
    }
}

复制代码

M层

public class UserModel {

    public User getUserById(String id) {
        return new User(id, "张三");
    }
}
复制代码

User

public class User implements Serializable {

    public String id;
    public String name;

    public User(String id, String name) {
        this.id = id;
        this.name = name;
    }
}
复制代码

打开页面后的输出结果如下:

UserActivity E/onChanged: user.name 张三
复制代码

从上述简单的调用例子可以看出,ViewModel实例化创建是依赖于ViewModelProvider。

new ViewModelProvider(UserActivity.this).get(UserViewModel.class);
复制代码

注:ViewModelProviders的绑定方式已经废弃了,androidx新版本推荐是采用new ViewModelProvider的形式

observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer)
复制代码

在V层我们通过ViewModel拿到的User类MutableLiveData对象,在添加观察时需要添加一个LifecycleOwner实现类,而这个接口实现类我们在上篇讲解lifecycle有说到,Activity和Frament就是LifecycleOwner的实现类,所以在LiveData中持有LifecycleOwner就可以进行生命周期的监听。

注:MutableLiveData数据变更提供了两个方法,一个是postValue(T value),一个是setValue(T value)postValue方法是在非UI线程调用,setValue方法在UI线程中调用

LiveData的更多用用法

LiveData除了可以在外部调用observe进行监听,还可以自定义实现MutableLiveData在内部监听onActiveonInactive两个函数。

public class CustomLiveData extends MutableLiveData<String> {
    private static final String TAG = "CustomLiveData";

    @Override
    protected void onActive() {
        super.onActive();
        Log.e(TAG, "onActive: LiveData进入活跃状态");
    }

    @Override
    protected void onInactive() {
        super.onInactive();
        Log.e(TAG, "onActive: LiveData进入不活跃状态");
    }
}

复制代码

既然能够监听到活跃状态,我们试着把地图定位的逻辑放到LiveData中是怎样的:

public class LocationLiveData extends MutableLiveData<BDLocation> {

    private LocationClient locationClient;
    private InternalListener internalListener;
    public Context context;

    public LocationLiveData(Context context) {
        this.context = context;

    }

    @Override
    protected void onActive() {
        super.onActive();
        locationClient = new LocationClient(context);
        locationClient.registerLocationListener(internalListener = new BDLocationListener() {

            @Override
            public void onReceiveLocation(BDLocation bdLocation) {
                setValue(bdLocation);

            }
        });
        locationClient.setLocOption(getLocationClientOption());
    }

    @Override
    protected void onInactive() {
        super.onInactive();
        if (locationClient != null) {
            locationClient.unRegisterLocationListener(internalListener);
            locationClient.stop();
        }
    }
}
复制代码

可以看到,当LiveData处于活跃时,我们自动开启定位。当不活跃时,我们自动关闭定位,除了可以自管理定位的生命周期,还能及时切断与其他层的关联,防止内存泄露。

userViewModel.getLocation().observe(this, new Observer<BDLocation>() {
    @Override
    public void onChanged(BDLocation bdLocation) {

    }
});
复制代码

observeForever

liveData还未我们提供一个不关联生命周期的观察者监听,对观察者来说LiveData会一直活跃状态。

userViewModel.getLocation().observeForever(new Observer<BDLocation>() {
    @Override
    public void onChanged(BDLocation bdLocation) {

    }
});
复制代码

关于LiveData的使用就介绍这么多,如有遗漏的点后续再进行补充,最后我们来总结下引入LiveData的好处:

  • 自管理生命周期(告别手工处理的胶水代码)
  • 保证数据实时性(数据集中管理,一处刷新,可多处同步,还可用于模块通信)
  • 拒绝内存泄露(绑定生命周期,切端与其他模块的关联)
  • 搭配ViewModel使用(可处理Activity/Fragment的异常销毁恢复情况)

ViewModel通讯

从官方文档描述我们看到,ViewModel除了负责准备和管理数据,还可以进行Activity/Frgment之间的通讯,我们通过下面的例子来看看利用ViewModel是如何进行通讯交互的

public class UserFragmentActivity extends AppCompatActivity implements View.OnClickListener {
    private static final String TAG = "UserFragmentActivity";
    UserViewModel userViewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_fragment_user);
        userViewModel = new ViewModelProvider(UserFragmentActivity.this).get(UserViewSaveModel.class);
        findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.e(TAG, "第一次拉取 user.name");
                userViewModel.loadUser("1001");
            }
        });
    }

    @Override
    public void onClick(View v) {
        Log.e(TAG, "第一次拉取 user.name");
        userViewModel.loadUser("1001");
    }
    @Override
    protected void onStart() {
        super.onStart();
        Log.e(TAG, "onStart" );
    }

    @Override
    protected void onPause() {
        super.onPause();
        Log.e(TAG, "onPause" );
    }

    @Override
    protected void onResume() {
        super.onResume();
        Log.e(TAG, "onResume" );
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.e(TAG, "onDestroy" );
    }
}
复制代码

我们在xml文件中加载两个fragment

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/button"
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:text="拉取用户数据" />

    <fragment
        android:name="com.waylenw.adr.mvvm.vm.fragment.UserFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:tag="fragment1" />

    <fragment
        android:name="com.waylenw.adr.mvvm.vm.fragment.UserFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:tag="fragment2" />
</LinearLayout>
复制代码

Fragment

public class UserFragment extends Fragment {
private static final String TAG = "UserFragment";

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
}

@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    return LayoutInflater.from(getContext()).inflate(R.layout.fragment_user, container, false);
}

@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
 super.onViewCreated(view, savedInstanceState);
    TextView tvUserName = view.findViewById(R.id.tv_user_name);
    UserViewSaveModel userViewModel = new ViewModelProvider(getActivity()).get(UserViewSaveModel.class);
    userViewModel.getUser().observe((LifecycleOwner) UserFragment.this, new Observer<User>() {
        @Override
        public void onChanged(User user) {
            if (user == null) {
                Log.e(TAG, getTag() + " onChanged:" + null);
                return;
            }
            Log.e(TAG, getTag() + " onChanged: user.name" + user.name);
            Log.e(TAG, getTag() + " onChanged: user" + user);
            tvUserName.setText(user.name);
        }
    });
    tvUserName.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Log.e(TAG, getTag() + "重新设置 user.name");
            userViewModel.getUser().getValue().name = "李四";
            userViewModel.setUserMutableLiveData(userViewModel.getUser().getValue());
    
        }
    });
}
复制代码

打开页面后,通过按钮点击进行User的第一次加载,输出内容如下:

com.waylenw.adr.mvvm E/UserFragmentActivity: onCreate
com.waylenw.adr.mvvm E/UserFragmentActivity: onStart
com.waylenw.adr.mvvm E/UserFragmentActivity: onResume
com.waylenw.adr.mvvm E/UserFragment: fragment1 onChanged:null
com.waylenw.adr.mvvm E/UserFragment: fragment2 onChanged:null

com.waylenw.adr.mvvm E/UserFragmentActivity: 第一次拉取 user.name
com.waylenw.adr.mvvm E/UserFragment: fragment1 onChanged: user.name张三
com.waylenw.adr.mvvm E/UserFragment: fragment1 onChanged: usercom.waylenw.adr.mvvm.entity.User@30b4c83
com.waylenw.adr.mvvm E/UserFragment: fragment2 onChanged: user.name张三
com.waylenw.adr.mvvm E/UserFragment: fragment2 onChanged: usercom.waylenw.adr.mvvm.entity.User@30b4c83

复制代码

然后我们在其中的一个Fragmnet中进行点击,将用户名称更改为李四

com.waylenw.adr.mvvm E/UserFragment: fragment1重新设置 user.name
com.waylenw.adr.mvvm E/UserFragment: fragment1 onChanged: user.name李四
com.waylenw.adr.mvvm E/UserFragment: fragment1 onChanged: usercom.waylenw.adr.mvvm.entity.User@30b4c83
com.waylenw.adr.mvvm E/UserFragment: fragment2 onChanged: user.name李四
com.waylenw.adr.mvvm E/UserFragment: fragment2 onChanged: usercom.waylenw.adr.mvvm.entity.User@30b4c83
复制代码

可以发现,两个Fragmnet共享了同一个ViewModel,所以当注册观察者时,获取到的是同一个数据源,User@30b4c83并没有发生变更

ViewModel的生命周期

image

上图是官方给出的ViewModel生命周期变化。当Activity旋转时,此时Activity重走生命周期,但是ViewModel并未跟随重新创建,只有页面真正标记关闭时,ViewModel才会重新创建。所以ViewModel中的数据并不会不会被清楚。ViewModel还帮我处理了Activity异常销毁时的数据保存工作。

我们也不用手动在onSaveInstanceState()onRestoreInstanceState()/onCreate()函数中来保存和恢复数据了。

以下我们通过两个场景来测试一下ViewModel生命周期。

旋转屏幕的表现

还是我们刚才测试ViewModel的Demo,此时我们进行一次旋屏操作,看看输出的结果是怎样的:

com.waylenw.adr.mvvm E/UserFragmentActivity: onPause
com.waylenw.adr.mvvm E/UserFragmentActivity: onDestroy
com.waylenw.adr.mvvm E/UserFragmentActivity: onCreate
com.waylenw.adr.mvvm E/UserFragment: fragment1 onChanged: user.name李四
com.waylenw.adr.mvvm E/UserFragment: fragment1 onChanged: usercom.waylenw.adr.mvvm.entity.User@30b4c83
com.waylenw.adr.mvvm E/UserFragment: fragment2 onChanged: user.name李四
com.waylenw.adr.mvvm E/UserFragment: fragment2 onChanged: usercom.waylenw.adr.mvvm.entity.User@30b4c83
com.waylenw.adr.mvvm E/UserFragmentActivity: onStart
com.waylenw.adr.mvvm E/UserFragmentActivity: onResume
复制代码

从日志输出中可以看到LiveData中所引用的user对象没有发生变更,当Activity从非活跃状态切换到活跃状态时,会自动触发一次监听回调给到观察者,所以ViewModel的声明生命周期是伴随Activity finish销毁而销毁。

页面销毁恢复

这个场景,我们通过设置=>开发者模式=>不保留活动,即用户离开后销毁当前这个页面。

模拟的输出结果如下:

com.waylenw.adr.mvvm E/UserFragmentActivity: onPause
com.waylenw.adr.mvvm E/UserFragmentActivity: onDestroy
com.waylenw.adr.mvvm E/UserFragmentActivity: onCreate
com.waylenw.adr.mvvm E/UserFragmentActivity: onStart
com.waylenw.adr.mvvm E/UserFragmentActivity: onResume
com.waylenw.adr.mvvm E/UserFragment: fragment1 onChanged:null
com.waylenw.adr.mvvm E/UserFragment: fragment2 onChanged:null
复制代码

Activity已经进行销毁重新创建了,两个Fragment中拿到数据也是null,很明显数据并没有保留下来。 那ViewModel如何才能在页面销毁恢复的情况下把数据进行保存呢?答案使用是SavedStateHandle。SavedStateHandle该如何使用呢?我们把刚才ViewModel进行改造一下

public class UserViewSaveModel extends ViewModel {
    public static final String SAVE_KEY = "SAVE_KEY";

    private MutableLiveData<User> userMutableLiveData;

    private SavedStateHandle savedStateHandle;

    public UserViewSaveModel(SavedStateHandle savedStateHandle) {
        this.savedStateHandle = savedStateHandle;
    }

    public MutableLiveData<User> getUser() {
        if (userMutableLiveData == null) {
            userMutableLiveData = new MutableLiveData<>();
            if (savedStateHandle.contains(SAVE_KEY)) {
                userMutableLiveData.setValue(savedStateHandle.get(SAVE_KEY));
            } else {
                userMutableLiveData.setValue(null);
            }
        }

        return userMutableLiveData;
    }

    public void setUserMutableLiveData(User user) {
        userMutableLiveData.setValue(user);
        savedStateHandle.set(SAVE_KEY, user);
    }

    public void loadUser(String userId) {
        User user = new UserModel().getUserById(userId);
        setUserMutableLiveData(user);
    }
}
复制代码

UserFragment替换为UserViewSaveModel

public class UserFragment extends Fragment {
    private static final String TAG = "UserFragment";

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        return LayoutInflater.from(getContext()).inflate(R.layout.fragment_user, container, false);
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        TextView tvUserName = view.findViewById(R.id.tv_user_name);
        UserViewSaveModel userViewModel = new ViewModelProvider(getActivity()).get(UserViewSaveModel.class);
        userViewModel.getUser().observe((LifecycleOwner) UserFragment.this, new Observer<User>() {
            @Override
            public void onChanged(User user) {
                if (user == null) {
                    Log.e(TAG, getTag() + " onChanged:" + null);
                    return;
                }
                Log.e(TAG, getTag() + " onChanged: user.name" + user.name);
                Log.e(TAG, getTag() + " onChanged: user" + user);
                tvUserName.setText(user.name);
            }
        });
        tvUserName.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.e(TAG, getTag() + "重新设置 user.name");
                userViewModel.getUser().getValue().name = "李四";
                userViewModel.setUserMutableLiveData(userViewModel.getUser().getValue());

            }
        });
    }
}
复制代码

UserFragmentActivit也替换为UserViewSaveModel

public class UserFragmentActivity extends AppCompatActivity {
    private static final String TAG = "UserFragmentActivity";
    UserViewSaveModel userViewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.e(TAG, "onCreate");
        setContentView(R.layout.activity_fragment_user);
        userViewModel = new ViewModelProvider(UserFragmentActivity.this).get(UserViewSaveModel.class);
        findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.e(TAG, "第一次拉取 user.name");
                userViewModel.loadUser("1001");
            }
        });
    }

    @Override
    protected void onStart() {
        super.onStart();
        Log.e(TAG, "onStart");
    }

    @Override
    protected void onPause() {
        super.onPause();
        Log.e(TAG, "onPause");
    }

    @Override
    protected void onResume() {
        super.onResume();
        Log.e(TAG, "onResume");
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.e(TAG, "onDestroy");
    }
}
复制代码

我们在来看看实际的运行效果

1.初始化+点击加载

com.waylenw.adr.mvvm E/UserFragmentActivity: onCreate
com.waylenw.adr.mvvm E/UserFragment: fragment1 onChanged:null
com.waylenw.adr.mvvm E/UserFragment: fragment2 onChanged:null
com.waylenw.adr.mvvm E/UserFragmentActivity: onStart
com.waylenw.adr.mvvm E/UserFragmentActivity: onResume
com.waylenw.adr.mvvm E/UserFragmentActivity: 第一次拉取 user.name
com.waylenw.adr.mvvm E/UserFragment: fragment1 onChanged: user.name张三
com.waylenw.adr.mvvm E/UserFragment: fragment1 onChanged: usercom.waylenw.adr.mvvm.entity.User@30b4c83
com.waylenw.adr.mvvm E/UserFragment: fragment2 onChanged: user.name张三
com.waylenw.adr.mvvm E/UserFragment: fragment2 onChanged: usercom.waylenw.adr.mvvm.entity.User@30b4c83
复制代码
  1. 返回桌面进入应用进入后台,在切回到应用中
com.waylenw.adr.mvvm E/UserFragmentActivity: onPause
com.waylenw.adr.mvvm E/ResMng NATIVE_MSG_FILTER: endActivityTransaction: margin state not match
com.waylenw.adr.mvvm E/UserFragmentActivity: onDestroy
com.waylenw.adr.mvvm E/UserFragmentActivity: onCreate
com.waylenw.adr.mvvm E/UserFragment: fragment1 onChanged: user.name张三
com.waylenw.adr.mvvm E/UserFragment: fragment1 onChanged: usercom.waylenw.adr.mvvm.entity.User@484f3f0
com.waylenw.adr.mvvm E/UserFragment: fragment2 onChanged: user.name张三
com.waylenw.adr.mvvm E/UserFragment: fragment2 onChanged: usercom.waylenw.adr.mvvm.entity.User@484f3f0
com.waylenw.adr.mvvm E/UserFragmentActivity: onStart
com.waylenw.adr.mvvm E/UserFragmentActivity: onResume
复制代码

通过上述两个例子,可以论证ViewModel只有当页面finish真实关闭时,才会跟随销毁,所以当我们旋转屏幕时,页面重走生命周期时,ViewModel并没有重新创建。在ViewModel中我们使用SavedStateHandle进行数据保存后,页面销毁恢复后,数据也能够相应的被还原。那么在这过程中SavedStateHandle替我们做了哪些事情呢?

下一篇文章我们将从ViewModel的源码来分析讲解SavedStateHandle是如何在页面销毁时完成保存数据的操作。

End

注:Jetpack AAC架构组件系列后续还会继续分析详解,觉得本文对你有帮助,可以扫描下面公众号二维码图片关注,更多实用内容正在更新中....

这篇关于Android Jetpack之AAC最佳拍档 LiveData+ViewModel(二)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!