使用OkHttp高效開發(fā)調(diào)試

本文使用的OkHttp版本是okhttp3,使用okhttp2的情況稍有不同擅腰,使用okhttp2的同學(xué)替換為okhttp3也不麻煩蟋恬,API都很接近;當(dāng)然趁冈,如果想要使用okhttp2使用stetho & 攔截器也是可以的歼争,可以參考官方文檔。

先貼出Gradle依賴渗勘,基本上我使用的都是最新版本的庫(kù)沐绒。

def okhttp3Version = '3.4.1'
compile('com.squareup.okhttp3:okhttp:' + okhttp3Version)
compile('com.squareup.okhttp3:logging-interceptor:' + okhttp3Version) 
testCompile('com.squareup.okhttp3:mockwebserver:' + okhttp3Version)
compile 'com.facebook.stetho:stetho-okhttp3:1.3.1'    

1. 定義一個(gè)全局的OkHttp請(qǐng)求單例類,全局統(tǒng)一使用單一OkHttpClient:

/** 
  * 全局統(tǒng)一使用的OkHttpClient工具旺坠,okhttp版本:okhttp3 
  */
public class OkHttpUtils {    
    public static final long DEFAULT_READ_TIMEOUT_MILLIS = 15 * 1000;    
    public static final long DEFAULT_WRITE_TIMEOUT_MILLIS = 20 * 1000;    
    public static final long DEFAULT_CONNECT_TIMEOUT_MILLIS = 20 * 1000;    
    private static final long HTTP_RESPONSE_DISK_CACHE_MAX_SIZE = 10 * 1024 * 1024;    
    private static volatile OkHttpUtils sInstance;
    private OkHttpClient mOkHttpClient;
    private OkHttpUtils() {
        HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
        //包含header乔遮、body數(shù)據(jù)
        loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);

        mOkHttpClient = new OkHttpClient.Builder()
                .readTimeout(DEFAULT_READ_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)
                .writeTimeout(DEFAULT_WRITE_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)
                .connectTimeout(DEFAULT_CONNECT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)
                //FaceBook 網(wǎng)絡(luò)調(diào)試器,可在Chrome調(diào)試網(wǎng)絡(luò)請(qǐng)求取刃,查看SharePreferences,數(shù)據(jù)庫(kù)等
                .addNetworkInterceptor(new StethoInterceptor())
                //http數(shù)據(jù)log蹋肮,日志中打印出HTTP請(qǐng)求&響應(yīng)數(shù)據(jù)
                .addInterceptor(loggingInterceptor)
                .build();
    }

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

    public OkHttpClient getOkHttpClient() {
        return mOkHttpClient;
    }

    public void setCache(Context appContext) {
        final File baseDir = appContext.getApplicationContext().getCacheDir();
        if (baseDir != null) {
            final File cacheDir = new File(baseDir, "HttpResponseCache");
            mOkHttpClient.newBuilder().cache((new Cache(cacheDir, HTTP_RESPONSE_DISK_CACHE_MAX_SIZE)));
        }
    }
}

在所有需要用到OkHttpClient的地方使用以下代碼,全局使用同一個(gè)OkHttpClient實(shí)例:

OkHttpClient okHttpClient = OkHttpUtils.getInstance().getOkHttpClient();

全局使用一個(gè)OkHttpClient的原因是所有請(qǐng)求沒(méi)必要?jiǎng)?chuàng)建多個(gè)請(qǐng)求客戶端實(shí)例璧疗,一個(gè)好處是節(jié)省內(nèi)存坯辩,另外一個(gè)是全局設(shè)定了一些監(jiān)控工具,如Facebook的Stetho 和 OkHttp自帶的HttpLoggingInterceptor病毡,你就可以監(jiān)管你的所有Http請(qǐng)求濒翻。

2. HttpLoggingInterceptor Log直接觀察HTTP請(qǐng)求&響應(yīng)數(shù)據(jù)

HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
//包含header、body數(shù)據(jù)
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);

//在build OkHttpClient的時(shí)候加入Log攔截器
OkHttpClient.Builder().addInterceptor(loggingInterceptor)

HttpLoggingInterceptor的效果如下圖,開發(fā)時(shí)如果需要看整個(gè)App的所有請(qǐng)求及相應(yīng)有送,可以使用okhttp關(guān)鍵字過(guò)濾淌喻。

OkHttp3攔截器Log打印.png

在實(shí)例化你的HttpLoggingInterceptor的時(shí)候傳入一個(gè)Logger參數(shù),可以定制化OkHttp輸出的格式化http請(qǐng)求體&響應(yīng)體log雀摘。

3. Facebook強(qiáng)大的監(jiān)測(cè)工具:Stetho

3.1 在build OkHttpClient時(shí)需要添加網(wǎng)絡(luò)攔截器

OkHttpClient.Builder().addNetworkInterceptor(new StethoInterceptor())

3.2 在Application的OnCreate中初始化

//FaceBook調(diào)試器,可在Chrome調(diào)試網(wǎng)絡(luò)請(qǐng)求,查看SharePreferences,數(shù)據(jù)庫(kù)等
Stetho.initializeWithDefaults(this);

3.3 連接手機(jī)裸删,在Chrome中打開<a>chome://inspect/#devices</a>

看到如下界面,則代表監(jiān)測(cè)成功阵赠,如果沒(méi)有App顯示涯塔,那應(yīng)該就是忘了在Application中初始化Stetho

chrome://inspect/#devices.png

3.4 查看網(wǎng)絡(luò)請(qǐng)求

請(qǐng)求概覽
HTTP響應(yīng)body

3.5查看數(shù)據(jù)庫(kù)

查看數(shù)據(jù)庫(kù)

查看SharePreferences也同理,點(diǎn)擊Local Storage就是你的App的所有SharePreferences清蚀。

4. 自定義OkHttp攔截器

當(dāng)你與服務(wù)端對(duì)接的時(shí)候匕荸,當(dāng)服務(wù)端功能還沒(méi)開發(fā)完成,而你等待著服務(wù)器的接口測(cè)試時(shí)枷邪,自定義攔截器就可以幫你無(wú)需等待服務(wù)端完成功能先進(jìn)行開發(fā)榛搔。
步驟:
1)先與服務(wù)端協(xié)商接口返回?cái)?shù)據(jù)格式,拿到協(xié)商的數(shù)據(jù)东揣,寫一些假數(shù)據(jù)践惑,每個(gè)接口對(duì)應(yīng)建立一個(gè)JSON文本文件,里面放接口定義的數(shù)據(jù)嘶卧。

數(shù)據(jù)文件尔觉,服務(wù)端同學(xué)提供測(cè)試數(shù)據(jù).png

2)自定義攔截器

/** * 自定義okhttp攔截器,可定制接口偽造Http響應(yīng)數(shù)據(jù) */
public final class MockDataApiInterceptor implements Interceptor {    
public static final String TAG = MockDataApiInterceptor.class.getSimpleName();
    @Override
    public Response intercept(Chain chain) throws IOException {
        Response response = null;
        String path = chain.request().url().uri().getPath();
        LogUtil.d(TAG, "intercept: path=" + path);
        response = interceptRequestWhenDebug(chain, path);
        if (null == response) {
            LogUtil.i(TAG, "intercept: null == response");
            response = chain.proceed(chain.request());
        }        return response;
    }

    /**
     * 測(cè)試環(huán)境下攔截需要的接口請(qǐng)求芥吟,偽造數(shù)據(jù)返回
     *
     * @param chain 攔截器鏈
     * @param path  請(qǐng)求的路徑path
     * @return 偽造的請(qǐng)求Response侦铜,有可能為null
     */
    private Response interceptRequestWhenDebug(Chain chain, String path) {
        Response response = null;
        if (BuildConfig.DEBUG) {
            Request request = chain.request();
            if (path.equalsIgnoreCase("/api/event")) {
                //活動(dòng)列表接口
                response = getMockEventListResponse(request);
            } else if (path.startsWith("/api/event/")) {
                //活動(dòng)詳情接口
                response = getMockEventDetailResponse(request);
            } 
        }
        return response;
    }

    /**
     * 偽造活動(dòng)詳情接口響應(yīng)
     *
     * @param request 用戶的請(qǐng)求
     * @return 偽造的活動(dòng)詳情HTTP響應(yīng)
     */
    private Response getMockEventDetailResponse(Request request) {
        Response response;
        String data = MockDataGenerator.getMockDataFromJsonFile("mock/EventDetail.json");
        response = getHttpSuccessResponse(request, data);
        return response;
    }

    /**
     * 偽造活動(dòng)列表接口響應(yīng)
     *
     * @param request 用戶的請(qǐng)求
     * @return 偽造的活動(dòng)列表HTTP響應(yīng)
     */
    private Response getMockEventListResponse(Request request) {
        Response response;
        String data = MockDataGenerator.getMockDataFromJsonFile("mock/EventList.json");
        response = getHttpSuccessResponse(request, data);
        return response;
    }
   
     /**
     * 根據(jù)數(shù)據(jù)JSON字符串構(gòu)造HTTP響應(yīng),在JSON數(shù)據(jù)不為空的情況下返回200響應(yīng)运沦,否則返回500響應(yīng)
     *
     * @param request  用戶的請(qǐng)求
     * @param dataJson 響應(yīng)數(shù)據(jù)泵额,JSON格式
     * @return 構(gòu)造的HTTP響應(yīng)
     */
    private Response getHttpSuccessResponse(Request request, String dataJson) {
        Response response;
        if (TextUtils.isEmpty(dataJson)) {
            LogUtil.w(TAG, "getHttpSuccessResponse: dataJson is empty!");
            response = new Response.Builder()
                    .code(500)
                    .protocol(Protocol.HTTP_1_0)
                    .request(request)
                    //必須設(shè)置protocol&request,否則會(huì)拋出異常
                    .build();
        } else {
            response = new Response.Builder()
                    .code(200)
                    .message(dataJson)
                    .request(request)
                    .protocol(Protocol.HTTP_1_0)
                    .addHeader("Content-Type", "application/json")
                    .body(ResponseBody.create(MediaType.parse("application/json"), dataJson))                    .build();
        }
        return response;
    }

    private Response getHttpFailedResponse(Chain chain, int errorCode, String errorMsg) {
        if (errorCode < 0) {
            throw new IllegalArgumentException("httpCode must not be negative");
        }
        Response response;
        response = new Response.Builder()
                .code(errorCode)
                .message(errorMsg)
                .request(chain.request())
                .protocol(Protocol.HTTP_1_0)
                .build();
        return response;
    }
}

接口返回什么數(shù)據(jù)都由你自己定義携添,即使是Http的響應(yīng)碼等嫁盲。這里的響應(yīng)body我們使用服務(wù)端同學(xué)給我們提供的JSON文件

3)在build OkHttpClient時(shí)添加自定義攔截器

OkHttpClient.Builder().addInterceptor(new MockDataApiInterceptor())

4)進(jìn)行開發(fā),完善數(shù)據(jù)解析邏輯等烈掠,與View結(jié)合等羞秤。

5. OkHttp與Retrofit/RxJava組合請(qǐng)求

準(zhǔn)備寫一篇文章詳細(xì)講

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市左敌,隨后出現(xiàn)的幾起案子瘾蛋,更是在濱河造成了極大的恐慌,老刑警劉巖矫限,帶你破解...
    沈念sama閱讀 216,919評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件哺哼,死亡現(xiàn)場(chǎng)離奇詭異佩抹,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)取董,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,567評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門棍苹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人茵汰,你說(shuō)我怎么就攤上這事枢里。” “怎么了蹂午?”我有些...
    開封第一講書人閱讀 163,316評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵栏豺,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我豆胸,道長(zhǎng)奥洼,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,294評(píng)論 1 292
  • 正文 為了忘掉前任配乱,我火速辦了婚禮溉卓,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘搬泥。我一直安慰自己,他們只是感情好伏尼,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,318評(píng)論 6 390
  • 文/花漫 我一把揭開白布忿檩。 她就那樣靜靜地躺著,像睡著了一般爆阶。 火紅的嫁衣襯著肌膚如雪燥透。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,245評(píng)論 1 299
  • 那天辨图,我揣著相機(jī)與錄音班套,去河邊找鬼。 笑死故河,一個(gè)胖子當(dāng)著我的面吹牛吱韭,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播鱼的,決...
    沈念sama閱讀 40,120評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼理盆,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了凑阶?” 一聲冷哼從身側(cè)響起猿规,我...
    開封第一講書人閱讀 38,964評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎宙橱,沒(méi)想到半個(gè)月后姨俩,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蘸拔,經(jīng)...
    沈念sama閱讀 45,376評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,592評(píng)論 2 333
  • 正文 我和宋清朗相戀三年环葵,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了都伪。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,764評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡积担,死狀恐怖陨晶,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情帝璧,我是刑警寧澤先誉,帶...
    沈念sama閱讀 35,460評(píng)論 5 344
  • 正文 年R本政府宣布,位于F島的核電站的烁,受9級(jí)特大地震影響褐耳,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜渴庆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,070評(píng)論 3 327
  • 文/蒙蒙 一铃芦、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧襟雷,春花似錦刃滓、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,697評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至计呈,卻和暖如春砰诵,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背捌显。 一陣腳步聲響...
    開封第一講書人閱讀 32,846評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工茁彭, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人扶歪。 一個(gè)月前我還...
    沈念sama閱讀 47,819評(píng)論 2 370
  • 正文 我出身青樓理肺,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親击罪。 傳聞我的和親對(duì)象是個(gè)殘疾皇子哲嘲,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,665評(píng)論 2 354

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