天天看點

RxJava2和Retrofit2的網絡請求

文章目錄

    • 寫在前邊:
      • 依賴:
      • 自定義Gradle配置
      • gradle.properties配置簽名資訊
      • app下build.gradle引入依賴:
    • 隻用retrofit2進行網絡請求
      • 實體類,用于接受傳回資料
      • 封裝網絡請求接口
      • Retrofit2的實作(使用EventBus更新UI)
    • 結合RxJava2實作網絡請求輪詢(無條件)
    • 結合RxJava2實作網絡請求輪詢(有條件)
    • 結合RxJava2網絡請求嵌套回調(FlatMap)
      • 接口封裝(注冊接口和登入接口)
      • 使用flatMap進行嵌套請求
    • 網絡請求出錯重連(結合Retrofit)
    • 補充:
      • retrofit封裝
      • 線程排程
        • 日志攔截器
      • 請求頭攔截器
      • 公共查詢參數攔截器
      • 緩存攔截器
      • presenter中請求網絡
      • acvitity中處理資料
    • 全局日志儲存和上傳
    • 檢測網絡
    • 全局Toast管理
根據前輩經驗,本文依然采用金山詞霸公用api進行測試

寫在前邊:

依賴:

implementation 'io.reactivex.rxjava2:rxjava:2.2.1'
    implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'
    implementation 'com.amitshekhar.android:rx2-android-networking:1.0.0'
    //支援把json解析成Java對象
    implementation 'com.squareup.okhttp3:okhttp:3.11.0'
    implementation 'com.squareup.okhttp3:logging-interceptor:3.11.0'
    implementation 'com.google.code.gson:gson:2.8.5'
    implementation 'com.squareup.retrofit2:retrofit:2.4.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.4.0'
    implementation 'com.squareup.retrofit2:adapter-rxjava2:2.4.0'
    implementation 'com.jakewharton:butterknife:8.4.0'
    annotationProcessor 'com.jakewharton:butterknife-compiler:8.4.0'
    implementation 'org.greenrobot:eventbus:3.1.1'
    implementation 'com.android.support:multidex:1.0.3'
           

自定義Gradle配置

config.buildgradle
ext.versions = [
        // 本地倉庫版本
        compileSdkVersion: 28,
        buildToolsVersion: "28.0.0 rc1",
        minSdkVersion    : 15,
        targetSdkVersion : 28,
        versionCode      : 1,
        versionName      : "1.0",
        supportVersion   : "27.3.0"
]
// app 相關資訊總配置
ext.appConfig = [
        applicationId    : "com.app.ytf.netdemo",
        LOCALHOST_DEBUG  : "\"http://fy.iciba.com/\"",
        LOCALHOST_RELEASE: "\"http://fy.iciba.com/\"",
        DEBUGABLE        : true,
//        GETUI_APP_ID     : "PhmRY1Uy8A9VrK2L2H8Qz3",
        GETUI_APP_KEY    : "2pjtQwS6Oghremzsy8vVZE4CSLip6egajMYuUx8Vo8WvayD21ttphi5fuYiGjoaM",
//        GETUI_APP_SECRET : "ZmO60vhutt6NxMauNZJjl8",
]
ext.channel = [
        Baidu  : "Baidu",
        Xiaomi : "Xiaomi",
        Huawei : "huawei",
        Vivo   : "vivo",
        Oppo   : "oppo",
        Tencent: "tencent",
        Server : "server"
]

           

gradle.properties配置簽名資訊

org.gradle.parallel=true
KEY_ALIAS =netdemo
KEY_PASSWORD=123456
STORE_FILE= C:/Users/ytf/netdemo.jks
STORE_PASSWORD=123456
           

app下build.gradle引入依賴:

apply plugin: 'com.android.application'
apply from:"$rootDir/config.gradle"

android {
    signingConfigs {
        debug {
//            keyAlias KEY_ALIAS
//            keyPassword KEY_PASSWORD
//            storeFile file(STORE_FILE)
//            storePassword STORE_PASSWORD
        }
        release {
            keyAlias KEY_ALIAS
            keyPassword KEY_PASSWORD
            storeFile file(STORE_FILE)
            storePassword STORE_PASSWORD
        }
    }
    compileSdkVersion versions.compileSdkVersion
    buildToolsVersion versions.buildToolsVersion
    defaultConfig {
        applicationId appConfig.applicationId
        minSdkVersion versions.minSdkVersion
        targetSdkVersion versions.targetSdkVersion
        versionCode versions.versionCode
        versionName versions.versionName
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        multiDexEnabled true
    }
    buildTypes {
        release {
            buildConfigField "String", "LOCALHOST", appConfig.LOCALHOST_RELEASE
            minifyEnabled false
            debuggable true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
//            debuggable appConfig.DEBUGABLE
            signingConfig signingConfigs.release
            manifestPlaceholders=[
                    GETUI_APP_KEY      : appConfig.GETUI_APP_KEY
            ]
        }
        debug {
            buildConfigField "String", "LOCALHOST", appConfig.LOCALHOST_DEBUG
            minifyEnabled false
            debuggable true
            signingConfig signingConfigs.debug
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
//            debuggable appConfig.DEBUGABLE
            manifestPlaceholders=[
                    GETUI_APP_KEY      : appConfig.GETUI_APP_KEY
            ]
        }
    }
    productFlavors {
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}


           

隻用retrofit2進行網絡請求

實體類,用于接受傳回資料

先來看不用RxJava2的情況
/**
 * @author: yangtianfu
 * @time: 2018/9/3  11:04
 * @more: https://blog.csdn.net/ytfunnysite
 * @Describe
 */
public class Translation {
    private int status;

    private content content;
    private static class content {
        private String from;
        private String to;
        private String vendor;
        private String out;
        private int errNo;
    }

    //定義 輸出傳回資料 的方法
    public String show() {
        System.out.println(status+"--"+content.from+"--"+content.to+"--"
                +content.vendor+"--"+content.out+"--"+content.errNo);
        return content.out;

    }
}
           

封裝網絡請求接口

import com.app.ytf.netdemo.model.Translation;

import io.reactivex.Observable;
import retrofit2.http.GET;

/**
 * @author: yangtianfu
 * @time: 2018/9/3  16:36
 * @more: https://blog.csdn.net/ytfunnysite
 * @Describe 注解裡傳入 網絡請求 的部分URL位址
 *         Retrofit把網絡請求的URL分成了兩部分:一部分放在Retrofit對象裡,另一部分放在網絡請求接口裡
 *         如果接口裡的url是一個完整的網址,那麼放在Retrofit對象裡的URL可以忽略
 *        采用Observable<...>接口
 *         getCall()是接受網絡請求資料的方法
 */
public interface GetRequest {
    @GET("ajax.php?a=fy&f=auto&t=auto&w=hi%20world")
    Observable<Translation> getCall();
}

           

Retrofit2的實作(使用EventBus更新UI)

public void request() {

        //步驟4:建立Retrofit對象
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("http://fy.iciba.com/") // 設定 網絡請求 Url
                .addConverterFactory(GsonConverterFactory.create()) //設定使用Gson解析(記得加入依賴)
                .build();

        // 步驟5:建立 網絡請求接口 的執行個體
        GetRequest_Interface request = retrofit.create(GetRequest_Interface.class);

        //對 發送請求 進行封裝
        Call<Translation> call = request.getCall();

        //步驟6:發送網絡請求(異步)
        call.enqueue(new Callback<Translation>() {
            //請求成功時候的回調
            @Override
            public void onResponse(Call<Translation> call, Response<Translation> response) {
                //請求處理,輸出結果
                response.body().show();
                EventBus.getDefault().post(response);
            }

            //請求失敗時候的回調
            @Override
            public void onFailure(Call<Translation> call, Throwable throwable) {
                System.out.println("連接配接失敗");
            }
        });
    }

           
此處我們采用了EventBus來更新UI,傳遞請求結果至主線程,要記得登出
@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
        EventBus.getDefault().register(this);


    }
    ......

 @Subscribe(threadMode = ThreadMode.MAIN)
    public void setRestult(Response<Translation> response) {
        tvContent.setText(response.body().show());

    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        EventBus.getDefault().unregister(this);
    }
           

結合RxJava2實作網絡請求輪詢(無條件)

場景:采用Get方法對 金山詞霸API 按規定時間 重複發送網絡請求,進而模拟 輪詢 需求實作

實體類和接口同上不變,直接看處理方式:

/**
     * 無條件輪詢發送網絡請求
     * 參數1 = 第1次延遲時間;
     * 參數2 = 間隔時間數字;
     * 參數3 = 時間機關;
     * 該例子發送的事件特點:延遲2s後發送事件,每隔1秒産生1個數字(從0開始遞增1,無限個)
     * 步驟2:每次發送數字前發送1次網絡請求(doOnNext()在執行Next事件前調用)
     * 即每隔1秒産生1個數字前,就發送1次網絡請求,進而實作輪詢需求
     */
    private void getByRxJava() {


//      步驟1:采用interval()延遲發送
        Observable.interval(2, 1, TimeUnit.SECONDS)
//                步驟2:每次發送數字前發送1次網絡請求(doOnNext()在執行Next事件前調用)
//                   即每隔1秒産生1個數字前,就發送1次網絡請求,進而實作輪詢需求
                .doOnNext(new Consumer<Long>() {
                    @Override
                    public void accept(Long aLong) throws Exception {
                        Log.d(TAG, "第 " + aLong + " 次輪詢");
//                        步驟3:通過Retrofit發送網絡請求
                        Retrofit retrofit = new Retrofit.Builder()
                                .baseUrl(BuildConfig.LOCALHOST)
                                .addConverterFactory(GsonConverterFactory.create())
                                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                                .build();
//                          建立 網絡請求接口 的執行個體
                        GetRequest getRequest = retrofit.create(GetRequest.class);
                        // c. 采用Observable<...>形式 對 網絡請求 進行封裝
                        Observable<Translation> observable = getRequest.getCall();
                        // d. 通過線程切換發送網絡請求
                        observable.subscribeOn(Schedulers.io()) // 切換到IO線程進行網絡請求
                                .observeOn(AndroidSchedulers.mainThread()) // 切換回到主線程 處理請求結果
                                .subscribe(new Observer<Translation>() {
                                    @Override
                                    public void onSubscribe(Disposable d) {

                                    }

                                    @Override
                                    public void onNext(Translation translation) {
                                        String result = translation.show();
                                        tvContent.setText(result);

                                    }

                                    @Override
                                    public void onError(Throwable e) {


                                    }

                                    @Override
                                    public void onComplete() {

                                    }
                                });


                    }
                }).subscribe(new Observer<Long>() {
            @Override
            public void onSubscribe(Disposable d) {
                Log.e(TAG, "執行doOnNext->onSubscribe方法!");
            }

            @Override
            public void onNext(Long aLong) {
                Log.e(TAG, "網絡初始化完成!");

            }

            @Override
            public void onError(Throwable e) {
                Log.e(TAG, "網絡初始化失敗!" + e.toString());
            }

            @Override
            public void onComplete() {
                Log.e(TAG, "執行doOnNext->onComplete方法!");
            }
        });
    }
           

結合RxJava2實作網絡請求輪詢(有條件)

基本操作依然同上,加入輪詢條件變量 int i=0;
/**
     * @author: yangtianfu
     * @time: 2018/9/4  16:26
     * @more: https://blog.csdn.net/ytfunnysite
     * @Describe 有條件的網絡輪詢,定義i為4的時候停止輪詢
     */
    private void getByRxJavaWithConditional() {
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(BuildConfig.LOCALHOST)
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .addConverterFactory(GsonConverterFactory.create())
                .build();
        GetRequest getRequest = retrofit.create(GetRequest.class);
        Observable<Translation> call = getRequest.getCall();
        call.repeatWhen(new Function<Observable<Object>, ObservableSource<?>>() {
            // 在Function函數中,必須對輸入的 Observable<Object>進行處理,此處使用flatMap操作符接收上遊的資料
            @Override
            public ObservableSource<?> apply(Observable<Object> objectObservable){
                // 将原始 Observable 停止發送事件的辨別(Complete() / Error())轉換成1個 Object 類型資料傳遞給1個新被觀察者(Observable)
                // 以此決定是否重新訂閱 & 發送原來的 Observable,即輪詢 // 此處有2種情況:
                // 1. 若傳回1個Complete() / Error()事件,則不重新訂閱 & 發送原來的 Observable,即輪詢結束
                // 2. 若傳回其餘事件,則重新訂閱 & 發送原來的 Observable,即繼續輪詢


                return objectObservable.flatMap(new Function<Object, ObservableSource<?>>() {
                    @Override
                    public ObservableSource<?> apply(Object o) {
                        // 加入判斷條件:當輪詢次數 = 5次後,就停止輪詢
                        if (i > 3) {
                    // 此處選擇發送onError事件以結束輪詢,因為可觸發下遊觀察者的onError()方法回調
                            Observable.error(new Throwable("輪詢結束"));
                        }
                        // 若輪詢次數<4次,則發送1Next事件以繼續輪詢
                        // 注:此處加入了delay操作符,作用 = 延遲一段時間發送(此處設定 = 2s)
                        return Observable.just(1).delay(2000,TimeUnit.SECONDS);
                    }
                });
            }
        }).subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Observer<Translation>() {
                    @Override
                    public void onSubscribe(Disposable d) {

                    }

                    @Override
                    public void onNext(Translation translation) {
                        // e.接收伺服器傳回的資料
                        String result = translation.show();
                        tvContent.setText(result);
                        i++;
                        Log.e(TAG, "onNext: 第"+i+"次輪詢" );


                    }

                    @Override
                    public void onError(Throwable e) {
                        // 擷取輪詢結束資訊
                        Log.d(TAG, e.toString());

                    }

                    @Override
                    public void onComplete() {

                    }
                });

    }
           

結合RxJava2網絡請求嵌套回調(FlatMap)

場景:在第1個網絡請求成功後,繼續再進行一次網絡請求

如 先進行 使用者注冊 的網絡請求, 待注冊成功後回再繼續發送 使用者登入 的網絡請求

結合 RxJava2中的變換操作符FlatMap()實作嵌套網絡請求,采用Get方法對 金山詞霸API 發送網絡請求

即先翻譯 Register(注冊),再翻譯 Login(登入)

###兩個網絡請求對應的實體類

public class Translation {
    private int status;

    private content content;
    private static class content {
        private String from;
        private String to;
        private String vendor;
        private String out;
        private int errNo;
    }

    //定義 輸出傳回資料 的方法
    public String show() {
        System.out.println(status+"--"+content.from+"--"+content.to+"--"
                +content.vendor+"--"+content.out+"--"+content.errNo);
        return content.out;

    }
}
           
public class Translation2 {
    private int status;

    private content content;
    private static class content {
        private String from;
        private String to;
        private String vendor;
        private String out;
        private int errNo;
    }

    //定義 輸出傳回資料 的方法
    public String show() {
        System.out.println(status+"--"+content.from+"--"+content.to+"--"
                +content.vendor+"--"+content.out+"--"+content.errNo);
        return content.out;

    }
}

           

接口封裝(注冊接口和登入接口)

/**
 * @author: yangtianfu
 * @time: 2018/9/4  17:19
 * @more: https://blog.csdn.net/ytfunnysite
 * @Describe注解裡傳入 網絡請求 的部分URL位址
 * Retrofit把網絡請求的URL分成了兩部分:一部分放在Retrofit對象裡,另一部分放在網絡請求接口裡
 * 如果接口裡的url是一個完整的網址,那麼放在Retrofit對象裡的URL可以忽略
 * 采用Observable<...>接口 // getCall()是接受網絡請求資料的方法
 */
public interface GetRequest_Interface {
    // 網絡請求1
    @GET("ajax.php?a=fy&f=auto&t=auto&w=hi%20register")
    Observable<Translation> getCall();
    // 網絡請求2
    @GET("ajax.php?a=fy&f=auto&t=auto&w=hi%20login")
    Observable<Translation2> getCall_2();
}
           

使用flatMap進行嵌套請求

/**
* @author: yangtianfu
* @time: 2018/9/4  17:17
* @more: https://blog.csdn.net/ytfunnysite
* @Describe 通過 公共的金山詞霸API 來模拟 “注冊 - 登入”嵌套網絡請求
 * 即先翻譯 Register(注冊),再翻譯 Login(登入)
*/
    private void getByRxJavaInFlatMap() {
        // 步驟1:建立Retrofit對象
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(BuildConfig.LOCALHOST)
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .addConverterFactory(GsonConverterFactory.create())
                .build();
        // 步驟2:建立 網絡請求接口 的執行個體
        GetRequest_Interface getRequest_interface=retrofit.create(GetRequest_Interface.class);
        // 步驟3:采用Observable<...>形式 對 2個網絡請求 進行封裝
        observable1= getRequest_interface.getCall();
        observable2=getRequest_interface.getCall_2();
        observable1.subscribeOn(Schedulers.io())
                    .observeOn(AndroidSchedulers.mainThread())
                    .doOnNext(new Consumer<Translation>() {
                        @Override
                        public void accept(Translation translation) throws Exception {
                            Log.d(TAG, "第1次網絡請求成功");
                            String result=translation.show();
                            tvContent.setText(result); // 對第1次網絡請求傳回的結果進行操作 = 顯示翻譯結果
                        }
                    })
                //再次切回Io線程進行第二次網絡請求
                .observeOn(Schedulers.io())
                 // (新被觀察者,同時也是新觀察者)切換到IO線程去發起登入請求
                // 特别注意:因為flatMap是對初始被觀察者作變換,是以對于舊被觀察者,它是新觀察者,是以通過observeOn切換線程
                .flatMap(new Function<Translation, ObservableSource<Translation2>>() {
                    @Override
                    public ObservableSource<Translation2> apply(Translation result) throws Exception {
                        // 将網絡請求1轉換成網絡請求2,即發送網絡請求2
                        return observable2;
                    }
                })
                // (初始觀察者)切換到主線程 處理網絡請求2的結果
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Consumer<Translation2>() {
                    @Override
                    public void accept(Translation2 translation2) throws Exception {
                        Log.d(TAG, "第2次網絡請求成功");
                        String result2 = translation2.show();
                        String text = tvContent.getText().toString();
                        tvContent.setText(text + "-" + result2);
                    }
                }, new Consumer<Throwable>() {
                    @Override
                    public void accept(Throwable throwable) throws Exception {
                        System.out.println("登入失敗");
                    }
                });

    }
           

網絡請求出錯重連(結合Retrofit)

Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(BuildConfig.LOCALHOST)
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .addConverterFactory(GsonConverterFactory.create())
                .build();
        GetRequest getRequest = retrofit.create(GetRequest.class);
        Observable<Translation> call = getRequest.getCall();
        call.repeatWhen(new Function<Observable<Object>, ObservableSource<?>>() {
            // 在Function函數中,必須對輸入的 Observable<Object>進行處理,此處使用flatMap操作符接收上遊的資料
            @Override
            public ObservableSource<?> apply(Observable<Object> objectObservable) {
                // 将原始 Observable 停止發送事件的辨別(Complete() / Error())轉換成1個 Object 類型資料傳遞給1個新被觀察者(Observable)
                // 以此決定是否重新訂閱 & 發送原來的 Observable,即輪詢 // 此處有2種情況:
                // 1. 若傳回1個Complete() / Error()事件,則不重新訂閱 & 發送原來的 Observable,即輪詢結束
                // 2. 若傳回其餘事件,則重新訂閱 & 發送原來的 Observable,即繼續輪詢


                return objectObservable.flatMap(new Function<Object, ObservableSource<?>>() {
                    @Override
                    public ObservableSource<?> apply(Object o) {
                        // 加入判斷條件:當輪詢次數 = 5次後,就停止輪詢
                        if (i > 3) {
                            // 此處選擇發送onError事件以結束輪詢,因為可觸發下遊觀察者的onError()方法回調
                            Observable.error(new Throwable("輪詢結束"));
                        }
                        // 若輪詢次數<4次,則發送1Next事件以繼續輪詢
                        // 注:此處加入了delay操作符,作用 = 延遲一段時間發送(此處設定 = 2s)
                        return Observable.just(1).delay(2000, TimeUnit.SECONDS);
                    }
                });
            }
        })
//                .retryWhen(new Function<Observable<Throwable>, ObservableSource<?>>() {
//            // 參數Observable<Throwable>中的泛型 = 上遊操作符抛出的異常,可通過該條件來判斷異常的類型
//            // 傳回Observable<?> = 新的被觀察者 Observable(任意類型)
//            // 此處有兩種情況:
//            // 1. 若 新的被觀察者 Observable發送的事件 = Error事件,那麼 原始Observable則不重新發送事件:
//            // 2. 若 新的被觀察者 Observable發送的事件 = Next事件 ,那麼原始的Observable則重新發送事件:
//            @Override
//            public ObservableSource<?> apply(Observable<Throwable> throwableObservable) throws Exception {
//                // 1. 若傳回的Observable發送的事件 = Error事件,則原始的Observable不重新發送事件
//                // 該異常錯誤資訊可在觀察者中的onError()中獲得
//
//                return Observable.error(new Throwable("retryWhen終止啦"));
//                // 2. 若傳回的Observable發送的事件 = Next事件,則原始的Observable重新發送事件(若持續遇到錯誤,則持續重試)
//                // return Observable.just(1);
//
//            }
//        })
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Observer<Translation>() {
                    @Override
                    public void onSubscribe(Disposable d) {

                    }

                    @Override
                    public void onNext(Translation translation) {
                        // e.接收伺服器傳回的資料
                        String result = translation.show();
                        tvContent.setText(result);
                        i++;
                        Log.e(TAG, "onNext: 第" + i + "次輪詢");


                    }

                    @Override
                    public void onError(Throwable e) {
                        // 擷取輪詢結束資訊
                        Log.d(TAG, e.toString());

                    }

                    @Override
                    public void onComplete() {

                    }
                });
           

補充:

retrofit封裝

package com.thesis.mentor.retrofit_v2;

import com.thesis.mentor.BuildConfig;
import com.thesis.mentor.api_v2.ApiService;
import com.thesis.mentor.retrofit_v2.interceptor.HeaderInterceptor;
import com.thesis.mentor.retrofit_v2.interceptor.LoggingInterceptor;

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

import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.RequestBody;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
import retrofit2.converter.gson.GsonConverterFactory;

/**
 * @autor YangTianFu
 * @Date 2019/3/22  17:10
 * @Description Retrofit+RxJava聯網的統一管理類
 */
public class RetrofitManager {
    private static volatile RetrofitManager mInstance;
    private static final long DEFAULT_TIMEOUT = 60L;
    private Retrofit retrofit = null;
//    private String userAgent = "Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.87 Safari/537.36";

    public static RetrofitManager getInstance() {
        if (mInstance == null) {
            synchronized (RetrofitManager.class) {
                if (mInstance == null) {
                    mInstance = new RetrofitManager();
                }
            }
        }
        return mInstance;
    }

    private Retrofit getRetrofit() {
        if (retrofit == null) {
            synchronized (RetrofitManager.class) {
                if (retrofit == null) {
                    OkHttpClient mClient = new OkHttpClient.Builder()
                            .addInterceptor(new HeaderInterceptor())
                            .addInterceptor(new LoggingInterceptor())
                            .retryOnConnectionFailure(true)
                            .connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
                            .writeTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
                            .readTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
                            .build();
                    retrofit = new Retrofit.Builder()
                            .baseUrl(BuildConfig.LOCALHOST_BASEURL)
                            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                            .addConverterFactory(GsonConverterFactory.create())
                            .client(mClient)
                            .build();
                }
            }
        }
        return retrofit;
    }
    public ApiService getRequestService() {
        return getRetrofit().create(ApiService.class);
    }
    /**
     * Retrofit上傳檔案
     */
    public RequestBody getUploadFileRequestBody(String mImagePath) {
        File file = new File(mImagePath);
        RequestBody requestBody = new MultipartBody.Builder().setType(MultipartBody.FORM)
                .addFormDataPart("file", file.getName(), RequestBody.create(MediaType.parse("multipart/form-data"), file))
                .build();
        return requestBody;
    }

}

           

線程排程

package com.thesis.mentor.retrofit_v2;

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

/**
 * @autor YangTianFu
 * @Date 2019/3/26  10:18
 * @Description rxjava線程排程的封裝
 */
public class RxSchedulers {
    public static <T> ObservableTransformer<T,T> io_main(){
        return new ObservableTransformer<T, T>() {
            @Override
            public ObservableSource<T> apply(Observable<T> upstream) {
                return upstream.subscribeOn(Schedulers.io())
                        .observeOn(AndroidSchedulers.mainThread());
            }
        };
    }

}

           

日志攔截器

package com.thesis.mentor.retrofit_v2.interceptor;

import android.util.Log;

import org.json.JSONObject;

import java.io.IOException;

import okhttp3.FormBody;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okio.Buffer;

/**
 * @autor YangTianFu
 * @Date 2019/3/22  17:25
 * @Description 應用日志攔截器
 */
public class LoggingInterceptor implements Interceptor {

    @Override
    public Response intercept(Chain chain) throws IOException {
        //這個chain裡面包含了request和response
        Request request = chain.request();
        //請求發起的時間
        long t1 = System.nanoTime();
        String method = request.method();
        JSONObject jsonObject = new JSONObject();
        if ("POST".equals(method) || "PUT".equals(method)){
            if (request.body() instanceof FormBody){
                FormBody body = (FormBody) request.body();
                if (body != null){
                    for (int i = 0; i < body.size(); i++) {
                        try {
                            jsonObject.put(body.name(i),body.encodedValue(i));
                        }catch (Exception e){
                            e.printStackTrace();
                        }
                    }
                }
                Log.i("request",String.format("發送請求 %s on %s  %nRequestParams:%s%nMethod:%s",
                        request.url(),chain.connection(),jsonObject.toString(),request.method()));
            }else {
                Buffer buffer = new Buffer();
                RequestBody requestBody = request.body();
                if (requestBody != null){
                    request.body().writeTo(buffer);
                    String body = buffer.readUtf8();
                    Log.i("request", String.format("發送請求 %s on %s  %nRequestParams:%s%nMethod:%s",
                            request.url(), chain.connection(), body, request.method()));
                }
            }
        }else {
            Log.i("request", String.format("發送請求 %s on %s%nMethod:%s",
                    request.url(), chain.connection(), request.method()));
        }

        Response response = chain.proceed(request);
        //收到響應的時間
        long t2 = System.nanoTime();
        ResponseBody responseBody = response.peekBody(1024 * 1024);
        Log.i("request",
                String.format("Retrofit接收響應: %s %n傳回json:%s %n耗時:%.1fms",
                        response.request().url(),
                        responseBody.string(),
                        (t2 - t1) / 1e6d
                ));
        return response;
    }
}

           

請求頭攔截器

package com.thesis.mentor.retrofit_v2.interceptor;

import com.thesis.mentor.application.MyApplication;
import com.thesis.mentor.uitl_v2.LogUtil;

import java.io.IOException;

import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;

import static com.thesis.mentor.me.bean.UserInfoBean.getUserInfo;

/**
 * @autor YangTianFu
 * @Date 2019/3/26  9:17
 * @Description 添加請求頭需要攜帶的參數
 */
public class HeaderInterceptor implements Interceptor {
    //請求頭資訊
    private final String HEADER_CONNECTION = "keep-alive";
    String userAgent = "Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.87 Safari/537.36";
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
//        Log.i(TAG, "HeaderInterceptor: "+request.toString());
        LogUtil.i("HeaderInterceptor: "+request.toString());
        Request requestBuilder = request.newBuilder()
                .addHeader("Connection",HEADER_CONNECTION)
                .addHeader("User-Agent",userAgent)
                .addHeader("source", "Android")
                .addHeader("token", getUserInfo(MyApplication.context).getToken())
                .addHeader("uid", getUserInfo(MyApplication.context).getUid())
                .method(request.method(),request.body())
                .build();
        return chain.proceed(requestBuilder);
    }
}

           

公共查詢參數攔截器

package com.thesis.mentor.retrofit_v2.interceptor;

import java.io.IOException;

import okhttp3.HttpUrl;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;

/**
 * @autor YangTianFu
 * @Date 2019/3/26  9:22
 * @Description 公共查詢參數攔截器
 */
public class CommonQueryParamsInterceptor implements Interceptor {

    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        HttpUrl url = request.url().newBuilder()
                .addQueryParameter("paramsA", "a")
                .addQueryParameter("paramsB", "b")
                .build();
        return chain.proceed(request.newBuilder().url(url).build());
    }
}

           

緩存攔截器

package com.thesis.mentor.retrofit_v2.interceptor;

import android.util.Log;

import com.thesis.mentor.application.MyApplication;
import com.thesis.mentor.uitl_v2.NetUtils;

import java.io.IOException;

import okhttp3.CacheControl;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;

/**
 * @autor YangTianFu
 * @Date 2019/3/26  9:26
 * @Description 設定緩存的攔截器
 */
public class CacheInterceptor implements Interceptor {

    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request =  chain.request();
        if (!NetUtils.isNetworkConnected(MyApplication.getApplicationContext1())){
            request = request.newBuilder().cacheControl(CacheControl.FORCE_CACHE).build();
        }
        Response response = chain.proceed(request);
        if (NetUtils.isNetworkConnected(MyApplication.getApplicationContext1())){
            String cacheControl = request.cacheControl().toString();
            Log.e("net","網絡連接配接正常");
            return response.newBuilder().header("Cache-Control",cacheControl)
                    .removeHeader("Pragma").build();
        }else {
            Log.e("net","斷網了");
            return  response.newBuilder().header("Cache-Control", "public, only-if-cached, max-stale=" + "60 * 60 * 24 * 7")
                    .removeHeader("Pragma").build();
        }
    }
}

           

presenter中請求網絡

public class MyStudyPresenter {

    private Context context;
    private INetCallBackListener listener;

    public MyStudyPresenter(Context context, INetCallBackListener listener) {
        this.context = context;
        this.listener = listener;
    }

    /**
     * 擷取系統時間
     */
    public void getAppTime(){
        if (getUserInfo(context)!=null){
            String utoken=getUserInfo(context).getToken();
            String uid=getUserInfo(context).getUid();
            if (utoken==null||uid==null){
                listener.onFailure("403");
                return;
            }
        }else {
            listener.onFailure("403");
            return;
        }
        Observable<AppTime> appTimeObservable = RetrofitManager.getInstance().getRequestService().getAppTime();
        appTimeObservable.compose(RxSchedulers.io_main())
                .subscribe(new Consumer<AppTime>() {
                    @Override
                    public void accept(AppTime appTime) throws Exception {
                        if (appTime != null){
                            listener.onSuccess(appTime);
                        }
                    }
                });
    }

    public void getJobListPage(String course_id, int pageNum){
        Map<String,String> map = new HashMap<>();
        String utoken=getUserInfo(context).getToken();
        String uid=getUserInfo(context).getUid();
        if (utoken==null||uid==null){
            return;
        }
        String url = "";
        int student_id = 0;
        if (UserInfoBean.getUserInfo(context) != null && UserInfoBean.getUserInfo(context).getStudent() != null) {
            student_id = UserInfoBean.getUserInfo(context).getStudent().getId();
        }
        if (student_id != 0){
            map.put("student_id",String.valueOf(student_id));
            map.put("course_id",course_id);
            map.put("pageNum",String.valueOf(pageNum));
        }
        Observable<HomeWorkResponse> homeWorkResponseObservable = RetrofitManager.getInstance()
                .getRequestService()
                .getJobListPage((HashMap<String, String>) map);
        homeWorkResponseObservable.compose(RxSchedulers.io_main())
                .subscribe(new Consumer<HomeWorkResponse>() {
                    @Override
                    public void accept(HomeWorkResponse homeWorkResponse){
                        if (homeWorkResponse != null){
                            listener.onSuccess(homeWorkResponse);
                        }
                    }
                });
    }
}

           
/**
 * @autor YangTianFu
 * @Date 2019/4/17  16:59
 * @Description 網絡請求回調處理接口
 */
public interface INetCallBackListener<T> {
    void onSuccess(T response);

    void onError(Object error);

    void onFailure(String dsc);
}
           

acvitity中處理資料

public void getAppTime(View view) {
        if (myStudyPresenter != null){
            myStudyPresenter.getAppTime();
        }
    }

    public void getHomeWorkList(View view) {
        if (myStudyPresenter != null){
            myStudyPresenter.getJobListPage(courseId,1);
        }
    }

    @Override
    public void onSuccess(Object response) {
        if (response instanceof AppTime){
            tv_time.setText(((AppTime) response).getTime());
        }else if (response instanceof HomeWorkResponse){
            tv_homework.setText(((HomeWorkResponse) response).getList().toString());
        }
    }

    @Override
    public void onError(Object error) {

    }

    @Override
    public void onFailure(String dsc) {

    }
           

全局日志儲存和上傳

package com.thesis.mentor.uitl_v2;

import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Environment;
import android.util.Log;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * @autor YangTianFu
 * @Date 2019/4/12  17:20
 * @Description 日志儲存SD卡并上傳伺服器的工具類
 */
public class LogUtil {
    private static final String TAG = "LogUtil";
    // 是否需要列印bug,在application的onCreate函數裡面初始化
    public static boolean isDebug;
    private static final String PATH = Environment.getExternalStorageDirectory().getPath() + "/CrashKZ/log/";
    private static final String FILE_NAME = "crash";
    private static final String FILE_NAME_SUFFIX = ".txt";
    private static Context mContext;

    public static void init(Context context) {
        mContext = context.getApplicationContext();
    }

    // 下面四個是預設tag的函數
    public static void i(String msg) {
        if (isDebug) {
            Log.i(TAG, msg);
            dumpLogErrorToSDCard(msg);
        }
    }

    public static void d(String msg) {
        if (isDebug) {
            Log.d(TAG, msg);
        }
    }

    public static void e(String msg) {
        if (isDebug) {
            Log.e(TAG, msg);
            dumpLogErrorToSDCard(msg);
        }
    }

    public static void e(String tag, String msg) {
        if (isDebug) {
            Log.e(tag, msg);
            dumpLogErrorToSDCard(msg);
        }
    }

    public static void v(String msg) {
        if (isDebug) {
            Log.v(TAG, msg);
        }
    }

    // 下面是傳入自定義tag的函數
    public static void i(String tag, String msg) {
        if (isDebug) {
            Log.i(tag, msg);
            dumpLogErrorToSDCard(msg);
        }
    }

    /**
     * 截斷輸出日志
     * @param msg
     */
    public static void eLength(String tag, String msg) {
        if (isDebug) {
            if (tag == null || tag.length() == 0
                    || msg == null || msg.length() == 0) {
                return;
            }
            int segmentSize = 3 * 1024;
            long length = msg.length();
            if (length <= segmentSize) {// 長度小于等于限制直接列印
                Log.e(tag, msg);
            } else {
                while (msg.length() > segmentSize) {// 循環分段列印日志
                    String logContent = msg.substring(0, segmentSize);
                    msg = msg.replace(logContent, "");
                    Log.e(tag, logContent);
                }
                Log.e(tag, msg);// 列印剩餘日志
            }
        }
    }

    /**
     * 分段列印出較長log文本
     * @param log 原log文本
     * @param
     */
    public static void showLogCompletion(String string, String log) {
        if (log.length() > 4000) {
            for (int i = 0; i < log.length(); i += 4000) {
                if (i + 4000 < log.length()) {
                    Log.i(string + i, log.substring(i, i + 4000));
                } else {
                    Log.i(string + i, log.substring(i, log.length()));
                }
            }
        } else {
            Log.i(string, log);
        }
    }

    /**
     * @Author:yangtianfu
     * @Date:{2019/4/16 16:36}
     * @Description 将崩潰日志和自定義錯誤日志寫入SD卡根目錄
     */
    public static void dumpLogErrorToSDCard(String msg) {
        //如果SD卡不存在或無法使用,則無法把異常資訊寫入SD卡
        if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
            if (isDebug) {
                Log.e(TAG, "sdcard unmounted,skip dump exception");
                return;
            }
        }
        long current = System.currentTimeMillis();
        String time = new SimpleDateFormat("yyyy-MM-dd HH:MM:SS").format(new Date(current));
        File dir = new File(PATH);
        if (!dir.exists()) {
            dir.mkdirs();
        }
        File file = new File(PATH + FILE_NAME + FILE_NAME_SUFFIX);
        try {
            PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter(file, true)));
            pw.println(time);
            dumpPhoneInfo(pw);
            pw.println(msg);
            pw.println();
            pw.close();
            Log.i(TAG, "日志寫入成功");
        } catch (Exception e) {
            Log.e(TAG, e.getMessage());
            Log.e(TAG, "日志寫入失敗");
        }
    }

    /**
     * @Author:yangtianfu
     * @Date:{2019/4/16 16:37}
     * @Description 擷取目前裝置資訊
     */
    private static void dumpPhoneInfo(PrintWriter pw) throws PackageManager.NameNotFoundException {
        PackageManager pm = mContext.getPackageManager();
        PackageInfo pi = pm.getPackageInfo(mContext.getPackageName(), PackageManager.GET_ACTIVITIES);
        pw.print("App Version: ");
        pw.print(pi.versionName);
        pw.print('_');
        pw.println(pi.versionCode);
        //Android版本号
        pw.print("OS Version: ");
        pw.print(Build.VERSION.RELEASE);
        pw.print("_");
        pw.print(Build.VERSION.SDK_INT);
        //手機制造商
        pw.print("Vendor: ");
        pw.print(Build.MANUFACTURER);
        //手機型号
        pw.print("Model: ");
        pw.println(Build.MODEL);
        //CPU架構
        pw.print("CPU ABI: ");
        pw.println(Build.CPU_ABI);
    }
}

           

檢測網絡

package com.thesis.mentor.uitl_v2;

import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;

import java.util.Locale;

/**
 * @autor YangTianFu
 * @Date 2019/3/26  9:26
 * @Description 檢測網絡
 */
public class NetUtils {

    /**
     * 四種網絡類型
     * WIFI: wifi
     * CMNET:中國移動網際網路
     * CMWAP :中國移動夢網
     * NONE: 無網絡連接配接
     */
    public static enum NetType {
        WIFI, CMNET, CMWAP, NONE
    }

    /**
     * 目前網絡是否可用
     * @param context
     * @return
     */
    public static boolean isNetworkAvailable(Context context) {
        ConnectivityManager mgr = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo[] info = mgr.getAllNetworkInfo();
        if (info != null) {
            for (int i = 0; i < info.length; i++) {
                if (info[i].getState() == NetworkInfo.State.CONNECTED) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * 網絡是否連接配接成功
     * @param context
     * @return
     */
    public static boolean isNetworkConnected(Context context) {
        if (context != null) {
            ConnectivityManager mConnectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
            NetworkInfo mNetworkInfo = mConnectivityManager.getActiveNetworkInfo();
            if (mNetworkInfo != null) {
                return mNetworkInfo.isAvailable();
            }
        }
        return false;
    }

    /**
     * wifi網絡連接配接是否可用
     * @param context
     * @return
     */
    public static boolean isWifiConnected(Context context) {
        if (context != null) {
            ConnectivityManager mConnectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
            NetworkInfo mWiFiNetworkInfo = mConnectivityManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
            if (mWiFiNetworkInfo != null) {
                return mWiFiNetworkInfo.isAvailable();
            }
        }
        return false;
    }

    /**
     * 移動網絡連接配接是否可用
     * @param context
     * @return
     */
    public static boolean isMobileConnected(Context context) {
        if (context != null) {
            ConnectivityManager mConnectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
            NetworkInfo mMobileNetworkInfo = mConnectivityManager.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
            if (mMobileNetworkInfo != null) {
                return mMobileNetworkInfo.isAvailable();
            }
        }
        return false;
    }

    /**
     * 網絡類型判斷
     * @param context
     * @return
     */
    public static int getConnectedType(Context context) {
        if (context != null) {
            ConnectivityManager mConnectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
            NetworkInfo mNetworkInfo = mConnectivityManager.getActiveNetworkInfo();
            if (mNetworkInfo != null && mNetworkInfo.isAvailable()) {
                return mNetworkInfo.getType();
            }
        }
        return -1;
    }

    /**
     * 網絡類型判斷
     *
     * @param context
     * @return
     */
    public static NetType getAPNType(Context context) {
        ConnectivityManager connMgr = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
        if (networkInfo == null) {
            return NetType.NONE;
        }
        int nType = networkInfo.getType();

        if (nType == ConnectivityManager.TYPE_MOBILE) {
            if (networkInfo.getExtraInfo().toLowerCase(Locale.getDefault()).equals("cmnet")) {
                return NetType.CMNET;
            } else {
                return NetType.CMWAP;
            }
        } else if (nType == ConnectivityManager.TYPE_WIFI) {
            return NetType.WIFI;
        }
        return NetType.NONE;
    }
}

           

全局Toast管理

package com.thesis.mentor.uitl_v2;

import android.content.Context;
import android.view.Gravity;
import android.widget.Toast;

/**
 * @autor YangTianFu
 * @Date 2019/3/25  13:47
 * @Description 全局Toast統一管理類,避免多個吐司重複彈出
 */
public class ToastUtil {
    public static volatile ToastUtil instance;

    public static Toast toast = null;

    private ToastUtil() {
    }

    /**
     * @Author:yangtianfu
     * @Date:{2019/4/16 16:34}
     * @Description 雙重驗鎖,保證全局單例模式
     */
    public static ToastUtil getInstance() {
        if (instance == null) {
            synchronized (ToastUtil.class) {
                if (instance == null) {
                    instance = new ToastUtil();
                }
            }
        }
        return instance;
    }
// 是否顯示吐司的開關,可以在Application中進行設定,預設為true。
    public static boolean isShow = true;

    /**
     * 短時間顯示Toast
     *
     * @param context
     * @param message
     */
    public static void show(Context context, CharSequence message) {
        if (isShow) {
            Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
        }
    }

    /**
     * 中間吐司
     *
     * @param title
     */
    public static void showDefultToast(Context context, String title) {
        if (toast == null) {
            toast = Toast.makeText(context, title, Toast.LENGTH_SHORT);
            toast.setGravity(Gravity.CENTER, 0, 0);
            toast.show();
        } else {
            toast.setText(title);
            toast.show();
        }
    }

}