Jetpack
架构组件及 “标准化开发模式” 确立,意味着Android
开发已步入成熟阶段,只有对 MVVM
确有深入理解,才能自然而然写出标准化、规范化代码。
本次笔者会浅入浅出的介绍以下内容,由于它是一个我的学习总结记录,所以比较适合对MVVM
不是很熟悉,但又想了解下全貌的读者:
在正文开始前,先回顾下MVP
:
MVP,Model-View-Presenter,职责分类如下:
Activity/Fragment
我们知道,MVP
是对MVC
的改进,解决了MVC
的两个问题:
View
责任明确,逻辑不再写在Activity
中,放到了Presenter
中;Model
不再持有View
MVP
最常用的实现方式是这样的:
View
层接收到用户操作事件,通知到Presenter
,Presenter
进行逻辑处理,然后通知Model
更新数据,Model
把更新的数据给到Presenter
,Presenter
再通知到View
更新界面。
MVP
本质是面向接口编程,它也存在一些痛点:
IView
、IPresenter
接口,增加实现的复杂度。View
和Presenter
相互持有,形成耦合。随着发展,Jetpack MVVM 就应势而生,它是MVVM
模式在Android
开发中的一个具体实现,是Google
官方提供并推荐的MVVM
实现方式。它的分层:
Activity/Fragment
MVVM
的核心是 数据驱动,把解耦做的更彻底(ViewModel
不持有view
)。
View
产生事件,使用ViewModel
进行逻辑处理后,通知Model
更新数据,Model
把更新的数据给ViewModel
,ViewModel
自动通知View更新界面
在没有Lifecycle
之前,生命周期的管理都是靠手工维持。比如我们经常会在Activity
的onStart
初始化某些成员(比如MVP
的Presenter
, MediaPlayer
)等,然后在onStop
中释放这些成员的内部资源。
class MyActivity extends AppCompatActivity {
private MyPresenter presenter;
public void onStart(...) {
presenter= new MyPresenter ();
presenter.start();
}
public void onStop() {
super.onStop();
presenter.stop();
}
}
class MyPresenter{
public MyPresenter() {
}
void start(){
// 耗时操作
checkUserStatus{
if (result) {
myLocationListener.start();
}
}
}
void stop() {
// 释放资源
myLocationListener.stop();
}
}
上述的代码本身是没有太大问题的。它的缺点在于实际生产环境下,会有很多的页面和组件需要响应生命周期的状态变化,就得在生命周期方法中放置大量的代码,这样的方式就会导致代码(如 onStart()
和onStop()
)变得臃肿,难以维护。
除此之外还有一个问题就是:
MyPresenter
类中onStart
里的checkUserStatus
是个耗时操作,如果耗时过长,Activity
销毁的时候,还没有执行过来,就已经stop
了,然后等一会儿执行过来的时候,myLocationListener
又start
,但后面不会再有myLocationListener
的stop
,这样这个组件的资源就不能正常释放了。如果它内部还持有Activity
的引用,还会造成内存泄露。
于是,Lifecycle
就出来了,它通过 “模板方法模式” 和 “观察者模式”,将生命周期管理的复杂操作,放到LifecycleOwner
(如 Activity、Fragment 等 “视图控制器” 基类)中封装好。
对于开发者来说,在 “视图控制器” 的类中只需一句 getLifecycle().addObserver(new MyObserver())
,当Lifecycle
的生命周期发生变化时,MyObserver
就可以在自己内部感知到。
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_lifecycle);
// 使MyObserver感知生命周期
getLifecycle().addObserver(new MyObserver());
}
看看它是怎么实现的:
# ComponentActivity
private final LifecycleRegistry mLifecycleRegistry = new LifecycleRegistry(this);
public Lifecycle getLifecycle() {
return mLifecycleRegistry;
}
# LifecycleRegistry
public LifecycleRegistry(@NonNull LifecycleOwner provider) {
this(provider, true);
}
private FastSafeIterableMap mObserverMap =
new FastSafeIterableMap<>();
public void addObserver(@NonNull LifecycleObserver observer) {
mObserverMap.putIfAbsent(observer, statefulObserver);
...
}
public void removeObserver(@NonNull LifecycleObserver observer) {
mObserverMap.remove(observer);
}
void dispatchEvent(LifecycleOwner owner, Event event) {
State newState = event.getTargetState();
mState = min(mState, newState);
mLifecycleObserver.onStateChanged(owner, event);
mState = newState;
}
正因为Activity
实现了LifecycleOwner
,所以才能直接使用getLifecycle()
# ComponentActivity
protected void onCreate(@Nullable Bundle savedInstanceState) {
// 关键代码:通过ReportFragment完成生命周期事件分发
ReportFragment.injectIfNeededIn(this);
if (mContentLayoutId != 0) {
setContentView(mContentLayoutId);
}
}
# ReportFragment
static void dispatch(@NonNull Activity activity, @NonNull Lifecycle.Event event) {
if (activity instanceof LifecycleOwner) {
Lifecycle lifecycle = ((LifecycleOwner) activity).getLifecycle();
if (lifecycle instanceof LifecycleRegistry) {
// 处理生命周期事件,更新当前都状态并通知所有的注册的LifecycleObserver
((LifecycleRegistry) lifecycle).handleLifecycleEvent(event);
}
}
}
# LifecycleRegistry
public void handleLifecycleEvent(@NonNull Lifecycle.Event event) {
enforceMainThreadIfNeeded("handleLifecycleEvent");
moveToState(event.getTargetState());
}
所以Lifecycle
的存在,是为了解决 “生命周期管理” 一致性的问题。
在没有LiveData
的时候,我们在网络请求回调、跨页面通信等场景分发消息,大多是通过EventBus
、接口callback
的方式去完成。
比如经常使用的EventBus
等消息总线的方式会有问题:
它缺乏一种约束,当我们去使用时,很容易因为随处使用,最后追溯数据来源的难度就会很大。
另外,EventBus
在处理生命周期上也很麻烦,由于需要手动去控制,会容易出现生命周期管理不一致的问题。
先看下官方的介绍:
LiveData
是一种可观察的数据存储器类。与常规的可观察类不同,LiveData
具有生命周期感知能力,意味着它遵循其他应用组件(如 Activity/Fragment)的生命周期。这种感知能力可确保LiveData
仅更新处于活跃生命周期状态的应用组件观察者。
如果观察者的生命周期处于 STARTED
或 RESUMED
状态,则 LiveData
会认为该观察者处于活跃状态,就会将更新通知给活跃的观察者,非活跃的观察者不会收到更改通知。
LiveData
是 观察者模式 的体现,先从LiveData
的observe
方法看起:
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer super T> observer) {
// LifecycleOwner是DESTROYED状态,直接忽略
if (owner.getLifecycle().getCurrentState() == DESTROYED) {
return;
}
// 绑定生命周期的Observer
LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
// 让该Observer可以感知生命周期
owner.getLifecycle().addObserver(wrapper);
}
observeForever
和observe()
类似,只不过它会认为观察者一直是活跃状态,不会自动移除观察者。
LiveData
很重要的一部分就是数据更新:·
LiveData
原生的API提供了2种方式供开发者更新数据, 分别是setValue()
和postValue()
,调用它们都会 触发观察者并更新UI。
setValue()
方法必须在 主线程 进行调用,而postValue()
方法更适合在 子线程 中进行调用。postValue()
最终也会调用setValue
,只需要看下setValue
方法就可以了:
protected void setValue(T value) {
assertMainThread("setValue");
mVersion++;
mData = value;
dispatchingValue(null);
}
void dispatchingValue(@Nullable ObserverWrapper initiator) {
...
for (Iterator, ObserverWrapper>> iterator =
mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
considerNotify(iterator.next().getValue());
}
}
private void considerNotify(ObserverWrapper observer) {
if (!observer.mActive) {
return;
}
...
observer.mObserver.onChanged((T) mData);
}
小问题:我们在使用LiveData
有一个优势是不会发生内存泄漏,是怎么做到的呢?
这需要从上面提到的observe
方法中寻找答案
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer super T> observer) {
LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
owner.getLifecycle().addObserver(wrapper);
}
传递的第一个是 LifecycleOwner
,第二个参数Obserser
实际就是我们的观察后的回调。这两个参数被封装成了LifecycleBoundObserver
对象。
class LifecycleBoundObserver extends ObserverWrapper implements LifecycleEventObserver {
@Override
public void onStateChanged(@NonNull LifecycleOwner source,
@NonNull Lifecycle.Event event) {
Lifecycle.State currentState = mOwner.getLifecycle().getCurrentState();
if (currentState == DESTROYED) {
// Destoryed状态下,自动移除mObserver,避免内存泄漏
removeObserver(mObserver);
return;
}
activeStateChanged(shouldBeActive());
...
}
这里就解释了为什么LiveData
能够 自动解除订阅而避免内存泄漏 了,因为它内部能够感应到Activity
或者Fragment
的生命周期。
PS:这种设计非常巧妙,给我们一个启发点:
在我们初识 Lifecycle 组件对它不是理解很透彻的时候,总是下意识认为它能够对大的对象进行有效生命周期的管理(比如
Presenter
),实际上,这种生命周期的管理我们完全可以应用到各个功能的基础组件中,比如大到吃内存的MediaPlayer
、绘制设计复杂的自定义View
,小到随处可见的LiveData
,都可以通过实现LifecycleObserver
接口达到感应生命周期的能力,并内部释放重资源的目的。
LiveData
在感知生命周期的能力下,让应用数据发生变化时通过观察者去更新界面,并且不会出现内存泄露的情况。
在没有ViewModel
,我们用MVP
开发的时候,我们为了实现数据在UI上的展示,往往会写很多UI
层和Model
层相互调用的代码,这些代码写起来繁琐且一定程度的模版化。另外,某些场景(例如屏幕旋转)销毁和重新创建界面,那么存储在其中的界面相关数据都会丢失,一般都需要手动存储和恢复。
为了解决这两个痛点,ViewModel
就出场,用ViewModel
用于代替MVP
中的Presenter
ViewModel
的概念就是这样被提出来的,它就像一个 状态存储器 ,存储着UI中各种各样的状态。
1.更规范化的抽象接口
Google
官方建议ViewModel
尽量保证 纯的业务代码,不要持有任何View
层(Activity
或者Fragment
)或Lifecycle
的引用,这样保证了ViewModel
内部代码的可测试性,避免因为Context
等相关的引用导致测试代码的难以编写(比如,MVP
中Presenter
层代码的测试就需要额外成本,比如依赖注入或者Mock
,以保证单元测试的进行)。
也正是这样的规范要求,ViewModel
不能持有UI层引用,自然也就避免了可能发生的内存泄漏。
2.更便于保存数据
当组件被销毁并重建后,原来组件相关的数据也会丢失。最简单的例子就是屏幕的旋转,如果数据类型比较简单,同时数据量也不大,可以通过onSaveInstanceState()
存储数据,组件重建之后通过onCreate()
,从中读取Bundle
恢复数据。但如果是大量数据,不方便序列化及反序列化,则上述方法将不适用。
ViewModel
的扩展类则会在这种情况下自动保留其数据,如果Activity
被重新创建了,它会收到被之前相同ViewModel
实例。当所属Activity
终止后,框架调用ViewModel
的onCleared()
方法释放对应资源。
3.更方便UI组件之间的通信
一个Activity
中的多个Fragment
相互通讯是很常见的,如果ViewModel
的实例化作用域为Activity
的生命周期,则两个Fragment
可以持有同一个ViewModel
的实例,这也就意味着数据状态的共享。
接下来,分析它的源码是怎么做到这些的:
我们可以通过ViewModelProvider
注入ViewModelStoreOwner
,从而为引用ViewModel
的页面(比如Activity)创建一个临时的、单独的 ViewModelProvider
实例。并通过这个ViewModelProvider
可以获取到ViewModel
# this: ViewModelStoreOwner(interface)
ViewModelProvider(this).get(viewModelClass)
分创建、获取两步来看,先看创建ViewModelProvider
做了什么:
# ViewModelProvider
public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
// owner.getViewModelStore(),比如:owner是ComponentActivity
this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
: NewInstanceFactory.getInstance());
}
public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
mFactory = factory;
mViewModelStore = store;
}
public interface ViewModelStoreOwner {
ViewModelStore getViewModelStore();
}
# ComponentActivity implements ViewModelStoreOwner
public ViewModelStore getViewModelStore() {
// 为空就创建
ensureViewModelStore();
return mViewModelStore;
}
void ensureViewModelStore() {
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
}
这一步是基石:把ViewModelStoreOwner
的mViewModelStore
绑定到了ViewModelProvider
中。简单点说就是同一个ViewModelStoreOwner
拿到的是同一个mViewModelStore
。
如何获取对应的ViewModel
:
# ViewModelProvider
private final ViewModelStore mViewModelStore;
public T get(@NonNull Class modelClass) {
String canonicalName = modelClass.getCanonicalName();
return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
}
public T get(@NonNull String key, @NonNull Class modelClass) {
ViewModel viewModel = mViewModelStore.get(key);
// 直接返回已存在的viewModel
if (modelClass.isInstance(viewModel)) {
return (T) viewModel;
}
if (mFactory instanceof KeyedFactory) {
viewModel = ((KeyedFactory) mFactory).create(key, modelClass);
} else {
viewModel = mFactory.create(modelClass);
}
// 存储viewModel
mViewModelStore.put(key, viewModel);
return (T) viewModel;
}
# ViewModelStore
public class ViewModelStore {
private final HashMap mMap = new HashMap<>();
final void put(String key, ViewModel viewModel) {
ViewModel oldViewModel = mMap.put(key, viewModel);
if (oldViewModel != null) {
oldViewModel.onCleared();
}
}
}
即通过这样的设计,来实现类似于单例的效果:每个页面都可以通过ViewModelProvider
注入Activity
这个ViewModelStoreOwner
,来共享跨页面的状态;
同时,又不至于完全沦为简单粗暴的单例:每个页面都可以通过 ViewModelProvider
注入this
,来管理私有的状态。
比如下面这个具体的例子:
当应用中某个ViewModel
存在既被ViewModelProvider
传入过 Activity
,又被传入过某个 Fragment
的this
情况,实际上是生成了两个不同的 ViewModel
实例,属于不同的 ViewModelStoreOwner
。当引用被this
持有的ViewModel
的 页面destory
时,被Activity
持有的ViewModel
的页面并不受影响。
ViewModel
是为了解决 “状态管理” 和 “页面通信” 问题。有了ViewModel
,我们在开发的时候,可以大幅减少UI
层和Model
层相互调用的代码,将更多的重心投入到业务代码的编写。
在DataBinding
出现以前,想要更新视图就要引用该视图,然后调用setxxx
方法:
TextView textView = findViewById(R.id.sample_text);
if (textView != null && viewModel != null) {
textView.setText(viewModel.getUserName());
}
这种方式有几个不好的地方:
findViewById
DataBinding
是个受争议比较大的组件。很多人对 DataBinding
的认知就是在xml
中写逻辑:
xml
中写表达式逻辑,出错了debug
不了xml
里面的话 xml
就承担了 Presenter/ViewModel
的职责,职责变得混乱了当然如果站在把逻辑写在xml
中的角度看,确实会造成xml
中是不能调试的、职责混乱。
但这不是DataBinding
的本质。DataBinding
,含义是 数据绑定,即 布局中的控件 与 可观察的数据 进行绑定。
当user.name
被set
新值时,被绑定了该数据的控件即可获得通知和刷新。就是说,在使用DataBinding
后,唯一的改变是,你无需手动调用视图来 set 新状态,你只需 set 数据本身。
所以,DataBinding
并非是将 UI 逻辑搬到 XML 中写导致而难以调试 ,它只负责绑定数据,将 UI 控件与其需要的终态数据进行绑定。
上面介绍的例子,数据的流向是单向的,只需要监听到数据的变更然后展示到UI上,是个单向绑定。
但有些场景,UI的变化需要影响到ViewModel
层的数据状态,比如UI层的EditText
,对它进行编辑并需要更新LiveData
的数据。这时就需要 双向绑定。
Android
原生控件中,绝大多数的双向绑定使用场景,DataBinding
都已经帮我们实现好了,比如EditText
相比单向绑定,只需要多一个=
符号,就能保证View
层和ViewModel
层的 状态同步 了
双向绑定使用起来很简单,但定义却稍微比单向绑定麻烦一些,即使原生的控件DataBinding
已经帮助我们实现好了,对于三方的控件或者自定义控件,还需要我们自己实现。
这里举个下拉刷新SwipeRefreshLayout
的例子,来看看双向绑定是怎么实现的:
我们的需求时:当我们为LiveData
手动设置值时,SwipeRefreshLayout
的UI也会发生对应的变更;反之,当用户手动下拉执行刷新操作时,LiveData
的值也会对应的变成为true
(代表刷新中的状态):
// refreshing实际是一个LiveData:
val refreshing: MutableLiveData = MutableLiveData()
object SwipeRefreshLayoutBinding {
// 1.@BindingAdapter 在数据发生更改时要执行的操作:
// 每当LiveData的状态发生了变更,SwipeRefreshLayout的刷新状态也会发生对应的更新。
@JvmStatic
@BindingAdapter("app:bind_swipeRefreshLayout_refreshing")
fun setSwipeRefreshLayoutRefreshing(
swipeRefreshLayout: SwipeRefreshLayout,
newValue: Boolean
) {
// 判断值是否变化了,避免无限循环
if (swipeRefreshLayout.isRefreshing != newValue)
swipeRefreshLayout.isRefreshing = newValue
}
// 2.@InverseBindingAdapter: view视图发生更改时要调用的内容
// 但是它不知道特性何时或如何更改,所以还需要设置视图监听器
@JvmStatic
@InverseBindingAdapter(
attribute = "app:bind_swipeRefreshLayout_refreshing",
event = "app:bind_swipeRefreshLayout_refreshingAttrChanged" // tag
)
fun isSwipeRefreshLayoutRefreshing(swipeRefreshLayout: SwipeRefreshLayout): Boolean =
swipeRefreshLayout.isRefreshing
}
// 3. @BindingAdapter: 事件监听器与相应的 View 实例相关联
// 观察view的状态变化,每当swipeRefreshLayout刷新状态被用户的操作改变
@JvmStatic
@BindingAdapter(
"app:bind_swipeRefreshLayout_refreshingAttrChanged", // tag
requireAll = false
)
fun setOnRefreshListener(
swipeRefreshLayout: SwipeRefreshLayout,
bindingListener: InverseBindingListener?
) {
if (bindingListener != null)
// 监听下拉刷新
swipeRefreshLayout.setOnRefreshListener {
bindingListener.onChange()
}
}
双向绑定将SwipeRefreshLayout
的刷新状态抽象成为了一个LiveData
,我们只需要在xml
中定义好,之后就可以在ViewModel
中围绕这个状态进行代码的编写。
注意事项:避免死循环
双向绑定有一个致命的问题,那就是无限循环会导致的ANR
异常。
当View
层UI状态被改变,ViewModel
对应发生更新,同时,这个更新又回通知View
层去刷新UI,这个刷新UI的操作又会通知ViewModel
去更新…
因此,为了保证不会无限的死循环导致App
的ANR
异常的发生,我们需要在最初的代码块中加一个判断,保证只有View
状态发生了变更,才会去更新UI。
DataBinding
通过让 “控件” 与 “可观察数据” 发生绑定,它的本质是将终态数据 绑定到View ,而不是在xml写逻辑,当该数据被 set 新内容时,被绑定该数据的控件即可被通知和刷新。
为了帮助大家更好的熟知Jetpack 这一套体系的知识点,这里记录比较全比较细致的《Jetpack 入门到精通》(内含Compose) 学习笔记!!! 对Jetpose Compose这块感兴趣的小伙伴可以参考学习下……
Jetpack 部分
Compose 部分
1.Jetpack Compose入门详解
2.Compose学习笔记
3.Compose 动画使用详解