天天看點

android中mvp+retrofit+rxjava處理mvp記憶體洩漏整合的demo

java版:https://github.com/wj576038874/mvp-rxjava-retrofit-okhttp

kotlin版:https://github.com/wj576038874/MvpKotlin

代碼如下:

項目結構:

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 是否顯示加載的對話框
     */
    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;
    }
}