1. 程式人生 > >android中mvp+retrofit+rxjava處理mvp記憶體洩漏整合的demo

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