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();
。
- 發(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。 - 這一部分的可以暫時不要關(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");
}
}
});
}
- 同步請求用
execute
方法骗爆,異步就用call.enqueue(new Callback()
方法。 - 這個
Callback
接口提供了兩個方法蔽介,一個是onFailure
摘投,一個是onResponse
。這兩個方法分別在請求失敗和成功的時候調(diào)用虹蓄。 - 本來一切都似乎應該很簡單犀呼。網(wǎng)絡請求成功或者失敗直接在界面更新了。但是木有想到這樣會拋異常薇组。然后看了看發(fā)現(xiàn)原來
onFailure
和onResponse
兩個方法不是在主線程執(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");
}
});
三阔馋、其他
- 用Handler玛荞,這個前面的okHttp同步請求的例子可以用娇掏。
-
AsyncTask
, 有兩個方法可以在主線程中執(zhí)行:onProgressUpdate
和onPostExecute
。這里我們并不是要更新進度勋眯,所以考慮的是后一個方法婴梧。
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)用Handler
的sendMessageDelayed
娩井。但是這里只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
蟹但。