1.簡介
在過去,Android上發送HTTP請求一般有兩種方式:HttpURLConnection和HttpClient(Apache HttpClient)。不過由於HttpClient存在API數量過多、擴展困難等缺點,Android團隊越來越不建議我們使用這種方式。終於在Android 6.0系統中,HttpClient的功能被完全移除了,標誌着此功能被正式棄用。雖然官方推薦使用HttpURLConnection,但是在代碼編寫的過程中仍然會比較繁瑣。此時,一個將要替代它們的網絡層框架出現了:也就是Okhttp,該框架也是我在《Android 第一行代碼》上學到的僅此於Litepal之後的第二個框架。今天重新學習了一下Okhttp,將學習歷程記錄到博客上,望對讀者有幫助。
OkHttp是由鼎鼎大名的Square公司開發的,這個公司在開源事業上面貢獻良多,除了 OkHttp 之外,還開發了像Picasso,Retrofit等著名的開源項目。OkHttp不僅在接口封裝上面做得簡單易用,就連在底層實現上也是自成一派,比起原生的HttpURLConnection,可以說是有過之而無不及,現在已經成了廣大Android開發者首選的網絡通信庫。
2.特性
OkHttp是一個高效的HTTP客戶端,它有以下默認特性:
- 支持HTTP/2,允許所有同一個主機地址的請求共享同一個socket連接
- 連接池減少請求延時
- 透明的GZIP壓縮減少響應數據的大小
- 緩存響應內容,避免一些完全重複的請求
當網絡出現問題的時候OkHttp依然堅守自己的職責,它會自動恢復一般的連接問題,如果你的服務有多個IP地址,當第一個IP請求失敗時,OkHttp會交替嘗試你配置的其他IP,OkHttp使用現代TLS技術(SNI, ALPN)初始化新的連接,當握手失敗時會回退到TLS 1.0。
OkHttp 支持 Android 2.3 及以上版本Android平臺, 以及 Java,JDK 1.7及以上.
OkHttp的使用是非常簡單的. 它的請求/響應 API 使用構造器模式builders來設計,它支持阻塞式的同步請求和帶回調的異步請求。
爲了更好地演示OkHttp,這裏僅展示出同步/異步執行get/post請求的相關api,至於關於OkHttp的相關調用,建議參考OkHttp的官方文檔:OkHttp官網,無需翻牆
關於使用post請求發送流/文件/表單等,這些功能則要配合另一個框架:OkIo,將在下一篇博客中專門展示出來。
話不多說,讓我們馬上開始使用OkHttp吧。
3.演示
3.1 集成
又到了喜聞樂見的集成環節,我們只需要修改module下的build.gradle,加入OkHttp的依賴,代碼如下:
implementation("com.squareup.okhttp3:okhttp:4.6.0")
記得重新Sync一下,確保OkHttp集成到了你的項目中。作者在寫下此篇博客時OkHttp3的穩定版本號以達到4.6.0
,如果讀者在OkHttp的官網上發現了更新的版本,記得及時更新,做到與時俱進。
3.2 配置
既然你集成了OkHttp,就不可不免地要進行與網絡有關的操作。既然如此,就需要在清單文件下聲明網絡權限,即:
<uses-permission android:name="android.permission.INTERNET" />
另外:如果你的Android版本爲Android P(即targetSdkVersion 27),在運行該項目時可能會報有關網絡的錯誤,如圖所示:
原因:在Android P系統的設備上,如果應用使用的是非加密的明文流量的http網絡請求,則會導致該應用無法進行網絡請求,https則不會受影響,同理若應用內使用WebView加載網頁 則加載網頁也需要是https請求。
解決方法:
- APP整體網絡請求改用https
- 將targetSdkVersion 版本下調至27以下
- 更改項目網絡安全配置
三種方法亦可,這裏主要介紹一下第三種解決方法,以拓寬解決思路
- 在res目錄下新建xml文件夾 在xml文件夾內新建名爲
network_config(名字非固定)的xml
,代碼如下:
<network-security-config>
<base-config cleartextTrafficPermitted="true" />
</network-security-config>
- 然後,在AndroidManifest中增加
android:networkSecurityConfig
屬性,代碼如下:
<application
...
android:networkSecurityConfig="@xml/network_security_config"
...
/>
- 以上兩個步驟就完成了網絡安全配置,如果實在不行的話可以嘗試另外兩種方法或者百度
3.3 佈局文件和URL封裝
接下來,我們直接開始佈局文件activity_main.xml的編寫。該佈局很簡單,僅有四個按鈕,代碼如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:orientation="vertical">
<Button
android:id="@+id/btn_get_syn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="發送get請求——同步"/>
<Button
android:id="@+id/btn_get_asy"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="發送get請求——異步"/>
<Button
android:id="@+id/btn_post_syn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="發送post請求——異步"/>
<Button
android:id="@+id/btn_post_asy"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="發送post請求——異步"/>
</LinearLayout>
之後,我們簡單用字符串封裝一下要請求的URL,即http://wwww.baidu.com
。這裏建議想要深入OkHttp的讀者自己搭建一個Tomcat/Apache服務器,將資源文件上傳到服務器上,然後訪問一下本地路徑,這樣可以加深對於網絡請求的理解。這裏爲了便於演示就直接網絡請求百度,代碼如下:
private static final String URL = "http://wwww.baidu.com";
3.4 同步GET請求
同步GET請求的步驟很簡單,大抵分爲:
- 構造OkHttpClient對象;
- 構造Request對象;
- 通過前兩步中的對象構建Call對象;
- 通過
call.execute()
方法來提交同步請求。注意,由於是同步請求,所以該api要放在子線程中,避免阻塞主線程,造成ANR異常。另外,Android3.0 以後已經不允許在主線程訪問網絡。
代碼如下:
private void getBySynchronized() {
btn_get_syn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 1.構建Client對象
OkHttpClient client = new OkHttpClient();
// 2.採用建造者模式和鏈式調用構建Request對象
final Request request = new Request.Builder()
.url(URL) // 請求URL
.get() // 默認就是get請求,可以不寫
.build();
// 3.通過1和2產生的Client和Request對象生成Call對象
final Call call = client.newCall(request);
// 4.同步發送get請求需要使用execute()方法,並且爲了防止主線程阻塞需要放在子線程中自行
new Thread(new Runnable() {
@Override
public void run() {
try {
// 6.構建response對象
Response response = call.execute();
Log.d(TAG, "同步發送get請求成功!請求到的信息爲:" + response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
});
}
3.5 異步GET請求
異步GET請求的前面幾個步驟和同步方式一樣,只是最後一部是通過call.enqueue(Callback())
來提交請求,代碼如下:
private void getByAsynchronized() {
btn_get_asy.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 1.構建Client對象
OkHttpClient client = new OkHttpClient();
// 2.採用建造者模式和鏈式調用構建Request對象
final Request request = new Request.Builder()
.url(URL) // 請求URL
.get() // 默認就是get請求,可以不寫
.build();
// 3.通過1和2產生的Client和Request對象生成Call對象
Call call = client.newCall(request);
// 4.調用Call對象的enqueue()方法,並且實現一個回調實現類
call.enqueue(new Callback() {
@Override
public void onFailure(@NotNull Call call, @NotNull IOException e) {
Log.d(TAG, "異步發送get請求失敗!");
e.printStackTrace();
}
@Override
public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
Log.d(TAG, "異步發送get請求成功!請求到的信息爲:" + response.body().string());
}
});
}
});
}
3.6 同步POST請求
同步POST請求的步驟類似於同步GET請求,大抵分爲:
- 構造OkHttpClient對象;
- 構造FormBody對象(鍵值對)
- 構造Request對象;
- 通過前兩步中的對象構建Call對象;
- 通過
call.execute()
方法來提交同步請求。
代碼如下:
private void postBySynchronized() {
btn_post_syn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 1.構建Client對象
OkHttpClient client = new OkHttpClient();
// 2.採用建造者模式和鏈式調用構建鍵值對對象
FormBody formBody = new FormBody.Builder()
.add("username", "admin")
.add("password", "123456")
.build();
// 3.採用建造者模式和鏈式調用構建Request對象
final Request request = new Request.Builder()
.url(URL) // 請求URL
.post(formBody) // 默認就是get請求,可以不寫
.build();
// 4.通過1和3產生的Client和Request對象生成Call對象
final Call call = client.newCall(request);
// 5.同步發送post請求需要使用execute()方法,並且爲了防止主線程阻塞需要放在子線程中自行
new Thread(new Runnable() {
@Override
public void run() {
try {
// 6.構建response對象
Response response = call.execute();
Log.d(TAG, "同步發送post請求成功!請求到的信息爲:" + response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
});
}
3.7 異步POST請求
異步POST請求的前面幾個步驟和同步方式一樣,只是最後一部是通過call.enqueue(Callback())
來提交請求,代碼如下:
private void postByAsynchronized() {
btn_post_asy.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 1.構建Client對象
OkHttpClient client = new OkHttpClient();
// 2.採用建造者模式和鏈式調用構建鍵值對對象
FormBody formBody = new FormBody.Builder()
.add("username", "admin")
.add("password", "123456")
.build();
// 3.採用建造者模式和鏈式調用構建Request對象
final Request request = new Request.Builder()
.url(URL) // 請求URL
.post(formBody) // 默認就是get請求,可以不寫
.build();
// 4.通過1和3產生的Client和Request對象生成Call對象
Call call = client.newCall(request);
// 5.調用Call對象的enqueue()方法,並且實現一個回調實現類
call.enqueue(new Callback() {
@Override
public void onFailure(@NotNull Call call, @NotNull IOException e) {
Log.d(TAG, "異步發送post請求失敗!");
e.printStackTrace();
}
@Override
public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
Log.d(TAG, "異步發送post請求成功!請求到的信息爲:" + response.body().string());
}
});
}
});
}
4 總結
- 同步調用:編寫簡單,但是會阻塞主線程,一般不適用
- 異步調用:回調函數是在子線程,我們不能在子線程更新UI,需要藉助於runOnUiThread()方法或者Handler來處理
一般來說,雖說同步/異步調用各有優劣,在實際項目裏,我們還是異步調用會相對用得多一些,搭配RxJava/RxAndroid以及EventBus,可以提升用戶的體驗。當然,如果是爲了快速開發比較簡單的小demo,同步調用也夠用了。