Android:Retrofit + RxJava MVP 架构实际应用

前言

目前 MVP 架构在 Android app 开发中非常流行。通过 谷歌官方 例子和个人的一些理解形成自己的 Retrofit + RxJava 的 MVP 架构,并且用这实际开发当中。看此文需要一定的 Retrofit 和 RxJava 基础,对 mvp-clean 有一定了解。

MVP 简单介绍

  • M:Model,数据处理层,获取数据、整理数据等;
  • V:View,展示数据层,ActivityFragmentDialog 等都可以充当这个角色;
  • P:Presenter,逻辑处理层,处理数据和 UI 的关系、处理来自 V 层逻辑等;

下图分析:
V 层和 M 层直接关联了吗?并没有,这是 MVP 和 MVC 一个很大的区别。V 层和 M 层是通过 P 层联系起来的。反映到代码中就是 P 持有 V 实例和 M 实例。

MVP

具体实现

1. 总体包目录

  • base 包:一些 base 基类,BaseFragmentBaseMvpFragmentBaseActivityBaseMvpFragment 差不多。
  • net 包:网络请求和处理相关的类在此包下。
  • utils 包:需要用到的简单工具类。
image.png

2. 关键类的说明

  • BaseMvpActivity:封装了公共方法的 Activity,用了处理项目里 Activity一般都会使用到的方法,例如配合 Presenter 统一处理内存泄漏问题。注意:BaseMvpActivity Presenter 泛型是主 Presenter,其他的为次 Presenter,需要在 getPresenter() 中初始化,并且添加到 Presenter 集合中。
public abstract class BaseMvpActivity<P extends BasePresenter<? extends IBaseView>> extends BaseActivity implements IBaseView {

    //主Presenter
    protected P mPresenter;
    //多个Presenter时候需要的容器
    private ArraySet<BasePresenter> mPresenters = new ArraySet<>(4);

    @Override
    protected void init(@Nullable Bundle savedInstanceState) {
        initLoading();
        mPresenter = getPresenter();
        addToPresenters(mPresenter);
        initView();
        initListener();
        initData();
    }

    @Override
    protected void onDestroy() {
        for (BasePresenter presenter : mPresenters) {
            presenter.detachView();
        }
        mPresenters.clear();
        super.onDestroy();
    }

    @Override
    public void showLoading() {

    }

    @Override
    public void showLoading(String msg) {

    }

    @Override
    public void hideLoading() {

    }

    @Override
    public void showMsg(String msg) {
        toastS(msg);
    }

    /**
     * 初始化Presenter,其他多个Presenter也在该方法创建并调用addToPresenters加入到集合
     * @return 主Presenter
     */
    protected abstract P getPresenter();

    /**
     * 根据具体项目需求创建loading
     */
    protected void initLoading() {

    }

    /**
     * 初始化View
     */
    protected void initView(){

    }

    /**
     * 初始化Listener
     */
    protected abstract void initListener();

    /**
     * 初始化数据
     */
    protected abstract void initData();

    /**
     * 把其他的Presenter添加到Presenters集合里
     * 这样会自动绑定View和管理内存释放
     */
    protected <T extends BasePresenter> void addToPresenters(T presenter) {
        presenter.attachView(this);
        mPresenters.add(presenter);
    }

}

  • BasePresenter:Presenter 基类,里面包含 Presenter 需要公共方法,例如统一处理内存泄漏问题。Presenter 我坚决不把 ContextActivityFragment之类的)传进来,要使用 Context 就用 ApplicationContext 。带个 Context 可能有些方便,但是我觉得很影响我的单元测试。
public abstract class BasePresenter<V extends IBaseView> {

    private V mView;

    //Disposable容器,收集Disposable,主要用于内存泄漏管理
    private CompositeDisposable mDisposables;

    protected V getView() {
        return mView;
    }

    /**
     * @param view 绑定View
     */
    @SuppressWarnings("unchecked")
    public <T extends IBaseView> void attachView(T view) {
        this.mView = (V) view;
        mDisposables = new CompositeDisposable();
    }

    /**
     * 解绑关联
     */
    public void detachView() {
        mDisposables.clear();
        mDisposables = null;
        mView = null;
    }

    /**
     * @param disposable 添加Disposable到CompositeDisposable
     *                   通过解除disposable处理内存泄漏问题
     */
    protected boolean addDisposable(Disposable disposable) {
        if (isNullOrDisposed(disposable)) {
            return false;
        }
        return mDisposables.add(disposable);
    }

    /**
     * @param d 判断d是否为空或者dispose
     * @return true:一次任务未开始或者已结束
     */
    protected boolean isNullOrDisposed(Disposable d) {
        return d == null || d.isDisposed();
    }

    /**
     * @param d 判断d是否dispose
     * @return true:一次任务还未结束
     */
    protected boolean isNotDisposed(Disposable d) {
        return d != null && !d.isDisposed();
    }

    /**
     * 获取 Model 实例
     */
    protected <M extends IBaseModel> M getModel(Class<M> clazz) {
        return ModelManager.getInstance().create(clazz);
    }

}
  • HttpManager:网络访问管理,例如 baseUrl 配置;
public final class HttpManager {

    private Retrofit mRetrofit;
    private String mBaseUrl;
    private OkHttpClient mOkHttpClient;
    private Boolean debug = true;

    private static final Logger LOG = Logger.getLogger(HttpManager.class.getName());

    private HttpManager() {
    }

    public static HttpManager getInstance() {
        return Holder.INSTANCE;
    }

    /**
     * @param mBaseUrl 设置BaseUrl
     *                 放在第一位设置
     */
    public HttpManager setBaseUrl(String mBaseUrl) {
        this.mBaseUrl = mBaseUrl;
        return Holder.INSTANCE;
    }

    /**
     * 设置OkHttpClient
     */
    public HttpManager setOkHttpClient(OkHttpClient okHttpClient) {
        this.mOkHttpClient = okHttpClient;
        return Holder.INSTANCE;
    }

    /**
     * @param retrofit 设置retrofit
     *                 放在最后设置
     */
    public void setRetrofit(Retrofit retrofit) {
        this.mRetrofit = retrofit;
    }

    /**
     * debug
     */
    public HttpManager setDebug(Boolean debug) {
        this.debug = debug;
        return Holder.INSTANCE;
    }

    /**
     * @return mRetrofit.create(clazz)
     */
    public <T> T getApiService(Class<T> clazz) {
        return mRetrofit.create(clazz);
    }

    /**
     * 自带创建retrofit
     */
    public Retrofit createRetrofit() {
        Retrofit.Builder builder = new Retrofit.Builder()
                .baseUrl(mBaseUrl)
                .client(mOkHttpClient)
                .addConverterFactory(ScalarsConverterFactory.create())
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(ObserveOnMainCallAdapterFactory.createMainScheduler())
                .addCallAdapterFactory(RxJava2CallAdapterFactory.createWithScheduler(Schedulers.io()));
        return builder.build();
    }

    /**
     * @return OkHttpclient
     */
    public OkHttpClient createDefaultClient() {
        ...
    }

    private static class Holder {
        private static final HttpManager INSTANCE = new HttpManager();
    }

    /**
     * info 等级log
     */
    public static class InterceptorLogInfo implements HttpLoggingInterceptor.Logger {
        @Override
        public void log(@NonNull String message) {
            LOG.log(Level.INFO, message);
        }
    }

}
  • HttpResultObserver:DisposableSingleObserver 的子类,用来接收网络数据回调;
public abstract class HttpResultObserver<T> extends DisposableSingleObserver<T> {

    @Override
    public void onSuccess(T t) {
        //dispose 一次任务
        dispose();
        onResult(t);
    }

    @Override
    public void onError(Throwable e) {
        //dispose 一次任务
        dispose();
        onFailure(e);
    }

    /**
     * @param t 获取结果
     */
    protected abstract void onResult(T t);

    /**
     * @param e 获取结果失败
     */
    protected abstract void onFailure(Throwable e);

}
  • ObserveOnMainCallAdapterFactory:指定数据回调到主线程。配合 Retrofit 使用的。
public final class ObserveOnMainCallAdapterFactory extends CallAdapter.Factory{

    private final Scheduler mScheduler;

    public ObserveOnMainCallAdapterFactory(Scheduler scheduler) {
        this.mScheduler = scheduler;
    }

    @Nullable
    @Override
    public CallAdapter<?, ?> get(@NonNull Type returnType, @NonNull Annotation[] annotations, @NonNull Retrofit retrofit) {
        Class<?> rawType = getRawType(returnType);
        if (rawType != Single.class) {
            return null;
        }
        final CallAdapter<Object, Single<?>> delegate =
                (CallAdapter<Object, Single<?>>) retrofit.nextCallAdapter(this, returnType, annotations);
        return new CallAdapter<Object, Object>() {
            @Override
            public Type responseType() {
                return delegate.responseType();
            }

            @Override
            public Object adapt(@NonNull Call<Object> call) {
                Single<?> s = delegate.adapt(call);
                return s.observeOn(mScheduler);
            }
        };
    }

    /**
     * 在android主线程处理下游数据
     */
    public static CallAdapter.Factory createMainScheduler() {
        return new ObserveOnMainCallAdapterFactory(AndroidSchedulers.mainThread());
    }
}
  • ModelManager:创建管理 Model,这里我把 Model 看做是数据仓库,用来提供直观数据(比如提供 User 数据)的。Model 是通过 ModelManager 创建的。每个 Model 只创建一次。因为 Model 相对比较独立而且不需要频繁创建销毁。
public final class ModelManager {

    private final ConcurrentHashMap<Class<? extends IBaseModel>, ? extends IBaseModel> DATA_CACHE;

    private ModelManager() {
        DATA_CACHE = new ConcurrentHashMap<>(8);
    }

    /**
     * @return ModelManager单例实例
     */
    public static ModelManager getInstance() {
        return Holder.INSTANCE;
    }

    private static class Holder {
        private static final ModelManager INSTANCE = new ModelManager();
    }

    /**
     * 创建获取 Model 层实例
     * @param clazz IBaseModel 子类 class
     */
    @SuppressWarnings("unchecked")
    public <M extends IBaseModel> M create(final Class<M> clazz) {

        IBaseModel model = DATA_CACHE.get(clazz);
        if (model != null) {
            return (M) model;
        }

        synchronized (DATA_CACHE) {
            model = DATA_CACHE.get(clazz);
            if (model == null) {
                try {
                    Constructor<M> constructor = clazz.getDeclaredConstructor();
                    constructor.setAccessible(true);
                    model = constructor.newInstance();
                } catch (... e) {
                ....
                }
            }
        }
        return (M) model;
    }

}

更多代码细节可以去 我的GitHub 看下。

3. 使用方式

以在一个 Activity 请求 GitHub 两个接口为例,一个接口请求个人用户信息,一个接口请求公司用户信息,所以例子将模拟两个模块,user 和 orgs 。使用过程中你会发现一个View 可以有多个 Presenter,一个 Activity 可以实现多个 View 接口。其实还不止这些,其实 Presenter 也可以用有多个 Model。
完整例子

图片发自简书App
  • 目录结构
    XxContract 为模板创建生成,可以在 Setting > Editor > File and Code Templates > Files 设置。我的模板如下:
package ${PACKAGE_NAME};
#parse("File Header.java")
public interface ${NAME}{
    interface View extends IBaseView{
    }
    interface Presenter{   
    }
}
目录结构
  • 在合适的位置初始化 HttpManager
public class SimpleApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        HttpManager.getInstance()
                .setBaseUrl("https://api.github.com/")
                .setDebug(BuildConfig.DEBUG)
                .setOkHttpClient(HttpManager.getInstance().createDefaultClient())
                .setRetrofit(HttpManager.getInstance().createRetrofit());
    }

}
  • MainActivity: 一般主页可能会有多个 Presenter,这里有两个 Presenter
public class MainActivity extends BaseMvpActivity<UserPresenter> implements UserContract.View, OrgContract.View {
      ...
    //公司信息Presenter(次Presenter)
    private OrgPresenter mOrgPresenter;

    private ProgressDialog mLoading;

    @Override
    protected int getLayoutId() {
        return R.layout.activity_main;
    }

    @Override
    protected void initLoading() {
        mLoading = new ProgressDialog(this);
    }

    @Override
    protected UserPresenter getPresenter() {
        mOrgPresenter = new OrgPresenter();
        addToPresenters(mOrgPresenter);
        return new UserPresenter();
    }

    @Override
    protected void initView() {
       ...
    }

    @Override
    protected void initListener() {

        //点击获取个人信息
        btnClick.setOnClickListener(new android.view.View.OnClickListener() {
            @Override
            public void onClick(android.view.View v) {
                mPresenter.getUser("togallop");
            }
        });

        //点击获取用户信息
        btnClick2.setOnClickListener(new android.view.View.OnClickListener() {
            @Override
            public void onClick(android.view.View v) {
                mOrgPresenter.getOrg("google");
            }
        });
    }

    @Override
    public void showUser(String msg) {
        tvMsg.setText(msg);
    }

    @Override
    public void showOrg(String org) {
        tvMsg.setText(org);
    }

    @Override
    public void showLoading(String msg) {
        mLoading.setMessage(msg);
        if (!mLoading.isShowing()) {
            mLoading.show();
        }
    }

    @Override
    public void showLoading() {
        if (!mLoading.isShowing()) {
            mLoading.show();
        }
    }

    @Override
    public void showMsg(String msg) {
        toastS(msg);
    }

    @Override
    public void hideLoading() {
        if (mLoading.isShowing()) {
            mLoading.dismiss();
        }
    }
}

  • UserPresenter:联结 UserContract.ViewUserModel,在 Presenter 里,addDisposable(disposable) 主要作用是用来防止内存泄漏的,如果网络请求还没结束,页面已经关闭,则可能造成内存泄漏,通过 addDisposable(disposable) 管理,可以切断 P 和 V 的关联,从而防止内存泄漏,也可以取消网络请求。
public class UserPresenter extends BasePresenter<UserContract.View> implements UserContract.Presenter {

    private UserModel mModel;

    public UserPresenter() {
        mModel = getModel(UserModel.class);
    }

    @Override
    public void getUser(String userName) {

        //做一些判断
        if (TextUtils.isEmpty(userName)) {
            getView().showMsg("用户名不能为空");
            return;
        }

        //显示loading
        getView().showLoading("正在加载...");
        Disposable disposable = mModel.getUser(userName, new HttpResultObserver<String>() {
            @Override
            protected void onResult(String s) {
                //结果回调显示
                getView().showUser(s);
                getView().hideLoading();
            }

            @Override
            protected void onFailure(Throwable e) {
                //获取数据是失败回调处理
                getView().showMsg(e.getMessage());
                getView().hideLoading();
            }
        });
        addDisposable(disposable);
    }
}
  • UserMode: 就是一个用户相关的模块,和用户相关的请求都可以写在这里
public class UserModel extends BaseModel {

    public Disposable getUser(String userName, HttpResultObserver<String> observer) {
        return getApiService().getUser(userName).subscribeWith(observer);
    }

}
  • ApiService: 接口管理
public interface ApiService {

    @GET("users/{username}")
    Single<String> getUser(@Path("username") String userName);

    @GET("orgs/{org}")
    Single<String> getOrg(@Path("org") String org);

}

对于有后台有固定返回格式的的数据,也可以统一处理。比如返回结果类似这样的:

{
    code:200
    msg:"success"
    data:{}
}

那么可以这么处理,创建回调泛型类

public abstract class HttpResultObserver2<T> extends HttpResultObserver<Result<T>> {

    @Override
    public void onSuccess(Result<T> t) {
        switch (t.code) {
            case 200:
                onSuccess(t.data);
                break;
            default: {
                HttpResultException e = new HttpResultException(t.code, t.msg);
                toast(e.getMsg());
                onFailure(HttpError.RESULT_ERROR, e);
                e.printStackTrace();
                break;
            }
        }
    }

    @Override
    public void onError(Throwable t) {
        onFailure(error, (Exception) t);
    }

    /**
     * 请求成功回调
     *
     * @param result 回调数据
     */
    public abstract void onResult(T result);

    /**
     * 请求失败回调
     *
     * @param error,自定义异常
     * @param e     失败异常信息
     */
    public abstract void onFailure(HttpError error, @NonNull Exception e);

}

APIService:Single 泛型类型为 <Result<xxx>>

Single<Result<String>> uploadImage(...)

使用方式和上面介绍的一样。

总结

MVP 架构是死的,具体实现是活的;任何离开具体业务的代码都是不现实的,我的这部分代码只是一个参考,可以根据具体需求在我的这份代码上扩展,实现自己的需求。举些例子:要统一处理异常信息,可以继承 HttpResultObserver 统一处理异常;处理内存泄漏也可以有很多方式,RxLifecycle/Reference/LifeCycle 等;不一定要用 Single,用 Flowable/Maybe 等都行,看具体需要,网络请求是一次性的,所以我用 Single,处理也方便。总而言之,最好的不一定适合你,适合你的才是最好的。

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

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,780评论 25 707
  • 用两张图告诉你,为什么你的 App 会卡顿? - Android - 掘金 Cover 有什么料? 从这篇文章中你...
    hw1212阅读 12,699评论 2 59
  • 转载至://www.greatytc.com/p/9a6845b26856 “Android MVP 详解...
    SnowDragonYY阅读 10,320评论 5 241
  • 作者:李旺成 时间:2016年4月3日 “Android MVP 详解(下)”已经发布,欢迎大家提建议。 MVP ...
    diygreen阅读 128,841评论 86 1,321
  • 也许平时里表现最乐观的人,受到的伤害也是最深的。 我自己是一个很乐观的人,甚至在许多同学和朋友的心里我可能...
    WIFIgawaine阅读 162评论 0 0