打造終極MVP+Retrofit2+okhttp3+Rxjava2網絡請求,開發實用,簡約

抓住人生中的一分一秒,勝過虛度中的一月一年!

小做個動圖開篇引題


懶洋洋.gif

前言

目前較火的網絡框架有MVP+Retrofit2+okhttp3+Rxjava2,于是也加入了使用行列,本框架為Retrofit基本寫法及特殊情況處理衍生,為大家學習使用提供幫助,本次優化對使用過程中所遇到問題進行總結,基本滿足實際開發需求,有不足地方我將繼續完善



相關業務需求及解決方案
一、 MVP+Retrofit2+okhttp3+Rxjava2框架基本搭建及使用
二、BaseActivityBaseFragment封裝協調框架更好使用
三、 Android部分手機4G網第一次請求很慢(wifi正常)解決方案
四、 Retrofit運行時動態改變BaseUrl解決方案
五、 Retrofit文件上傳(本片文章介紹中包含進度條)
六、 Retrofit文件下載(含進度條)
七、 Retrofit,Gson解析,請求返回的類型不統一,假如double返回的是null
八、 Retrofit實現cookie自動化管理
九、 路由判斷第二種解決方案(文章為舊版,提供思路)
十、 Retrofit配置及各情況處理(緩存攔截、日志打印、替換接口內容、參數添加等)
十一、 后記
十二、 本文譩在一篇文章搞定所有,上述描述文章都有講解

一、MVP+Retrofit2+okhttp3+Rxjava2框架基本搭建

1、 相關依賴引用
    implementation 'com.squareup.okhttp3:okhttp:3.11.0'
    implementation 'com.squareup.retrofit2:retrofit:2.4.0'
    //ConverterFactory的Gson依賴包
    implementation 'com.squareup.retrofit2:converter-gson:2.4.0'
    //CallAdapterFactory的Rx依賴包
    implementation 'com.squareup.retrofit2:adapter-rxjava2:2.4.0'
    implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'
    //cookie管理
    implementation 'com.github.franmontiel:PersistentCookieJar:v1.0.1'
    //日志
    implementation 'com.squareup.okhttp3:logging-interceptor:3.10.0'
2、 創建接口類ApiServer,定義接口方法
public interface ApiServer {
    @FormUrlEncoded
    @POST("/api/table_list/")
    Observable<BaseModel<Object>> getCeShi(@FieldMap HashMap<String, String> params);
}
3、 創建Retrofit
public class ApiRetrofit {
    private static ApiRetrofit mApiRetrofit;
    private Retrofit retrofit;
    private ApiServer apiServer;
    private static final int DEFAULT_TIMEOUT = 15;
    public static String mBaseUrl = BaseContent.baseUrl;

    public ApiRetrofit() {
        OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder();
        httpClientBuilder
                .cookieJar(new CookieManger(App.getContext()))
                .connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
                .writeTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
                .readTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS);

        retrofit = new Retrofit.Builder()
                .baseUrl(mBaseUrl)
                .addConverterFactory(GsonConverterFactory.create())
                //支持RxJava2
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .client(httpClientBuilder.build())
                .build();

        apiServer = retrofit.create(ApiServer.class);
    }

    public static ApiRetrofit getInstance() {
        if (mApiRetrofit == null) {
            synchronized (Object.class) {
                if (mApiRetrofit == null) {
                    mApiRetrofit = new ApiRetrofit();
                }
            }
        }
        return mApiRetrofit;
    }

    public ApiServer getApiService() {
        return apiServer;
    }
}
4、 定義常用的接口,如網絡請求開始,結束,進度條加載,錯誤碼等
public interface BaseView {
    //顯示dialog
    void showLoading();
    //隱藏 dialog
    void hideLoading();
    //顯示錯誤信息
    void showError(String msg);
    //錯誤碼
    void onErrorCode(BaseModel model);
    //進度條顯示
    void showProgress();
    //進度條隱藏
    void hideProgress();
    //文件下載進度監聽
    void onProgress(int progress);
}
5、 BaseModel封裝

封裝理由:一個項目一般情況下json返回格式外層都是統一的

public class BaseModel<T> implements Serializable {
    private String reason;
    private int error_code;
    private T result;
    public BaseModel(String reason, int error_code) {
        this.reason = reason;
        this.error_code = error_code;
    }
    public String getReason() {
        return reason;
    }
    public void setReason(String reason) {
        this.reason = reason;
    }
    public int getError_code() {
        return error_code;
    }
    public void setError_code(int error_code) {
        this.error_code = error_code;
    }
    public T getResult() {
        return result;
    }
    public void setResult(T result) {
        this.result = result;
    }
}
6、 BasePresenter封裝,協調m層v層的中間信使通用代碼封裝
public class BasePresenter<V extends BaseView> {
    private CompositeDisposable compositeDisposable;
    public V baseView;
    protected ApiServer apiServer = ApiRetrofit.getInstance().getApiService();

    public BasePresenter(V baseView) {
        this.baseView = baseView;
    }
    /**
     * 解除綁定
     */
    public void detachView() {
        baseView = null;
        removeDisposable();
    }
    /**
     * 返回 view
     *
     * @return
     */
    public V getBaseView() {
        return baseView;
    }

    public void addDisposable(Observable<?> observable, BaseObserver observer) {
        if (compositeDisposable == null) {
            compositeDisposable = new CompositeDisposable();
        }
        compositeDisposable.add(observable.subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribeWith(observer));
    }

    public void addFileDisposable(Observable<?> observable, FileObserver observer) {
        if (compositeDisposable == null) {
            compositeDisposable = new CompositeDisposable();
        }
        compositeDisposable.add(observable.subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribeWith(observer));
    }

    public void removeDisposable() {
        if (compositeDisposable != null) {
            compositeDisposable.dispose();
        }
    }
}
6、 BaseObserver封裝,數據等異常處理路由

封裝理由:處理業務邏輯路由,與錯誤信息處理
注:在正常開發中,前后臺會約定相關字段如code的值代表各情況,在此路由通道,另一種路由方案是重寫Gson解析類,文章不做體現,demo中有相關代碼

public abstract class BaseObserver<T> extends DisposableObserver<BaseModel<T>> {
    protected BaseView view;
    /**
     * 網絡連接失敗  無網
     */
    public static final int NETWORK_ERROR = 100000;
    /**
     * 解析數據失敗
     */
    public static final int PARSE_ERROR = 1008;
    /**
     * 網絡問題
     */
    public static final int BAD_NETWORK = 1007;
    /**
     * 連接錯誤
     */
    public static final int CONNECT_ERROR = 1006;
    /**
     * 連接超時
     */
    public static final int CONNECT_TIMEOUT = 1005;
    /**
     * 其他所有情況
     */
    public static final int NOT_TRUE_OVER = 1004;

    public BaseObserver(BaseView view) {
        this.view = view;
    }

    public BaseObserver() {
    }

    @Override
    protected void onStart() {
        if (view != null) {
            view.showLoading();
        }
    }

    @Override
    public void onNext(BaseModel<T> o) {
//        T t = o.getData();
        try {
            if (view != null) {
                view.hideLoading();
            }
            if (o.getError_code() == BaseContent.basecode) {
                onSuccess(o);
            } else {
                if (view != null) {
                    view.onErrorCode(o);
                }
                //非  true的所有情況
                onException(PARSE_ERROR, o.getReason());
            }
        } catch (Exception e) {
            e.printStackTrace();
            onError(e.toString());
        }
    }

    @Override
    public void onError(Throwable e) {
        if (view != null) {
            view.hideLoading();
        }
        if (e instanceof HttpException) {
            //   HTTP錯誤
            onException(BAD_NETWORK, "");
        } else if (e instanceof ConnectException
                || e instanceof UnknownHostException) {
            //   連接錯誤
            onException(CONNECT_ERROR, "");
        } else if (e instanceof InterruptedIOException) {
            //  連接超時
            onException(CONNECT_TIMEOUT, "");
        } else if (e instanceof JsonParseException
                || e instanceof JSONException
                || e instanceof ParseException) {
            //  解析錯誤
            onException(PARSE_ERROR, "");
            e.printStackTrace();
        } else {
            if (e != null) {
                onError(e.toString());
            } else {
                onError("未知錯誤");
            }
        }
    }

    private void onException(int unknownError, String message) {
        switch (unknownError) {
            case CONNECT_ERROR:
                onError("連接錯誤");
                break;
            case CONNECT_TIMEOUT:
                onError("連接超時");
                break;
            case BAD_NETWORK:
                onError("網絡超時");
                break;
            case PARSE_ERROR:
                onError("數據解析失敗");
                break;
            //非true的所有情況
            case NOT_TRUE_OVER:
                onError(message);
                break;
            default:
                break;
        }
    }
    //消失寫到這 有一定的延遲  對dialog顯示有影響
    @Override
    public void onComplete() {
       /* if (view != null) {
            view.hideLoading();
        }*/
    }
    public abstract void onSuccess(BaseModel<T> o);
    public abstract void onError(String msg);
}

如上,相關框架已封裝完畢,下面看下如何使用

8、 定義MainView,并繼承BaseView
public interface MainView extends BaseView {
    void onTextSuccess(BaseModel<TextBean> o);
}
9、 定義MainPresenter,并繼承BasePresenter
public class MainPresenter extends BasePresenter<MainView> {
    public MainPresenter(MainView baseView) {
        super(baseView);
    }
    /**
     * 寫法好多種  怎么順手怎么來
     */
    public void getTextApi() {
        HashMap<String, String> params = new HashMap<>();
        params.put("type", "junshi");
        params.put("key", "2c1cb93f8c7430a754bc3ad62e0fac06");
        addDisposable(apiServer.getText(params), new BaseObserver(baseView) {
            @Override
            public void onSuccess(BaseModel o) {
                baseView.onTextSuccess((BaseModel<TextBean>) o);
            }
            @Override
            public void onError(String msg) {
                if (baseView != null) {
                    baseView.showError(msg);
                }
            }
        });
    }
}
10、 在Activity中進行網絡請求,如下
public class MainActivity extends AppCompatActivity implements MainView {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        MainPresenter presenter = new MainPresenter(this);
        //網絡請求
        presenter.getTextApi();
    }
    @Override
    public void onTextSuccess(BaseModel<TextBean> o) {
    //我是網絡請求成功后的結果
    }

    @Override
    public void showLoading() {
    //網絡開始請求時我會執行
    }
    @Override
    public void hideLoading() {
    //網絡請求完畢時我會執行
    }
    @Override
    public void showError(String msg) {
    //異常情況下我會提示內容
    }
    @Override
    public void onErrorCode(BaseModel model) {
    //在異常時候我會回調
    }
    @Override
    public void showProgress() {
    //需要顯示進度條時候我是開始標識
    }
    @Override
    public void hideProgress() {
    //需要隱藏進度條時候我是結束標識
    }
    @Override
    public void onProgress(int progress) {
    //進度條最主要的是我
    }
}

第一章結束(MVP+Retrofit2+okhttp3+Rxjava2框架基本搭建及使用)
activity內容太多了,閱讀性差,引發了強烈的需求對Activity封裝與Fragment封裝,請往下看

二、對BaseActivity、BaseFragment封裝協調框架更好使用

淺談BaseActivity寫法,促使我們更高效開發
Fragment懶加載實現,BaseFragment封裝
有興趣的擼友們可以轉戰我其他倆篇文章,本文意在一篇掌握網絡請求,如下繼續介紹如何封裝

1、 BaseActivity相關內容進行封裝,BaseFragment可到demo中查看
public abstract class BaseActivity<P extends BasePresenter> extends AppCompatActivity implements BaseView {
    protected final String TAG = this.getClass().getSimpleName();
    public Context mContext;
    protected P mPresenter;

    protected abstract P createPresenter();

    private LoadingDialog loadingDialog;
    private ProgressDialog progressDialog;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mContext = this;
        setContentView(getLayoutId());
        mPresenter = createPresenter();
        setStatusBar();

        this.initData();
    }

    /**
     * 獲取布局ID
     *
     * @return
     */
    protected abstract int getLayoutId();

    /**
     * 數據初始化操作
     */
    protected abstract void initData();

    /**
     * 此處設置沉浸式地方
     */
    protected void setStatusBar() {
        StatusBarUtil.setTranslucentForImageViewInFragment(this, 0, null);
    }

    /**
     * 封裝toast方法(自行定制實現)
     *
     * @param str
     */
    public void showToast(String str) {
        ToastUtils.show(str);
    }

    public void showLongToast(String str) {
        ToastUtils.show(str);
    }

    @Override
    public void showError(String msg) {
        showToast(msg);
    }

    /**
     * 返回所有狀態  除去指定的值  可設置所有(根據需求)
     *
     * @param model
     */
    @Override
    public void onErrorCode(BaseModel model) {
        if (model.getError_code() == 10000000) {
            //處理些后續邏輯   如果某個頁面不想實現  子類重寫這個方法  將super去掉  自定義方法
//            App.put();
//            startActivity(LoginActivity.class);
        }
    }

    @Override
    public void showLoading() {
        showLoadingDialog();
    }

    @Override
    public void hideLoading() {
        dissMissDialog();
    }

    public void showLoadingDialog() {
        showLoadingDialog("加載中...");
    }

    /**
     * 加載  黑框...
     */
    public void showLoadingDialog(String msg) {
        if (loadingDialog == null) {
            loadingDialog = new LoadingDialog(this);
        }
        loadingDialog.setMessage(msg);
        if (!loadingDialog.isShowing()) {
            loadingDialog.show();
        }
    }

    /**
     * 消失  黑框...
     */
    public void dissMissDialog() {
        if (loadingDialog != null) {
            loadingDialog.dismiss();
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mPresenter != null) {
            mPresenter.detachView();
        }
        if (loadingDialog != null) {
            loadingDialog.dismiss();
        }
        if (progressDialog != null) {
            progressDialog.dismiss();
        }
        if (mPresenter != null) {
            mPresenter.detachView();
        }
    }

    /**
     * 進度條顯示
     */
    @Override
    public void showProgress() {
        if (progressDialog == null) {
            progressDialog = new ProgressDialog(this);
        }
        progressDialog.getProgressBar().performAnimation();
        if (!progressDialog.isShowing()) {
            progressDialog.show();
        }
    }

    /**
     * 進度條隱藏
     */
    @Override
    public void hideProgress() {
        if (progressDialog != null) {
            progressDialog.getProgressBar().releaseAnimation();
            progressDialog.dismiss();
        }
    }

    /**
     * 進度條 回調
     * @param progress
     */
    @Override
    public void onProgress(int progress) {
        if (progressDialog != null) {
            progressDialog.updateProgress(progress);
        }
    }
}
2、 定義MainView,并繼承BaseView(同上)
3、 定義MainPresenter,并繼承BasePresenter(同上)
4、 在Activity中進行網絡請求,如下
public class MainActivity extends BaseActivity<MainPresenter> implements MainView {
    @Override
    protected MainPresenter createPresenter() {
        return new MainPresenter(this);
    }

    @Override
    protected int getLayoutId() {
        return R.layout.activity_main;
    }

    @Override
    protected void initData() {
        //網絡請求
        mPresenter.getTextApi();
    }

    @Override
    public void onTextSuccess(BaseModel<TextBean> o) {
        //我是網絡請求成功后的結果
    }
}

三、Android部分手機4G網第一次請求很慢(wifi正常)解決方案

Android部分手機4G網第一次請求很慢(wifi正常)解決方案

1、出現此類問題場景

經測試,一般手機都沒有發現網絡請求慢現象,只有部分手機會出現,如(小米手機)

2、出現此類問題現象

手機4G網網絡請求特別慢,第一次進入app加載網絡會出現30s+延遲現象,只有第一次慢,第二次網絡訪問回歸正常,但重新進入又會出現網絡延遲30s+

3、出現此類問題排查

通過網上查閱資料,都趨向于ipv4、ipv6地址問題,經對應手機測試發現,DNS 解析的 IP 地址①.連接到wifi,只解析到 ipv4 地址,②.連接到4G網,解析到了ipv4、ipv6倆個地址,但是ipv6默認為集合中的第一個,是否我們可以嘗試修改集合第一個為ipv4呢?

4、出現此類問題解決方案
解決方案:集合中ipv4,ipv6調換位置,將ipv4當到集合首位
調換集合中ipv4 ipv6位置,將ipv4當到集合首位
import okhttp3.Dns;

public class ApiDns implements Dns {
    @Override
    public List<InetAddress> lookup(String hostname) throws UnknownHostException {
        if (hostname == null) {
            throw new UnknownHostException("hostname == null");
        } else {
            try {
                List<InetAddress> mInetAddressesList = new ArrayList<>();
                InetAddress[] mInetAddresses = InetAddress.getAllByName(hostname);
                for (InetAddress address : mInetAddresses) {
                    if (address instanceof Inet4Address) {
                        mInetAddressesList.add(0, address);
                    } else {
                        mInetAddressesList.add(address);
                    }
                }
                return mInetAddressesList;
            } catch (NullPointerException var4) {
                UnknownHostException unknownHostException = new UnknownHostException("Broken system behaviour");
                unknownHostException.initCause(var4);
                throw unknownHostException;
            }
        }
    }
}
第二步,將自定義方法插入到okhttp中
  OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder();

        ClearableCookieJar cookieJar =
                new PersistentCookieJar(new SetCookieCache(), new SharedPrefsCookiePersistor(AppUMS.mContent));

        httpClientBuilder
                .cookieJar(cookieJar)
                .addInterceptor(interceptor)
                .addInterceptor(new HeadUrlInterceptor())
                //設置請求超時時長
                .connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
                .writeTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
                .readTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
                .dns(new ApiDns());//添加如下方法

四、Retrofit運行時動態改變BaseUrl解決方案

Retrofit運行時動態改變BaseUrl解決方案

1、出現此類問題場景

Android正式項目中可能會涉及到多個BaseUrl,使用Retrofit開發者可能會遇到多BaseUrl不是很好處理情況

2、第一種解決方案

簡單粗暴解決方案,利用Retrofit請求優先級,因為Retrofit支持全路徑,比如

 @GET("http://www.baidu.com")
 Observable<Object> getApi(@Path("param") String param);
3、第二種解決方案

Retrofit默認只能設置一個BaseUrl,沒有提供其Api去修改,所以我們只能通過其他方案去實現,網上也有很多介紹的,但嘗試用了下感覺很不理想,于是自己稍加封裝了下,思路其實簡單。

思路:一個Retrofit只能設置一個BaseUrl,這樣我們可以創建多個Retrofit不就可以了嗎?但如果一個請求創建一個Retrofit必然是不理想的,所以我們可以有幾個BaseUrl創建幾個,有人會說這樣不會造成內存的開銷嗎?答案是不會的,一個項目中也不會出現N多個BaseUrl,所以這點開銷不用過于糾結

代碼實現:在代碼設計時可以盡可能去優化,所以當我們用到此BaseUrl時,再去創建,用不到不創建,這樣便會出現個問題,怎樣知道我應該使用哪個RetrofitRetrofit怎么去保存等問題,本人思路是創建成功便添加到集合緩存下載,使用的時候去比對集合中BaseUrl和當前是否匹配,如果一致從集合中獲取,如果不一致去創建新的,如果使用沒有傳入BaseUrl便用默認的,最基本的判斷,實現代碼如下

4、正常創建Retrofit
public class ApiRetrofit {

    private static ApiRetrofit mApiRetrofit;
    private Retrofit retrofit;
    private ApiServer apiServer;
    public static String mBaseUrl = BaseContent.baseUrl;

    public ApiRetrofit() {
        OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder();
        httpClientBuilder
                .connectTimeout(10, TimeUnit.SECONDS)
                .writeTimeout(10, TimeUnit.SECONDS)
                .readTimeout(10, TimeUnit.SECONDS)
                .retryOnConnectionFailure(true);//錯誤重聯
        retrofit = new Retrofit.Builder()
                .baseUrl(mBaseUrl )
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .client(httpClientBuilder.build())
                .build();

        apiServer = retrofit.create(ApiServer.class);
    }

    public static ApiRetrofit getInstance() {
        if (mApiRetrofit == null) {
            synchronized (Object.class) {
                if (mApiRetrofit == null) {
                    mApiRetrofit = new ApiRetrofit();
                }
            }
        }
        return mApiRetrofit;
    }
}
5、對創建Retrofit稍加封裝,已適應我們的需求

新建保存對象的集合

private static List<Retrofit> mRetrofitList = new ArrayList<>();
private static List<ApiRetrofit> mApiRetrofitList = new ArrayList<>();

修改創建時候的邏輯,如果請求接口時傳入BaseUrl,檢測BaseUrl是否為空,如果為空使用默認接口,如果不為空,再從緩存的Retrofit中查找是否已經才創建過了,如果創建了用緩存的,如果沒有創建則創建

注:這塊可以用正則檢查下傳入的url是否為正規的域名,再做下判斷

//創建Retrofit代碼中加入
 apiServer = retrofit.create(ApiServer.class);
 mRetrofitList.add(retrofit);

public static ApiRetrofit getInstance() {
        mBaseUrl = BaseContent.baseUrl;

        int mIndex = -1;
        for (int i = 0; i < mRetrofitList.size(); i++) {
            if (BaseContent.baseUrl.equals(mRetrofitList.get(i).baseUrl().toString())) {
                mIndex = i;
                break;
            }
        }

        //新的baseUrl
        if (mIndex == -1) {
            synchronized (Object.class) {
                mApiRetrofit = new ApiRetrofit();
                mApiRetrofitList.add(mApiRetrofit);
                return mApiRetrofit;
            }
        } else {
            //以前已經創建過的baseUrl
            return mApiRetrofitList.get(mIndex);
        }
    }


    public static ApiRetrofit getInstance(String baseUrl) {
        if (!TextUtils.isEmpty(baseUrl)) {
            mBaseUrl = baseUrl;
        } else {
            mBaseUrl = BaseContent.baseUrl;
        }

        int mIndex = -1;
        for (int i = 0; i < mRetrofitList.size(); i++) {
            if (baseUrl.equals(mRetrofitList.get(i).baseUrl().toString())) {
                mIndex = i;
                break;
            }
        }

        //新的baseUrl
        if (mIndex == -1) {
            synchronized (Object.class) {
                mApiRetrofit = new ApiRetrofit();
                mApiRetrofitList.add(mApiRetrofit);
                return mApiRetrofit;
            }
        } else {
            //以前已經創建過的baseUrl
            return mApiRetrofitList.get(mIndex);
        }
    }
6、使用時寫法

地址可以寫成常量,不要我這樣寫,寫成常量判斷準確

ApiRetrofit.getInstance("http://www.baidu.com/").getApiService().getCeShi(params)

五、Retrofit文件上傳(含進度條)

Retrofit文件上傳
文件上傳已封裝到框架中,目的是讓寫法更簡便

1、 FileObserver封裝,文件上傳下載時所用

上述文章有BaseObserver,現封裝FileObserver單獨用來文件上傳下載時候所用,內容大同小異

public abstract class FileObserver<T> extends DisposableObserver<T> {
    protected BaseView view;
    /**
     * 網絡連接失敗  無網
     */
    public static final int NETWORK_ERROR = 100000;
    /**
     * 解析數據失敗
     */
    public static final int PARSE_ERROR = 1008;
    /**
     * 網絡問題
     */
    public static final int BAD_NETWORK = 1007;
    /**
     * 連接錯誤
     */
    public static final int CONNECT_ERROR = 1006;
    /**
     * 連接超時
     */
    public static final int CONNECT_TIMEOUT = 1005;
    /**
     * 其他所有情況
     */
    public static final int NOT_TRUE_OVER = 1004;
    public FileObserver(BaseView view) {
        this.view = view;
    }
    @Override
    protected void onStart() {
        if (view != null) {
            view.showProgress();
        }
    }
    @Override
    public void onNext(T t) {
        onSuccess(t);
    }
    @Override
    public void onError(Throwable e) {
        if (view != null) {
            view.hideProgress();
        }
        if (view != null) {
            view.hideLoading();
        }
        if (e instanceof HttpException) {
            //   HTTP錯誤
            onException(BAD_NETWORK, "");
        } else if (e instanceof ConnectException
                || e instanceof UnknownHostException) {
            //   連接錯誤
            onException(CONNECT_ERROR, "");
        } else if (e instanceof InterruptedIOException) {
            //  連接超時
            onException(CONNECT_TIMEOUT, "");
        } else if (e instanceof JsonParseException
                || e instanceof JSONException
                || e instanceof ParseException) {
            //  解析錯誤
            onException(PARSE_ERROR, "");
            e.printStackTrace();
        } else {
            if (e != null) {
                onError(e.toString());
            } else {
                onError("未知錯誤");
            }
        }
    }
    private void onException(int unknownError, String message) {
        switch (unknownError) {
            case CONNECT_ERROR:
                onError("連接錯誤");
                break;
            case CONNECT_TIMEOUT:
                onError("連接超時");
                break;
            case BAD_NETWORK:
                onError("網絡超時");
                break;
            case PARSE_ERROR:
                onError("數據解析失敗");
                break;
            //非true的所有情況
            case NOT_TRUE_OVER:
                onError(message);
                break;
            default:
                break;
        }
    }
    @Override
    public void onComplete() {
        if (view != null) {
            view.hideProgress();
        }
    }
    public abstract void onSuccess(T o);
    public abstract void onError(String msg);
}
2、定義接口
public interface ApiServer {
    @Multipart
    @POST("/wxapp/public/upload")
    Observable<BaseModel<Object>> getUpload(@PartMap Map<String, RequestBody> map,
                                            @Part MultipartBody.Part parts
    );
}
3、 定義MainView,并繼承BaseView
public interface MainView extends BaseView {
    void onUpLoadImgSuccess(BaseModel<Object> o);
}
4、 定義ProgressRequestBody,監聽上傳進度
public class ProgressRequestBody extends RequestBody {
    private File mFile;
    private String mPath;
    private String mMediaType;
    private BaseView mListener;

    private int mEachBufferSize = 1024;

    public ProgressRequestBody(final File file, String mediaType, BaseView baseView) {
        mFile = file;
        mMediaType = mediaType;
        mListener = baseView;
    }

    public ProgressRequestBody(final File file, String mediaType, int eachBufferSize, BaseView baseView) {
        mFile = file;
        mMediaType = mediaType;
        mEachBufferSize = eachBufferSize;
        mListener = baseView;
    }

    @Override
    public MediaType contentType() {
        // i want to upload only images
        return MediaType.parse(mMediaType);
    }

    @Override
    public void writeTo(BufferedSink sink) throws IOException {
        long fileLength = mFile.length();
        byte[] buffer = new byte[mEachBufferSize];
        FileInputStream in = new FileInputStream(mFile);
        long uploaded = 0;

        try {
            int read;
            Handler handler = new Handler(Looper.getMainLooper());
            while ((read = in.read(buffer)) != -1) {
                // update progress on UI thread
                handler.post(new ProgressUpdater(uploaded, fileLength));
                uploaded += read;
                sink.write(buffer, 0, read);

            }
        } finally {
            in.close();
        }
    }

    private class ProgressUpdater implements Runnable {
        private long mUploaded;
        private long mTotal;

        public ProgressUpdater(long uploaded, long total) {
            mUploaded = uploaded;
            mTotal = total;
        }

        @Override
        public void run() {
            mListener.onProgress((int) (100 * mUploaded / mTotal));
        }
    }
}
5、 定義MainPresenter,并繼承BasePresenter
public class MainPresenter extends BasePresenter<MainView> {
    public MainPresenter(MainView baseView) {
        super(baseView);
    }
    /**
     * 演示 文件上傳進度監聽
     *
     * @param url
     */
    public void upLoadVideoApi(String url) {
        HashMap<String, RequestBody> params = new HashMap<>();
        params.put("fileType", RetrofitUtil.convertToRequestBody("video"));

        MultipartBody.Part parts = MultipartBody.Part.createFormData("file", new File(url).getName(), new ProgressRequestBody(new File(url),"video/mpeg", baseView));

        ApiServer apiServer = ApiRetrofit.getBaseUrlInstance("https://bjlzbt.com/").getApiService();
        addFileDisposable(apiServer.getUpload(params, parts), new FileObserver(baseView) {
            @Override
            public void onSuccess(Object o) {
                baseView.onUpLoadImgSuccess((BaseModel<Object>) o);
            }

            @Override
            public void onError(String msg) {
                if (baseView != null) {
                    baseView.showError(msg);
                }
            }
        });
    }
}
6、 在Activity中進行網絡請求,如下
public class MainActivity extends BaseActivity<MainPresenter> implements MainView {
    @Override
    protected MainPresenter createPresenter() {
        return new MainPresenter(this);
    }

    @Override
    protected int getLayoutId() {
        return R.layout.activity_main;
    }

    @Override
    protected void initData() {
        //文件上傳
         mPresenter.upLoadVideoApi(BaseContent.baseFileName+"ceshi.mp4");
    }
   @Override
    public void onUpLoadImgSuccess(BaseModel<Object> o) {
        L.e("文件視頻路徑==" + o.getResult());
    }
}
7、 有人會問,說好的進度條去哪了?

進度條已封裝到BaseActivity中了,相關代碼如下

@Override
    public void showProgress() {
        if (progressDialog == null) {
            progressDialog = new ProgressDialog(this);
        }
        progressDialog.getProgressBar().performAnimation();
        if (!progressDialog.isShowing()) {
            progressDialog.show();
        }
    }

    @Override
    public void hideProgress() {
        if (progressDialog != null) {
            progressDialog.getProgressBar().releaseAnimation();
            progressDialog.dismiss();
        }
    }

    @Override
    public void onProgress(int progress) {
        if (progressDialog != null) {
            progressDialog.updateProgress(progress);
        }
    }

六、Retrofit文件下載(含進度條)

1、 FileObserver封裝,文件上傳下載時所用

如上

2、定義接口
public interface ApiServer {
     /**
     * 大文件官方建議用 @Streaming 來進行注解,不然會出現IO異常,小文件可以忽略不注入
     */
    @Streaming
    @GET
    Observable<ResponseBody> downloadFile(@Url String fileUrl);
}
3、 定義MainView,并繼承BaseView
public interface MainView extends BaseView {
     void onFileSuccess(File file);
}
4、 定義MainPresenter,并繼承BasePresenter
public class MainPresenter extends BasePresenter<MainView> {
    public MainPresenter(MainView baseView) {
        super(baseView);
   public void downFile(String url, final String destFileDir, final String destFileName) {
        ApiServer apiServer = ApiRetrofit.getFileInstance(baseView).getApiService();
        Observable<String> observable = apiServer.downloadFile(url).map(new Function<ResponseBody, String>() {
            @Override
            public String apply(ResponseBody body) throws Exception {
                File file = FileUtil.saveFile(destFileDir+destFileName, body);
                return file.getPath();
            }
        });
        addFileDisposable(observable, new FileObserver(baseView) {
            @Override
            public void onSuccess(Object o) {
                baseView.onFileSuccess(new File(o.toString()));
            }

            @Override
            public void onError(String msg) {
                if (baseView != null) {
                    baseView.showError(msg);
                }
            }
        });
    }

    }
}
5、創建Retrofit來實現進度條

說明:本人思路是通過okhttp攔截器攔截來檢測文件下載進度,相關代碼已放入到創建Retrofit中,詳情請看Demo,demo封裝為只有文件下載okhttp才會添加ProgressInterceptor下載進度監聽,如下所示(okhttp添加)

/**
     * 文件處理
     *
     * @param httpClientBuilder
     */
    public void initFileClient(OkHttpClient.Builder httpClientBuilder) {
        /**
         * 處理文件下載進度展示所需
         */
        httpClientBuilder.addNetworkInterceptor(new ProgressInterceptor());
    }

/**
     * 文件下載進度攔截
     */
    public class ProgressInterceptor implements Interceptor {
        @Override
        public Response intercept(Chain chain) throws IOException {
            Request request = chain.request();
            if (mBaseView != null) {
                Response response = chain.proceed(request);
                return response.newBuilder().body(new ProgressResponseBody(response.body(),
                        new ProgressResponseBody.ProgressListener() {
                            @Override
                            public void onProgress(long totalSize, long downSize) {
                                int progress = (int) (downSize * 100 / totalSize);
                                if (mBaseView != null) {
                                    mBaseView.onProgress(progress);
                                    L.e("文件下載速度 === " + progress);
                                }
                            }
                        })).build();
            } else {
                return chain.proceed(request);
            }
        }
    }
6、 在Activity中進行網絡請求,如下
public class MainActivity extends BaseActivity<MainPresenter> implements MainView {
    @Override
    protected MainPresenter createPresenter() {
        return new MainPresenter(this);
    }

    @Override
    protected int getLayoutId() {
        return R.layout.activity_main;
    }

    @Override
    protected void initData() {
        //文件上傳
         String url = "https://bjlzbt.com/upload/default//20190725//c13948258c6ef6a36cbe2d3322b98f5c.mp4";
         if (FileUtils.createOrExistsDir(BaseContent.baseFileName)) {//刪除此行代碼也可以
             mPresenter.downFile(url, BaseContent.baseFileName, "ceshi.mp4");
         }
    }
   @Override
    public void onUpLoadImgSuccess(BaseModel<Object> o) {
        L.e("文件視頻路徑==" + o.getResult());
    }
}

七、Retrofit,Gson解析,請求返回的類型不統一,假如double返回的是null

對應文章解析

現實開發中,往往會遇到后臺返回數據格式不規范情況,比如前端字段原本定義為int類型,而數據返回為空,如果用Gson解析會導致解析失敗,比如字段定義為double類型,而返回的格式為字符串null,導致解析失敗等等(只在后臺返回數據格式不規范情況下出現,如果后臺返回格式規范并不用考慮此問題)

1、 實現目標

1、格式化數據不規范【格式化int類型數據】
2、格式化數據不規范【格式化Long類型數據】
3、格式化數據不規范【格式化Double類型數據】
4、格式化數據不規范【格式化String類型數據】
5、格式化數據不規范【格式化Null類型數據】

2、 添加格式化工具方法到Gson解析中
     if (gson == null) {
            gson = new GsonBuilder()
                    .registerTypeAdapter(Integer.class, new IntegerDefaultAdapter())
                    .registerTypeAdapter(int.class, new IntegerDefaultAdapter())
                    .registerTypeAdapter(Double.class, new DoubleDefaultAdapter())
                    .registerTypeAdapter(double.class, new DoubleDefaultAdapter())
                    .registerTypeAdapter(Long.class, new LongDefaultAdapter())
                    .registerTypeAdapter(long.class, new LongDefaultAdapter())
                    .registerTypeAdapter(String.class, new StringNullAdapter())
                    .create();
        }
        return gson;
    }


 public ApiRetrofit() {
        OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder();
        httpClientBuilder
                .connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
                .writeTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
                .readTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
                .retryOnConnectionFailure(true);//錯誤重聯

        retrofit = new Retrofit.Builder()
                .baseUrl(BASE_SERVER_URL)
                .addConverterFactory(GsonConverterFactory.create(buildGson()))//添加json轉換框架buildGson()根據需求添加
                //支持RxJava2
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .client(httpClientBuilder.build())
                .build();
        apiServer = retrofit.create(ApiServer.class);
    }

3、 對double類型處理,返回“”,或“null”,動態更改為默認值0.00,新建DoubleDefaultAdapter類
public class DoubleDefault0Adapter implements JsonSerializer<Double>, JsonDeserializer<Double> {
    @Override
    public Double deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
        try {
            if (json.getAsString().equals("") || json.getAsString().equals("null")) {//定義為double類型,如果后臺返回""或者null,則返回0.00
                return 0.00;
            }
        } catch (Exception ignore) {
        }
        try {
            return json.getAsDouble();
        } catch (NumberFormatException e) {
            throw new JsonSyntaxException(e);
        }
    }

    @Override
    public JsonElement serialize(Double src, Type typeOfSrc, JsonSerializationContext context) {
        return new JsonPrimitive(src);
    }
}
4、 對int類型處理,返回“”,或“null”,動態更改為默認值0,新建DoubleDefaultAdapter類
public class IntegerDefaultAdapter implements JsonSerializer<Integer>, JsonDeserializer<Integer> {
    @Override
    public Integer deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
            throws JsonParseException {
        try {
            if (json.getAsString().equals("") || json.getAsString().equals("null")) {//定義為int類型,如果后臺返回""或者null,則返回0
                return 0;
            }
        } catch (Exception ignore) {
        }
        try {
            return json.getAsInt();
        } catch (NumberFormatException e) {
            throw new JsonSyntaxException(e);
        }
    }

    @Override
    public JsonElement serialize(Integer src, Type typeOfSrc, JsonSerializationContext context) {
        return new JsonPrimitive(src);
    }
}
5、 對Long類型處理,返回“”,或“null”,動態更改為默認值0,新建DoubleDefaultAdapter類
public class LongDefault0Adapter implements JsonSerializer<Long>, JsonDeserializer<Long> {
    @Override
    public Long deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
            throws JsonParseException {
        try {
            if (json.getAsString().equals("") || json.getAsString().equals("null")) {//定義為long類型,如果后臺返回""或者null,則返回0
                return 0l;
            }
        } catch (Exception ignore) {
        }
        try {
            return json.getAsLong();
        } catch (NumberFormatException e) {
            throw new JsonSyntaxException(e);
        }
    }

    @Override
    public JsonElement serialize(Long src, Type typeOfSrc, JsonSerializationContext context) {
        return new JsonPrimitive(src);
    }
}
5、 重點說一下String類型

根據上邊其他類型處理代碼可以看出,String也就是把上述類中代碼改成String就可以了,答案是可以的,如下,處理的內容為如果服務器返回字符串類型“null”,我們將其格式化成“”,空類型,但是我們為什么不直接寫,請往下看

public class StringDefaultConverter implements JsonSerializer<String>, JsonDeserializer<String> {
    @Override
    public String deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
        try {
            if (json.getAsString().equals("null")) {
                return "";
            }
        } catch (Exception ignore) {
        }
        try {
            return json.getAsJsonPrimitive().getAsString();
        } catch (NumberFormatException e) {
            throw new JsonSyntaxException(e);
        }
    }

    @Override
    public JsonElement serialize(String src, Type typeOfSrc, JsonSerializationContext context) {
        return new JsonPrimitive(src);
    }
}

但是有種比較常見的不規范數據返回,為null,不是字符串的"null",是這個null,如果返回null,會進入到上邊這個類嗎,經過測試,返回null的直接跳過,所以出現了個問題,null到底是什么類型?

通過讀源碼可知,我們可以自定義TypeAdapter,將其放入facotries中,并且gson在解析json時使用對應的TypeAdapter來的,而我們手動添加的TypeAdapter會優先于預設的TypeAdapter被使用。

于是乎找到了一種其他方法來解決這個問題

新建個類來集成TypeAdapter,這樣就便優先于預設的TypeAdapter

public class StringNullAdapter extends TypeAdapter<String> {
    @Override
    public String read(JsonReader reader) throws IOException {
        if (reader.peek() == JsonToken.NULL) {
            reader.nextNull();
            return "";//原先是返回Null,這里改為返回空字符串
        }

        String jsonStr = reader.nextString();
        if(jsonStr.equals("null")) {
            return "";
        }else {
            return jsonStr;
        }
    }

    @Override
    public void write(JsonWriter writer, String value) throws IOException {
        if (value == null) {
            writer.nullValue();
            return;
        }
        writer.value(value);
    }
}

定義的類型為String,這樣為null的情況會都歸這個類來處理,但是String的所有情況也會走里邊的方法,所以為了同樣的類型不執行倆遍,String和null都在此類處理,就沒必要寫上邊那個方法了, 處理所有情況為返回null,或字符串"null",格式化為"" 空

八、Retrofit實現cookie自動化管理

對應文章解析
在現實開發中,我們可能會遇到這樣的需求,需要保持長登陸狀態,登陸失效為服務器判斷,在我們不想往接口添加任何參數處理時,我們便想到cookie

最終實現效果為:登錄成功后將將服務器返回的cookie保存到本地(每次接口請求成功,更新本地保存Cookie值,目的讓本地的cookie值一直為最新的),下次請求接口時將本地最新cookie帶上,用來告訴哪個用戶與服務器之間的交互

1、 第一種實現方方法(第三方庫實現Cookie自動化管理)

(1)依賴第三方庫

implementation 'com.github.franmontiel:PersistentCookieJar:v1.0.1'

(2)創建OkHttpClient時添加cookieJar

 PersistentCookieJar cookieJar = new PersistentCookieJar(new  SetCookieCache(), new SharedPrefsCookiePersistor(context));

  OkHttpClient okHttpClient = new OkHttpClient().newBuilder()
                .connectTimeout(10, TimeUnit.SECONDS)
                .readTimeout(20, TimeUnit.SECONDS)
                .addInterceptor(new LoginInterceptor())
                .cookieJar(cookieJar)// 設置封裝好的cookieJar
                .build();
2、 第二種實現方方法(手寫cookie管理類),自我操控性強

(1)創建CookieManger類

public class CookieManger implements CookieJar {
    private static Context mContext;

    private static PersistentCookieStore cookieStore;

    public CookieManger(Context context) {
        mContext = context;
        if (cookieStore == null) {
            cookieStore = new PersistentCookieStore(mContext);
        }
    }

    @Override
    public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
        if (cookies != null && cookies.size() > 0) {
            for (Cookie item : cookies) {
                cookieStore.add(url, item);
                if (item.name() != null && !TextUtils.isEmpty(item.name()) &&
                        item.value() != null && !TextUtils.isEmpty(item.value())) {
                    /*保存cookie到sp地方  可能會用到 */
//                    PrefUtils.setString(mContext, "cookie_name", item.name());
//                    PrefUtils.setString(mContext, "cookie_value", item.value());
                }
            }
        }
    }

    @Override
    public List<Cookie> loadForRequest(HttpUrl url) {
        List<Cookie> cookies = cookieStore.get(url);
        for (int i = 0; i < cookies.size(); i++) {
            Log.e("", "拿出來的cookies name()==" + cookies.get(i).name());
            Log.e("", "拿出來的cookies value()==" + cookies.get(i).value());
        }
        return cookies;
    }
}

(2)創建OkHttpCookies類

public class OkHttpCookies  implements Serializable {
    private transient final Cookie cookies;
    private transient Cookie clientCookies;

    public OkHttpCookies(Cookie cookies) {
        this.cookies = cookies;
    }

    public Cookie getCookies() {
        Cookie bestCookies = cookies;
        if (clientCookies != null) {
            bestCookies = clientCookies;
        }
        return bestCookies;
    }

    private void writeObject(ObjectOutputStream out) throws IOException {
        out.writeObject(cookies.name());
        out.writeObject(cookies.value());
        out.writeLong(cookies.expiresAt());
        out.writeObject(cookies.domain());
        out.writeObject(cookies.path());
        out.writeBoolean(cookies.secure());
        out.writeBoolean(cookies.httpOnly());
        out.writeBoolean(cookies.hostOnly());
        out.writeBoolean(cookies.persistent());
    }

    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        String name = (String) in.readObject();
        String value = (String) in.readObject();
        long expiresAt = in.readLong();
        String domain = (String) in.readObject();
        String path = (String) in.readObject();
        boolean secure = in.readBoolean();
        boolean httpOnly = in.readBoolean();
        boolean hostOnly = in.readBoolean();
        boolean persistent = in.readBoolean();
        Cookie.Builder builder = new Cookie.Builder();
        builder = builder.name(name);
        builder = builder.value(value);
        builder = builder.expiresAt(expiresAt);
        builder = hostOnly ? builder.hostOnlyDomain(domain) : builder.domain(domain);
        builder = builder.path(path);
        builder = secure ? builder.secure() : builder;
        builder = httpOnly ? builder.httpOnly() : builder;
        clientCookies =builder.build();
    }
}

(3)創建PersistentCookieStore類

public class PersistentCookieStore {
    private static final String LOG_TAG = "PersistentCookieStore";
    private static final String COOKIE_PREFS = "Cookies_Prefs";

    private final Map<String, ConcurrentHashMap<String, Cookie>> cookies;
    private final SharedPreferences cookiePrefs;


    public PersistentCookieStore(Context context) {
        cookiePrefs = context.getSharedPreferences(COOKIE_PREFS, 0);
        cookies = new HashMap<>();

        //將持久化的cookies緩存到內存中 即map cookies
        Map<String, ?> prefsMap = cookiePrefs.getAll();
        for (Map.Entry<String, ?> entry : prefsMap.entrySet()) {
            String[] cookieNames = TextUtils.split((String) entry.getValue(), ",");
            for (String name : cookieNames) {
                String encodedCookie = cookiePrefs.getString(name, null);
                if (encodedCookie != null) {
                    Cookie decodedCookie = decodeCookie(encodedCookie);
                    if (decodedCookie != null) {
                        if (!cookies.containsKey(entry.getKey())) {
                            cookies.put(entry.getKey(), new ConcurrentHashMap<String, Cookie>());
                        }
                        cookies.get(entry.getKey()).put(name, decodedCookie);
                    }
                }
            }
        }
    }

    protected String getCookieToken(Cookie cookie) {
        return cookie.name() + "@" + cookie.domain();
    }

    public void add(HttpUrl url, Cookie cookie) {
        String name = getCookieToken(cookie);

        //將cookies緩存到內存中 如果緩存過期 就重置此cookie
        if (!cookie.persistent()) {
            if (!cookies.containsKey(url.host())) {
                cookies.put(url.host(), new ConcurrentHashMap<String, Cookie>());
            }
            cookies.get(url.host()).put(name, cookie);
        } else {
            if (cookies.containsKey(url.host())) {
                cookies.get(url.host()).remove(name);
            }
        }

        //講cookies持久化到本地
        SharedPreferences.Editor prefsWriter = cookiePrefs.edit();
        prefsWriter.putString(url.host(), TextUtils.join(",", cookies.get(url.host()).keySet()));
        prefsWriter.putString(name, encodeCookie(new OkHttpCookies(cookie)));
        prefsWriter.apply();
    }

    public List<Cookie> get(HttpUrl url) {
        ArrayList<Cookie> ret = new ArrayList<>();
        if (cookies.containsKey(url.host())) {
            ret.addAll(cookies.get(url.host()).values());
        }
        return ret;
    }

    public boolean removeAll() {
        SharedPreferences.Editor prefsWriter = cookiePrefs.edit();
        prefsWriter.clear();
        prefsWriter.apply();
        cookies.clear();
        return true;
    }

    public boolean remove(HttpUrl url, Cookie cookie) {
        String name = getCookieToken(cookie);

        if (cookies.containsKey(url.host()) && cookies.get(url.host()).containsKey(name)) {
            cookies.get(url.host()).remove(name);

            SharedPreferences.Editor prefsWriter = cookiePrefs.edit();
            if (cookiePrefs.contains(name)) {
                prefsWriter.remove(name);
            }
            prefsWriter.putString(url.host(), TextUtils.join(",", cookies.get(url.host()).keySet()));
            prefsWriter.apply();

            return true;
        } else {
            return false;
        }
    }

    public List<Cookie> getCookies() {
        ArrayList<Cookie> ret = new ArrayList<>();
        for (String key : cookies.keySet()) {
            ret.addAll(cookies.get(key).values());
        }

        return ret;
    }

    /**
     * cookies 序列化成 string
     *
     * @param cookie 要序列化的cookie
     * @return 序列化之后的string
     */
    protected String encodeCookie(OkHttpCookies cookie) {
        if (cookie == null) {
            return null;
        }
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        try {
            ObjectOutputStream outputStream = new ObjectOutputStream(os);
            outputStream.writeObject(cookie);
        } catch (IOException e) {
            Log.d(LOG_TAG, "IOException in encodeCookie", e);
            return null;
        }

        return byteArrayToHexString(os.toByteArray());
    }

    /**
     * 將字符串反序列化成cookies
     *
     * @param cookieString cookies string
     * @return cookie object
     */
    protected Cookie decodeCookie(String cookieString) {
        byte[] bytes = hexStringToByteArray(cookieString);
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
        Cookie cookie = null;
        try {
            ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
            cookie = ((OkHttpCookies) objectInputStream.readObject()).getCookies();
        } catch (IOException e) {
            Log.d(LOG_TAG, "IOException in decodeCookie", e);
        } catch (ClassNotFoundException e) {
            Log.d(LOG_TAG, "ClassNotFoundException in decodeCookie", e);
        }

        return cookie;
    }

    /**
     * 二進制數組轉十六進制字符串
     *
     * @param bytes byte array to be converted
     * @return string containing hex values
     */
    protected String byteArrayToHexString(byte[] bytes) {
        StringBuilder sb = new StringBuilder(bytes.length * 2);
        for (byte element : bytes) {
            int v = element & 0xff;
            if (v < 16) {
                sb.append('0');
            }
            sb.append(Integer.toHexString(v));
        }
        return sb.toString().toUpperCase(Locale.US);
    }

    /**
     * 十六進制字符串轉二進制數組
     *
     * @param hexString string of hex-encoded values
     * @return decoded byte array
     */
    protected byte[] hexStringToByteArray(String hexString) {
        int len = hexString.length();
        byte[] data = new byte[len / 2];
        for (int i = 0; i < len; i += 2) {
            data[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4) + Character.digit(hexString.charAt(i + 1), 16));
        }
        return data;
    }
}

(4)創建OkHttpClient時添加cookieJar

OkHttpClient okHttpClient = new OkHttpClient().newBuilder()
                .connectTimeout(10, TimeUnit.SECONDS)
                .readTimeout(20, TimeUnit.SECONDS)
                .addInterceptor(new LoginInterceptor())
                .cookieJar(new CookieManger (context))// 設置封裝好的cookieJar
                .build();

九、路由判斷第二種解決方案(文章為舊版,只提供思路)

參考連接如下

上述文章提到了路由這個概念,其實自己命名的,實際意義和這個此比較契合,本文章目的只服務器下發數據,當我們得到的值代表數據正常,(比如code=0數據正常),我們會正常執行我們的解析數據并處理顯示數據內容等操作,如果服務器下發的數據為異常信息,前端只需要個提示操作,這樣我們就沒必要執行解析+顯示等操作,所以我們會想到怎樣可以一次性判斷,終身不用管走向呢?

1、 第一種判斷方法,在Rxjava的OnNext中判斷
 @Override
    public void onNext(BaseModel<T> o) {
        T t = o.getData();
        try {
           /* if (t!=null){
                L.e("返回數據="+o.toString());
            }else {
                L.e("返回數據=null");
            }*/
            if (view != null) {
                view.hideLoading();
            }
            if (o.getErrcode() == mSuccessCode) {
                onSuccess(t, o.getMsg(), o.getErrcode());
            } else {
                view.onErrorCode(o);
            }

        } catch (Exception e) {
            e.printStackTrace();
            onError(e.toString());
        }
    }

當我們執行到OnNext方法中,此時已經執行了Gson解析代碼,所以我們是否可以將判斷提前到Gson解析時候判斷呢? 請看第二種方法

2、 第二種判斷方法,Gson解析期間判斷

如果想通過Gson解析期間判斷,這樣必然會設計到Gson源碼如果走向,我們通過更改源碼來自定義操作,通過閱讀源碼我們會發現解析數據會涉及到三個類,GsonConverterFactory,GsonRequestBodyConverter,GsonResponseBodyConverter這三個類,我們需要重寫這個三個類,閱讀代碼會返現主要執行解析代碼在GsonResponseBodyConverter中,所以我們的目標便是這里。

思路:Gosn解析數據時,如果出現服務器下發非正常標識,此刻我們已判斷服務器返回數據不是我們需要展示的,那我們解析到這一步已不用再向下解析,可以通過拋異常來釋放當前任務代碼如下

@Override
    public T convert(ResponseBody value) throws IOException {
        String response = value.string();
        BaseResult re = gson.fromJson(response, BaseResult.class);
        //關注的重點,自定義響應碼中非0的情況,一律拋出ApiException異常。
        //這樣,我們就成功的將該異常交給onError()去處理了。
        if (re.getCode() != BaseContent.basecode) {
            value.close();
            throw new ApiException(re.getCode(), re.getMessage());
        }

        MediaType mediaType = value.contentType();
        Charset charset = mediaType != null ? mediaType.charset(UTF_8) : UTF_8;
        ByteArrayInputStream bis = new ByteArrayInputStream(response.getBytes());
        InputStreamReader reader = new InputStreamReader(bis, charset);
        JsonReader jsonReader = gson.newJsonReader(reader);
        try {
            return adapter.read(jsonReader);
        } finally {
            value.close();
        }
    }

異常已成功拋出,那異常信息到哪里了呢?答案是到Rxjava的OnError中,異常我們拋的是自定義實體類ApiException,內含code,message,那我們到Rxjava中OnError獲取到異常信息 e,e instanceof ApiException通過分析異常是否為我們自定義實體類來判斷下一步如何操作,此方法為路由的第二種判斷,示例如下

@Override
    public void onError(Throwable e) {
        if (view != null) {
            view.hideLoading();
        }
        if (e instanceof HttpException) {
            //   HTTP錯誤
            onException(BAD_NETWORK, "");
        } else if (e instanceof ConnectException
                || e instanceof UnknownHostException) {
            //   連接錯誤
            onException(CONNECT_ERROR, "");
        } else if (e instanceof InterruptedIOException) {
            //  連接超時
            onException(CONNECT_TIMEOUT, "");
        } else if (e instanceof JsonParseException
                || e instanceof JSONException
                || e instanceof ParseException) {
            //  解析錯誤
            onException(PARSE_ERROR, "");
            e.printStackTrace();
            //這里
        } else if (e instanceof ApiException) {
            ApiException exception = (ApiException) e;
            int code = exception.getErrorCode();
            switch (code) {
                //未登錄(此處只是案例 供理解)
                case CONNECT_NOT_LOGIN:
                    view.onErrorCode(new BaseModel(exception.getMessage(), code));
                    onException(CONNECT_NOT_LOGIN, "");
                    break;
                //其他不等于0 的所有狀態
                default:
                    onException(OTHER_MESSAGE, exception.getMessage());
                    view.onErrorCode(new BaseModel(exception.getMessage(), code));
                    break;
            }
        } else {
            if (e != null) {
                onError(e.toString());
            } else {
                onError("未知錯誤");
            }
        }

    }

十、Retrofit配置及各情況處理(緩存攔截、日志打印、替換接口內容、參數添加等

相關參考跳轉此鏈接

十一、后記

一、問:這樣封裝每個Activity對應一個Presenter,有些接口會多次用不想多次寫
答:onCreate隨便寫

 @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        MainPresenter1 presenter1 = new MainPresenter1(this);
        presenter.getTextApi();
        MainPresenter2 presenter2 = new MainPresenter2(this);
        presenter2.getTextApi();
        MainPresenter3 presenter3 = new MainPresenter3(this);
        presenter3getTextApi();
    }


二、問:有人問dialog加載圈封裝的不夠好,這樣每個接口都得顯示加載圈,不想實現都不行
答:BaseActivity和BaseFragment中都有這倆個方法

 //顯示加載進度框回調
    @Override
    public void showLoading() {
        showLoadingDialog();
    }
    //隱藏進度框回調
    @Override
    public void hideLoading() {
        closeLoadingDialog();
    }

如果說我本頁面都不想顯示Loading動畫,那就在對應的Activity重寫下父類的方法,比如

 @Override
    public void showLoading() {
    //    super.showLoading();  //將super去掉  就不會顯示Loading動畫了
    }

如果我們需要顯示就在對應的Fragment調用請求方法之后手動掉一下父類的顯示Loading方法,如下:

 mPresenter.collectApi("id");
 showLoadingDialog();


三、問:假如接口返回1001,代表重寫登錄或者token失效,我想在對應activity拿到狀態或者做統一操作
答:可以在BaseActivity判斷跳頁面

//BaseActivity代碼
 @Override
    public void onErrorCode(BaseModel model) {
       if (model.getErrcode() == 1001) {
            startLogin();
        }
    }

    private void startLogin() {
        startActivity(LoginActivity.class);
    }

如果想在對應Activity操作,那就在對應Activity重寫此方法

//對應Activity代碼
 @Override
    public void onErrorCode(BaseModel model) {
        //super.onErrorCode(model);
     if (model.getErrcode()==1001){
            //............................................
        }else if (model.getErrcode()==1002){
                  //............................................
        }
    }

github地址:https://github.com/LPTim/MVP-Retrofit2-okhttp3-Rxjava2

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,401評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,011評論 3 413
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,263評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,543評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,323評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,874評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,968評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,095評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,605評論 1 331
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,551評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,720評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,242評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,961評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,358評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,612評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,330評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,690評論 2 370

推薦閱讀更多精彩內容