代码已上传到Github,由于接口都是模拟没法进行测试,明白大概的逻辑就好了!java
欢迎浏览个人博客——https://pushy.sitereact
若是熟悉MVP模式架构的话,对下图各组件的调用关系应该不陌生:android
和其余传统模式相比,MVP有如下的几个特色:git
那么这三者的分工分别是什么呢?github
Model:处理业务逻辑,工做职责:加载远程网络或者本地的数据。
View:视图,工做职责:控制显示数据的方式。
Presenter:中间者,工做职责:绑定Model、View。json
咱们仿造GitHub中谷歌官方例子中的安卓架构蓝图来搭建Android中MVP架构模式的结构:api
下面,咱们详细来讲明MVP中各个组件的含义和调用方式:缓存
你可能会好奇,MVP中不是只有三个组件吗?为何多了一个!没错,多出来的这个出现正是LoginContract
,在MVP模式中,Presenter与View须要提供各自的接口供其余组件调用。一般,咱们把Presenter和View的接口都定义在*Contract
类中:服务器
public class LoginContract { interface View { void setLoading(boolean v); // 显示加载中 } interface Presenter { void login(); // 登陆逻辑调用 } }
在Android中,Model层主要的职责是用来加载数据。在这里,咱们一般请求远程网络的数据或者加载本地缓存的数据:网络
public class LoginModel { public void login() { /* 请求网络数据 */ } }
MVP中,Presenter主要用于绑定View和Model,并组织调用不一样层提供的接口。因此在Presenter层必须持有View和Model对象。
因此咱们让Presenter实现Contract.Presenter
的接口,并提供构造函数注入View的实现类和Model对象:
public class LoginPresenter implements LoginContract.Presenter { private LoginContract.View view; private LoginModel model; public LoginPresenter(LoginContract.View view, LoginModel model) { this.view = view; this.model = model; } @Override public void login() { view.setLoading(true); // 显示加载中 model.login(); // 向服务器请求登陆 } }
在Android中,Activity每每当成是View的实现类。所以咱们让LoginActivity实现Contract.View
接口:
public class LoginActivity extends AppCompatActivity implements LoginContract.View { private LoginPresenter presenter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); presenter = new LoginPresenter(this, new LoginModel()); } @Override public void setLoading(boolean v) { /* 显示加载中的UI操做 */ } }
而且,咱们在onCreate
方法中实例化出LoginPresenter
对象,并注入View的实现类(即当前这个Activity)和Model对象。
这样,当用户触发按钮的点击事件时,咱们就能够调用Presenter提供的接口来向远程服务器进行登陆的请求:
@Override public void onClick(View v) { presenter.login(name, password); }
下面,咱们来正式地讲解Retrofit + RxJava的封装过程,并将上面的MVP中各层具体逻辑替换。
首先,咱们先在app/build.gradle
中添加retrofit2
和rxJava
库的相关依赖:
// rxJava相关依赖 implementation 'io.reactivex.rxjava2:rxjava:2.2.2' implementation 'io.reactivex.rxjava2:rxandroid:2.1.0' // retrofit2相关依赖和插件 implementation 'com.squareup.retrofit2:retrofit:2.4.0' implementation 'com.squareup.retrofit2:converter-gson:2.4.0' implementation 'com.squareup.retrofit2:adapter-rxjava2:2.4.0'
首先,咱们定义RetrofitServiceManager
统一辈子成接口实例的管理类:
public class RetrofitServiceManager { private static final String BASE_URL = "https://api.example.com"; private Retrofit mRetrofit; public RetrofitServiceManager() { // 初始化OkHttpClient对象,并配置相关的属性 OkHttpClient okHttpClient = new OkHttpClient.Builder() .connectTimeout(5, TimeUnit.SECONDS) // 设置超时时间 .build(); mRetrofit = new Retrofit.Builder() .baseUrl(BASE_URL) .client(okHttpClient) .addConverterFactory(GsonConverterFactory.create()) // 支持Gson自动解析JSON .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) // 支持RxJava .build(); } private static class SingletonHolder{ private static final RetrofitServiceManager INSTANCE = new RetrofitServiceManager(); } public static RetrofitServiceManager getInstance() { // 返回一个单例对象 return SingletonHolder.INSTANCE; } public <T> T create(Class<T> service) { // 返回Retrofit建立的接口代理类 return mRetrofit.create(service); } }
下一步,咱们修改LoginModel里的具体请求逻辑。在默认构造函数中经过RetrofitServiceManager
建立LoginModelService
的代理对象,并定义公共的login
方法让Presenter
来调用:
public class LoginModel extends BaseModel { private LoginModelService service; public LoginModel() { this.service = RetrofitServiceManager.getInstance().create(LoginModelService.class); } public Observable<BaseResponse<String>> login(LoginBody body) { // 调用父类BaseModel的observe方法进行请求 return observe(service.login(body)); } interface LoginModelService { @POST("/login") Observable<BaseResponse<String>> login(@Body LoginBody body); } }
另外,咱们让LoginModel
继承了BaseModel
。在该类中,作了线程切换的操做,所以在请求时只须要简单地嗲用父类的observe
便可:
public class BaseModel { protected <T> Observable<T> observe(Observable<T> observable){ return observable .subscribeOn(Schedulers.io()) .unsubscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()); } }
如今,在LoginPresenter
的login
方法中,咱们就能够同时操做view
和model
来控制登陆的UI和请求的逻辑了:
@Override public void login(String name, String password) { LoginBody body = new LoginBody(); body.name = name; body.password = password; view.setLoading(true); model.login(body) .subscribe(response -> { view.callback(true); // 成功回调 view.setLoading(false); }, throwable -> { view.callback(false); // 失败回调 view.setLoading(false); }); }
能够看到,Presenter对于不一样的请求成功或失败的接口调用View提供的接口,展现给用户登陆或者失败的结果。所以咱们只须要在LoginActivity
中定义不一样结果的提示便可:
@Override public void callback(boolean v) { if (v) { Toast.makeText(this, "登陆成功", Toast.LENGTH_LONG).show(); } else { Toast.makeText(this, "登陆失败", Toast.LENGTH_LONG).show(); } }
最后,咱们只须要完成如下登陆的UI视图和调用Presenter提供接口的简单逻辑,就能够实现完整的登陆逻辑了:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <EditText android:id="@+id/et_name" android:layout_width="match_parent" android:layout_height="wrap_content" /> <EditText android:id="@+id/et_password" android:layout_width="match_parent" android:layout_height="wrap_content" /> <Button android:id="@+id/btn_submit" android:layout_width="match_parent" android:layout_height="wrap_content" android:textAllCaps="false" android:text="登陆"/> </LinearLayout>
在登陆按钮的点击事件逻辑中调用Presenter的login
方法请求登陆:
@Override public void onClick(View v) { switch (v.getId()) { case R.id.btn_submit: presenter .login(etName.getText().toString(), etPassword.getText().toString()); break; } }
假设服务器返回的基本数据为:
// 成功返回的数据 { "code":200, "data": "Hello World", "message": "" } // 失败返回的数据 { "code":401, "data": "", "message": "Unauthorized" }
咱们针对这种返回数据,BaseResponse
提供一个isSuccess
方法来判断的结果是否有错:
public class BaseResponse<T> { public int code; public String message; public T data; /* 是否成功 */ public boolean isSuccess() { return code == 200; } }
而后修改LoginModel
的login
方法,经过Map的操做符来处理错误抛出异常,并进一步封装返回的数据:
public Observable<BaseResponse<String>> login(LoginBody body) { return observe(service.login(body)) .map(new PayLoad<>()) }
在PayLoad
类中,判断请求数据是否成功,若是失败,则抛出一个错误,不然返回成功的数据:
public class PayLoad<T> implements Function<BaseResponse<T>, BaseResponse<T>> { private static final String TAG = "PayLoad"; @Override public BaseResponse<T> apply(BaseResponse<T> response) throws Exception { if (!response.isSuccess()) { /* 服务器端返回errno失败 */ throw new ServerException(response.code, response.message); } /* 成功获取 */ return response; } }
在Presenter
中的订阅回调方法中就能够捕捉到ServerException
异常:
model.login(body) .subscribe(response -> { view.callback(true); // 成功回调 view.setLoading(false); }, throwable -> { ServerException exception = (ServerException) throwable; view.errorCallback(exception); view.setLoading(false); });
同时,在Activity中也能够根据服务端返回的不一样状态码来向用户展现不一样的错误结果:
@Override public void errorCallback(ServerException e) { switch (e.code) { case ServerError.NO_USER: Toast.makeText(this, "没有该用户", Toast.LENGTH_LONG).show(); break; case ServerError.UNAUTHORIZED: Toast.makeText(this, "密码错误", Toast.LENGTH_LONG).show(); break; } }