Android MVP+Retrofit2+RxAndroid解锁新姿势

开篇

  Android框架三剑客:MVC、MVP、MVVM。关于这三剑客模型,网上资料丰富得很,本篇就不予复述。因为MVP在我心目中占据着霸主地位,所以本篇通过实战讲讲我自己对MVP架构的理解。下面也推荐一些与这三剑客相关的博客文章,希望童鞋们能更好地理解这些框架。

立即体验

扫描以下二维码下载体验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<V extends IBaseView, M extends IBaseModel> {
    private V view;
    private M model;
    //...省略部分代码
}
  • 2、管理Presenter。把Presenter加入Activity的生命周期中,在BaseActivity的onDestroy()之前释放Presenter所持有的View,避免内存泄漏。
public abstract class BaseMVPActivity extends BaseAppCompatActivity {
    private List<BasePresenter> 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<String> loadVersionInfo(String baseUrl, String token, boolean showNetLog);
}

public class TestModel implements ITestModel {

    //利用Retrofit2网络框架创建一个被观察者对象。
    @Override
    public Observable<String> 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<V extends IBaseView, M extends IBaseModel> extends BasePresenter<V, M> {
    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<ITestView, ITestModel> {

    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<String>() {
                    @Override
                    public void accept(String s) throws Exception {
                        getView().onLoadVersionInfo(s);
                    }
                }, new Consumer<Throwable>() {
                    @Override
                    public void accept(Throwable throwable) throws Exception {
                        
                    }
                }, new Action() {
                    @Override
                    public void run() throws Exception {
                        
                    }
                }, new Consumer<Disposable>() {
                    @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<T> ls = new LambdaObserver<T>(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<T> implements Observer<T>, 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<String>(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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,265评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,078评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,852评论 0 347
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,408评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,445评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,772评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,921评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,688评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,130评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,467评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,617评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,276评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,882评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,740评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,967评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,315评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,486评论 2 348

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,754评论 25 707
  • 转载至://www.greatytc.com/p/9a6845b26856 “Android MVP 详解...
    SnowDragonYY阅读 10,320评论 5 241
  • afinalAfinal是一个android的ioc,orm框架 https://github.com/yangf...
    wgl0419阅读 6,266评论 1 9
  • 多是深情留不住,偏偏套路得人心。
    Elaine_lin阅读 151评论 0 0
  • 文/刘镜姝 老姚、傻红、大头小杰是有名的“城西三怪”,可是比起前两者遭受到的恶意,大头小杰得到的是更多怜悯与同情。...
    刘镜姝阅读 351评论 2 2