开篇
Android框架三剑客:MVC、MVP、MVVM。关于这三剑客模型,网上资料丰富得很,本篇就不予复述。因为MVP在我心目中占据着霸主地位,所以本篇通过实战讲讲我自己对MVP架构的理解。下面也推荐一些与这三剑客相关的博客文章,希望童鞋们能更好地理解这些框架。
- Android高精战争(MVC、MVP、MVVM)
- 认清Android框架 MVC,MVP和MVVM
- Android开发模式:MVP Vs MVVM
- Android MVC,MVP和MVVM 思想&例子
- Android App的设计架构:MVC, MVP, MVVM
- 如何构建Android MVVM 应用框架
- 细说Android框架设计三剑客MVC、MVP和MVVM
立即体验
扫描以下二维码下载体验App(从0.2.3
版本开始,体验App内嵌版本更新检测功能):
JSCKit库传送门:https://github.com/JustinRoom/JSCKit
MVP通信模型
从MVP模型图中可以看出:Presenter持有View和Model的引用,Presenter与View之间相互通信、Presenter与Model之间相互通信,View与Model被Presenter完全隔离。理解了MVP的思想模型之后,我们接下来实战指导。
实战指导
- 1、三个要素:View、Model和Presenter:
View:负责刷新视图,在UI线程中执行。
Model:负责数据处理。例如加载网络数据、从本地数据库获取数据,写数据到本地数据库...
Presenter:负责调度View与Model。它是View与Model的连接桥梁。
public interface IBaseView {
}
public interface IBaseModel {
}
Presenter 持有View、Model的引用:
public abstract class BasePresenter {
private V view;
private M model;
//...省略部分代码
}
- 2、管理Presenter。把Presenter加入Activity的生命周期中,在BaseActivity的
onDestroy()
之前释放Presenter所持有的View,避免内存泄漏。
public abstract class BaseMVPActivity extends BaseAppCompatActivity {
private List presenterManager = null;
/**
* Add presenter into presenter manager.
* @param presenter presenter instance
*/
public final void addToPresenterManager(@NonNull BasePresenter presenter){
if (presenterManager == null){
presenterManager = new ArrayList<>();
}
presenterManager.add(presenter);
}
/**
* Remove presenter from presenter manager.
* @param presenter presenter instance
*/
public final void removeFromPresenterManager(@NonNull BasePresenter presenter){
if (presenterManager != null && !presenterManager.isEmpty()){
presenterManager.remove(presenter);
}
}
/**
* Release presenters' resources.
*/
public void recyclePresenterResources(){
if (presenterManager != null && !presenterManager.isEmpty()){
for (BasePresenter presenter : presenterManager) {
presenter.release();
presenter = null;
}
}
}
@Override
protected void onDestroy() {
recyclePresenterResources();
super.onDestroy();
}
这就是最简洁的MVP架构了。接下来我们要在这个框架里融合Retrofit2和RxAndroid技术。
融合Retrofit2和RxAndroid
我们先看一下项目结构:
- activity —— 管理所有的界面
- model —— 管理所有的Model
- presenter —— 管理所有的Presenter
- view —— 管理所有的View
每一个Activity都对应有一个view、一个model、一个presenter。例如结构中:
TestActivity——ITestView——TestModel——TestPresenter。
1、创建Model。示例中TestModel实现了ITestMode接口,而ITestModel继承IBaseModel。
public interface ITestModel extends IBaseModel {
Observable loadVersionInfo(String baseUrl, String token, boolean showNetLog);
}
public class TestModel implements ITestModel {
//利用Retrofit2网络框架创建一个被观察者对象。
@Override
public Observable loadVersionInfo(String baseUrl, String token, boolean showNetLog) {
return new CustomRetrofit()
.setBaseUrl(baseUrl)
.setOkHttpClient(new CustomHttpClient()
.addHeader(new Pair<>("token", token))
.setConnectTimeout(5_000)
.setShowLog(showNetLog)
.createOkHttpClient())
.createRetrofit()
.create(ApiService.class)
.getVersionInfo();
}
}
2、创建View。ITestView继承IBaseView。(携带网络请求结果)通知Activity刷新UI视图。
public interface ITestView extends IBaseView{
void onLoadVersionInfo(String result);
}
3、创建Presenter。因为加入了Retrofit2和RxAndroid,所以我们要对RxAndroid调度中所产生的Disposable进行生命周期管理。基于这点,我们可以封装一个用于管理的BasePresenter,示例中命名为MyBasePresenter。MyBasePresenter继承BasePresenter。
MyBasePresenter:
public abstract class MyBasePresenter extends BasePresenter {
private CompositeDisposable compositeDisposable = null;
public MyBasePresenter() {
}
public MyBasePresenter(@NonNull V view, @NonNull M model) {
super(view, model);
}
@Override
public void release() {
clearAllDisposables();
super.release();
}
/**
* Add a disposable to manager.
*
* @param disposable disposable
*/
public void add(@NonNull Disposable disposable) {
if (compositeDisposable == null) {
compositeDisposable = new CompositeDisposable();
}
compositeDisposable.add(disposable);
}
/**
* Remove a disposable from manager.
*
* @param disposable disposable
*/
public void remove(@NonNull Disposable disposable) {
if (compositeDisposable == null) {
return;
}
compositeDisposable.remove(disposable);
}
/**
* Remove all disposables from manager.
*/
public void clearAllDisposables() {
if (compositeDisposable == null) {
return;
}
compositeDisposable.dispose();
compositeDisposable.clear();
}
}
TestPresenter:它需要持有ITestView和TestModel的引用。
public class TestPresenter extends MyBasePresenter {
private final String TAG = "TestPresenter";
public TestPresenter() {
}
public TestPresenter(@NonNull ITestView view, @NonNull ITestModel model) {
super(view, model);
}
public void loadVersionInfo() {
String baseUrl = getCommonView().getBaseUrl();
String token = getCommonView().getToken();
boolean isShowNetLog = getCommonView().isShowNetLog();
String userId = getCommonView().getCurrentUserId();
Disposable disposable = getModel().loadVersionInfo(baseUrl, token, isShowNetLog)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer() {
@Override
public void accept(String s) throws Exception {
getView().onLoadVersionInfo(s);
}
}, new Consumer() {
@Override
public void accept(Throwable throwable) throws Exception {
}
}, new Action() {
@Override
public void run() throws Exception {
}
}, new Consumer() {
@Override
public void accept(Disposable disposable) throws Exception {
}
});
}
}
4、在Activity中实例化Model、View、Presenter。
前面已经说过BaseMVPActivity 是基于对Presenter管理的封装。TestActivity继承BaseMVPActivity 。
public class TestActivity extends BaseMVPActivity implements ITestView{
private TextView textView;
private TestPresenter testPresenter;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
textView = new TextView(this);
textView.setGravity(Gravity.CENTER);
setContentView(textView, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
setTitle(getClass().getSimpleName().replace("Activity", ""));
textView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.i("TestActivity", "onClick: ");
}
});
//实例化Model、View、Presenter
testPresenter = new TestPresenter(this, new TestModel());
//把Presenter加入到Activity的生命周期管理中
addToPresenterManager(testPresenter);
sendUIEmptyMessageDelay(0, 350L);
}
@Override
public void handleUIMessage(Message msg) {
super.handleUIMessage(msg);
testPresenter.loadVersionInfo();
}
@Override
public void onLoadVersionInfo(String result, String message) {
if (!TextUtils.isEmpty(result)){
textView.setText(result);
} else {
textView.setText(message);
}
}
}
至此,MVP框架从封装到使用基本上算是完成了。但是,实际的开发中,为了更好的用户体验往往存在这样一个需求:
在加载过程中显示LoadingDialog
,并且在取消LoadingDialog
显示的同时取消本次网络请求。
分解上面的需求,我们要做两件事情:
- 一、在UI线程中显示
LoadingDialog
- 二、
LoadingDialog
绑定这次的请求事件
针对于事件一,我们必须对RxJava的线程调度有一定的理解。至于RxJava线程调度,这不是文章的重点,请自行参阅网络资料进行学习。
下面我们主要看看LoadingDialog
如何绑定本次网络请求?
RxJava下Observable相关源码:
@CheckReturnValue
@SchedulerSupport(SchedulerSupport.NONE)
public final Disposable subscribe(Consumer super T> onNext, Consumer super Throwable> onError,
Action onComplete, Consumer super Disposable> onSubscribe) {
ObjectHelper.requireNonNull(onNext, "onNext is null");
ObjectHelper.requireNonNull(onError, "onError is null");
ObjectHelper.requireNonNull(onComplete, "onComplete is null");
ObjectHelper.requireNonNull(onSubscribe, "onSubscribe is null");
LambdaObserver ls = new LambdaObserver(onNext, onError, onComplete, onSubscribe);
subscribe(ls);
return ls;
}
在方法的后面,创建了一个LambdaObserver
观察者并订阅该该事件ls:subscribe(ls)
。
@Override
public final void subscribe(Observer super T> observer) {
ObjectHelper.requireNonNull(observer, "observer is null");
try {
observer = RxJavaPlugins.onSubscribe(this, observer);
ObjectHelper.requireNonNull(observer, "Plugin returned null Observer");
subscribeActual(observer);
} catch (NullPointerException e) { // NOPMD
throw e;
} catch (Throwable e) {
Exceptions.throwIfFatal(e);
// can't call onError because no way to know if a Disposable has been set or not
// can't call onSubscribe because the call might have set a Subscription already
RxJavaPlugins.onError(e);
NullPointerException npe = new NullPointerException("Actually not, but can't throw other exceptions due to RS");
npe.initCause(e);
throw npe;
}
}
既然LoadingDialog
需要绑定该事件,所以我们是不是可以把它看做一个观察者Observer呢?当然是可以的。
public abstract class LoadingDialogObserver implements Observer, DialogInterface.OnCancelListener {
private final int SHOW_DIALOG = 0x6990;
private final int HIDE_DIALOG = 0x6991;
private Dialog loadingDialog;
private boolean ifShowDialog;
private Handler handler = new Handler(Looper.getMainLooper(), new Handler.Callback() {
@Override
public boolean handleMessage(Message message) {
switch (message.what) {
case SHOW_DIALOG:
if (loadingDialog != null && !loadingDialog.isShowing())
loadingDialog.show();
break;
case HIDE_DIALOG:
if (loadingDialog != null && loadingDialog.isShowing())
loadingDialog.dismiss();
break;
}
return true;
}
});
private Disposable disposable;
/**
* Constructor.
*/
public LoadingDialogObserver() {
this(null);
}
/**
* Constructor.
*/
public LoadingDialogObserver(Dialog loadingDialog) {
this(loadingDialog, true);
}
/**
* Constructor.
*
* @param loadingDialog
* @param ifShowDialog Show loadingDialog if true else not.
*/
public LoadingDialogObserver(Dialog loadingDialog, boolean ifShowDialog) {
this.loadingDialog = loadingDialog;
this.ifShowDialog = ifShowDialog;
if (loadingDialog != null)
loadingDialog.setOnCancelListener(this);
}
@Override
public void onSubscribe(Disposable d) {
disposable = d;
if (ifShowDialog)
handler.sendEmptyMessage(SHOW_DIALOG);
onStart(d);
}
@Override
public void onNext(T t) {
try {
onResult(t);
} catch (Exception e){
onError(e);
}
}
@Override
public void onError(Throwable e) {
if (ifShowDialog)
handler.sendEmptyMessage(HIDE_DIALOG);
onException(e);
onCompleteOrCancel(disposable);
}
@Override
public void onComplete() {
if (ifShowDialog)
handler.sendEmptyMessage(HIDE_DIALOG);
onCompleteOrCancel(disposable);
}
@Override
public void onCancel(DialogInterface dialog) {
disposable.dispose();
onCompleteOrCancel(disposable);
}
/**
* Show loading dialog here if necessary.
* @param disposable disposable, the same as the return of {@link io.reactivex.Observable#subscribe(Observer)}.
*/
public abstract void onStart(Disposable disposable);
/**
* Call back the response.
* @param t response
*/
public abstract void onResult(T t);
/**
* Call back when a exception appears.
* @param e exception
*/
public abstract void onException(Throwable e);
/**
* Call back when {@link Observer#onComplete()} or loading dialog is canceled.
* @param disposable disposable, the same as the return of {@link io.reactivex.Observable#subscribe(Observer)}.
*/
public abstract void onCompleteOrCancel(Disposable disposable);
改写TestPresenter中的loadUserInfo()
方法如下:
public void loadVersionInfo() {
String baseUrl = getCommonView().getBaseUrl();
String token = getCommonView().getToken();
boolean isShowNetLog = getCommonView().isShowNetLog();
String userId = getCommonView().getCurrentUserId();
Dialog loadindDialog = getCommonView().getLoadingDialog();
getModel().loadVersionInfo(baseUrl, token, isShowNetLog)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new LoadingDialogObserver(loadindDialog) {
@Override
public void onStart(Disposable disposable) {
//加入到事件管理中
add(disposable);
}
@Override
public void onResult(String s) {
getView().onLoadVersionInfo(s);
}
@Override
public void onException(Throwable e) {
}
@Override
public void onCompleteOrCancel(Disposable disposable) {
//该事件已经执行完毕,或者被取消,从事件管理中移除,让GC回收资源。
remove(disposable);
}
});
}
到此,完美实现MVP+Retrofit2+RxAndroid架构。
使用示例:
https://github.com/JustinRoom/JSCKit/blob/master/app/src/main/java/jsc/exam/jsckit/ui/mvp
篇尾
原创不易,给个爱心,谢谢!QQ:1006368252
。
旧书不厌百回读,熟读精思子自知。——苏轼