Android MVP 模式与使用

参考资料:####

MVP模式也使用了一段时间,没有特定成熟的框架,各有各的实现方式;之前不太清楚为什么要这么设计,后来项目中,增加了新的模块,完全采用MVP来实现,刚开始很痛苦,分离了一大堆接口,直到有一次,某个业务方,要求替换页面,由于采用了MVP,发现只需修改V层,然后根据约定的接口方式,一个个实现,即可,M与P层,不动;这才有点感觉了。
通过查看崩溃日志,发现采用MVP设计的功能,崩溃率很低;

目前总结的2个好处:

  1. 降低耦合度,让各层各司其事,给测试与开发带来好处;移动端开发,变化最大的可能就是界面,如果在界面中,柔和了逻辑与数据处理,导致维护成本急剧增加;
  2. 降低出错率;

目前遇到的问题:

  1. 大量接口的引入,对开发的前期接口设计能力有一定要求;
  2. view层,界面是会回收的,如何恢复,目前没找到很好的方案,实际操作中,我还是将这部分,让 activity or fragment 自己处理;
  3. 关于对应一个 页面 V是否可使用多个P问题;在实际中,我们的项目都是一个V对应一个P,但是P可能会对应多个M,来完成功能;
  4. 如果在V层中,需要取消某一个网络请求(V不销毁),这种情况,目前我们项目没有遇到过,但如有此情况,就智者见智;

demo代码:##

https://github.com/zhaoyubetter/mvpDemo

1. MVP的分层####

  • M 数据层:所有与数据操作相关的都放入这里,比如:网络请求,数据库操作等;
  • P 业务逻辑层:与业务相关的操作,放入这里,P分别引用了M与V层;
    M不直接与V打交道,区别于MVC;
    http://www.jianshu.com/p/ee8125b1429d
  • V 视图层:用来展示页面,如:Activity,Fragment等;

2.MVP调用简单类图

MVP

登录图例:


登录图例

3.show me the code MVP框架

  1. View层公共接口:

/**
 * MVP中View层抽象接口
 */
public interface IMVPView {
    /**
     * 加载填充界面数据时显示加载进度
     * @param msg 提示文字。传null时显示默认
     */
    void showLoading(String msg);

    /**
     * 失败
     * @param msg 提示文字。传null时显示默认
     */
    void showError(String msg);
    void removeLoading();
    /**
     * 获取Context对象
     */
    Context getContext();
}

2.Model(Repo)层公共接口:

/**
 * mvp中M层抽象接口,这里采用 Repo 仓库,参考了google mvp demo
 */
public interface IMVPRepo {
    /**
     * 销毁方法
     * 1,释放P引用<br/>
     * 2,释放具体逻辑上的数据
     */
    void onDestroy();
}

3.Presenter层公共接口:

/**
 * MVP层中P层顶层抽象
 */
public interface IMVPPresenter {
    /**
     * 加载填充界面需要的数据
     */
    void onCreate();

    /**
     * <ol>
     *     <li>释放View</li>
     *     <li>释放Model</li>
     *     <li>释放其他资源</li>
     * </ol>
     */
    void onDestroy();
}

4.Presenter抽象类的引入,在抽象层,没有强制要求需要传递 M层,
是这样考虑的,

在传统的 JavaEE开发中,Dao层Service层是分开的,
一个Service层可以引入多个Dao层,来实现业务;
在Android中,我们给P层模拟类似于EE开发中的Service层与Dao层的结合,避免层次太多,也就是说:我们可在P层引入多个M,来完成业务;
另: P层只引用一个V,一般情况下,我们是一个页面对应一个具体的业务,多V对应一P,还没用接触过
总结: 可以多M对应一P对应一V;

/**
 * MVP层中P层抽象类
 */
public abstract class AbsMVPPresenter<T extends IMVPView> implements IMVPPresenter {

    protected WeakReference<T> mViewRef;

    /**
     * 必要的构造方法
     * 可以在构造方法中创建对应的Model
     *
     * @param view : 绑定对应的View
     */
    public AbsMVPPresenter(T view) {
        this.mViewRef = new WeakReference<>(view);
    }

    /**
     * 界面恢复时,调用该方法通过M层恢复数据。不需要的界面不要重写
     * 此方法为扩展,界面恢复时的处理,目前暂没想好放在哪里,这里留一个入口
     *
     * @param bundle 恢复数据时需要的参数
     */
    public void restore(Bundle bundle) {
        //empty
    }

    /**
     * view 是否存活
     *
     * @return
     */
    protected boolean isAlive() {
        return mViewRef != null && mViewRef.get() != null && mViewRef.get().getContext() != null;
    }
  
    @Override
    public void onDestroy() {
        if (null != mViewRef) {
            mViewRef.clear();
            mViewRef = null;
        }
    }
}

4.MVP 框架的实战练习

需求:访问网络,将结果显示在界面上,现设计约束接口如下:

4. 1 新建Contract契约接口:######

每个业务建立自己的契约接口,如下:

public interface SimpleContract {
    interface View extends IMVPView {
        void showContent(String data);
    }

    interface Presenter extends IMVPPresenter {
        void doGetData(String url);
    }

    interface Repo extends IMVPRepo {
        void getData(String url, final LoadDataCallback<String> callback);
    }
}
4. 2 先从M层下手:######

先将M层实现,这个时候,我们可以针对M来编写测试脚本,测试M层

public class SimpleRepo implements SimpleContract.Repo {

    private AbsRequest mHttpRequest;

    @Override
    public void getData(final String url, final LoadDataCallback<String> callback) {
        mHttpRequest = new OkHttpRequest.Builder().url(url).callback(new AbsRequestCallBack<String>() {
            @Override
            public void onSuccess(Response<String> response) {
                super.onSuccess(response);
                callback.onDataLoaded(response.responseBody);
            }

            @Override
            public void onFailure(Throwable e) {
                super.onFailure(e);
                callback.onDataNotAvailable(e.toString(), 0);
            }
        }).build();
        mHttpRequest.request();
    }

    @Override
    public void onDestroy() {
        // 销毁网络请求
        if (mHttpRequest != null) {
            mHttpRequest.cancel();
        }
    }
}
4. 2 编写P层:#####

P层比较关键,M与V如何进行交互,就在这里了

public class SimplePresenter extends AbsMVPPresenter<SimpleContract.View> implements SimpleContract.Presenter {
    /**
     * 接口
     */
    SimpleContract.Repo mRepo;

    /**
     * 也可以在 V 层中传入M,这里没这样做,各有各的好处
     * @param view
     */
    public SimplePresenter(SimpleContract.View view) {
        super(view);
        mRepo = new SimpleRepo();
    }

    @Override
    public void onCreate() {
    }

    @Override
    public void doGetData(String url) {
        if (!isAlive()) return;
        final SimpleContract.View view = mViewRef.get();

        mRepo.getData(url, new LoadDataCallback<String>() {
            @Override
            public void onDataLoaded(String data) {
                if (isAlive()) {
                    view.showContent(data);
                }
            }

            @Override
            public void onDataNotAvailable(String msg, int code) {
                if (isAlive()) {
                    view.showError(msg);
                }
            }
        });
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        if (mRepo != null) {
            mRepo.onDestroy();
        }
    }
}
4. 3 编写V层:######

V层,再这里简单实现契约接口的View接口:

public class SimpleActivity extends AppCompatActivity implements SimpleContract.View {
    private ProgressDialog mDialog;
    private TextView content;
    private SimpleContract.Presenter mPresenter;
    private EditText et;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_simple);
        content = (TextView) findViewById(R.id.content);
        et = (EditText) findViewById(R.id.et);

        // 初始化P
        mPresenter = new SimplePresenter(this);
        findViewById(R.id.request).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                final String url = et.getText().toString();
                content.setText("");
                mPresenter.doGetData(url);
            }
        });
    }

    @Override
    public void showLoading(String msg) {
        if (mDialog == null) {
            mDialog = new ProgressDialog(this);
        }
        mDialog.setMessage(msg);
        mDialog.show();
    }

    @Override
    public void showError(String msg) {
        Toast.makeText(getContext(), msg, Toast.LENGTH_SHORT).show();
    }

    @Override
    public void removeLoading() {
        if (mDialog != null) {
            mDialog.dismiss();
        }
    }

    @Override
    public Context getContext() {
        return getApplicationContext();
    }

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

推荐阅读更多精彩内容