(四)安卓框架搭建之MVP+Retrofit+RxJava優(yōu)化

BasePresenter的優(yōu)化

RxJava也需要管理生命周期王悍,即添加訂閱和解除訂閱。這里我們使之與presenter的addtachView()和detachView()同步鲤妥,修改BasePresenter里面內容如下:

package com.example.burro.demo.appframework.mvp.presenter;

import com.example.burro.demo.appframework.mvp.view.BaseView;

import rx.Subscription;
import rx.subscriptions.CompositeSubscription;

/**
 * Presenter基類歌溉。目的是統(tǒng)一處理綁定和解綁
 * Created by ex.zhong on 2017/9/23.
 */
public class BasePresenter<T extends BaseView> implements IPresenter<T> {

    protected T mView;
    protected CompositeSubscription mCompositeSubscription;

    @Override
    public void attachView(T mView) {
        mView = mView;
    }

    @Override
    public void detachView() {
        mView = null;
        unSubscribe();
    }

    //增加訂閱者
    protected void addSubscrebe(Subscription subscription) {
        if (mCompositeSubscription == null) {
            mCompositeSubscription = new CompositeSubscription();
        }
        mCompositeSubscription.add(subscription);
    }
    //解綁訂閱者
    protected void unSubscribe() {
        if (mCompositeSubscription != null) {
            mCompositeSubscription.unsubscribe();
        }
    }

}

RetrofitManager的封裝

看下先前TestPresenterImpl中的getMovieListData()內容云芦,每次請求數(shù)據(jù)都重復新建一個Retrofit厘擂,絕對不能接受的,因此我們要對retrofit進行統(tǒng)一的封裝嚼吞,其中需使用單例模式幔嫂。類中注釋比較清楚,直接看下面內容:

package com.example.burro.demo.appframework.http;

/**
 * Created by ex.zhong on 2017/9/24.
 */

import com.example.burro.demo.appframework.BaseApplication;
import com.example.burro.demo.appframework.util.FileUtils;
import com.example.burro.demo.appframework.util.LogUtils;
import com.example.burro.demo.appframework.util.NetWorkUtils;

import java.io.File;
import java.io.IOException;
import java.util.concurrent.TimeUnit;

import okhttp3.Cache;
import okhttp3.CacheControl;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory;
import retrofit2.converter.gson.GsonConverterFactory;
import rx.Observable;
import rx.Subscriber;
import rx.android.schedulers.AndroidSchedulers;
import rx.schedulers.Schedulers;

/**
 * retrofit封裝方法
 * Created by ex.zhong on 2017/8/13.
 */
public class RetrofitManager {
    private static RetrofitManager instance;
    public static int MAXSTALE = 60 * 60 * 24 * 28; // 無網絡時誊薄,設置超時為4周
    public static int CONNECT_OUTTIME = 10; // 鏈接超時時間 unit:S
    public static int READ_OUTTIME = 20; // 讀取數(shù)據(jù)超時時間 unit:S
    public static int WRITE_OUTTIME = 20; // 寫入超時時間 unit:S
    public static long CACHE_SIZE = 1024*1024*50; // 緩存大小 50M
    private final OkHttpClient mOkHttpClient;
    private final Retrofit mRetrofit;

    /**
     * 創(chuàng)建單例
     */
    public static RetrofitManager getInstace() {
        if (instance == null) {
            synchronized (RetrofitManager.class) {
                instance = new RetrofitManager();
            }
        }
        return instance;
    }

    /**
     * 獲取retrofit
     */
    public Retrofit getRetrofit() {
        return mRetrofit;
    }

    /**
     * 創(chuàng)建服務類
     * @return
     */
    public <T> T create(Class<T> service) {
        return mRetrofit.create(service);
    }

    /**
     * rx訂閱
     */
    public  <T> void toSubscribe(Observable<T> o, Subscriber<T> s) {

        o.subscribeOn(Schedulers.io())

                .unsubscribeOn(Schedulers.io())

                .observeOn(AndroidSchedulers.mainThread())

                .subscribe(s);

    }

    private RetrofitManager() {
        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        //打印日志
        if(LogUtils.LOG_FLAG){
            // https://drakeet.me/retrofit-2-0-okhttp-3-0-config
            HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
            loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
            builder.addInterceptor(loggingInterceptor);
        }
        //設置緩存
        File cacheFile=new File(FileUtils.getInstance().getHttpCachePath());
        Cache cache=new Cache(cacheFile,CACHE_SIZE);
        Interceptor cacheInterceptor=new Interceptor() {
            @Override
            public Response intercept(Chain chain) throws IOException {
                Request request = chain.request();
                if (!NetWorkUtils.isOnline(BaseApplication.getAppContext())) {
                    request = request.newBuilder()
                            .cacheControl(CacheControl.FORCE_CACHE)
                            .build();
                }
                Response response = chain.proceed(request);
                if (NetWorkUtils.isOnline(BaseApplication.getAppContext())) {
                    int maxAge = 0;
                    response.newBuilder()
                            .header("Cache-Control", "public, max-age=" + maxAge)
                            .removeHeader("Pragma")
                            .build();
                } else {
                    response.newBuilder()
                            .header("Cache-Control", "public, only-if-cached, max-stale=" + MAXSTALE)
                            .removeHeader("Pragma")
                            .build();
                }
                return response;
            }
        };
        //設置緩存
        builder.addNetworkInterceptor(cacheInterceptor);
        builder.addInterceptor(cacheInterceptor);
        builder.cache(cache);
        //設置超時
        builder.connectTimeout(CONNECT_OUTTIME, TimeUnit.SECONDS);
        builder.readTimeout(READ_OUTTIME, TimeUnit.SECONDS);
        builder.writeTimeout(WRITE_OUTTIME, TimeUnit.SECONDS);
        //錯誤重連
        builder.retryOnConnectionFailure(true);
        mOkHttpClient = builder.build();
        mRetrofit = new Retrofit.Builder()
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .baseUrl(HttpConfig.BASE_URL)
                .client(mOkHttpClient)
                .build();

    }

}

這里注意的是我把rxjava的訂閱也放在了RetrofitManager內履恩,這樣不必每次在presenter實現(xiàn)類里重復的寫下面方法

public  <T> void toSubscribe(Observable<T> o, Subscriber<T> s) {
       o.subscribeOn(Schedulers.io())
               .unsubscribeOn(Schedulers.io())
               .observeOn(AndroidSchedulers.mainThread())
               .subscribe(s);
   }

Subscriber的封裝

我們再看時解決每次重寫Subscriber的這四個onstart()、onCompleted()呢蔫、onError()切心、onNext()的問題,在此大致的說一下思路了片吊,創(chuàng)建一個ProgressSubscriber.java讓它繼承自Subscriber绽昏,并添加網絡請求時所需要的加載框,在onStart()方法中顯示加載框【加載框我會傳一個參數(shù)來控制是否需要加載】俏脊,在onCompleted()全谤、onError()隱藏加載框,再通過接口回調將onNext()中產生的數(shù)據(jù)回調給presenter中去通知UI更新就行爷贫,
這里一共新建了5個類认然,如下:

ProgressCancelListener

主要是dialog取消時的回調時的接口补憾,會在取消時執(zhí)行解綁操作

package com.example.burro.demo.appframework.http;
/*progressDialog,消失時回調
 *Created by ex.zhong on 2017/9/25.
 */
public interface ProgressCancelListener {
    void onCancelProgress();
}

SubscriberOnResponseListenter

主要是Subscriber回調時封裝的接口,即在回調中只處理onNext()onError()

package com.example.burro.demo.appframework.http;


import com.example.burro.demo.dataframework.model.BaseResultBean;

/**Subscriber回調卷员,統(tǒng)一歸結為next()【成功】 error()【失敗】
 * Created by ex.zhong on 2017/9/25
 */
public interface SubscriberOnResponseListenter<T> {
    void next(T t);
    void error(BaseResultBean t);
}


ProgressDialogHandler

ProgressDialogHandler實為handler盈匾,內部主要控制dialog的顯示和隱藏

package com.example.burro.demo.appframework.http;

import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
import android.os.Handler;
import android.os.Message;

import com.afollestad.materialdialogs.MaterialDialog;
import com.afollestad.materialdialogs.Theme;


/** 加載框展示,隱藏控制
 * Created by ex.zhong on 2017/9/25.
 */
public class ProgressDialogHandler extends Handler {
    public static final int SHOW_PROGRESS_DIALOG = 1;
    public static final int DISMISS_PROGRESS_DIALOG = 2;
    private Context context;
    private boolean cancelable;
    private ProgressCancelListener mProgressCancelListener;

    private MaterialDialog mProgressDialog;

    public ProgressDialogHandler(Context context, ProgressCancelListener mProgressCancelListener, boolean cancelable) {
        super();
        this.context = context;
        this.mProgressCancelListener = mProgressCancelListener;
        this.cancelable = cancelable;
    }
    //顯示dialog
    private void initProgressDialog() {
        if (mProgressDialog == null) {
            mProgressDialog = new MaterialDialog.Builder(context)
                    .canceledOnTouchOutside(cancelable)
                    .content("正在加載...")
                    .progress(true, 0)
                    .theme(Theme.LIGHT)
                    .build();
            mProgressDialog.setOnCancelListener(new OnCancelListener() {
                @Override
                public void onCancel(DialogInterface dialogInterface) {
                    mProgressCancelListener.onCancelProgress();
                }
            });
            if (!mProgressDialog.isShowing()) {
                mProgressDialog.show();
            }
        }
    }
    //隱藏dialog
    private void dismissProgressDialog() {
        if (mProgressDialog != null) {
            mProgressDialog.dismiss();
        }
    }

    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {

            case SHOW_PROGRESS_DIALOG:
                initProgressDialog();
                break;

            case DISMISS_PROGRESS_DIALOG:
                dismissProgressDialog();
                break;

        }
    }
}

ProgressSubscriber

ProgressSubscriber繼承Subscriber,是對Subscriber的進一步封裝毕骡,即在請求開始時提示dialog【可控制】削饵,完成時,隱藏dialog等

package com.example.burro.demo.appframework.http;

import android.content.Context;
import android.net.ParseException;

import com.example.burro.demo.dataframework.model.BaseResultBean;
import com.google.gson.JsonParseException;

import org.apache.http.conn.ConnectTimeoutException;
import org.json.JSONException;

import java.net.ConnectException;
import java.net.SocketTimeoutException;

import retrofit2.adapter.rxjava.HttpException;
import rx.Subscriber;

/** 并添加網絡請求時所需要的加載框未巫,異常情況統(tǒng)一處理
 * Created by ex.zhong on 2017/9/25.
 */
public class ProgressSubscriber<T> extends Subscriber<T> implements ProgressCancelListener {

    private SubscriberOnResponseListenter mSubscriberOnResponseListenter;
    private ProgressDialogHandler mProgressDialogHandler;
    private boolean isShowProgress;

    public ProgressSubscriber(SubscriberOnResponseListenter mSubscriberOnResponseListenter, Context context, boolean isShowProgress) {
        this.mSubscriberOnResponseListenter = mSubscriberOnResponseListenter;
        this.isShowProgress = isShowProgress;
        mProgressDialogHandler = new ProgressDialogHandler(context, this, false);
    }

    /**
     * 開始訂閱的時候顯示加載框
     */
    @Override
    public void onStart() {
        if (isShowProgress)
            showProgressDialog();
    }

    @Override
    public void onCompleted() {
        dismissProgressDialog();
    }

    @Override
    public void onError(Throwable e) {
        dismissProgressDialog();
        BaseResultBean errorBean;
        //錯誤碼要以服務器返回錯誤碼為準窿撬。此處只是舉例
        if (e instanceof HttpException) {             //HTTP 錯誤
            HttpException httpException = (HttpException) e;
            switch (httpException.code()) {
                case BaseResultBean.ERROR_CODE_UNAUTHORIZED:
                    errorBean = new BaseResultBean(BaseResultBean.ERROR_CODE_UNAUTHORIZED, "當前請求需要用戶驗證");
                    break;
                case BaseResultBean.ERROR_CODE_FORBIDDEN:
                    errorBean = new BaseResultBean(BaseResultBean.ERROR_CODE_FORBIDDEN, "但是拒絕執(zhí)行它");
                    break;
                case BaseResultBean.ERROR_CODE_NOT_FOUND:
                    errorBean = new BaseResultBean(BaseResultBean.ERROR_CODE_NOT_FOUND, "服務器異常,請稍后再試");
                    break;
                default:
                    //其它均視為網絡錯誤
                    errorBean = new BaseResultBean(BaseResultBean.ERROR_CODE_FORBIDDEN, "網絡錯誤");
                    break;
            }
        } else if (e instanceof JsonParseException
                || e instanceof JSONException
                || e instanceof ParseException) {
            errorBean = new BaseResultBean(BaseResultBean.ERROR_CODE_PARSE_JSON, "解析錯誤");
        } else if (e instanceof ConnectException || e instanceof SocketTimeoutException || e instanceof ConnectTimeoutException) {
            errorBean = new BaseResultBean(BaseResultBean.ERROR_CODE_NETWORK, "網絡連接失敗叙凡,請檢查是否聯(lián)網");
        } else {
            errorBean = new BaseResultBean(BaseResultBean.ERROR_CODE_UNKNOWN, "未知錯誤");
        }
        mSubscriberOnResponseListenter.error(errorBean);
    }
    //成功執(zhí)行下一步
    @Override
    public void onNext(T t) {
        mSubscriberOnResponseListenter.next(t);
    }

    @Override
    public void onCancelProgress() {
        if (!this.isUnsubscribed()) {
            this.unsubscribe();
        }
    }
    //顯示dialog
    private void showProgressDialog() {
        if (mProgressDialogHandler != null) {
            mProgressDialogHandler.obtainMessage(ProgressDialogHandler.SHOW_PROGRESS_DIALOG).sendToTarget();
        }
    }
    //隱藏dialog
    private void dismissProgressDialog() {
        if (mProgressDialogHandler != null) {
            mProgressDialogHandler.obtainMessage(ProgressDialogHandler.DISMISS_PROGRESS_DIALOG).sendToTarget();
        }
    }
}


HttpResultFunc

HttpResultFunc對返回的結果進行預處理尤仍。也是鏈式結構中重要的一環(huán)。具體請看注釋:

package com.example.framework.appframework.http;

import com.example.framework.dataframework.model.BaseResultBean;

import rx.functions.Func1;

/**預處理:
 * 由于我們每次請求的時候有可能會出現(xiàn)一些請求的錯誤,
 * 若返回碼非正確狭姨,且服務器返回的錯誤信息可能是用戶讀不懂的信息。
 * 比如豆瓣一個錯誤碼1001 msg信息為uri_not_found苏遥,此時我們可以轉換成用戶可以讀懂的文字饼拍,即可設置msg為“資源不存在”
 * Created by ex.zhong on 2017/9/25.
 */
public class HttpResultFunc<T> implements Func1<T,T> {
    @Override
    public T call(T t) {
        if(t!=null&&t instanceof BaseResultBean){
            int code=((BaseResultBean) t).getCode();
            switch (code){
                case 1001:
                    ((BaseResultBean) t).setMessage("資源不存在");
                    break;
                case 1002:
                    ((BaseResultBean) t).setMessage("參數(shù)不全");
                    break;
                //這里不再全部寫出,實際根據(jù)情況寫自己的業(yè)務處理
            }
        }
        return t;
    }
}

好了田炭,封裝部分全部結束师抄,我們回頭過來看看TestPresenterImpl

package com.example.burro.demo.appbiz.test;

import android.content.Context;

import com.example.burro.demo.appframework.http.HttpResultFunc;
import com.example.burro.demo.appframework.http.ProgressSubscriber;
import com.example.burro.demo.appframework.http.RetrofitManager;
import com.example.burro.demo.appframework.http.SubscriberOnResponseListenter;
import com.example.burro.demo.appframework.mvp.presenter.BasePresenter;
import com.example.burro.demo.appbiz.test.TestContract.*;
import com.example.burro.demo.appframework.util.StringUtils;
import com.example.burro.demo.databiz.model.test.MovieListBean;
import com.example.burro.demo.databiz.service.ApiService;
import com.example.burro.demo.dataframework.model.BaseResultBean;

import java.util.HashMap;

import rx.Subscriber;
import rx.Subscription;

/**測試presenter
 * Created by ex.zhong on 2017/9/23.
 */
public class TestPresenterImpl extends BasePresenter<View> implements Presenter {

    public TestPresenterImpl(Context mContext) {
        this.mContext = mContext;
    }
    @Override
    public void getMovieListData(int start, int count) {
        //獲取數(shù)據(jù)
        HashMap<String,String> map=new HashMap<>();
        map.put("start", StringUtils.getString(start));
        map.put("count", StringUtils.getString(count));
        rx.Observable<MovieListBean> observable = RetrofitManager.getInstace().create(ApiService.class).getMovieListData(map).map((new HttpResultFunc<MovieListBean>()));
        Subscription rxSubscription = new ProgressSubscriber<>(new SubscriberOnResponseListenter<MovieListBean>() {
            @Override
            public void next(MovieListBean testBean) {
                mView.setMovieListData(testBean);
            }
            @Override
            public void error(BaseResultBean errResponse) {
                mView.showError(errResponse);
            }
        },mContext,false);
        RetrofitManager.getInstace().toSubscribe(observable, (Subscriber) rxSubscription);
        addSubscrebe(rxSubscription);
    }
}

是不是簡單了許多!教硫!注意封裝完以后要查看在TestActivity頁面是否打印出請求結果叨吮!

相關鏈接

(五)安卓框架搭建之BaseFragment,MainActivity, Toolbar細化

github源碼地址

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末瞬矩,一起剝皮案震驚了整個濱河市茶鉴,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌景用,老刑警劉巖涵叮,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異伞插,居然都是意外死亡割粮,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進店門媚污,熙熙樓的掌柜王于貴愁眉苦臉地迎上來舀瓢,“玉大人,你說我怎么就攤上這事耗美【┧瑁” “怎么了航缀?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長朵锣。 經常有香客問我谬盐,道長,這世上最難降的妖魔是什么诚些? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任飞傀,我火速辦了婚禮,結果婚禮上诬烹,老公的妹妹穿的比我還像新娘砸烦。我一直安慰自己,他們只是感情好绞吁,可當我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布幢痘。 她就那樣靜靜地躺著,像睡著了一般家破。 火紅的嫁衣襯著肌膚如雪颜说。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天汰聋,我揣著相機與錄音门粪,去河邊找鬼。 笑死烹困,一個胖子當著我的面吹牛玄妈,可吹牛的內容都是我干的。 我是一名探鬼主播髓梅,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼拟蜻,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了枯饿?” 一聲冷哼從身側響起酝锅,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎奢方,沒想到半個月后屈张,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡袱巨,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年阁谆,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片愉老。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡场绿,死狀恐怖,靈堂內的尸體忽然破棺而出嫉入,到底是詐尸還是另有隱情焰盗,我是刑警寧澤璧尸,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站熬拒,受9級特大地震影響爷光,放射性物質發(fā)生泄漏。R本人自食惡果不足惜澎粟,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一蛀序、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧活烙,春花似錦徐裸、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至回懦,卻和暖如春气笙,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背怯晕。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工潜圃, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人贫贝。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像稚晚,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子客燕,可洞房花燭夜當晚...
    茶點故事閱讀 42,792評論 2 345

推薦閱讀更多精彩內容