我正在参加「启航方案」
前言
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 完全把握!
