本章节讲述MVP模式实现Okhttp框架
MVP作为MVC的演化,解决了MVC不少的缺点,对于Android来说,MVP的model层相对于MVC是一样的,而activity和fragment不再是controller层,而是纯粹的view层,所有关于用户事件的转发全部交由presenter层处理。下面还是让我们看图
从图中就可以看出,最明显的差别就是view层和model层不再相互可知,完全的解耦,取而代之的presenter层充当了桥梁的作用,用于操作view层发出的事件传递到presenter层中,presenter层去操作model层,并且将数据返回给view层,整个过程中view层和model层完全没有联系。看到这里大家可能会问,虽然view层和model层解耦了,但是view层和presenter层不是耦合在一起了吗?其实不是的,对于view层和presenter层的通信,我们是可以通过接口实现的。
具体的意思就是说我们的activity,fragment可以去实现实现定义好的接口,而在对应的presenter中通过接口调用方法。不仅如此,我们还可以编写测试用的View,模拟用户的各种操作,从而实现对Presenter的测试。这就解决了MVC模式中测试,维护难的问题。
当然,其实最好的方式是使用fragment作为view层,而activity则是用于创建view层(fragment)和presenter层(presenter)的一个控制器。
和MVC最大的不同,MVP把activity作为了view层,通过代码也可以看到,整个activity没有任何和model层相关的逻辑代码,取而代之的是把代码放到了presenter层中,presenter获取了model层的数据之后,通过接口的形式将view层需要的数据返回给它就OK了。
这样的好处是。首先,activity的代码逻辑减少了,其次,view层和model层完全解耦,具体来说,如果你需要测试一个http请求是否顺利,你不需要写一个activity,只需要写一个java类,实现对应的接口,presenter获取了数据自然会调用相应的方法,相应的,你也可以自己在presenter中mock数据,分发给view层,用来测试布局是否正确。
详解:Android MVC MVP MVVM 模式
二.OKHttp+MVP
项目截图
以登录为例
M层代码 Model
1.常量 constant
设置 OKHttp网络超时时间 各个接口Url等等。
2.OkHttpClient工具类
/** * Created by wjn on 2017/11/22. * OkHttp框架 封装方法 */ public class RxAndroidOkhttp { private OkHttpClient mOkHttpClient = null;//OkHttpClient 对象 private static RxAndroidOkhttp mRxAndroidOkhttp = null;//RxAndroidOkhttp 对象 /** * 构造方法私有化 */ private RxAndroidOkhttp() { //创建okhttp对象 以及连接,读,取超时时间 mOkHttpClient = new OkHttpClient.Builder() .connectTimeout(DataConstant.nettimeout, TimeUnit.SECONDS)//连接时间 .readTimeout(DataConstant.nettimeout, TimeUnit.SECONDS)//读时间 .writeTimeout(DataConstant.nettimeout, TimeUnit.SECONDS)//写时间 .build(); } /** * 获取此单例类对象的方法 */ public static RxAndroidOkhttp getInstance() { if (null == mRxAndroidOkhttp) {//单例对象为空 synchronized (RxAndroidOkhttp.class) { mRxAndroidOkhttp = new RxAndroidOkhttp(); } } return mRxAndroidOkhttp; } /** * get请求方法 */ public Observable<String> get(final String url) { //创建被观察者 Observable<String> observable = Observable.create(new Observable.OnSubscribe<String>() { @Override public void call(Subscriber<? super String> subscriber) { final Subscriber mSubscriber = subscriber; //没有取消订阅的时候 if (!mSubscriber.isUnsubscribed()) { //get请求 Request request = null; String cookie = MyApplication.getOkhttpCookie(); if (!BooleanUtils.isEmpty(cookie)) { request = new Request.Builder() .addHeader("cookie", cookie) .url(url) .get() .build(); } else { request = new Request.Builder() .url(url) .get() .build(); } if (null != mOkHttpClient) { mOkHttpClient.newCall(request).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { //通知订阅者的错误信息 mSubscriber.onError(e); } @Override public void onResponse(Call call, Response response) throws IOException { if (null != response) {//response 不为空 if (response.isSuccessful()) {//response 请求成功 Headers headers = response.headers(); List<String> cookies = headers.values("Set-Cookie"); if (cookies.size() > 0) { String session = cookies.get(0); String result = session.substring(0, session.indexOf(";")); MyApplication.setOkhttpCookie(result); } //通知订阅者的成功信息 mSubscriber.onNext(response.body().string()); } else {//response 请求失败 //通知订阅者的错误信息 IOException IOExceptionx = new IOException(); mSubscriber.onError(IOExceptionx); } } else {//response 为空 //通知订阅者的错误信息 IOException IOExceptionx = new IOException(); mSubscriber.onError(IOExceptionx); } //通知完毕 mSubscriber.onCompleted(); } }); } } } }); return observable; } /** * post请求方法 */ public Observable<String> post(final String url, final Map<String, String> params) { //创建被观察者 Observable<String> observable = Observable.create(new Observable.OnSubscribe<String>() { @Override public void call(Subscriber<? super String> subscriber) { final Subscriber mSubscriber = subscriber; //没有取消订阅的时候 if (!mSubscriber.isUnsubscribed()) { //获取FormBody.Builder 对象 FormBody.Builder builder = new FormBody.Builder(); //遍历params 将其放入到FormBody.Builder 对象 if (params != null && !params.isEmpty()) { for (Map.Entry<String, String> entry : params.entrySet()) { builder.add(entry.getKey(), entry.getValue()); } } //获取RequestBody 对象 RequestBody requestBody = builder.build(); //构建post请求 Request request = null; String cookie = MyApplication.getOkhttpCookie(); if (!BooleanUtils.isEmpty(cookie)) { request = new Request.Builder() .addHeader("cookie", cookie) .url(url) .post(requestBody) .build(); } else { request = new Request.Builder() .url(url) .post(requestBody) .build(); } if (mOkHttpClient != null) { mOkHttpClient.newCall(request).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { //通知订阅者的错误信息 mSubscriber.onError(e); } @Override public void onResponse(Call call, Response response) throws IOException { if (null != response) {//response 不为空 if (response.isSuccessful()) {//response 请求成功 Headers headers = response.headers(); List<String> cookies = headers.values("Set-Cookie"); if (cookies.size() > 0) { String session = cookies.get(0); String result = session.substring(0, session.indexOf(";")); MyApplication.setOkhttpCookie(result); } //通知订阅者的成功信息 mSubscriber.onNext(response.body().string()); } else {//response 请求失败 //通知订阅者的错误信息 IOException IOExceptionx = new IOException(); mSubscriber.onError(IOExceptionx); } } else {//response 为空 //通知订阅者的错误信息 IOException IOExceptionx = new IOException(); mSubscriber.onError(IOExceptionx); } //通知完毕 mSubscriber.onCompleted(); } }); } } } }); return observable; } /** * 自定义onError字符串(欢迎页面) */ public static String getonErrorSplashString(Throwable e) { String result = StringConstant.Splashing3; if (null != e) { Throwable throwable = e.getCause(); if (null != throwable) { if (throwable.equals(SocketTimeoutException.class)) { result = StringConstant.Splashing5; } } String msg = e.getMessage(); if (!BooleanUtils.isEmpty(msg) && msg.contains("timeout")) { result = StringConstant.Splashing5; } } return result; } /** * 自定义onError字符串(其他页面) */ public static String getonErrorOtherString(Throwable e) { String result = StringConstant.errorstate1; if (null != e) { Throwable throwable = e.getCause(); if (null != throwable) { if (throwable.equals(SocketTimeoutException.class)) { result = StringConstant.errorstate4; } } String msg = e.getMessage(); if (!BooleanUtils.isEmpty(msg) && msg.contains("timeout")) { result = StringConstant.errorstate4; } } return result; } }
P层 Presenter
1.基础Presenter
public class BasePresenter { protected RxAndroidOkhttp mRxAndroidOkhttp=null;//RxAndroidOkhttp对象 protected Observable<String> mObservable=null;//get post 方式请求的Observable对象 public BasePresenter(){ mRxAndroidOkhttp=RxAndroidOkhttp.getInstance(); } }
2.登录Presenter
public class LoginPresenter extends BasePresenter{ /** * 构造方法 * */ public LoginPresenter(){ super(); } /** * 响应回调 * */ private OkHttpResponseInterface mOkHttpResponseInterface=null; public void setOnOkHttpResponseListener(OkHttpResponseInterface okHttpResponseInterface){ mOkHttpResponseInterface=okHttpResponseInterface; } /** * 请求数据 * */ public void requestService(final String type,final String posturl,final Map<String, String> params){ if(null!=mRxAndroidOkhttp){ //observable定义被观察者 mObservable=mRxAndroidOkhttp.post(posturl,params); if(null!=mObservable){ //定义观察者 Subscriber<String> mSubscriber=new Subscriber<String>(){ @Override public void onCompleted() { } @Override public void onError(Throwable e) { if(mOkHttpResponseInterface!=null) { mOkHttpResponseInterface.onOkHttpError(type,e); } } @Override public void onNext(String s) { if(mOkHttpResponseInterface!=null) { mOkHttpResponseInterface.onOkHttpNext(type,s); } } }; /** * 订阅者关联被观察者 * Schedulers.io()说明是输入输出的计划任务 * AndroidSchedulers.mainThread()说明订阅者是中ui主线程中执行 * */ mObservable.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(mSubscriber); } } } }
接口
/** * @param :OkHttpResponse接口 * @author :wjn * */ public interface OkHttpResponseInterface { void onOkHttpError(String type,Throwable e); void onOkHttpNext(String type,String s); }
V层 View
1.基础Activity
public abstract class BaseActivity extends AppCompatActivity { /** * onCreate 方法 * */ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(getLayoutId()); setStatusBarMode(); initView(); initPresenter(); } /** * 获取布局id */ public abstract int getLayoutId(); /** * 初始化视图 */ protected abstract void initView(); /** * 初始化视图 */ protected abstract void initPresenter(); /** * 初始化状态栏 * 文字、图标是否为黑色 (false为默认的白色) */ protected abstract boolean isTextDark(); /** * 初始化状态栏 * 本地颜色还是获取的rgb颜色值 */ protected abstract boolean islocalcolor(); /** * 初始化状态栏 * 状态栏颜色 */ protected abstract int colorId(); /** * 初始化状态栏 */ protected void setStatusBarMode(){ //根据状态栏颜色来决定 状态栏背景 用黑色还是白色 true:是否修改状态栏字体颜色 StatusBarUtil.setStatusBarMode(this, isTextDark(), islocalcolor(), colorId()); } }
2.登录Activity
public class MVP_LoginActivity extends BaseActivity implements OkHttpResponseInterface, View.OnClickListener { private TextView textView1; private TextView textView11; private TextView resultTextView; private LoginPresenter loginPresenter=null; /** * 获取布局 * */ @Override public int getLayoutId() { return R.layout.activity_login; } /** * 初始化状态栏 * 文字、图标是否为黑色 (false为默认的白色) */ @Override protected boolean isTextDark() { return true; } /** * 初始化状态栏 * 本地颜色还是获取的rgb颜色值 */ @Override protected boolean islocalcolor() { return false; } /** * 初始化状态栏 * 状态栏颜色 */ @Override protected int colorId() { return R.color.baise; } /** * 初始化View方法 * */ @Override protected void initView() { textView1=findViewById(R.id.activity_login_textview1); textView11=findViewById(R.id.activity_login_textview11); resultTextView=findViewById(R.id.activity_login_textview2); textView1.setOnClickListener(this); textView11.setOnClickListener(this); } /** * 初始化Presenter * */ @Override protected void initPresenter() { loginPresenter=new LoginPresenter(); loginPresenter.setOnOkHttpResponseListener(this); } /** * 各种点击事件的方法 * */ @Override public void onClick(View v) { switch (v.getId()) { case R.id.activity_login_textview1://登录 Post loginMethod(); break; case R.id.activity_login_textview11://翻译 Post fanyiMethod(); break; default: break; } } /** * 自定义 OkHttp onError方法 * */ @Override public void onOkHttpError(String type, Throwable e) { resultTextView.setText(e.getMessage()); } /** * 自定义 OkHttp onNext方法 * */ @Override public void onOkHttpNext(String type, String s) { if("Login".equals(type)){ resultTextView.setText("登录结果显示:\n\n"+s); }else if("fanyi".equals(type)){ resultTextView.setText("翻译结果显示:\n\n"+s); } } /** * 登录方法 * */ private void loginMethod(){ resultTextView.setText("登录数据加载中..."); Map<String, String> params = new HashMap<>(); params.put("userName", "");//用户名 params.put("password", "");//密码 params.put("JPID", "");//JPID 手机型号 params.put("token", "");//登录token loginPresenter.requestService("Login",StringConstant.loginposturl,params); } /** * 翻译方法 * */ private void fanyiMethod(){ resultTextView.setText("翻译数据加载中..."); Map<String, String> params = new HashMap<>(); params.put("keyfrom", ""); params.put("key", ""); params.put("type", ""); params.put("doctype", ""); params.put("version", ""); params.put("q", ""); loginPresenter.requestService("fanyi",StringConstant.fanyiposturl,params); } }
3.登录Activity布局
<?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"> <TextView android:layout_width="match_parent" android:layout_height="70dp" android:background="#FFFFFF" android:gravity="center" android:text="登录" android:textColor="#000000" android:textSize="18sp" android:textStyle="bold" /> <ScrollView android:layout_width="match_parent" android:layout_height="match_parent" android:scrollbars="none"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <TextView android:id="@+id/activity_login_textview1" android:layout_width="match_parent" android:layout_height="70dp" android:layout_margin="10dp" android:background="#3F51B5" android:gravity="center" android:text="登录Post" android:textColor="#FFFFFF" android:textSize="14sp" /> <TextView android:id="@+id/activity_login_textview11" android:layout_width="match_parent" android:layout_height="70dp" android:layout_margin="10dp" android:background="#3F51B5" android:gravity="center" android:text="翻译Post" android:textColor="#FFFFFF" android:textSize="14sp" /> <TextView android:id="@+id/activity_login_textview2" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margin="10dp" android:background="#3F51B5" android:gravity="center" android:padding="10dp" android:lineSpacingExtra="10dp" android:text="" android:textColor="#FFFFFF" android:textSize="14sp" /> </LinearLayout> </ScrollView> </LinearLayout>