Android MVP+Retrofit2+RxAndroid解锁新姿势

开篇

  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架构了。接下来我们要在这个框架里融合Retrofit2RxAndroid技术。

融合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 onNext, Consumer onError,
            Action onComplete, Consumer 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 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

旧书不厌百回读,熟读精思子自知。——苏轼

你可能感兴趣的:(Android MVP+Retrofit2+RxAndroid解锁新姿势)