OkHttp3幾個簡單的例子和在子線程更新UI線程的方法

okHttp用于android的http請求淹遵。據(jù)說很厲害珍语,我們來一起嘗嘗鮮。但是使用okHttp也會有一些小坑集漾,后面會講到如何掉進坑里并爬出來切黔。

代碼地址:https://github.com/future-challenger/kotlinAndroid

首先需要了解一點,這里說的UI線程和主線程是一回事兒具篇。就是唯一可以更新UI的線程纬霞。這個只是點會在給okHttp填坑的時候用到。而且驱显,這個內(nèi)容本身在日常的開發(fā)中也經(jīng)常用到诗芜,值得好好學一學。

okHttp發(fā)起同步請求

第一個列子是一個同步請求的例子埃疫。

private void performSyncHttpRequest() {
    OkHttpClient client = new OkHttpClient();

    Request request = new Request.Builder()
        .url("http://www.baidu.com")
        .build();
    Call call = client.newCall(request);
    Response response = call.execute();
}

但是這樣的直接在android的主線程里調(diào)用一個網(wǎng)絡請求的方法是行不通的伏恐,直接拋出UI Thread 請求網(wǎng)絡的異常。所以我們這里為了可以掩飾要做一點小小的改動栓霜。把請求寫成同步請求的方式翠桦,但是放在一個worker線程里異步的做這個操作。

    private Handler requestHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case REQUEST_SUCCESS:
                    Toast.makeText(MainActivity.this, "SUCCESSFUL", Toast.LENGTH_SHORT).show();
                    break;
                case REQUEST_FAIL:
                    Toast.makeText(MainActivity.this, "request failed", Toast.LENGTH_SHORT).show();
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    };

    private void performSyncHttpRequest() {
        Runnable requestTask = new Runnable() {
            @Override
            public void run() {
                Message msg = requestHandler.obtainMessage();
                try {
                    OkHttpClient client = new OkHttpClient();

                    Request request = new Request.Builder()
                            .url("http://www.baidu.com")
                            .build();
                    Call call = client.newCall(request);
                    // 1
                    Response response = call.execute();

                    if (!response.isSuccessful()) {
                        msg.what = REQUEST_FAIL;
                    } else {
                        msg.what = REQUEST_SUCCESS;
                    }
                } catch (IOException ex) {
                    msg.what = REQUEST_FAIL;
                } finally {
                    // send the message
                    // 2
                    msg.sendToTarget();
                }
            }
        };

        Thread requestThread = new Thread(requestTask);
        requestThread.start();
    }  

所以同步的請求都是這么做的Response response = call.execute();

  1. 發(fā)起同步請求之前先新初始化一個OkHttpClient销凑。然后是具體的請求丛晌,用請求builder來創(chuàng)建這個Request。我們這里為了簡單url就是http://www.baidu.com了斗幼。接下來用前面初始化好的client發(fā)起一個call:Call call = client.newCall(request);澎蛛。最后執(zhí)行這個call:Response response = call.execute();并獲得請求的response。
  2. 這一部分的可以暫時不要關(guān)注蜕窿。因為這個例子只是為了能以運行起來的方式展示okHttp如何發(fā)起同步請求谋逻。

okHttp發(fā)起異步請求

既然android本身不支持發(fā)起同步請求,當然也沒人要發(fā)起同步請求桐经。這么做是能導致嚴重的用戶體驗問題毁兆。想象一下,如果你有一個瀑布流次询,然后瀑布流里全部顯示的都是圖片∮校現(xiàn)在用戶要不斷地往下翻看瀑布流的圖片。如果這些圖片都用同步請求的話屯吊,什么時候可以翻一頁不說送巡,系統(tǒng)的ANR早就跳出來了。

所以我們就探究一下如何發(fā)起一個異步的請求盒卸。

    private void performAsyncHttpRequest() {
        OkHttpClient client = new OkHttpClient();

        Request request = new Request.Builder()
                .url("http://www.baidu.com")
                .build();
        Call call = client.newCall(request);
        // 1
        call.enqueue(new Callback() {
            // 2
            @Override
            public void onFailure(Call call, IOException e) {
                //Toast.makeText(MainActivity.this, e.getMessage(), Toast.LENGTH_SHORT).show();
                
                if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
                    Log.d(TAG, "Main Thread");
                } else {
                    Log.d(TAG, "Not Main Thread");
                }
            }

            @Override
            public void onResponse(Call call, final Response response) throws IOException {
                // 3
                if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
                    Log.d(TAG, "Main Thread");
                } else {
                    Log.d(TAG, "Not Main Thread");
                }
            }
        });
    }    
  1. 同步請求用execute方法骗爆,異步就用call.enqueue(new Callback()方法。
  2. 這個Callback接口提供了兩個方法蔽介,一個是onFailure摘投,一個是onResponse。這兩個方法分別在請求失敗和成功的時候調(diào)用虹蓄。
  3. 本來一切都似乎應該很簡單犀呼。網(wǎng)絡請求成功或者失敗直接在界面更新了。但是木有想到這樣會拋異常薇组。然后看了看發(fā)現(xiàn)原來onFailureonResponse兩個方法不是在主線程執(zhí)行外臂。打印出來的log是:okhttp.demo.com.okhttpdemo D/###okHttp: Not Main Thread

所以要在主線程中更新view只好想別的辦法了律胀。在worker線程里更新主線程會拋異常宋光。一般來說有這么幾個方法在子線程里更新view。

在子線程更新UI線程

一炭菌、Activity的runOnUiThread方法

Activity中有這么一個方法runOnUiThread罪佳。這個方法需要一個Runnable實例作為參數(shù)。

    MainActivity.this.runOnUiThread(new Runnable() {
        @Override
        public void run() {
            Log.d(TAG, "code: ");
            Toast.makeText(MainActivity.this, String.valueOf(response.code()), Toast.LENGTH_SHORT).show();
        }
    });

二黑低、View的post方法

View的post方法也是一樣赘艳,扔一個Runnable的實例進去。然后就在主線程執(zhí)行了。Toast肯定是沒有這個方法的第练。

MainActivity.this.mView.post(new Runnable() {
    public void run() {
        Log.d("UI thread", "I am the UI thread");
    }
});

三阔馋、其他

  1. 用Handler玛荞,這個前面的okHttp同步請求的例子可以用娇掏。
  2. AsyncTask, 有兩個方法可以在主線程中執(zhí)行:onProgressUpdateonPostExecute。這里我們并不是要更新進度勋眯,所以考慮的是后一個方法婴梧。
    private class BackgroundTask extends AsyncTask<String, Void, Bitmap> {
        protected void onPostExecute(Bitmap result) {
            Log.d("UI thread", "I am the UI thread");
        }
    }

綜合以上,更新UI線程的方法里最后說到的Handler方法和AsyncTask都太重客蹋。尤其是AsyncTask塞蹭。還要繼承實現(xiàn)一堆的方法之后才可以能達到目的,同時還和我們要用的okHttp的使用方法很多不兼容的地方讶坯。

所以我們只考慮前面的兩種番电。但是兩種方法其實是不一樣的。當然辆琅,這里并不是說方法的名字不一樣漱办。我們來看看android的源代碼,這兩個方法是如何實現(xiàn)的婉烟。

    public final void runOnUiThread(Runnable action) {
        if (Thread.currentThread() != mUiThread) {
            mHandler.post(action);
        } else {
            action.run();
        }
    }
    // mHandler.post(action); 之post方法的實現(xiàn)
    public final boolean post(Runnable r) {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }

方法runOnUiThread最后會調(diào)用HandlersendMessageDelayed娩井。但是這里只delay了0。也就是方法傳到這里的時候會立即執(zhí)行runOnUiThread的參數(shù)Runnable實例會立即執(zhí)行似袁。

下面看看View的post方法:

    public boolean post(Runnable action) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            return attachInfo.mHandler.post(action);
        }
        // Assume that post will succeed later
        ViewRootImpl.getRunQueue().post(action);
        return true;
    }

一般會執(zhí)行的是ViewRootImpl.getRunQueue().post(action);洞辣。也就是Runnable的實例只是添加到了事件隊列中,按照順序執(zhí)行昙衅。并不一定會立即執(zhí)行扬霜。

我們探討了那么多,最后就使用runOnUiThread來更新界面而涉,也就是方法一了著瓶。

call.enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
        final String errorMMessage = e.getMessage();

        if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
            Log.d(TAG, "Main Thread");
        } else {
            Log.d(TAG, "Not Main Thread");
        }

        MainActivity.this.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                Toast.makeText(MainActivity.this, errorMMessage, Toast.LENGTH_SHORT).show();
            }
        });
    }

    @Override
    public void onResponse(Call call, final Response response) throws IOException {
        if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
            Log.d(TAG, "Main Thread");
        } else {
            Log.d(TAG, "Not Main Thread");
        }

        MainActivity.this.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                Log.d(TAG, "code: ");
                Toast.makeText(MainActivity.this, String.valueOf(response.code()), Toast.LENGTH_SHORT).show();
            }
        });
    }
});

無論請求成功還是失敗,都彈出一個Toast婴谱。用MainActivity.this.runOnUiThread在UI線程中彈出這個Toast蟹但。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市谭羔,隨后出現(xiàn)的幾起案子华糖,更是在濱河造成了極大的恐慌,老刑警劉巖瘟裸,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件客叉,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機兼搏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進店門卵慰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人佛呻,你說我怎么就攤上這事裳朋。” “怎么了吓著?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵鲤嫡,是天一觀的道長。 經(jīng)常有香客問我绑莺,道長暖眼,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任纺裁,我火速辦了婚禮诫肠,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘欺缘。我一直安慰自己栋豫,他們只是感情好,可當我...
    茶點故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布浪南。 她就那樣靜靜地躺著笼才,像睡著了一般。 火紅的嫁衣襯著肌膚如雪络凿。 梳的紋絲不亂的頭發(fā)上骡送,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天,我揣著相機與錄音絮记,去河邊找鬼摔踱。 笑死,一個胖子當著我的面吹牛怨愤,可吹牛的內(nèi)容都是我干的派敷。 我是一名探鬼主播,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼撰洗,長吁一口氣:“原來是場噩夢啊……” “哼篮愉!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起差导,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤试躏,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后设褐,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體颠蕴,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡泣刹,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了犀被。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片椅您。...
    茶點故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖寡键,靈堂內(nèi)的尸體忽然破棺而出掀泳,到底是詐尸還是另有隱情,我是刑警寧澤昌腰,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布开伏,位于F島的核電站膀跌,受9級特大地震影響遭商,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜捅伤,卻給世界環(huán)境...
    茶點故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一劫流、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧丛忆,春花似錦祠汇、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至凰浮,卻和暖如春我抠,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背袜茧。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工菜拓, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人笛厦。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓纳鼎,卻偏偏與公主長得像,于是被迫代替她去往敵國和親裳凸。 傳聞我的和親對象是個殘疾皇子贱鄙,可洞房花燭夜當晚...
    茶點故事閱讀 44,724評論 2 354

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