我的Android应用架构设计演进之路

最近在思考做一个Android架构设计的分享,主要目的是通过回顾架构设计的演进过程,帮助我们审查和改进当前工程设计中的一些缺陷。

说起Android架构设计,马上就会有熟悉的几个词蹦出来,MVC, MVP, MVVM, 这些架构设计的内涵是什么?有优劣之分吗?除了它们,还有哪些架构设计模式?

为了回答上面的问题,我从自己的项目架构演进来分别说明一下各种架构设计的特点。我们今天讨论的都是构建用户交互应用程序的架构设计,是细粒度功能模块代码组织与划分,组件化,插件化等工程模块的拆分不在讨论范围之内。

一、MVC的内涵

做移动客户端开发,无论Android还是iOS, MVC都是大家最熟悉的一种架构设计模式。M指Model层,职责是数据存取,V指View层,职责是展示数据到View, C指Controller层,负责具体的业务逻辑,隔离Model与View, 外部触发Controller通过Model获取到数据后,通知View更新数据,起到了连接Model与View的桥梁作用。

映射到Android中,Activity, Fragment对应什么角色,承担的职责是什么?

先不急着回答这个问题,回想下在我们开始写Android应用,还没有架构设计概念时,了解了Activity是干什么的之后,让Activity完成所有的事情。在Activity发起网络请求,开启一个子线程或者AsyncTask, 获取响应结果后显示数据内容。

上图展示的是一种最原始的代码组织方式,随着业务的扩展,Activity里会遍布各种业务逻辑,同时要处理与生命周期有关的回调,Activity会变得越来越臃肿。一不小心,没处理好对象在生命周期方法里释放造成内存泄漏也是家常便饭。趁着代码还没到臃肿腐烂到难以重构之前,我们先重构一版吧。

将Activity, Fragment作为一个控制器Controller.

Controller控制器&View

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public class MainActivity extends FragmentActivity implements ResponseListener<Weather>, View.OnClickListener {

private WeatherModel mWeatherModel;
private EditText mCityNOInput;
private TextView mCity;

...

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mWeatherModel = new WeatherModelImpl();
initView();
}

//初始化View
private void initView() {
mCityNOInput = findViewById(R.id.et_city_no);
mCity = findViewById(R.id.tv_city);
...
findView(R.id.btn_go).setOnClickListener(this);
}

//显示结果
public void displayResult(Weather weather) {
city.setText(weather.getCity());
...
}

@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_go:
mWeatherModel.getWeather(cityNOInput.getText().toString().trim(), this);
break;
}
}

@Override
public void onSuccess(Weather weather) {
displayResult(weather);
}

@Override
public void onError(Throwable throwable) {
Toast.makeText(this, 获取天气信息失败, Toast.LENGTH_SHORT).show();
}
}

Model模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public interface WeatherModel {
void getWeather(String cityNumber, ResponseListener<Weather> listener);
}

public interface ResponseListener<T> {
void onSuccess(T response);
void onError(Throwable throwable);
}


...

public class WeatherModelImpl implements WeatherModel {
@Override
public void getWeather(String cityNumber, final ResponseListener<Weather> listener) {

VolleyRequest.newInstance().newGsonRequest("http://www.weather.com.cn/data/sk/" + cityNumber + ".html",
Weather.class, new Response.Listener<weather>() {
@Override
public void onResponse(Weather weather) {
if (weather != null) {
listener.onSuccess(weather);
} else {
listener.onError(new EmptyResultException());
}
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
listener.onError(error);
}
});
}
}

看着是分层了,Activity轻了很多,但是依然承担了多个职责,既想当View, 又想立控制器,违背了设计模式的单一职责原则。即使只让Activity作为一个View, 它依然不是一个纯粹的View. Activity或者Fragment承担了系统赋予的一些职责,Fragment的管理及通信,生命周期回调的入口,这些是没办法避免的,只能让其承担的额外职责更少。

这是我们下一步优化的方向,从Activity中抽出Controller层,负责业务逻辑,Activity只负责View层显示及生命周期、Fragment管理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
public interface IWeatherView {
void showWeather(Weather weather);
void showError(Throwable throwable);
}

public class WeatherController {
private WeakReference<IWeatherView> mWeatherViewRef;
private WeatherModel mWeatherModel;

public WeatherController(IWeatherView view) {
mWeatherViewRef = new WeakReference<>(view);
}

public void getWeather(String cityNumber) {
mWeatherModel.getWeather(cityNumber, new ResponseListener<Weather>() {
@Override
public void onSuccess(Weather response) {
IWeatherView weatherView = mWeatherViewRef.get();
if (weatherView == null) {
return;
}
if (response != null) {
weatherView.showWeather(response);
} else {
weatherView.showError(new EmptyResultException());
}
}

@Override
public void onError(Throwable throwable) {
IWeatherView weatherView = mWeatherViewRef.get();
if (weatherView != null) {
weatherView.showError(throwable);
}
}
});
}
}

...

public class MainActivity extends FragmentActivity implements IWeatherView, View.OnClickListener {
private WeatherController mWeatherController;
private EditText mCityNOInput;
private TextView mCity;
...

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mWeatherController = new WeatherController(this);
initView();
}

//初始化View
private void initView() {
mCityNOInput = findViewById(R.id.et_city_no);
mCity = findViewById(R.id.tv_city);
...
findViewById(R.id.btn_go).setOnClickListener(this);
}

@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.btn_go:
mWeatherController.getWeather(mCityNOInput.getText().toString().trim());
break;
}
}

@Override
public void showWeather(Weather weather) {
mCity.setText(weather.getCity());
}

@Override
public void showError(Throwable throwable) {
Toast.makeText(this, "获取天气信息失败", Toast.LENGTH_SHORT).show();
}
}

既然Activity承担视图更新的职责,索性把UI逻辑抽象成接口,面向接口编程,Activity实现IWeatherView接口,只关注实现UI逻辑,当业务扩展到一定程度,我们要把这部分UI转为Fragment实现时,也可以快速重构。Activity可以只关注生命周期事件和管理Fragment.

架构分层的目的是为了解耦,目前的架构实现了Model与View的真正分离,修改View而不影响Model. 在工程实践中,也会发现一些缺点,如果一个Controller承担业务比较重,同样会造成Controller的臃肿,解决的办法也很简单,及时拆分多个Fragment, 对应拆分Controller.

有的同学会觉得这不就是MVP架构吗?只不过名字叫Controller而已。没错,如今流行的MVP也是从MVC延伸发展出来的。

在MVP里,各自层级的职责和依赖关系和MVC其实是一样的,但不同的是,MVP之间的交互主要是通过接口实现的,Model、View、Presenter都有各自的接口,定义各自的行为方法。针对接口编程,自然就能降低耦合。至于其他优点,提高复用性、更容易进行单元测试我们不去深究,因为在实际开发过程中单个feature业务基本没有复用的机会,有段时间我甚至参考了Clean架构把业务划分成了更细粒度的UseCase, 为了实现最大程度的复用,然而并没有什么用,根本用不上。如果需要写业务单元测试代码,推荐学习下google的architecture-samples项目。

二、由缓存引入的Loader框架

MVC, MVP定义了更多的接口,让我们面向接口编程而不是面向实现编程,进一步降低了耦合。但是Activity, Fragment生命周期时短暂的,系统配置发生变化,横竖屏变换,退回到后台被系统结束掉Activity后如何快速恢复当前的状态,这是我们不能回避的问题。

因为没有做好数据持久化,保存现场数据,没有考虑Activity销毁重建的问题,在打开开发者模式的保留单个活动时,使用应用很可能就会导致崩溃。

如何在Activity出现异常结束时保留现场数据?
Activity从API level 1就引入了保存和恢复现场数据的方法,onSaveInstanceState用于在onStop生命周期回调前保存Activity数据,onCreate方法中的Bundle参数savedInstanceState用于现场恢复,由ActivityManager进行管理,但受制于Binder数据传输大小的限制,只能保存不超过50K大小数据量的数据。
onSaveInstanceState 的数据存在哪里?为什么限制了大小?

这显然不能用于保存业务数据,Activity恢复时还需要再次加载数据,如果没有内存缓存,数据库缓存,从网络加载时会相对较慢,不是最好的用户体验。我们都使用过新闻类、视频类应用,比如百度APP, 爱奇艺首页有10多个栏目,可以通过左右划切换tab, 划到一个tab时就会触发网络请求,当还未收到响应数据的时候,如果切换到新的tab, 那么上一个tab页面的请求响应结果如何处理?丢弃,渲染,缓存?

对于排队中的请求,可以使用丢弃策略。渲染一个非可见的页面很可能会和其他页面渲染造成撞车,造成渲染卡顿等问题。更好的做法是工作线程中缓存数据,待回到页面时候进行渲染。

加入了缓存之后,有两种数据加载和缓存策略。
第一种策略:数据加载时先从缓存加载,缓存中无数据再从网络加载。从网络加载完成后,post到主线程渲染的同时缓存数据。
第二种策略:数据统一先入库,监听数据库发生变化时主动加载。

第一种策略可能大家比较熟知,对比起来第二种显得非主流一些。为什么选择第二种策略,起源于2010年Google I/O大会的一场演讲,主题是开发Android REST客户端应用程序。提到了三种架构设计模式,我使用了第三种,通过SyncAdapter与ContentProvider去同步数据的方式,我在看到这个演讲的时候大概是2014年底,接触到Google官方推荐的架构设计概念已经非常滞后了。这个演讲并没有提供什么源码参考,后来我按照这个设计实现了一个v2ex客户端(v2ex.com提供了访问网站数据的API)。

Google I/O 2010 - Android REST client applications
v2ex android client Github

SyncAdater是Android提供的数据同步框架,即使应用没有启动,framework也可以启动Service创建SyncAdater, 在后台根据网络连接、失败重试等不同条件下自动、周期性地同步数据,也可以设置强制立即执行数据同步。在这里我将它设置为立即同步数据,启动SyncAdapter进行数据同步时,会开启一个线程,网络耗时操作可以放到子线程回调任务中onPerformSync方法中执行,任务结束后线程自动结束。这也引入了一个问题,单线程模型无法支持多并发,也是后期抛弃SyncAdapter同步数据的一个原因。

获取到数据之后,通过Processor处理数据,组合ContentProvider batch操作,最终将数据保存到数据库中。当数据库内容发生变化时,监听uri触发CursorLoader重新加载数据,最终通过LoaderCallbacks回调onLoadFinished完成数据加载。值得一提的是,SyncAdapter(继承自AbstractThreadedSyncAdapter), ContentProvider, CursorLoader都是Android提供的API, SyncAdapter实现数据请求加载,ContentProvider实现数据存取,CursorLoader和Activity生命周期完美匹配,当Activity因为系统配置发生变化销毁重建时,Loader之前加载的数据并不会丢失,一旦Activity/Fragment被永久销毁,Loader也会随之被清理,也就意味着Loader不会再Activity/Fragment被销毁后继续加载数据,增加App负担。

这些优势是系统管理的LoaderManager帮我们实现的,LoaderManager关注Activity/Fragment的生命周期状态,并管理着多个Loader实例。到目前为止,基于SyncAdapter + ContentProvider + CurorLoader的架构已经实现了业务逻辑和UI逻辑的解耦,Loader与Activity生命周期的绑定又把我们从生命周期回调中要做的善后工作解放出来了,即使系统配置发生变化,数据也不会丢失。

基于这个架构,我完成了3个小型项目,在当时我觉得是一种不错的架构。但是也有很明显的缺点,比如前面提到的SyncAdapter运行时开启一个线程,任务执行完毕关闭线程不适合多线程并发的场景;所有的网络操作最后都要通过更新数据库表来通知页面更新,显得比较重,进而带来的是开发效率的降低。

三、更灵活轻量的基于Loader的架构

系统提供的CursorLoader只能从数据库加载数据,可以通过继承AsyncTaskLoader来实现基于内存缓存的数据存取框架,既保留Loader的优点,又满足线程并发的需求和降低对数据库的依赖。

于是,在上面框架的基础上,衍生发展了完全基于Loader的架构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
public abstract class RemoteResourceLoader<R> extends Loader<Result<R>> implements ILoadListener {

private boolean isLoading;

private boolean isFirstPageDataExist;

private Notifiable mNotifiable;

private boolean hasDelivered;

public RemoteResourceLoader(Context context, Notifiable notifiable) {
super(context);
this.mNotifiable = notifiable;
}

public boolean isLoading() {
return isLoading;
}

public void setNotifiable(Notifiable notifiable) {
this.mNotifiable = notifiable;
}

public boolean reload(Object... params) {
if(isLoading) {
return false;
}
hasDelivered = false;
onForceLoad();
return true;
}

@Override
public void onLoadNextPage() {
reload();
}

@Override
protected void onForceLoad() {
if(isLoading || hasDelivered) {
return;
}
new WorkTask().execute();
super.onForceLoad();
}

@Override
protected void onStartLoading() {
forceLoad();
}

@Override
protected void onStopLoading() {

}

public void resetFirstPageDataExist() {
isFirstPageDataExist = false;
}

protected abstract Result<R> createResult();

class WorkTask extends AsyncTask<Void, Integer, Result<R>> {
@Override
protected void onPreExecute() {
isLoading = true;
if(mNotifiable != null) {
mNotifiable.onLoadStart(isFirstPageDataExist);
}
}

@Override
protected Result<R> doInBackground(Void... params) {
Result<R> result = createResult();
if(!isFirstPageDataExist) {
isFirstPageDataExist = result.isDataValid();
}
return result;
}

@Override
protected void onPostExecute(Result<R> result) {
isLoading = false;
deliverResult(result);
stopLoading();
hasDelivered = true;
if(mNotifiable != null) {
mNotifiable.onLoadStop(isFirstPageDataExist, result.getCode(), result.getError());
}
}
}
}

public abstract class Result<R> {

private int mCode;
private String mError;
private R mResult;

public Result(int code, String error, R result) {
this.mCode = code;
this.mError = error;
this.mResult = result;
}

public int getCode() {
return mCode;
}

public void setCode(int code) {
mCode = code;
}

public R getResult() {
return mResult;
}

public void setResult(R result) {
mResult = result;
}

public void setError(String error) {
mError = error;
}

public String getError() {
return mError;
}

public abstract boolean isDataValid();
}

public interface Notifiable {

/**
* 开始载入数据
* @param hasFirstPageData 第一页数据是否存在
*/
void onLoadStart(boolean hasFirstPageData);

/**
* 载入过程停止
*/
void onLoadStop(boolean hasFirstPageData, int code, String error);
}

interface ILoadListener {
void onLoadNextPage();
}

仿照AsyncTaskLoader的实现,内部工作线程使用AsyncTask, 并从外部传入Notifiable对象,Notifiable是一个接口,主要用于更新View开启加载loading和停止loading. Result封装了结果和错误提示。对象的创建createResult和加载策略及动态加载下一页数据留给RemoteResourceLoader的子类去实现。

RemoteResourceLoader各个子类完成业务逻辑的封装,在LoaderManager的加持下,实现了业务逻辑和UI逻辑的完全独立,进而到达业务逻辑的复用。基于不同场景,自定义Loader, 比如加载信息流,可以继承RemoteResourceLoader实现网络加载及缓存的管理;按钮点赞可以继承AsyncTaskLoader实现网络操作,不考虑缓存管理;从数据库加载联系人则可以使用系统提供的CursorLoader.

当手机状态发生改变比如旋转时,Activity会重新启动,Loader却不会销毁,只有当Activity/Fragment被永久销毁时才会清除Loader.

Loader的生命周期是是由系统控制的,在手机状态改变时不会被销毁,只有当Activity/Fragment被永久销毁时才清除Loader. 那么是否可以通过Loader的特性去保持Presenter不被销毁,特别是当Presenter保存了业务数据,在Activity销毁重建时维持一个Presenter更有用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class PresenterLoader<T extends Presenter> extends Loader<T>{

private final PresenterFactory<T> factory;
private T presenter;

// Constructor...

@Override
protected void onStartLoading() {

// If we already own an instance, simply deliver it.
if (presenter != null) {
deliverResult(presenter);
return;
}

// Otherwise, force a load
forceLoad();
}

@Override
protected void onForceLoad() {
// Create the Presenter using the Factory
presenter = factory.create();

// Deliver the result
deliverResult(presenter);
}

@Override
protected void onReset() {
presenter.onDestroyed();
presenter = null;
}
}

public interface Presenter<V>{
void onViewAttached(V view);
void onViewDetached();
void onDestroyed();
}

Activity销毁时,自动触发Presenter执行onDestroyed()方法,我们可以在这个方法里去做必要的资源释放。

Presenter surviving orientation changes with Loaders

四、MVVM模式

从前面的架构设计一路走来,逐步解决了层级的划分去除Activity/Fragment的臃肿,业务和UI逻辑的解耦,系统配置变化保持业务数据。MVVM的出现又能帮我们解决哪些问题?在回答这个问题之前,先熟悉下MVVM中的各个组件的功能吧。

View

View层做的就是和UI相关的工作,我们只在XML、Activity和Fragment写View层的代码,View层不做和业务相关的事,也就是我们在Activity不写业务逻辑和业务数据相关的代码,更新UI通过RxJava实现数据监听,Activity要做的事就是初始化一些控件(如控件的颜色,添加RecyclerView的分割线),View层可以自由封装一些更新UI的private方法,也不需要像MVP模式一下对外暴露UI接口。简单地说:View层不做任何业务逻辑、不涉及操作数据、不处理数据,UI和数据严格的分开。

ViewModel

ViewModel层做的事情刚好和View层相反,ViewModel只做和业务逻辑和业务数据相关的事,不做任何和UI相关的事情,ViewModel 层不会持有任何控件的引用,更不会在ViewModel中通过UI控件的引用去做更新UI的事情。ViewModel就是专注于业务的逻辑处理,做的事情也都只是对数据的操作。当处理完业务逻辑和数据后,需要将结果通知到UI层进行显示,在这里我更倾向于使用RxJava的PublishSubject或者BehaviorSubject, Subject同时继承了Observable和Observer两个接口,说明它既是被观察的对象,同时又是观察对象,可以生产,可以消费,也可以自己生产自己消费。和LiveData功能相似。在ViewModel中,PublishSubject也可以替换成LiveData. 但是我更推荐使用RxJava, 因为功能更强大,所受限制更少。

Model

Model层最大的特点是被赋予了数据获取的职责,与我们平常Model层只定义实体对象的行为截然不同。实例中,数据的获取、存储、数据状态变化都是Model层的任务。Model包括实体模型(Bean)、Retrofit的Service ,获取网络数据接口,本地存储(增删改查)接口,数据变化监听等。Model提供数据获取接口供ViewModel调用,经数据转换和操作并最终映射绑定到View层某个UI元素的属性上。

对比之下,MVP中View与Presenter还存在一定的耦合,如果业务发生变化,有可能需要修改View接口。ViewModel是不会持有任何View层引用的,耦合度更低,更容易复用代码,提高可测试性,配合RxJava, AutoDispose我们无需处理生命周期相关的逻辑,有效的避免了内存泄漏问题。View仅是ViewModel的消费者,当修改UI时, 不修改ViewModel.
根据业务关注点, 创建多个高内聚的View与ViewModel, 允许多个页面共享与替换。View与ViewModel一对多, ViewModel与Model多对多。

如上图,说明了 Activity 经历屏幕旋转而后结束的过程中所处的各种生命周期状态,Jetpack提供的ViewModel组件在这种场景下不会因为Activity的销毁重建而销毁和重建,依赖该特性我们可以使用ViewModel保存我们的业务数据,而在Activity真正finish时,在ViewModel的onCleared回调中进行资源释放。

此外,Activity和Fragment可以共用同一个ViewModel对象,这样我们可以在Activity创建一个ViewModel,实现Activity和Fragment之间,1个Activity承载的多个Fragment之间数据共享。

在上文中,无论是MVC, MVP,最后都是通过两个回调函数分别实现对正常响应数据和异常数据的渲染。一个简单的从后端拉取当前城市天气情况下,传统的MVP实现方式应该是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public interface ResponseListener<T> {
void onSuccess(T response);
void onError(Throwable throwable);
}

public class WeatherPresenter extends Presener<IWeatherView> {
private WeatherModel mWeatherModel;

public void getWeather(String cityNumber) {
getView().showLoading(true); // 加载中展示ProgressBar

mWeatherModel.getWeather(cityNumber, new ResponseListener<Weather>() {
@Override
public void onSuccess(Weather response) {
if (response != null) {
getView().showWeather(response);
} else {
getView().showError(new EmptyResultException());
}
}

@Override
public void onError(Throwable throwable) {
getView().showError(throwable);
}
});
}
}

这段代码的WeatherModel到底代表什么呢,是指后台的网络请求吗?不,那只是Web服务访问逻辑或者数据获取逻辑。是指请求结果的天气数据吗?不,它和ProgressBar, 错误信息展示一样,仅仅只代表了View层所能展示内容的一部分而已。

那么,Model层究竟代表什么呢?
从我个人理解来说,Model类应该定义成这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
data class BaseViewState<T>(val isLoading: Boolean,
val throwable: Throwable?,
val result: T?) {

companion object {
fun <T> initial(): BaseViewState<T> {
return BaseViewState(
isLoading = false,
throwable = null,
result = null
)
}
}

override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false

other as BaseViewState<*>

if (isLoading != other.isLoading) return false
if (throwable != other.throwable) return false
if (result != other.result) return false

return true
}

override fun hashCode(): Int {
var res = isLoading.hashCode()
res = 31 * res + (throwable?.hashCode() ?: 0)
res = 31 * res + (result?.hashCode() ?: 0)
return res
}

}

Presenter层应该这样实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class WeatherPresenter: Presenter<IWeatherView>() {
private var mWeatherModel: WeatherModel? = null;

fun getWeather(String cityNumber) {
getView().bind(BaseViewState(true, null, null)); // 加载中展示ProgressBar
mWeatherModel.getWeather(cityNumber, new ResponseListener<Weather>() {
@Override
public void onSuccess(Weather response) {
getView().bind(false, response, null);
}

@Override
public void onError(Throwable throwable) {
getView().bind(false, null, throwable);
}
});
}
}

从业务逻辑层到UI层,有且仅有一个真实描述状态的源,Model映射状态,代码更简洁,更容易维护。

现在,基于RxJava, 按照MVVM的设计模式我们重新实现一个天气App吧。

通常情况下,我们会定义一个数据仓库Repository, 数据仓库一个最重要的优点是集中管理许多位置访问数据源,获取一致访问规则和逻辑,并集中实现缓存策略。同时,由于将业务逻辑和数据和服务访问逻辑隔离,提高了代码的可读性和可维护性。

先定义获取天气数据仓库的接口及实现吧.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
interface IRepository

class WeatherRemoteRepository private constructor(): IRepository {
companion object {
val instance: WeatherRemoteRepository by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
WeatherRemoteRepository()
}
}

fun getWeather(city: String): Flowable<Either<Errors, Weather>> {
val map = HashMap<String, String>()
map["city"] = city
return NetworkManager.instance.getWeatherApiService()
.getWeather(buildRequestBody(map))
.subscribeOn(RxSchedulers.io)
.map { Either.right(it.data) }
}
}

class WeatherLocalRepository private constructor(): IRepository {
private var mPrefs: SharedPreferences? = null
private val mGson = Gson()
companion object {
val instance: WeatherLocalRepository by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
WeatherLocalRepository()
}
}

fun getWeather(city: String): Flowable<Either<Errors, Weather>> {
return Flowable.just(mPrefs.getString(city, ""))
.map { mGson.fromJson(it) }
.subscribeOn(RxSchedulers.io)
.map { Either.right(it) }
}

// 保存城市天气信息,可增加缓存过期时间,如24小时过期
fun saveWeather(city: String, weather: Weather) {
Flowable.just(weather).map { mGson.toJson(it) }
.subscribeOn(RxSchedulers.io)
.observeOn(RxSchedulers.io)
.subscribe { mPrefs.putString(city, it) }
}
}

class WeatherRepository private constructor(): IRepository {

private val mRemoteWeatherRepository = WeatherRemoteRepository.instance
private val mLocalWeatherRepository = WeatherLocalRepository.instance
companion object {
val instance: WeatherRepository by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
WeatherRepository()
}
}

fun getWeather(city: String): Flowable<Either<Errors, Weather>> {
val local = mLocalWeatherRepository.getWeather(city)
val remote = mRemoteWeatherRepository.getWeather(city)
.doOnNext { mLocalWeatherRepository.saveWeather(city, it) }
return Flowable.concat(local, remote)
.takeFirst()
.subscribeOn(RxSchedulers.io)
.map { Either.right(it) }
}
}

Arrow是提供了一些简单函数式编程的特性,利用Arrow提供的各种各样的函数,配合RxJava,你可以实现这样的代码以避免各种分支的处理,比如随时都有可能的if..else(),并将这些额外的操作放在最终的操作符中去处理。

我不厌其烦地对Repository进行封装,并使它成为一个获取数据的模板,就是为了将它和业务逻辑完全隔离,最终完成数据逻辑、业务逻辑、UI逻辑分层和解耦,更容易理解和debug.

接下来,我们实现一个ViewModel管理Weather相关的业务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
class WeatherViewModel: AutoDisposeViewModel() {
private val mWeatherStateSubject: BehaviorSubject<BaseViewState<Weather>> =
BehaviorSubject.createDefault(BaseViewState.initial())

fun observeWeatherState(): Observable<BaseViewState<Weather>> {
return mWeatherStateSubject
}

fun getWeather(city: String) {
WeatherRepository.instance.getWeather(city)
.map { either ->
either.fold({
Result.failure<Weather>(it)
}, {
Result.success(it)
})
}
.startWith(Result.loading())
.onErrorReturn { Result.failure(it) }
.subscribeOn(RxSchedulers.io)
.observeOn(RxSchedulers.ui)
.autoDisposable(this)
.subscribe({
state ->
when (state) {
is Result.Loading -> mPollOrderStateSubject.onNextWithLast {
it.copy(true, null, null)
}
is Result.Failure -> mPollOrderStateSubject.onNextWithLast {
it.copy(false, state.error, null)
}
is Result.Success -> mPollOrderStateSubject.onNextWithLast {
it.copy(false, null, state.data)
}
}
})
}

class ViewModelFactory: ViewModelProvider.Factory {
override fun <T: ViewModel?> create(modelClass: Class<T>): T = WeatherViewModel() as T
}
}

外部调用WeatherViewModel的getWeather方法,订阅时触发startWith发射loading状态,发生错误时发送failure状态,最后通过BehaviorSubject将数据发送出去。Activity或者Fragment通过监听获取状态进行数据绑定。这里也可以通过LiveData发射数据和监听状态。由于有统一的BaseViewState管理状态,UI逻辑的实现也收敛到具体的某一个方法中,对我们实现状态管理非常有帮助。

最后,来到View层实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class MainActivity: FragmentActivity() {
private mWeatherModel: WeatherViewModel by lazy {
ViewModelProvider(this, WeatherViewModel.ViewModelFactory()).get(WeatherViewModel::class.java)
}
...

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mWeatherModel.observeWeatherState().observeOn(RxSchedulers.ui)
.autoDisposible(scopeProvider).subscribe(this::onWeatherNewState)
}

override onClick(view: View!) {
switch (view.getId()) {
case R.id.btn_go:
mWeatherModel.getWeather(mCityNOInput.getText().toString().trim())
break;
}
}

private fun onWeatherNewState(state: BaseViewState<Wether>) {
when {
state.isLoading -> {}
state.throwable != null -> {
Toast.makeText(this, "获取天气信息失败", Toast.LENGTH_SHORT).show()
}
state.result != null -> {
mCity.setText(weather.getCity());
}
}
}
}

经过以上架构设计的迭代过程,不断地与生命周期回调的纠缠,业务逻辑和状态UI逻辑的达成解耦,你会发现每个阶段都能解决一些棘手问题,也引入了一些问题。比如,MVC架构中,View层被Model层持有,需要我们小心处理View的释放以防内存泄露。Loader框架引入解决了MVC中View被Model持有的问题和Configuration配置发生变化时保持业务数据,但是需要我们深入理解内部实现原理和定制。MVP将业务层和UI层进行了隔离,依然需要我们小心维护资源的释放。MVVM作为集大成者,结合Jetpack提供的趁手组件武器,基于RxJava, databinding均有不同实现。在某些场景中,依然可以使用Loader实现数据的加载,使用MVP中面相接口编程的思想。需要我们根据具体的应用场景使用不同的架构设计,达到动态平衡。