我正在参加「启航方案」
前言
Jetpack
架构组件及 “标准化开发形式” 建立,意味着Android
开发已步入成熟阶段,只有对 MVVM
确有深化理解,才干天然而然写出标准化、规范化代码。
本次笔者会浅入浅出的介绍以下内容,由于它是一个我的学习总结记载,所以比较适合对MVVM
不是很熟悉,但又想了解下全貌的读者:
- Jetpack MVVM
- Jetpack Lifecycle
- Jetpack LiveData
- Jetpack ViewModel
- Jetpack DataBinding
Jetpack MVVM
在正文开端前,先回忆下MVP
:
MVP,Model-View-Presenter,责任分类如下:
- Model,数据模型层,用于获取和存储数据。
- View,视图层,即
Activity/Fragment
- Presenter,操控层,担任事务逻辑。
咱们知道,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
完结办法。它的分层:
- Model层:用于获取和存储数据
- View层:即
Activity/Fragment
- ViewModel层:担任事务逻辑
MVVM
的核心是 数据驱动,把解耦做的更完全(ViewModel
不持有view
)。
View
产生事情,运用ViewModel
进行逻辑处理后,告诉Model
更新数据,Model
把更新的数据给ViewModel
,ViewModel
主动告诉View更新界面
Jetpack Lifecycle
来源
在没有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
所以,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<LifecycleObserver, ObserverWithState> 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());
}
当LifecycleRegistry
自身的生命周期改动后,LifecycleRegistry
就会逐个告诉每一个注册的LifecycleObserver
,并履行对应生命周期的办法。
小结
所以Lifecycle
的存在,是为了处理 “生命周期办理” 一致性的问题。
Jetpack LiveData
来源
在没有LiveData
的时分,咱们在网络请求回调、跨页面通讯等场景分发消息,大多是经过EventBus
、接口callback
的办法去完结。
比方常常运用的EventBus
等消息总线的办法会有问题:
它缺少一种约束,当咱们去运用时,很简单由于到处运用,最后追溯数据来源的难度就会很大。
别的,EventBus
在处理生命周期上也很费事,由于需求手动去操控,会简单呈现生命周期办理不一致的问题。
LiveData
先看下官方的介绍:
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<Map.Entry<Observer<? super T>, 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
在感知生命周期的才能下,让运用数据产生改动时经过调查者去更新界面,而且不会呈现内存泄露的状况。
Jetpack ViewModel
来源
在没有ViewModel
,咱们用MVP
开发的时分,咱们为了完结数据在UI上的展现,往往会写很多UI
层和Model
层彼此调用的代码,这些代码写起来繁琐且必定程度的模版化。别的,某些场景(例如屏幕旋转)毁掉和重新创立界面,那么存储在其间的界面相关数据都会丢掉,一般都需求手动存储和康复。
为了处理这两个痛点,ViewModel
就进场,用ViewModel
用于替代MVP
中的Presenter
ViewModel
的概念便是这样被提出来的,它就像一个 状况存储器 ,存储着UI中各式各样的状况。
ViewModel的优点
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 extends ViewModel> T get(@NonNull Class<T> modelClass) {
String canonicalName = modelClass.getCanonicalName();
return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
}
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> 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<String, ViewModel> 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
层彼此调用的代码,将更多的重心投入到事务代码的编写。
Jetpack DataBinding
来源
在DataBinding
呈现以前,想要更新视图就要引证该视图,然后调用setxxx
办法:
TextView textView = findViewById(R.id.sample_text);
if (textView != null && viewModel != null) {
textView.setText(viewModel.getUserName());
}
这种办法有几个欠好的地方:
- 简单呈现空指针(存在差异的横、竖两种布局,如横屏存在此 textView 控件,而竖屏没有),引证该视图一般要先判空
- 需求写模板代码
findViewById
- 事务杂乱的话,一个控件会在多处调用
DataBinding
DataBinding
是个受争议比较大的组件。很多人对 DataBinding
的认知便是在xml
中写逻辑:
- 在
xml
中写表达式逻辑,出错了debug
不了 - 逻辑写在
xml
里面的话xml
就承担了Presenter/ViewModel
的责任,责任变得混乱了
当然假如站在把逻辑写在xml
中的视点看,确实会形成xml
中是不能调试的、责任混乱。
但这不是DataBinding
的实质。DataBinding
,含义是 数据绑定,即 布局中的控件 与 可调查的数据 进行绑定。
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.name}"/>
当user.name
被set
新值时,被绑定了该数据的控件即可取得告诉和改写。便是说,在运用DataBinding
后,唯一的改动是,你无需手动调用视图来 set 新状况,你只需 set 数据自身。
所以,DataBinding
并非是将 UI 逻辑搬到 XML 中写导致而难以调试 ,它只担任绑定数据,将 UI 控件与其需求的终态数据进行绑定。
双向绑定
上面介绍的比方,数据的流向是单向的,只需求监听到数据的改动然后展现到UI上,是个单向绑定。
但有些场景,UI的改动需求影响到ViewModel
层的数据状况,比方UI层的EditText
,对它进行修改并需求更新LiveData
的数据。这时就需求 双向绑定。
Android
原生控件中,绝大多数的双向绑定运用场景,DataBinding
都现已帮咱们完结好了,比方EditText
<EditText
android:id="@+id/etPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@={fragment.viewModel.password }" />
比较单向绑定,只需求多一个=
符号,就能确保View
层和ViewModel
层的 状况同步 了
双向绑定运用起来很简单,但定义却稍微比单向绑定费事一些,即使原生的控件DataBinding
现已协助咱们完结好了,关于三方的控件或许自定义控件,还需求咱们自己完结。
举个栗子
这里举个下拉改写SwipeRefreshLayout
的比方,来看看双向绑定是怎样完结的:
咱们的需求时:当咱们为LiveData
手动设置值时,SwipeRefreshLayout
的UI也会产生对应的改动;反之,当用户手动下拉履行改写操作时,LiveData
的值也会对应的变成为true
(代表改写中的状况):
// refreshing实践是一个LiveData:
val refreshing: MutableLiveData<Boolean> = 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<Boolean>
,咱们只需求在xml
中定义好,之后就能够在ViewModel
中环绕这个状况进行代码的编写。
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:bind_swipeRefreshLayout_refreshing="@={fragment.viewModel.refreshing}">
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
注意事项:防止死循环
双向绑定有一个丧命的问题,那便是无限循环会导致的ANR
异常。
当View
层UI状况被改动,ViewModel
对应产生更新,一起,这个更新又回告诉View
层去改写UI,这个改写UI的操作又会告诉ViewModel
去更新…….
因此,为了确保不会无限的死循环导致App
的ANR
异常的产生,咱们需求在开始的代码块中加一个判断,确保只有View
状况产生了改动,才会去更新UI。
小结
DataBinding
经过让 “控件” 与 “可调查数据” 产生绑定,它的实质是将终态数据 绑定到View ,而不是在xml写逻辑,当该数据被 set 新内容时,被绑定该数据的控件即可被告诉和改写。
参阅
笔者学习进程参阅了以下博客,想深化细节的能够看看:
Android官方架构组件ViewModel:从宿世今生到追根究底
Android官方架构组件DataBinding-Ex: 双向绑定篇
“总算懂了“系列:Jetpack AAC完好解析(一)Lifecycle 完全把握!
“总算懂了“系列:Jetpack AAC完好解析(二)LiveData 完全把握!