通过简单案例来说明MVP的使用,retrofit2+rxjava+mvp
Switch branches/tags
Nothing to show
Clone or download
Latest commit c0742c2 Mar 18, 2017
Permalink
Failed to load latest commit information.
.idea 移除IPresenter类 Mar 18, 2017
app 移除IPresenter类 Mar 18, 2017
gradle/wrapper 提交 Feb 8, 2017
.gitignore 提交 Feb 8, 2017
README.md 移除IPresenter类 Mar 18, 2017
build.gradle 提交 Feb 8, 2017
gradle.properties 提交 Feb 8, 2017
gradlew 提交 Feb 8, 2017
gradlew.bat 提交 Feb 8, 2017
settings.gradle 提交 Feb 8, 2017

README.md

jjdxm_demomvp

通过简单案例来说明MVP的使用,retrofit2+rxjava+mvp

##前言 ###什么是MVP? MVP模式是一种架构模式,也是一种经典的界面模式。MVP中的M代表Model, V是View, P是Presenter。

Model 一部分是处理业务逻辑,一部分是提供View显示的数据。 View 代表的是一个接口,一个将UI界面提炼而抽象出来的接口。 Presenter Model和View之间的桥梁 ###MVP在Android项目中的其中一种体现方式 经过查阅网上一些MVP的文章之后,有部分案例在presenter中实现具体的逻辑或者把Model单纯的看作是具体的Bean,个人觉得是不太准确的,MVX(MVC、MVP和MVVM)中,M的职责都应该包含两部分业务逻辑和提供View显示的数据,而X的部分则是为了实现UI界面和业务逻辑解耦的桥梁,在Android项目中使用MVP架构模式,以下这两种架构方式是我比较能接受和认可的。

按照模块分包

|----包名
|	|----base
|	|		BaseActivity		Activity基类
|	|		BaseMVPActivity		MVP Activity基类
|	|		BaseModel			Model基类
|	|		BaseFragment		Fragment基类
|	|		IBaseDelegate		简化Presenter在Activity的实现
|	|		IBasePresenter		Presenter基类
|	|		IBaseView			View基类
|	|----模块名1
|	|	|----model				业务逻辑和bean
|	|	|		xxxModel
|	|	|		xxxBean
|	|	|----presenter			连接View和Model的桥梁
|	|	|		xxxPresenter
|	|	|----ui					UI界面相关的类
|	|	|		xxxActivity
|	|	|		xxxFragment
|	|	|----view				UI界面提炼出来的接口
|	|	|		xxxView
|	|----模块名2

按照功能分包

|----包名
|	|----activity				具体Activity
|	|		xxxActivity
|	|----adapter					具体Adapter
|	|		xxxAdapter
|	|----base
|	|		BaseActivity		Activity基类
|	|		BaseMVPActivity		MVP Activity基类
|	|		BaseModel			Model基类
|	|		BaseFragment		Fragment基类
|	|		IBaseDelegate		简化Presenter在Activity的实现
|	|		IBasePresenter		Presenter基类
|	|		IBaseView			View基类
|	|----fragment				具体Fragment
|	|		xxxFragment
|	|----hodler					具体Holder
|	|		xxxHodler
|	|----model					业务逻辑和bean
|	|		xxxModel
|	|		xxxBean
|	|----presenter				连接View和Model的桥梁
|	|		xxxPresenter
|	|----view					UI界面提炼出来的接口
|	|		xxxView
|	|----widget

##前期准备 这里使用聚合数据提供的免费API来实现两个具体的功能历史上的今天和笑话大全,注册并实名为聚合数据的用户后,生成属于自己的用户key即可。

##快速开始 ###step1 添加所需的依赖和权限

新建一个项目,在根目录的build.gradle的dependencies节点中添加,用于注解

    classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'

如图

主程序app module中build.gradle的第二行添加,用于注解

apply plugin: 'com.neenbedankt.android-apt'

dependencies节点中添加

compile 'com.android.support:appcompat-v7:24.2.1'
compile 'com.android.support:design:24.2.1'
//布局注解
apt 'com.jakewharton:butterknife-compiler:8.0.1'
compile 'com.jakewharton:butterknife:8.0.1'
//响应式编程
compile 'io.reactivex:rxandroid:1.1.0'
compile 'io.reactivex:rxjava:1.1.0'
//联网类库
compile 'com.squareup.retrofit2:retrofit:2.1.0'
compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0'
compile 'com.squareup.retrofit2:converter-scalars:2.1.0'
compile 'com.dou361.retrofit2:jjdxm-retrofit-converter-fastjson:1.0.0'
compile 'com.squareup.okhttp3:okhttp:3.3.0'

//自定义view
compile 'com.dou361.customui:jjdxm-customui:1.0.9'
//recyclerview基类
compile('com.dou361.recyclerview:jjdxm-recyclerview:1.0.2') {
    exclude group: 'com.android.support', module: 'design'
}

如图

清单文件AndroidManifest.xml中,添加权限

<uses-permission android:name="android.permission.INTERNET"/>

###step2 先写好两个网络请求方法,Observable searchHistory(String month, String day)和Observable loadJoke(String page)分别是查询历史今天方法和加载笑话列表

网络接口请求服务类

public interface IApiService {

    /** 查询历史的今天 */
    @GET("/japi/toh")
    Observable<RepoHistory> searchHistory(@QueryMap Map<String, String> map);

    /** 加载笑话列表 */
    @GET("/joke/content/list.from")
    Observable<RepoJoke> loadJoke(@QueryMap Map<String, String> map);
}

网络接口请求基类

public class ApiBase {

    /**历史上的今天 http://api.juheapi.com/japi/toh?key=7ac7e02ff7f1f8f1ccdc2f9e5dddb6be&v=1
     * .0&month=11&day=1*/
    /** 笑话大全 http://japi.juhe.cn/joke/content/list
     * .from?key=d796a03545bddee0b56d913111f5f199&page=2&pagesize=10&sort=asc&time=1418745237 */
    protected static IApiService getService() {
        return getService(null);
    }

    protected static IApiService getService(String ip) {
        return getService(ip, 0, 0);
    }

    protected static IApiService getService(String ip, long readTime, long connectTime) {
        OkHttpClient client = new OkHttpClient.Builder()
                .readTimeout(readTime <= 0 ? 30 : readTime, TimeUnit.SECONDS)
                .connectTimeout(connectTime <= 0 ? 30 : connectTime, TimeUnit.SECONDS)
                .build();
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(ip == null ? "http://api.juheapi.com" : ip)
                .client(client)
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .addConverterFactory(ScalarsConverterFactory.create())
                .addConverterFactory(FastJsonConverterFactory.create())
                .build();
        return retrofit.create(IApiService.class);
    }
}

网络接口请求工具类

public class ApiUtils extends ApiBase {

    public static Observable<RepoHistory> searchHistory(String month, String day) {
        /**key=7ac7e02ff7f1f8f1ccdc2f9e5dddb6be&v=1.0&month=11&day=1*/
        Map<String, String> map = new HashMap<>();
        map.put("key", "7ac7e02ff7f1f8f1ccdc2f9e5dddb6be");
        map.put("v", "1.0");
        map.put("month", month);
        map.put("day", day);
        return getService().searchHistory(map);
    }

    public static Observable<RepoJoke> loadJoke(String page) {
        /**key=d796a03545bddee0b56d913111f5f199&page=2&pagesize=10&sort=asc&time=1418745237*/
        Map<String, String> map = new HashMap<>();
        map.put("key", "d796a03545bddee0b56d913111f5f199");
        map.put("sort", "asc");
        map.put("time", "1418745237");
        map.put("page", page);
        map.put("pagesize", "10");
        return getService().loadJoke(map);
    }
}

如图 ###step3 开始架构MVP模式使用到的基类,这里没有使用网上所说的契约类xxxContract把View和Presenter写在一个类中维护,而是分开出来,主要看个人喜好,如图

UI界面抽象出来的接口

public interface IBaseView {

    /**
     * 显示加载
     */
    void showLoading();

    /**
     * 完成加载
     */
    void dismiss();
}

业务逻辑实现的基类

public abstract class BaseModel<SubP> {

    protected SubP mPresenter;

    public BaseModel(SubP presenter) {
        this.mPresenter = presenter;
    }

}

连接Model和View的桥梁的基类

public interface IBasePresenter<V extends IBaseView> {

    /**绑定接口*/
    void attachView(V view);

    /**释放接口*/
    void detachView();

}

persenter和activity绑定

public interface IBaseDelegate<V extends IBaseView, P extends IBasePresenter<V>> {

    /**初始化presenter*/
    @NonNull
    P createPresenter();

    /**获取presenter*/
    @NonNull
    P getPresenter();

}

最后是Activity的基类

public abstract class BaseActivity extends
        AppCompatActivity {

    protected void startActivity(Class<?> clz) {
        Intent intent = new Intent(this, clz);
        startActivity(intent);
    }
}

MVP的Activity基类

public abstract class BaseMVPActivity<V extends IBaseView, P extends IBasePresenter<V>> extends
        BaseActivity implements IBaseDelegate<V, P> {

    protected P mPresenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mPresenter = createPresenter();
    }

    @NonNull
    @Override
    public P getPresenter() {
        return mPresenter;
    }

    @Override
    protected void onDestroy() {
        mPresenter.detachView();
        super.onDestroy();
    }
}

###step4 针对模块用MVP模式去架构大概有一下4个步骤

步骤1:UI实现View方法,引用Presenter
步骤2:Presenter调用Model,走Model具体逻辑
步骤3:Model逻辑实现,回调Presenter方法
步骤4:Presenter回调View,即回到UI,回调View方法

###step5 具体模块功能的实现,历史的今天模块,先创建一个HistoryActivity继承BaseMVPActivity,新建IHistoryView并实现。

####1.View的接口的抽取 抽象出来三个功能和父类IBaseView的两个方法,分别是显示加载好的数据,显示空白数据提示,检测数据提示,显示加载中提示,隐藏加载中提示。

public interface IHistoryView extends IBaseView {

    /**显示数据*/
    void showData(List<HistoryBean> list);

    /**无数据*/
    void showEmpty();

    /**检测数据*/
    void showMessage(String msg);
}

####2.Model的实现 具体的逻辑实现,这里只有一个方法就是查询历史今天

public class HistoryModel extends BaseModel<HistoryPresenter> {

    public HistoryModel(HistoryPresenter iPresenter) {
        super(iPresenter);
    }

    public void searchHistory(String month, String day) {
        if (TextUtils.isEmpty(month)) {
            mIPresenter.showMessage("月份不能为空");
            return;
        }
        int iMonth = Integer.valueOf(month).intValue();
        if (iMonth <= 0 || iMonth > 12) {
            mIPresenter.showMessage("只能输入1-12的月份");
            return;
        }
        if (TextUtils.isEmpty(day)) {
            mIPresenter.showMessage("天不能为空");
            return;
        }
        int iDay = Integer.valueOf(day).intValue();
        if (iDay <= 0 || iDay > 31) {
            mIPresenter.showMessage("只能输入1-31的天");
            return;
        }
        ApiUtils.searchHistory(month, day)
                .observeOn(AndroidSchedulers.mainThread())
                .subscribeOn(Schedulers.newThread())
                .subscribe(new Action1<RepoHistory>() {
                    @Override
                    public void call(RepoHistory repoHistory) {
                        if (repoHistory == null || repoHistory.getResult() == null
                                || repoHistory.getResult().size() <= 0) {
                            mIPresenter.showEmpty();
                        } else {
                            mIPresenter.showData(repoHistory.getResult());
                        }
                    }
                });
    }
}

####3.Presenter桥梁的实现

public class HistoryPresenter implements IBasePresenter<IHistoryView> {

    private IHistoryView mView;
    private HistoryModel mModel;

    public HistoryPresenter(IHistoryView view) {
        attachView(view);
        mModel = new HistoryModel(this);
    }

    @Override
    public void attachView(IHistoryView view) {
        this.mView = view;
    }

    @Override
    public void detachView() {
        this.mView = null;
    }

    public void showData(List<HistoryBean> list) {
        mView.dismiss();
        mView.showData(list);
    }

    public void showEmpty() {
        mView.dismiss();
        mView.showEmpty();
    }

    public void showMessage(String msg) {
        mView.showMessage(msg);
    }

    public void searchHistory(String month, String day) {
        mView.showLoading();
        mModel.searchHistory(month, day);
    }
}

####4.最后在HistoryActivity里面去建立连接 最后创建的类架构图如下:

编译运行效果图如下

同理笑话大全也一样的创建对应的文件,最后运行如下