android中mvp+retrofit+rxjava处理mvp内存泄漏整合的demo

时间:2021-03-07 21:12:29

项目下载地址:http://download.csdn.net/download/csdn576038874/10261968

代码如下:

项目结构:

android中mvp+retrofit+rxjava处理mvp内存泄漏整合的demo


至于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);
    }
}


rxjava和retrofit+okhttp具体应用在M层中,通过rxjava的异步,以及okhttp网络请求结合获取数据请求接口
下面封装了一个网络请求

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();
}


创建一个Handler操作对话框,可以取消请求
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;
    }
}