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());
}
当LifecycleRegistry
本身的生命周期改变后,LifecycleRegistry
就会逐个通知每一个注册的LifecycleObserver
,并执行对应生命周期的方法。
所以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 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 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 新内容时,被绑定该数据的控件即可被通知和刷新。
笔者学习过程参考了以下博客,想深入细节的可以看看:
Android官方架构组件ViewModel:从前世今生到追本溯源
Android官方架构组件DataBinding-Ex: 双向绑定篇
“终于懂了“系列:Jetpack AAC完整解析(一)Lifecycle 完全掌握!
“终于懂了“系列:Jetpack AAC完整解析(二)LiveData 完全掌握!
作者:树獭非懒
链接:https://juejin.cn/post/7159404464313466894
如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。
如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。
1、深入理解Java泛型
2、注解深入浅出
3、并发编程
4、数据传输与序列化
5、Java虚拟机原理
6、高效IO
1.Retrofit 2.0源码解析
2.Okhttp3源码解析
3.ButterKnife源码解析
4.MPAndroidChart 源码解析
5.Glide源码解析
6.Leakcanary 源码解析
7.Universal-lmage-Loader源码解析
8.EventBus 3.0源码解析
9.zxing源码分析
10.Picasso源码解析
11.LottieAndroid使用详解及源码解析
12.Fresco 源码分析——图片加载流程
1、Kotlin入门教程
2、Kotlin 实战避坑指南
3、项目实战《Kotlin Jetpack 实战》
从一个膜拜大神的 Demo 开始
Kotlin 写 Gradle 脚本是一种什么体验?
Kotlin 编程的三重境界
Kotlin 高阶函数
Kotlin 泛型
Kotlin 扩展
Kotlin 委托
协程“不为人知”的调试技巧
图解协程:suspend
1.SmartRefreshLayout的使用
2.Android之PullToRefresh控件源码解析
3.Android-PullToRefresh下拉刷新库基本用法
4.LoadSir-高效易用的加载反馈页管理框架
5.Android通用LoadingView加载框架详解
6.MPAndroidChart实现LineChart(折线图)
7.hellocharts-android使用指南
8.SmartTable使用指南
9.开源项目android-uitableview介绍
10.ExcelPanel 使用指南
11.Android开源项目SlidingMenu深切解析
12.MaterialDrawer使用指南
1、NDK 模块开发
2、JNI 模块
3、Native 开发工具
4、Linux 编程
5、底层图片处理
6、音视频开发
7、机器学习
1、Flutter跨平台开发概述
2、Windows中Flutter开发环境搭建
3、编写你的第一个Flutter APP
4、Flutter开发环境搭建和调试
5、Dart语法篇之基础语法(一)
6、Dart语法篇之集合的使用与源码解析(二)
7、Dart语法篇之集合操作符函数与源码分析(三)
1、小程序概述及入门
2、小程序UI开发
3、API操作
4、购物商场项目实战……
一、面试合集
二、源码解析合集
三、开源框架合集
欢迎大家一键三连支持,若需要文中资料,直接扫描下方CSDN官方认证微信免费领取↓↓↓