项目下载地址:http://download.csdn.net/download/csdn576038874/10261968
代码如下:
项目结构:
至于mvp的原理,v层抽象出接口,供P层调用,M层进行数据处理,抽象出接口,供P调用,P层中可拿到M和V 的接口引用,进行方法调用等逻辑处理,再利用接口回调的方式将解析好的数据返回给V层,这样就打到M层不直接和V层打交道,实现解耦和的效果
mvp模式会存在一个内存泄漏的隐患,如何解决,我们在p层写一个解绑和绑定的方法,最后在Activity中创建Presenter时进行绑定,在onDestroy中进行解绑,这样我们就解决了内存泄露的问题,我们可以抽取出一个基类的Presenter和一个基类的Activity来做这个事情,让子类不用在写这些重复的代码。但是问题又来了,既然是基类,肯定不止有一个子类来继承基类,那么也就是说子类当中定义的View接口和需要创建的Presenter都不相同,我们肯定在基类当中不能写死吧,那就使用泛型来设计。
1.创建一个基类View,让所有View接口都必须实现,这个View可以什么都不做只是用来约束类型的
2.创建一个基类的Presenter,在类上规定View泛型,然后定义绑定和解绑的抽象方法,让子类去实现
3.创建一个基类的Activity,声明一个创建Presenter的抽象方法,因为要帮子类去绑定和解绑那么就需要拿到子类的Presenter才行,但是又不能随便一个类都能绑定的,因为只有基类的Presenter中才定义了绑定和解绑的方法,所以同样的在类上可以声明泛型在,方法上使用泛型来达到目的。
4.修改Presenter和Activity中的代码,各自继承自己的基类并去除重复代码
实现步骤:
1.创建一个基类View,让所有View接口都必须实现
package com.winfo.wenjie.mvp.base; /** * ProjectName: MvpRxjavaRetrofitDemo * PackageName com.winfo.wenjie.mvp.base * FileName: com.winfo.wenjie.mvp.base.IBaseMvpView.java * Author: wenjie * Date: 2016-12-12 14:47 * Description: IBaseMvpView */ public interface IBaseMvpView { }
2.创建一个基类的Presenter,在类上规定View泛型,然后定义绑定和解绑的方法,对外在提供一个获取View的方法,让子类直接通过方法来获取View使用即可
package com.winfo.wenjie.mvp.base; import java.lang.ref.WeakReference; /** * ProjectName: MvpRxjavaRetrofitDemo * PackageName com.winfo.wenjie.mvp.base * FileName: com.winfo.wenjie.mvp.base.BaseMvpPresenter.java * Author: wenjie * Date: 2016-12-12 14:47 * Description: BaseMvpPresenter */ public class BaseMvpPresenter<V extends IBaseMvpView> { /** * v层泛型引用 */ protected V mView; private WeakReference<V> weakReferenceView; public void attachMvpView(V view) { weakReferenceView = new WeakReference<>(view); this.mView = weakReferenceView.get(); } public void detachMvpView() { weakReferenceView.clear(); weakReferenceView = null; mView = null; } }
3.创建一个基类的Activity,声明一个创建Presenter的抽象方法,因为要帮子类去绑定和解绑那么就需要拿到子类的Presenter才行,但是又不能随便一个类都能绑定的,因为只有基类的Presenter中才定义了绑定和解绑的方法,所以同样的在类上可以声明泛型在方法上使用泛型来达到目的
package com.winfo.wenjie.mvp.base; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v7.app.AppCompatActivity; /** * ProjectName: MvpRxjavaRetrofitDemo * PackageName com.winfo.wenjie.mvp.base * FileName: com.winfo.wenjie.mvp.base.BaseMvpActivity.java * Author: wenjie * Date: 2016-12-12 14:47 * Description: BaseMvpActivity */ public abstract class BaseMvpActivity<V extends IBaseMvpView, P extends BaseMvpPresenter<V>> extends AppCompatActivity implements IBaseMvpView { protected P mPresenter; @SuppressWarnings("unchecked") @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (mPresenter == null) { mPresenter = createPresenter(); } mPresenter.attachMvpView((V) this); } protected abstract P createPresenter(); @Override protected void onDestroy() { super.onDestroy(); if (mPresenter != null) { mPresenter.detachMvpView(); } } }
4、新建自己的prsenter继承presenter基类
package com.winfo.wenjie.mvp.presenter; import android.text.TextUtils; import com.winfo.wenjie.domain.Token; import com.winfo.wenjie.mvp.base.BaseMvpPresenter; import com.winfo.wenjie.mvp.model.OnLoadDatasListener; import com.winfo.wenjie.mvp.model.impl.LoginModel; import com.winfo.wenjie.mvp.view.ILoginView; /** * ProjectName: MvpRxjavaRetrofitDemo * PackageName com.winfo.wenjie.mvp.presenter * FileName: com.winfo.wenjie.mvp.presenter.LoginPresenter.java * Author: wenjie * Date: 2016-12-12 14:12 * Description: p层 */ public class LoginPresenter extends BaseMvpPresenter<ILoginView> { /** * m层 */ private LoginModel loginModel; /** * mvp模式 p层持有 v 和m 的接口引用 来进行数据的传递 起一个中间层的作用 */ public LoginPresenter() { /* *示例化loginmodel对象 固定写法 Retrofit.create(Class); */ this.loginModel = new LoginModel(); } /** * 登陆 */ public void login() { if (mView == null) return; if (TextUtils.isEmpty(mView.getUserName()) || TextUtils.isEmpty(mView.getPassword())) { mView.showMsg("用户名或密码不能为空"); return; } loginModel.login(mView.getDialog(), "", "", "password", mView.getUserName(), mView.getPassword(), new OnLoadDatasListener<Token>() { @Override public void onSuccess(Token token) { //请求成功服务器返回的数据s mView.setText(token.getAccess_token()); } @Override public void onFailure(String eroor) { //请求成功服务器返回的错误信息 mView.showMsg(eroor); } }); } }
5、新建activity继承activity基类
package com.winfo.wenjie.mvp.view.impl; import android.app.Dialog; import android.os.Bundle; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; import com.winfo.wenjie.R; import com.winfo.wenjie.mvp.base.BaseMvpActivity; import com.winfo.wenjie.mvp.presenter.LoginPresenter; import com.winfo.wenjie.mvp.view.ILoginView; import com.winfo.wenjie.utils.DialogUtils; import butterknife.BindView; import butterknife.ButterKnife; import butterknife.OnClick; /** * ProjectName: MvpRxjavaRetrofitDemo * PackageName com.winfo.wenjie.mvp.view.impl * FileName: com.winfo.wenjie.mvp.view.impl.MainActivity.java * Author: wenjie * Date: 2016-12-12 14:47 * Description: v层 */ public class MainActivity extends BaseMvpActivity<ILoginView, LoginPresenter> implements ILoginView { @BindView(R.id.username) EditText etUserName; @BindView(R.id.password) EditText etPassword; @BindView(R.id.result) TextView textView; @BindView(R.id.login) Button btnLogin; private Dialog dialog; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ButterKnife.bind(this); dialog = DialogUtils.createLoadingDialog(this, "登陆中..."); } @Override public Dialog getDialog() { return dialog; } @Override public String getUserName() { return etUserName.getText().toString(); } @Override public String getPassword() { return etPassword.getText().toString(); } @Override public void showMsg(String msg) { Toast.makeText(this, msg, Toast.LENGTH_SHORT).show(); } @Override public void setText(String result) { textView.setText("登录成功!Token:\n"+result); } @OnClick(R.id.login) public void onClick() { /* * 调用登录方法进行登陆 */ mPresenter.login(); } @Override protected LoginPresenter createPresenter() { return new LoginPresenter(); } }
6、抽象出View的一个接口,提供M层所需要的参数数据,不直接将Activity传递到P,并继承v层基类
package com.winfo.wenjie.mvp.view; import android.app.Dialog; import com.winfo.wenjie.mvp.base.IBaseMvpView; /** * ProjectName: MvpRxjavaRetrofitDemo * PackageName com.winfo.wenjie.mvp.view.impl * FileName: com.winfo.wenjie.mvp.view.impl.ILoginView.java * Author: wenjie * Date: 2016-12-12 14:11 * Description: view层的接口 由view来实现也就是mainactivity来实现该接口 */ public interface ILoginView extends IBaseMvpView{ /** * 获取view层的dialog * @return retuen */ Dialog getDialog(); /** * 获取用户名 参数 * @return username */ String getUserName(); /** * 获取密码 * @return password */ String getPassword(); /** * 弹出消息 * @param msg msg */ void showMsg(String msg); /** * 将数据返回给view * @param result resuklt */ void setText(String result); }
7、新建m层接口和m层实现类实现解耦
package com.winfo.wenjie.mvp.model; import android.app.Dialog; import com.winfo.wenjie.domain.Token; import com.winfo.wenjie.mvp.model.impl.LoginModel; /** * ProjectName: MvpRxjavaRetrofitDemo * PackageName: com.winfo.wenjie.mvp.model * FileName: com.winfo.wenjie.mvp.model.ILoginModel.java * Author: wenjie * Date: 2017-01-03 13:54 * Description: m层接口 */ public interface ILoginModel { /** * 登陆方法 * @param dialog 对话框这里传递到model不是很好,但是也没办法,因为要做一个对话框消失,同时取消请求的操作 * @param client_id client_id * @param client_secret client_secret * @param grant_type grant_type * @param username 用户名 * @param password 用户密码 * @param onLoadDatasListener 监听函数 */ void login(Dialog dialog,String client_id, String client_secret, String grant_type, String username, String password, OnLoadDatasListener<Token> onLoadDatasListener); }
package com.winfo.wenjie.mvp.model; /** * ProjectName: DiycodeApp * PackageName: com.wenjie.diycode.mvp.model * FileName: com.wenjie.diycode.mvp.model.OnLoadDatasListener.java * Author: wenjie * Date: 2017-08-29 11:20 * Description: */ public interface OnLoadDatasListener<T> { /** * 成功 * @param t 数据 */ void onSuccess(T t); /** * 失败 * @param eroor 错误信息 */ void onFailure(String eroor); }
M层实现类
package com.winfo.wenjie.mvp.model.impl; import android.app.Dialog; import com.winfo.wenjie.domain.Token; import com.winfo.wenjie.mvp.model.ILoginModel; import com.winfo.wenjie.mvp.model.OnLoadDatasListener; import com.winfo.wenjie.request.ApiService; import com.winfo.wenjie.request.DialogSubscriber; import com.winfo.wenjie.request.OkHttpUtils; import com.winfo.wenjie.request.ResponseResult; import rx.Observable; import rx.Subscriber; import rx.android.schedulers.AndroidSchedulers; import rx.schedulers.Schedulers; /** * ProjectName: MvpRxjavaRetrofitDemo * PackageName com.winfo.wenjie.mvp.model * FileName: com.winfo.wenjie.mvp.model.impl.LoginModel.java * Author: wenjie * Date: 2016-12-12 14:47 * Description: m层实现类 */ public class LoginModel implements ILoginModel { @Override public void login(Dialog dialog, String client_id, String client_secret, String grant_type, String username, String password, final OnLoadDatasListener<Token> onLoadDatasListener) { /* * 被订阅者 */ Observable<Token> observable = OkHttpUtils.getRetrofit().create(ApiService.class).getToken(client_id, client_secret, grant_type, username, password); /* * 订阅者 */ Subscriber<Token> subscriber = new DialogSubscriber<Token>(dialog , true) { @Override protected void onSuccess(Token token) { onLoadDatasListener.onSuccess(token); } @Override protected void onFailure(String msg) { onLoadDatasListener.onFailure(msg); } }; observable.subscribeOn(Schedulers.io()) .unsubscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(subscriber); } }
下面封装了一个网络请求
ApiService主要是项目中所有需要调用的的接口,具体为什么这么写,不做多介绍了
package com.winfo.wenjie.request; import com.winfo.wenjie.domain.Token; import com.winfo.wenjie.utils.Constant; import retrofit2.http.Field; import retrofit2.http.FormUrlEncoded; import retrofit2.http.POST; import rx.Observable; /** * ProjectName: MvpRxjavaRetrofitDemo * PackageName: com.winfo.wenjie.request * FileName: com.winfo.wenjie.request.ApiService.java * Author: wenjie * Date: 2017-01-17 16:54 * Description: */ public interface ApiService { /** * 获取 Token (一般在登录时调用) * * @param client_id 客户端 id * @param client_secret 客户端私钥 * @param grant_type 授权方式 - 密码 * @param username 用户名 * @param password 密码 * @return Token 实体类 */ @POST("oauth/token") @FormUrlEncoded Observable<Token> getToken( @Field("client_id") String client_id, @Field("client_secret") String client_secret, @Field("grant_type") String grant_type, @Field("username") String username, @Field("password") String password); }
package com.winfo.wenjie.request; /** * ProjectName: MvpRxjavaRetrofitDemo * PackageName com.winfo.wenjie.request * FileName: com.winfo.wenjie.request.DialogCancelListener.java * Author: wenjie * Date: 2016-12-12 14:32 * Description: 对话框隐藏或者消失之后取消请求 */ public interface DialogCancelListener { /** * 取消网络请求 */ void onCancel(); }
package com.winfo.wenjie.request; import android.app.Dialog; import android.content.DialogInterface; import android.os.Handler; import android.os.Message; /** * ProjectName: MvpRxjavaRetrofitDemo * PackageName com.winfo.wenjie.request * FileName: com.winfo.wenjie.request.DialogHandler.java * Author: wenjie * Date: 2016-12-12 14:28 * Description: 创建一个dialoghandler类来操作dialog加载进度框 以便于 请求取消的处理 */ public class DialogHandler extends Handler { /** * 显示加载框 */ static final int SHOW_PROGRESS_DIALOG = 1; /** * 隐藏加载框 */ static final int DISMISS_PROGRESS_DIALOG = 2; private Dialog loadingDialog; private DialogCancelListener dialogCancelListener; /** * 构造方法接收一个加载框的对象 由各个view层创建之后传进来 因为每个对话框所提示的内容有所不同 * @param dialog dialog */ DialogHandler(Dialog dialog, DialogCancelListener dialogCancelListener) { this.loadingDialog = dialog; this.dialogCancelListener = dialogCancelListener; initDialogDismissListenner(); } private void initDialogDismissListenner() { loadingDialog.setOnDismissListener(new DialogInterface.OnDismissListener() { @Override public void onDismiss(DialogInterface dialog) { dialogCancelListener.onCancel(); } }); } /** * 显示加载框 */ private void showLodingDialog() { loadingDialog.show(); } /** * 隐藏加载框 */ private void dismissLodingDialog() { loadingDialog.dismiss(); } @Override public void handleMessage(Message msg) { switch (msg.what) { case SHOW_PROGRESS_DIALOG: showLodingDialog(); break; case DISMISS_PROGRESS_DIALOG: dismissLodingDialog(); break; } } }
package com.winfo.wenjie.request; import android.app.Dialog; import android.text.TextUtils; import java.net.ConnectException; import java.net.SocketTimeoutException; import rx.Subscriber; /** * ProjectName: MvpRxjavaRetrofitDemo * PackageName com.winfo.wenjie.request * FileName: com.winfo.wenjie.request.DialogSubscriber.java * Author: wenjie * Date: 2016-12-12 14:23 * Description: 订阅者 */ public abstract class DialogSubscriber<T> extends Subscriber<T> implements DialogCancelListener { private boolean isShowDialog; /** * 定义一个请求成功的抽象方法 子类必须实现并在实现中进行处理服务器返回的数据 * * @param t 服务器返回的数据 */ protected abstract void onSuccess(T t); /** * 定义一个请求失败的抽象方法 子类必须实现并在实现中进行服务器返回数据的处理 * * @param msg 服务器返回的错误信息 */ protected abstract void onFailure(String msg); private DialogHandler dialogHandler; /** * * @param dialog 对话框 * @param isShowDialog 是否显示加载的对话框 * 这里的dialog要特别说明一下,把它当做DialogHandler构造参数传递进去,来进行具体的对话框的操作 * 其实是可以直接将context作为构造参数传递进去,在DialogHandler中通过context对象来创建dialog对象 * 因为感觉传递context对象不是很好,而且dialog加载框的提示语也会根据请求的不同而不同比如: * 1、登陆请求 对话框需要是 登陆中... * 2、加载数据 对话框需要时 数据加载中... * 3、等等 * 所以我们直接将dialog的创建放在m层,作为参数传递进来,这样就可以适应很多请求 * 或许会有人觉得这事强迫症,可以直接 加载中...代替所有的啊,这样不是也可以吗? * 的确可以但是,这样的话,需要传递context对象到这里了。为了避免传递context对象 * 所以选择这样的方式,也会有人说dialog携带了context引用啊。传conxtexthedialog都一样, * 这里我就不处理了,你们自己想办法解决 * isShowDialog这个就比较简单,请求时控制dialog是否显示,不是所有的请求都需要加载框比如下拉刷新 * 这些就不需要,所以这里灵活一点,自己根据具体的请求传递对应的值 */ protected DialogSubscriber(Dialog dialog, boolean isShowDialog) { this.isShowDialog = isShowDialog; dialogHandler = new DialogHandler(dialog , this); } /** * 显示对话框 发送一个显示对话框的消息给dialoghandler 由他自己处理(也就是dialog中hanldermesage处理该消息) */ private void showProgressDialog() { if (dialogHandler != null) { dialogHandler.obtainMessage(DialogHandler.SHOW_PROGRESS_DIALOG).sendToTarget(); } } /** * 隐藏对话框 .... */ private void dismissProgressDialog() { if (dialogHandler != null) { dialogHandler.obtainMessage(DialogHandler.DISMISS_PROGRESS_DIALOG).sendToTarget(); dialogHandler = null; } } /** * 请求开始 * 先判断isShowDialog的值,如果为false就不显示对话框,为true才显示 */ @Override public void onStart() { if(isShowDialog){ showProgressDialog(); } } /** * 请求完成,隐藏对话框 */ @Override public void onCompleted() { dismissProgressDialog(); } /** * 请求出错 * 这里异常处理的不是很完善,你们自己多写一些请求可能出现的异常 * 进行捕获,这样可以直接将异常信息返回到view层可见页面,开发时一眼也可以看出具体的问题 * @param e e */ @Override public void onError(Throwable e) { dismissProgressDialog(); String msg; if (e instanceof SocketTimeoutException) { msg = "请求超时。请稍后重试!"; } else if (e instanceof ConnectException) { msg = "请求超时。请稍后重试!"; } else { msg = "请求未能成功,请稍后重试!"; } if (!TextUtils.isEmpty(msg)) { onFailure(msg); } } /** * 请求成功 * * @param t t */ @Override public void onNext(T t) { /* * 请求成功将数据发出去 */ onSuccess(t); } /** * 请求被取消 */ @Override public void onCancel() {
package com.winfo.wenjie.request; import java.util.concurrent.TimeUnit; import okhttp3.OkHttpClient; import retrofit2.Retrofit; import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory; import retrofit2.converter.gson.GsonConverterFactory; /** * ProjectName: MvpRxjavaRetrofitDemo * PackageName: com.winfo.wenjie.request * FileName: com.winfo.wenjie.request.OkHttpUtils.java * Author: wenjie * Date: 2016-12-12 14:17 * Description: 网络请求的工具类 */ public class OkHttpUtils { /** * okhttp */ private static OkHttpClient okHttpClient; /** * Retrofit */ private static Retrofit retrofit; /** * 获取Retrofit的实例 * * @return retrofit */ public static Retrofit getRetrofit() { if (retrofit == null) { retrofit = new Retrofit.Builder() .baseUrl("https://www.diycode.cc/") .addConverterFactory(GsonConverterFactory.create()) .client(getOkHttpClient()) .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) .build(); } return retrofit; } private static OkHttpClient getOkHttpClient() { if (okHttpClient == null) { OkHttpClient.Builder builder = new OkHttpClient.Builder(); builder.connectTimeout(15, TimeUnit.SECONDS); okHttpClient = builder.build(); } return okHttpClient; } }