Android MVP開發(fā)模式與Rxjava+Retrofit結(jié)合的使用(進(jìn)階版)

以前根據(jù)參考各路大神的博客苍糠,以及自己對(duì)mvp的理解穷当,早早就寫了這種不成熟的結(jié)合模式(舊版),歷時(shí)半年饺著,他們終于進(jìn)化了椰苟!

一、基礎(chǔ)介紹

1碌更、什么是mvp

mvp是android開發(fā)架構(gòu)之一裕偿,MVP每個(gè)字母分別代表Model、View和Presenter痛单。

①model負(fù)責(zé)處理網(wǎng)絡(luò)數(shù)據(jù)的處理
②presenter是model和view的橋梁嘿棘,負(fù)責(zé)與兩端的通信
③view是視圖層

2、為什么有mvp

mvp的誕生得益于mvc旭绒,mvc確實(shí)貢獻(xiàn)也不小而且也歷經(jīng)風(fēng)雨鸟妙,但mvc的耦合是在太嚴(yán)重,因此mvp就誕生了挥吵。在mvp模式下重父,model和view是不直接進(jìn)行交互的,而是要通過presenter作為橋梁忽匈,這樣一來房午,各層的分工就更加明確,
①model不需要管ui長(zhǎng)什么樣丹允,只要把對(duì)應(yīng)的數(shù)據(jù)給到p即可郭厌,至于p是直接給v還是處理了再給v袋倔,怎么給,都是p的事情折柠。
②而p這里宾娜,p不管model用的post還是get,參數(shù)格式是什么扇售,p只需要向m發(fā)起請(qǐng)求前塔,并在拿到結(jié)果后,把結(jié)果回調(diào)給v即可承冰,不需要管v是要彈出對(duì)話框還是吐司還是跳轉(zhuǎn)頁面华弓。
③v只需要把用戶的動(dòng)作或輸入的內(nèi)容給到p,等待p回應(yīng)即可巷懈!

3该抒、這個(gè)mvp和其他有些不太一樣

這個(gè)mvp主要還是表現(xiàn)在p有寫與眾不同,p層拿到v的實(shí)例后顶燕,通過動(dòng)態(tài)代理得到視圖實(shí)例凑保,view方法的執(zhí)行又是交給InvocationHandler來處理的,這樣可以有效避免view不存在時(shí)涌攻,還執(zhí)行view方法欧引。P中推薦傳入activity實(shí)例,其實(shí)要的是applicat和activity的name恳谎,暫時(shí)還沒想到更好的方法芝此,就這樣用著先。如果傳入的參數(shù)不是activity或者沒傳參數(shù)因痛,記得要在頁面退出的該回收的地方調(diào)用p的detachView方法婚苹。

二、實(shí)現(xiàn)步驟

1鸵膏、基于哪些基礎(chǔ)膊升?

首先一定有個(gè)大前提,就是所有接口返回的數(shù)據(jù)格式谭企,都是一樣的廓译,比如說現(xiàn)在我的接口返回?cái)?shù)據(jù)格式是這樣的

{
    "code": 200,
    "message": "提交成功"
}

又或者是這樣的

{
    "code": 200,
    "message": "",
    "data": {
        "userName": "張三",
        "headImage": "http://192.168.3.11/file/user/hf6d4g88a.jpg",
        "sex": 1,
        "bir": "2020-01-01"
    }
}

他們都有些共同點(diǎn),如code和message债查,那么data就是泛型了非区!所以定義的響應(yīng)體接收的類為

public class CallResult<T> {
    public int code;
    public String message;
    public T data;
}

2、封裝Retrofit

考慮到有時(shí)候需要在請(qǐng)求的header中加各種數(shù)據(jù)盹廷,比如說appVersion等征绸,并且上傳文件和普通的接口超時(shí)時(shí)間一般是不同的,因此就有了這種封裝

2.1開始封裝

import android.text.TextUtils;
import android.util.Log;

import com.example.mvp.OnHttpResultListener;
import com.example.mvp.retrofit2.convert.MyConverterFactory;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import okhttp3.Interceptor;
import okhttp3.OkHttpClient.Builder;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;

/**
 * @author Administrator
 */
public class Retrofit2Manager {
    /**
     * 默認(rèn)的請(qǐng)求時(shí)間
     */
    private long timeOut = 20000L;
    /**
     * 監(jiān)聽請(qǐng)求過程
     */
    private OnHttpResultListener onHttpResultListener;
    /**
     * 服務(wù)器地址
     */
    private final String baseUrl;
    /**
     * 請(qǐng)求頭
     */
    private final Map<String, String> map = new HashMap<>();
    /**
     * 自定義攔截器
     */
    private final List<Class<? extends Interceptor>> interceptors = new ArrayList<>();

    /**
     * 靜態(tài)方法,入口
     *
     * @param baseUrl 路徑
     * @return this
     */
    public static Retrofit2Manager with(String baseUrl) {
        return new Retrofit2Manager(baseUrl);
    }

    /**
     * 私有構(gòu)造方法
     *
     * @param baseUrl 服務(wù)器路徑
     */
    private Retrofit2Manager(String baseUrl) {
        this.baseUrl = baseUrl;
    }

    /**
     * 超時(shí)時(shí)間
     *
     * @param timeOut timeOut
     * @return this
     */
    public Retrofit2Manager setTimeOut(long timeOut) {
        this.timeOut = timeOut;
        return this;
    }

    /**
     * 監(jiān)聽請(qǐng)求過程
     *
     * @param onHttpResultListener onHttpResultListener
     * @return this
     */
    public Retrofit2Manager setOnHttpResultListener(OnHttpResultListener onHttpResultListener) {
        this.onHttpResultListener = onHttpResultListener;
        return this;
    }

    /**
     * 添加自定義請(qǐng)求頭
     *
     * @param key   key
     * @param value value
     * @return this
     */
    public Retrofit2Manager addHeadres(String key, String value) {
        if (TextUtils.isEmpty(key)) {
            return this;
        }
        if (TextUtils.isEmpty(value)) {
            value = "";
        }
        map.put(key, value);
        return this;
    }

    public Retrofit2Manager add(Class<? extends Interceptor> mClass) {
        interceptors.add(mClass);
        return this;


    }

    /**
     * 返回retrofit2的實(shí)例
     *
     * @return retrofit2
     */
    public Retrofit retrofit() {
        Builder okBuilder = new Builder();
        okBuilder.readTimeout(this.timeOut, TimeUnit.MILLISECONDS);
        okBuilder.writeTimeout(this.timeOut, TimeUnit.MILLISECONDS);
        okBuilder.connectTimeout(this.timeOut, TimeUnit.MILLISECONDS);
        okBuilder.addInterceptor(new LogInterceptor(map, onHttpResultListener));
        try {
            for (Class<? extends Interceptor> mClass : interceptors) {
                okBuilder.addInterceptor(mClass.newInstance());
            }
        } catch (Exception e) {
            Log.e("mvp[error]", e.getMessage());
            e.printStackTrace();
        }
        return (new Retrofit.Builder()).client(okBuilder.build())
                .baseUrl(this.baseUrl)
                //自定義解析
                .addConverterFactory(MyConverterFactory.create())
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .build();
    }
}

2.2為什么要自定義解析歹垫?

當(dāng)服務(wù)器返回的數(shù)據(jù)為

{
    "code": 200,
    "message": "",
    "data": [
        {
            "userName": "李四",
            "headImage": "http://192.168.3.11/file/user/hf6d4g3243288a.jpg",
            "sex": 1,
            "bir": "2020-01-01"
        },
        {
            "userName": "張三",
            "headImage": "http://192.168.3.11/file/user/hf6d4g84348a.jpg",
            "sex": 2,
            "bir": "2020-01-17"
        },
        {
            "userName": "王五",
            "headImage": "http://192.168.3.11/file/user/hf6d4345436g88a.jpg",
            "sex": 1,
            "bir": "2020-03-01"
        }
    ]
}

時(shí)剥汤,我們定義的實(shí)體類可以正常接收數(shù)據(jù)。如果接口返回的數(shù)據(jù)是

{
    "code": 401,
    "message": "登錄狀態(tài)已失效",
    "data": null
}

的時(shí)候排惨,你會(huì)發(fā)現(xiàn)直接json轉(zhuǎn)換閃退,因?yàn)閚ull無法轉(zhuǎn)換成list碰凶,因此我們要自己定義解析工廠暮芭,以下是部分代碼

    @Override
    public T convert(@NonNull ResponseBody value) {
        String str = "";
        Object var3;
        try {
            if (value.contentLength() != 0L) {
                str = value.source().readUtf8();
                var3 = this.convert(str, this.type);
                return (T) var3;
            }
            str = "{\"code\":90000,\"message\":\"服務(wù)器無響應(yīng)\"}";
            var3 = this.convert(str, CallResult.class);
        } catch (Exception var8) {
            //當(dāng)轉(zhuǎn)換出現(xiàn)異常,就用Void進(jìn)行轉(zhuǎn)換
            Object var4 = this.convert(str, CallResult.class);
            return (T) var4;
        } finally {
            value.close();
        }
        return (T) var3;
    }

3欲低、創(chuàng)建mvp的各種Base基類

3.1 model層BaseModel

OnHttpResultListener是自己定義的辕宏,用來接收接口參數(shù),對(duì)調(diào)試非常好用砾莱,上線版本可以忽略

import android.util.Log;

import com.example.mvp.OnHttpResultListener;
import com.example.mvp.retrofit2.Retrofit2Manager;

import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;

public class BaseModel implements OnHttpResultListener {

    protected <T> T createService(String ip, Class<T> mClass) {
        return Retrofit2Manager.with(ip).setOnHttpResultListener(this).retrofit().create(mClass);
    }

    @Override
    public void onResponse(String method, String requestUrl, String requestHeaders, String requestParams, int responseCode, String responseData) {
        String sb = "\n【請(qǐng)求方法】:" + method +
                "\n【請(qǐng)求路徑】:" + requestUrl +
                "\n【請(qǐng)求頭】:" + requestHeaders +
                "\n【請(qǐng)求參數(shù)】:" + requestParams +
                "\n【返回參數(shù)】:" + responseData;
        Log.d("exccd(mvp-http)", sb);
    }

    /**
     * 發(fā)起請(qǐng)求瑞筐,并且在ui線程執(zhí)行回調(diào)
     *
     * @param observable observable
     * @param <T>        泛型
     */
    protected <T> Observable<T> callBackOnUi(Observable<T> observable) {
        return observable.subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread());
    }

    /**
     * 發(fā)起請(qǐng)求,并且在新的子線程執(zhí)行回調(diào)
     *
     * @param observable observable
     * @param <T>        泛型
     */
    protected <T> Observable<T> callBackOnThread(Observable<T> observable) {
        return observable.subscribeOn(Schedulers.io())
                .observeOn(Schedulers.newThread());
    }
}

3.2 視圖層IBaseView<T>

為了讓ui實(shí)現(xiàn)這個(gè)回調(diào)更加簡(jiǎn)單腊瑟,我這里將服務(wù)器返回的message和其他所有可能有的提示都通過s字段來返回了聚假,因此不管是“登錄狀態(tài)已失效”還是“網(wǎng)絡(luò)連接異常”闰非,都是在s回調(diào)的膘格。根據(jù)項(xiàng)目不用,有需要的可以拆分财松。

/**
 * ui回調(diào)
 *
 * @param <T> 泛型
 */
public interface IBaseView<T> {
    /**
     * ui回調(diào)
     *
     * @param b    是否請(qǐng)求成功
     * @param i    類型
     * @param s    描述
     * @param data 泛型
     */
    void onCallBack(boolean b, int i, String s, T data);
}

3.3 Presenter層

這一層最關(guān)鍵的是IBasePresenter和它的實(shí)現(xiàn)類BasePresenter

3.3.1 IBasePresenter

start方法是發(fā)起請(qǐng)求的入口瘪贱,使用時(shí)需要將model的方法傳進(jìn)去,然后跟視圖綁定起來辆毡。

/**
 * presenter需要具備的基礎(chǔ)方法
 */
public interface IBasePresenter {
    /**
     * 開始發(fā)起請(qǐng)求
     *
     * @param observable model層返回的obs
     * @param iBaseView  視圖菜秦,回調(diào)
     * @param <T>        泛型
     */
    <T> void start(Observable<CallResult<T>> observable, IBaseView<T> iBaseView);

    /**
     * 成功回調(diào)
     *
     * @param iBaseView 視圖、回調(diào)
     * @param data      數(shù)據(jù)
     * @param <T>       泛型
     */
    <T> void viewCallBackSuccess(IBaseView<T> iBaseView, CallResult<T> data);

    /**
     * 錯(cuò)誤回調(diào)
     *
     * @param iBaseView 視圖舶掖、回調(diào)
     * @param e         錯(cuò)誤信息
     * @param <T>       泛型
     */
    <T> void viewCallBackError(IBaseView<T> iBaseView, Throwable e);

    /**
     * 解綁
     */
    void detachView();
}

3.3.2 BasePresenter

BasePresenter處理一些回調(diào)球昨,將一些非正常接口請(qǐng)求的結(jié)果轉(zhuǎn)換成中文(指定描述)在回調(diào)給view,這里的所有數(shù)據(jù)都是可以自己定義的访锻,另外如果在某種情況下需要彈窗退出登錄褪尝,建議您新建一個(gè)MyBasePresenter extend BasePresenter,然后重寫onTokenErrorCallBack()即可期犬,但判斷的邏輯需要更改一下河哑。

public abstract class BasePresenter<M> implements IBasePresenter {
    /**
     * 未授權(quán)登錄,登錄狀態(tài)已失效
     */
    public static final int UNAUTHORIZED = 401;
    /**
     * 請(qǐng)求成功
     */
    public static final int SUCCESS = 200;
    /**
     * 請(qǐng)求被禁止
     */
    public static final int FORBIDDEN = 403;
    /**
     * 接口失效
     */
    public static final int NOT_FOUND = 404;
    /**
     * 請(qǐng)求超時(shí)
     */
    public static final int REQUEST_TIMEOUT = 408;
    /**
     * 服務(wù)器錯(cuò)誤
     */
    public static final int INTERNAL_SERVER_ERROR = 500;
    /**
     * 錯(cuò)誤的網(wǎng)關(guān)
     */
    public static final int BAD_GATEWAY = 502;
    /**
     * 服務(wù)器不可用
     */
    public static final int SERVICE_UNAVAILABLE = 503;
    /**
     * 網(wǎng)絡(luò)超時(shí)
     */
    public static final int GATEWAY_TIMEOUT = 504;
    /**
     * 在默認(rèn)線程回調(diào)
     */
    private boolean callBackInLoop = false;
    /**
     * 是否已經(jīng)解綁了龟虎,避免重復(fù)解綁
     */
    private boolean isDttached = false;
    /**
     * model層
     */
    protected M module;
    /**
     * 視圖
     */
    private final Map<Integer, IBaseView<?>> mapView = new HashMap<>();
    /**
     * 視圖引用
     */
    private final Map<Integer, WeakReference<?>> mapReference = new HashMap<>();
    /**
     * 請(qǐng)求對(duì)象
     */
    private final Map<Integer, Disposable> mapDisposables = new HashMap<>();
    /**
     * 主線程
     */
    protected Handler handler;

    /**
     * 構(gòu)造方法
     * 您需要手動(dòng){@link #detachView()}解綁
     */
    public BasePresenter() {
        onCreate(null);
    }

    /**
     * 構(gòu)造方法
     *
     * @param activity activity的實(shí)例
     */
    public BasePresenter(Activity activity) {
        onCreate(activity);
    }

    /**
     * 構(gòu)造方法
     *
     * @param context 如果這是個(gè)activity的實(shí)例璃谨,那么不需要手動(dòng){@link #detachView()}即可解綁,否則您需要調(diào)用他
     */
    public BasePresenter(Context context) {
        if (context instanceof Activity) {
            onCreate((Activity) context);
        } else {
            onCreate(null);
        }
    }

    /**
     * 初始化方法
     */
    private void onCreate(Activity activity) {
        this.handler = new Handler(Looper.getMainLooper());
        if (this.module == null) {
            this.module = this.createModel();
        }
        if (activity != null) {
            String acName = activity.getLocalClassName();
            Application app = activity.getApplication();
            Application.ActivityLifecycleCallbacks callbacks = new Application.ActivityLifecycleCallbacks() {
                @Override
                public void onActivityCreated(Activity activity, Bundle bundle) {

                }

                @Override
                public void onActivityStarted(Activity activity) {

                }

                @Override
                public void onActivityResumed(Activity activity) {

                }

                @Override
                public void onActivityPaused(Activity activity) {

                }

                @Override
                public void onActivityStopped(Activity activity) {

                }

                @Override
                public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {

                }

                @Override
                public void onActivityDestroyed(Activity activity) {
                    if (acName.equals(activity.getLocalClassName())) {
                        detachView();
                        app.unregisterActivityLifecycleCallbacks(this);
                    }
                }
            };
            app.registerActivityLifecycleCallbacks(callbacks);
        }
    }

    /**
     * 綁定
     *
     * @param view 視圖
     */
    @SuppressWarnings("all")
    private <T, V extends IBaseView<T>> void attachView(V view) {
        if (view != null) {
            WeakReference<V> weakReference = new WeakReference<V>(view);
            mapReference.put(view.hashCode(), weakReference);
            ClassLoader classLoader = view.getClass().getClassLoader();
            Class<?>[] interfaces = view.getClass().getInterfaces();
            InvocationHandler invocationHandler = new MvpViewHandler((IBaseView) weakReference.get());
            IBaseView<T> v = (V) Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
            mapView.put(view.hashCode(), v);
        }
    }

    /**
     * 是否在默認(rèn)線程回調(diào)
     *
     * @param callBackInLoop 如果是false,回調(diào)會(huì)在ui線程處理佳吞,否則就是在發(fā)起默認(rèn)的線程回調(diào)
     */
    public void setCallBackInLoop(boolean callBackInLoop) {
        this.callBackInLoop = callBackInLoop;
    }

    /**
     * 頁面是否已經(jīng)不存在了
     *
     * @param iBaseView 視圖
     * @param <T>       泛型
     * @return true存在拱雏,則回調(diào),否則忽略
     */
    protected <T> boolean isViewAttached(IBaseView<T> iBaseView) {
        if (iBaseView == null) {
            return false;
        }
        int key = iBaseView.hashCode();
        IBaseView<?> view = mapView.get(key);
        WeakReference<?> weakReference = mapReference.get(key);
        return view != null && weakReference != null && weakReference.get() != null;
    }

    /**
     * 創(chuàng)建module
     *
     * @return M
     */
    protected abstract M createModel();

    /**
     * 請(qǐng)求是否成功
     *
     * @param data 響應(yīng)體
     * @return 成功true底扳,失敗false
     */
    protected <T> boolean isSuccess(CallResult<T> data) {
        return data != null && data.code == SUCCESS;
    }

    /**
     * 開始發(fā)起請(qǐng)求
     *
     * @param observable model層返回的obs
     * @param baseView   視圖铸抑、回調(diào)
     * @param <T>        泛型
     */
    @Override
    public <T> void start(Observable<CallResult<T>> observable, IBaseView<T> baseView) {
        attachView(baseView);
        mapDisposables.put(baseView.hashCode(), observable
                .subscribe(data -> viewCallBackSuccess(baseView, data), e -> viewCallBackError(baseView, e)));
    }

    /**
     * 成功回調(diào)
     *
     * @param view 視圖、回調(diào)
     * @param data 數(shù)據(jù)
     * @param <T>  泛型
     */
    @Override
    public <T> void viewCallBackSuccess(IBaseView<T> view, CallResult<T> data) {
        if (callBackInLoop) {
            _viewCallBackSuccess(view, data);
        } else {
            if (Looper.myLooper() == Looper.getMainLooper()) {
                _viewCallBackSuccess(view, data);
            } else {
                handler.post(() -> _viewCallBackSuccess(view, data));
            }
        }
    }

    /**
     * 錯(cuò)誤回調(diào)
     *
     * @param view 視圖衷模、回調(diào)
     * @param e    錯(cuò)誤信息
     * @param <T>  泛型
     */
    @Override
    public <T> void viewCallBackError(IBaseView<T> view, Throwable e) {
        if (callBackInLoop) {
            _viewCallBackError(view, e);
        } else {
            if (Looper.myLooper() == Looper.getMainLooper()) {
                _viewCallBackError(view, e);
            } else {
                handler.post(() -> _viewCallBackError(view, e));
            }
        }
    }

    /**
     * 解綁
     */
    @Override
    public void detachView() {
        if (isDttached) {
            return;
        }
        isDttached = true;
//        this.module = null;
        this.handler.removeCallbacksAndMessages(null);
        for (WeakReference<?> weakReference : mapReference.values()) {
            if (weakReference != null) {
                weakReference.clear();
            }
        }
        mapReference.clear();
        mapView.clear();
        try {
            for (Disposable disposable : mapDisposables.values()) {
                if (disposable != null && !disposable.isDisposed()) {
                    disposable.dispose();
                }
            }
        } catch (Exception e) {
            Log.e("mvp[error]", e.getMessage());
        }
    }

    /**
     * 統(tǒng)一執(zhí)行成功回調(diào)鹊汛,看{@link #viewCallBackSuccess}
     */
    private <T> void _viewCallBackSuccess(IBaseView<T> view, CallResult<T> data) {
        if (data.code == UNAUTHORIZED) {
            onTokenErrorCallBack(data.message);
        }
        if (isViewAttached(view)) {
            view.onCallBack(data.code == SUCCESS, data.code, data.message, data.data);
        }
    }

    /**
     * 統(tǒng)一執(zhí)行錯(cuò)誤回調(diào),看{@link #viewCallBackError}
     */
    private <T> void _viewCallBackError(IBaseView<T> view, Throwable e) {
        if (isViewAttached(view)) {
            try {
                if (e instanceof HttpException) {
                    HttpException httpException = (HttpException) e;
                    switch (httpException.code()) {
                        case UNAUTHORIZED:
                            callBackError(view, "登錄驗(yàn)證已過期");
                            onTokenErrorCallBack("登錄驗(yàn)證已過期");
                            break;
                        case INTERNAL_SERVER_ERROR:
                            callBackError(view, "服務(wù)器錯(cuò)誤");
                            break;
                        case FORBIDDEN:
                        case NOT_FOUND:
                            callBackError(view, "無效的請(qǐng)求");
                            break;
                        case REQUEST_TIMEOUT:
                        case GATEWAY_TIMEOUT:
                        case BAD_GATEWAY:
                        case SERVICE_UNAVAILABLE:
                        default:
                            callBackError(view, httpException.getMessage());
                            break;
                    }
                } else if (e instanceof ConnectException) {
                    callBackError(view, "網(wǎng)絡(luò)連接異常阱冶,請(qǐng)檢查您的網(wǎng)絡(luò)狀態(tài)");
                } else if (e instanceof SocketTimeoutException) {
                    callBackError(view, "網(wǎng)絡(luò)連接超時(shí)刁憋,請(qǐng)檢查您的網(wǎng)絡(luò)狀態(tài),稍后重試");
                } else if (e instanceof UnknownHostException) {
                    callBackError(view, "網(wǎng)絡(luò)異常木蹬,請(qǐng)檢查您的網(wǎng)絡(luò)狀態(tài)");
                } else if (e instanceof JSONException
                        || e instanceof ParseException) {
                    callBackError(view, "數(shù)據(jù)解析錯(cuò)誤");
                } else if (e instanceof SSLHandshakeException) {
                    callBackError(view, "證書驗(yàn)證失敗");
                } else if (e instanceof RuntimeException) {
                    callBackError(view, "運(yùn)行時(shí)異常");
                } else {
                    callBackError(view, e.toString());
                }
            } catch (Exception e1) {
                Log.e("mvp[error]", e.getMessage());
            }
        }
    }

    /**
     * {@link #_viewCallBackError}
     */
    private <T> void callBackError(IBaseView<T> view, String message) {
        view.onCallBack(false, 9000, message, null);
        Log.e("excce", "UI回調(diào)錯(cuò)誤信息:" + message);
    }

    /**
     * 返回一個(gè)value類型為Object的哈希表
     *
     * @param initSize 大小
     * @return Map
     */
    protected Map<String, Object> createMap(int initSize) {
        return new HashMap<>(initSize);
    }

    /**
     * 返回一個(gè)value類型為Integer的哈希表
     *
     * @param initSize 大小
     * @return Map
     */
    protected Map<String, Integer> createMapInt(int initSize) {
        return new HashMap<>(initSize);
    }

    /**
     * 返回一個(gè)value類型為String的哈希表
     *
     * @param initSize 大小
     * @return Map
     */
    protected Map<String, String> createMapStr(int initSize) {
        return new HashMap<>(initSize);
    }

    /**
     * 登錄狀態(tài)失效至耻,需要回到登錄頁
     *
     * @param message message
     */
    protected void onTokenErrorCallBack(String message) {

    }

    /**
     * 動(dòng)態(tài)代理
     *
     * @param <T> 泛型
     */
    private class MvpViewHandler<T> implements InvocationHandler {
        private final IBaseView<T> mvpView;

        MvpViewHandler(IBaseView<T> mvpView) {
            this.mvpView = mvpView;
        }

        @Override
        @SuppressWarnings("SuspiciousInvocationHandlerImplementation")
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if (isViewAttached(mvpView)) {
                return method.invoke(this.mvpView, args);
            } else {
                Log.d("excci", "頁面已關(guān)閉,不執(zhí)行view層方法镊叁!");
                return null;
            }
        }
    }
}

4尘颓、開始使用

經(jīng)過一系列漫長(zhǎng)又復(fù)雜的封裝,終于可以開始使用了意系,這里就以登錄接口和獲取用戶信息接口為例泥耀,展示兩個(gè)不同模塊的使用方法

4.1 根據(jù)接口文檔編寫公共模塊的IModelCom

這其實(shí)就是大家熟悉的Service

/**
 * 公共方法模塊
 */
public interface IModelCom {
    /**
     * 登錄
     *
     * @param map phone,passWord
     */
    @POST("api/user/login")
    Observable<CallResult<LoginResponseBean>> login(@Body Map<String, Object> map);

    /**
     * 注冊(cè)
     *
     * @param map phone蛔添,code痰催,passWord
     */
    @POST("api/user/register")
    Observable<CallResult<Void>> register(@Body Map<String, Object> map);

    /**
     * 忘記密碼
     *
     * @param map phone,code迎瞧,newPassWord
     */
    @POST("api/user/forget")
    Observable<CallResult<Void>> forgetPwd(@Body Map<String, Object> map);
}

另一個(gè)IModelUserCenter的寫法差不多的夸溶,忽略。

4.2來看看實(shí)現(xiàn)類

是否在主線程回調(diào)凶硅,看自己咯

public class ModelCom extends BaseModel implements IModelCom {

    private static final class IHolder {
        static final ModelCom i = new ModelCom();
    }

    public static ModelCom getInstance() {
        return IHolder.i;
    }

    private final IModelCom api;

    private ModelCom() {
        api = createService(UrlUtils.IP, IModelCom.class);
    }

    @Override
    public Observable<CallResult<LoginResponseBean>> login(Map<String, Object> map) {
        return callBackOnUi(api.login(map));
    }

    @Override
    public Observable<CallResult<Void>> register(Map<String, Object> map) {
        return callBackOnUi(api.register(map));
    }

    @Override
    public Observable<CallResult<Void>> forgetPwd(Map<String, Object> map) {
        return callBackOnUi(api.forgetPwd(map));
    }
}

4.3 契約類

就目前看來缝裁,契約類只定義了P層,原因是m層已經(jīng)是retrofit了足绅,而v層又只有回調(diào)參數(shù)捷绑。

/**
 * 公共模塊契約類
 */
public interface IContractCom {
    /**
     * 公共p層
     * {@link com.example.mvpdemo.mvp.presenter.PresenterCom}
     */
    interface IPresenterCom extends IBasePresenter {

        /**
         * 登錄
         *
         * @param phone    手機(jī)號(hào)
         * @param passWord 密碼
         */
        void login(String phone, String passWord, IBaseView<LoginResponseBean> view);

        /**
         * 注冊(cè)
         *
         * @param phone    手機(jī)號(hào)
         * @param passWord 密碼
         * @param code     驗(yàn)證碼
         * @param view     回調(diào)
         */
        void register(String phone, String passWord, String code, IBaseView<Void> view);

        /**
         * 忘記密碼
         *
         * @param phone       手機(jī)號(hào)
         * @param code        驗(yàn)證碼
         * @param newPassWord 新密碼
         * @param view        回調(diào)
         */
        void forGetPassWord(String phone, String code, String newPassWord, IBaseView<Void> view);
    }
}

4.4 P的實(shí)現(xiàn)

/**
 * 公共P
 */
public class PresenterCom extends BasePresenter<IModelCom> implements IContractCom.IPresenterCom {
    public PresenterCom(Activity activity) {
        super(activity);
    }

    @Override
    public void login(String phone, String passWord, IBaseView<LoginResponseBean> view) {
        Map<String, Object> map = createMap(2);
        map.put("phone", phone);
        map.put("passWord", passWord);
        start(module.login(map).map(resp -> {
            if (isSuccess(resp)) {
                //如果登錄成功了,則保存token氢妈,用戶名等信息
                Log.i("loginResult", resp.data.token);
                Log.i("loginResult", resp.data.userName);
                Log.i("loginResult", String.valueOf(resp.data.roleId));
            }
            return resp;
        }), view);
    }

    @Override
    public void register(String phone, String passWord, String code, IBaseView<Void> view) {
        Map<String, Object> map = createMap(3);
        map.put("phone", phone);
        map.put("passWord", passWord);
        map.put("code", code);
        start(module.register(map), view);
    }

    @Override
    public void forGetPassWord(String phone, String code, String newPassWord, IBaseView<Void> view) {
        Map<String, Object> map = createMap(3);
        map.put("phone", phone);
        map.put("code", code);
        map.put("newPassWord", newPassWord);
        start(module.forgetPwd(map), view);
    }

    @Override
    public IModelCom createModel() {
        return ModelCom.getInstance();
    }
}

4.5 頁面的使用

這是登錄頁的調(diào)用方法

public class LoginActivity extends AppCompatActivity {
    private ActivityLoginBinding view;
    private IContractCom.IPresenterCom presenterCom;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        view = ActivityLoginBinding.inflate(getLayoutInflater());
        setContentView(view.getRoot());
        presenterCom = new PresenterCom(this);
        view.btnLogin.setOnClickListener(v -> {
            String phone = view.edtPhone.getText().toString();
            String pwd = view.edtPwd.getText().toString();
            if (TextUtils.isEmpty(phone) || TextUtils.isEmpty(pwd)) {
                return;
            }
            presenterCom.login(phone, pwd, (b, i, s, data) -> {
                if (b) {
                    Toast.makeText(this, "登錄成功", Toast.LENGTH_SHORT).show();
                    startActivity(new Intent(this, MainActivity.class));
                    finish();
                } else {
                    Toast.makeText(this, s, Toast.LENGTH_SHORT).show();
                }
            });
        });
    }
}

這是首頁的調(diào)用方法

public class MainActivity extends AppCompatActivity {
    private ActivityMainBinding view;
    private IContractUser.IPresenterUser presenterUser;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        view = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(view.getRoot());
        //在這種使用方法下粹污,不需要手動(dòng)解綁
        presenterUser = new PresenterUser(this);
        presenterUser.getUserInfo((b, i, s, data) -> {
            if (b) {
                String str = "用戶名:" + data.userName + "\n生日:" + data.bir;
                view.tvMsg.setText(str);
            } else {
                Toast.makeText(this, s, Toast.LENGTH_SHORT).show();
            }
        });
    }
}

三、其他說明

3.1 demo下載地址

mvp_demo

3.2 我有一個(gè)想法

一般修改手機(jī)號(hào)首量、登錄和注冊(cè)壮吩,都會(huì)用到獲取驗(yàn)證碼的功能进苍,而這個(gè)功能并不需要在修改手機(jī)號(hào)的模塊和登錄注冊(cè)模塊都寫一次實(shí)現(xiàn)邏輯,要是有需要鸭叙,直接將獲取驗(yàn)證碼弄成單獨(dú)的模塊可能會(huì)更好

public interface IContractPhoneCode {
    interface IPresenterPhoneCode extends IBasePresenter {
        void getCode(String phone);
    }
}

如在登錄頁使用的時(shí)候觉啊,就這樣用

public class LoginActivity extends AppCompatActivity {
    private ActivityLoginBinding view;
    private IContractCom.IPresenterCom presenterCom;
    private IContractPhoneCode.IPresenterPhoneCode presenterPhoneCode;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        view = ActivityLoginBinding.inflate(getLayoutInflater());
        setContentView(view.getRoot());
        presenterCom = new PresenterCom(this);
        presenterPhoneCode = new PresenterPhoneCode(this);
        view.btnLogin.setOnClickListener(v -> {
            String phone = view.edtPhone.getText().toString();
            String pwd = view.edtPwd.getText().toString();
            if (TextUtils.isEmpty(phone) || TextUtils.isEmpty(pwd)) {
                return;
            }
            presenterCom.login(phone, pwd, (b, i, s, data) -> {
                if (b) {
                    Toast.makeText(this, "登錄成功", Toast.LENGTH_SHORT).show();
                    startActivity(new Intent(this, MainActivity.class));
                    finish();
                } else {
                    Toast.makeText(this, s, Toast.LENGTH_SHORT).show();
                }
            });
        });
        view.btnGetCode.setOnClickListener(v->{
            String phone = view.edtPhone.getText().toString();
            presenterPhoneCode.getCode(phone,(b,i,s,d)->{
                
            });
        });
    }
}

3.3 說明

知識(shí)無邊無際,文中如有不足之處沈贝,還望海涵杠人,若能提出您的高見,我將不勝感激宋下。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末搜吧,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子杨凑,更是在濱河造成了極大的恐慌,老刑警劉巖摆昧,帶你破解...
    沈念sama閱讀 218,284評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件撩满,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡绅你,警方通過查閱死者的電腦和手機(jī)伺帘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來忌锯,“玉大人伪嫁,你說我怎么就攤上這事∨伎澹” “怎么了张咳?”我有些...
    開封第一講書人閱讀 164,614評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)似舵。 經(jīng)常有香客問我脚猾,道長(zhǎng),這世上最難降的妖魔是什么砚哗? 我笑而不...
    開封第一講書人閱讀 58,671評(píng)論 1 293
  • 正文 為了忘掉前任龙助,我火速辦了婚禮,結(jié)果婚禮上蛛芥,老公的妹妹穿的比我還像新娘提鸟。我一直安慰自己,他們只是感情好仅淑,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,699評(píng)論 6 392
  • 文/花漫 我一把揭開白布称勋。 她就那樣靜靜地躺著,像睡著了一般漓糙。 火紅的嫁衣襯著肌膚如雪铣缠。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,562評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音蝗蛙,去河邊找鬼蝇庭。 笑死,一個(gè)胖子當(dāng)著我的面吹牛捡硅,可吹牛的內(nèi)容都是我干的哮内。 我是一名探鬼主播,決...
    沈念sama閱讀 40,309評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼壮韭,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼北发!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起喷屋,我...
    開封第一講書人閱讀 39,223評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤琳拨,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后屯曹,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體狱庇,經(jīng)...
    沈念sama閱讀 45,668評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,859評(píng)論 3 336
  • 正文 我和宋清朗相戀三年恶耽,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了密任。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,981評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡偷俭,死狀恐怖浪讳,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情涌萤,我是刑警寧澤淹遵,帶...
    沈念sama閱讀 35,705評(píng)論 5 347
  • 正文 年R本政府宣布,位于F島的核電站形葬,受9級(jí)特大地震影響合呐,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜笙以,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,310評(píng)論 3 330
  • 文/蒙蒙 一淌实、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧猖腕,春花似錦拆祈、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至老玛,卻和暖如春淤年,著一層夾襖步出監(jiān)牢的瞬間钧敞,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工麸粮, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留溉苛,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,146評(píng)論 3 370
  • 正文 我出身青樓弄诲,卻偏偏與公主長(zhǎng)得像愚战,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子齐遵,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,933評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容