高性能網(wǎng)絡(luò)框架OKHttp
出現(xiàn)背景
在okhttp出現(xiàn)以前愉棱,android上發(fā)起網(wǎng)絡(luò)請(qǐng)求要么使用系統(tǒng)自帶的HttpClient
肌稻、HttpURLConnection
、要么使用google開源的Volley
、要么使用第三方開源的AsyncHttpClient
, 隨著互聯(lián)網(wǎng)的發(fā)展徘意,APP的業(yè)務(wù)發(fā)展也越來越復(fù)雜,APP的網(wǎng)絡(luò)請(qǐng)求數(shù)量急劇增加轩褐,但是上述的網(wǎng)絡(luò)請(qǐng)求框架均存在難以性能和并發(fā)數(shù)量的限制
OkHttp
流行得益于它的良好的架構(gòu)設(shè)計(jì)椎咧,強(qiáng)大的攔截器(intercepts)
使得操縱網(wǎng)絡(luò)十分方便;OkHttp現(xiàn)在已經(jīng)得到Google官方認(rèn)可,大量的app都采用OkHttp做網(wǎng)絡(luò)請(qǐng)求勤讽,其源碼詳見OkHttp Github蟋座。
也得益于強(qiáng)大的生態(tài),大量的流行庫都以OkHttp
作為底層網(wǎng)絡(luò)框架或提供支持脚牍,比如Retrofit
向臀、Glide
、Fresco
诸狭、Moshi
券膀、Picasso
等。
當(dāng)OKhttp面世之后驯遇,瞬間成為各個(gè)公司的開發(fā)者的新寵三娩,常年霸占github star榜單,okhttp可以說是為高效而生,迎合了互聯(lián)網(wǎng)高速發(fā)展的需要
特點(diǎn)
- 同時(shí)支持HTTP1.1與支持HTTP2.0妹懒;
- 同時(shí)支持同步與異步請(qǐng)求雀监;
- 同時(shí)具備HTTP與WebSocket功能;
- 擁有自動(dòng)維護(hù)的socket連接池眨唬,減少握手次數(shù)会前;
- 擁有隊(duì)列線程池,輕松寫并發(fā)匾竿;
- 擁有Interceptors(攔截器)瓦宜,輕松處理請(qǐng)求與響應(yīng)額外需求(例:請(qǐng)求失敗重試、響應(yīng)內(nèi)容重定向等等)岭妖;
開始使用
在AndroidManifest.xml添加網(wǎng)絡(luò)訪問權(quán)限
<uses-permission android:name="android.permission.INTERNET" />
<application
...
android:usesCleartextTraffic="true"
...
</application>
添加依賴
在app/build.gradle
的dependencies
中添加下面的依賴
implementation("com.squareup.okhttp3:okhttp:4.9.0")
// 網(wǎng)絡(luò)請(qǐng)求日志打印
implementation("com.squareup.okhttp3:logging-interceptor:4.9.0")
初始化
val client = OkHttpClient.Builder() //builder構(gòu)造者設(shè)計(jì)模式
.connectTimeout(10, TimeUnit.SECONDS) //連接超時(shí)時(shí)間
.readTimeout(10, TimeUnit.SECONDS) //讀取超時(shí)
.writeTimeout(10, TimeUnit.SECONDS) //寫超時(shí)临庇,也就是請(qǐng)求超時(shí)
.build();
GET請(qǐng)求
同步GET請(qǐng)求
同步GET的意思是一直等待http請(qǐng)求, 直到返回了響應(yīng). 在這之間會(huì)阻塞線程, 所以同步請(qǐng)求不能在Android的主線程中執(zhí)行, 否則會(huì)報(bào)錯(cuò)NetworkMainThreadException.
val client = OkHttpClient()
fun run(url: String) {
val request: Request = Request.Builder()
.url(url)
.build()
val call =client.newCall(request)
val response=call.execute()
val body = response.body?.string()
println("get response :${body}")
}
發(fā)送同步GET
請(qǐng)求很簡單:
- 創(chuàng)建
OkHttpClient
實(shí)例client
- 通過
Request.Builder
構(gòu)建一個(gè)Request
請(qǐng)求實(shí)例request
- 通過
client.newCall(request)
創(chuàng)建一個(gè)Call
的實(shí)例 -
Call
的實(shí)例調(diào)用execute
方法發(fā)送同步請(qǐng)求 - 請(qǐng)求返回的
response
轉(zhuǎn)換為String
類型返回
異步GET請(qǐng)求
異步GET是指在另外的工作線程中執(zhí)行http請(qǐng)求, 請(qǐng)求時(shí)不會(huì)阻塞當(dāng)前的線程, 所以可以在Android主線程中使用.
onFailure
,onResponse
的回調(diào)是在子線程中的,我們需要切換到主線程才能操作UI控件
val client = OkHttpClient()
fun run(url: String) {
val request: Request = Request.Builder()
.url(url)
.build()
val call =client.newCall(request)
call.enqueue(object : Callback {
override fun onResponse(call: Call, response: Response) {
println("onResponse: ${response.body.toString()}")
}
override fun onFailure(call: Call, e: IOException) {
println("onFailure: ${e.message}")
}
})
}
異步請(qǐng)求的步驟和同步請(qǐng)求類似昵慌,只是調(diào)用了Call
的enqueue
方法異步請(qǐng)求假夺,結(jié)果通過回調(diào)Callback
的onResponse
方法及onFailure
方法處理。
看了兩種不同的Get請(qǐng)求斋攀,基本流程都是先創(chuàng)建一個(gè)OkHttpClient
對(duì)象已卷,然后通過Request.Builder()
創(chuàng)建一個(gè)Request
對(duì)象,OkHttpClient
對(duì)象調(diào)用newCall()
并傳入Request
對(duì)象就能獲得一個(gè)Call
對(duì)象淳蔼。
而同步和異步不同的地方在于execute()
和enqueue()
方法的調(diào)用侧蘸,
調(diào)用execute()
為同步請(qǐng)求并返回Response
對(duì)象;
調(diào)用enqueue()
方法測試通過callback的形式返回Response
對(duì)象鹉梨。
注意:無論是同步還是異步請(qǐng)求讳癌,接收到
Response
對(duì)象時(shí)均在子線程中,onFailure
存皂,onResponse
的回調(diào)是在子線程中的,我們需要切換到主線程才能操作UI控件
POST請(qǐng)求
POST請(qǐng)求與GET請(qǐng)求不同的地方在于Request.Builder
的post()
方法晌坤,post()
方法需要一個(gè)RequestBody
的對(duì)象作為參數(shù)
同步POST請(qǐng)求
val body = new FormBody.Builder()
.add(key,value)
.build();
val request = new Request.Builder()
.url(url)
.post(body)
.build();
val response = client.newCall(request).execute();
val body =response.body().string()
println("post response: ${body}")
和GET
同步請(qǐng)求類似,只是創(chuàng)建Request
時(shí)通過Request.Builder.post()
方法設(shè)置請(qǐng)求類型為POST
請(qǐng)求并設(shè)置了請(qǐng)求體。
異步表單提交
val body = FormBody.Builder()
.add(key,value)
.add(key1,value2)
.build();
val request = new Request.Builder()
.url(url)
.post(body)
.build();
val call = client.newCall(request)
call.enqueue(new Callback(){
@Override
public void onFailure(Request request, IOException e){
}
@Override
public void onResponse(final Response response) throws IOException{
// 回調(diào)的結(jié)果是在子線程中的,我們需要切換到主線程才能操作UI控件
String response = response.body().string();
}
}
異步表單文件上傳
val file = File(Environment.getExternalStorageDirectory(), "1.png")
if (!file.exists()) {
Toast.makeText(this, "文件不存在", Toast.LENGTH_SHORT).show()
return
}
val muiltipartBody: RequestBody = MuiltipartBody.Builder()
.setType(MultipartBody.FORM)//一定要設(shè)置這句
.addFormDataPart("username", "admin") //
.addFormDataPart("password", "admin") //
.addFormDataPart( "file", "1.png",RequestBody.create(MediaType.parse("application/octet-stream"), file))
.build()
異步提交字符串
val mediaType = MediaType.parse("text/plain;charset=utf-8")
val body = "{username:admin, password:admin}"
RequestBody body = RequestBody.create(mediaType,body);
val request = new Request.Builder()
.url(url)
.post(body)
.build();
val call = client.newCall(request)
call.enqueue(new Callback(){
@Override
public void onFailure(Request request, IOException e){
}
@Override
public void onResponse(final Response response) throws IOException{
// 回調(diào)的結(jié)果是在子線程中的,我們需要切換到主線程才能操作UI控件
String response = response.body().string();
}
}
攔截器LoggingInterceptor
攔截器是OkHttp當(dāng)中一個(gè)比較強(qiáng)大的機(jī)制泡仗,可以監(jiān)視、重寫和重試調(diào)用請(qǐng)求猜憎。
這是一個(gè)比較簡單的Interceptor
的實(shí)現(xiàn)娩怎,對(duì)請(qǐng)求的發(fā)送和響應(yīng)進(jìn)行了一些信息輸出。
// 添加攔截器
client.addInterceptor(LoggingInterceptor())
// 自定義日志打印攔截器
class LoggingInterceptor: Interceptor {
@Override fun intercept(chain:Interceptor.Chain):Response {
val request = chain.request();
val time_start = System.nanoTime();
println(String.format("Sending request %s on %s%n%s", request.url(), chain.connection(), request.headers()));
val response = chain.proceed(request);
long time_end = System.nanoTime();
println(String.format("Received response for %s in %.1fms%n%s",
response.request().url(), (t2 - t1) / 1e6d, response.headers()));
return response;
}
}
INFO: Sending request http://www.publicobject.com/helloworld.txt on null
User-Agent: OkHttp Example
INFO: Received response for https://publicobject.com/helloworld.txt in 1179.7ms
Server: nginx/1.4.6 (Ubuntu)
Content-Type: text/plain
Content-Length: 1759
Connection: keep-alive
使用Gson來解析網(wǎng)絡(luò)請(qǐng)求響應(yīng)
Gson是Google開源的一個(gè)JSON庫胰柑,被廣泛應(yīng)用在Android開發(fā)中
在app/build.gradle
中添加以下依賴配置
dependencies {
implementation 'com.google.code.gson:gson:2.8.6'
}
class Account {
var uid:String="00001;
var userName:String="Freeman";
var password:String="password";
var telNumber:String="13000000000";
}
將JSON轉(zhuǎn)換為對(duì)象
val json ="{\"uid\":\"00001\",\"userName\":\"Freeman\",\"telNumber\":\"13000000000\"}";
val account = gson.fromJson(json, Account.class);
println(receiveAccount.toString());
將對(duì)象轉(zhuǎn)換為JSON
val gson = GSON()
val account = new Account()
println(gson.toJson(account));
輸出結(jié)果===> {"uid":"00001","userName":"Freeman","telNumber":"13000000000"}
將集合轉(zhuǎn)換成JSON
val gson = GSON()
val accountList = ArrayList<Account>();
accountList.add(account);
println(gson.toJson(accountList));
輸出結(jié)果===> [{"uid":"00001","userName":"Freeman","telNumber":"13000000000"}]
將JSON轉(zhuǎn)換成集合
val json= "[{\"uid\":\"00001\",\"userName\":\"Freeman\",\"telNumber\":\"13000000000\"}]"
val accountList = gson.fromJson(json, TypeToken<List<Account>>(){}.getType());
println("accountList size:${accountList.size()}");