# 高性能网络框架 OKHttp

# 出现背景

在 okhttp 出现以前,android 上发起网络请求要么使用系统自带的 HttpClientHttpURLConnection 、要么使用 google 开源的 Volley 、要么使用第三方开源的 AsyncHttpClient , 随着互联网的发展,APP 的业务发展也越来越复杂,APP 的网络请求数量急剧增加,但是上述的网络请求框架均存在难以性能和并发数量的限制

OkHttp 流行得益于它的良好的架构设计,强大的 拦截器(intercepts) 使得操纵网络十分方便;OkHttp 现在已经得到 Google 官方认可,大量的 app 都采用 OkHttp 做网络请求,其源码详见 OkHttp Github

也得益于强大的生态,大量的流行库都以 OkHttp 作为底层网络框架或提供支持,比如 RetrofitGlideFrescoMoshiPicasso 等。

当 OKhttp 面世之后,瞬间成为各个公司的开发者的新宠,常年霸占 github star 榜单,okhttp 可以说是为高效而生,迎合了互联网高速发展的需要

# 特点

1. 同时支持 HTTP1.1 与支持 HTTP2.0

2. 同时支持同步与异步请求;

3. 同时具备 HTTP 与 WebSocket 功能;

4. 拥有自动维护的 socket 连接池,减少握手次数;

5. 拥有队列线程池,轻松写并发;

6. 拥有 Interceptors (拦截器),轻松处理请求与响应额外需求 (例:请求失败重试、响应内容重定向等等);

OkHttp

# 开始使用

在 AndroidManifest.xml 添加网络访问权限

<uses-permission android:name="android.permission.INTERNET" />

# 添加依赖

app/build.gradledependencies 中添加下面的依赖

implementation("com.squareup.okhttp3:okhttp:4.9.0")
// 网络请求日志打印
implementation("com.squareup.okhttp3:logging-interceptor")

# 初始化

val client  = OkHttpClient.Builder()    //builder 构造者设计模式
         .connectTimeout(10, TimeUnit.SECONDS) // 连接超时时间
         .readTimeout(10, TimeUnit.SECONDS)    // 读取超时  
         .writeTimeout(10, TimeUnit.SECONDS)  // 写超时,也就是请求超时
         .build();

# GET 请求

# 同步 GET 请求

同步 GET 的意思是一直等待 http 请求,直到返回了响应。在这之间会阻塞线程,所以同步请求不能在 Android 的主线程中执行,否则会报错 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}")
}

发送同步 GET 请求很简单:

  1. 创建 OkHttpClient 实例 client
  2. 通过 Request.Builder 构建一个 Request 请求实例 request
  3. 通过 client.newCall(request) 创建一个 Call 的实例
  4. Call 的实例调用 execute 方法发送同步请求
  5. 请求返回的 response 转换为 String 类型返回

# 异步 GET 请求

异步 GET 是指在另外的工作线程中执行 http 请求,请求时不会阻塞当前的线程,所以可以在 Android 主线程中使用.

onFailureonResponse 的回调是在子线程中的,我们需要切换到主线程才能操作 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}")
        }
    })
}

异步请求的步骤和同步请求类似,只是调用了 Callenqueue 方法异步请求,结果通过回调 CallbackonResponse 方法及 onFailure 方法处理。

看了两种不同的 Get 请求,基本流程都是先创建一个 OkHttpClient 对象,然后通过 Request.Builder() 创建一个 Request 对象, OkHttpClient 对象调用 newCall() 并传入 Request 对象就能获得一个 Call 对象。

而同步和异步不同的地方在于 execute()enqueue() 方法的调用,

调用 execute() 为同步请求并返回 Response 对象;

调用 enqueue() 方法测试通过 callback 的形式返回 Response 对象。

注意:无论是同步还是异步请求,接收到 Response 对象时均在子线程中, onFailureonResponse 的回调是在子线程中的,我们需要切换到主线程才能操作 UI 控件

# POST 请求

POST 请求与 GET 请求不同的地方在于 Request.Builderpost() 方法, post() 方法需要一个 RequestBody 的对象作为参数

# 同步 POST 请求

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 同步请求类似,只是创建 Request 时通过 Request.Builder.post() 方法设置请求类型为 POST 请求并设置了请求体。

# 异步表单提交

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{
       // 回调的结果是在子线程中的,我们需要切换到主线程才能操作 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)// 一定要设置这句
     .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{
       // 回调的结果是在子线程中的,我们需要切换到主线程才能操作 UI 控件 
       String response =  response.body().string();
   }
}

# 拦截器 LoggingInterceptor

拦截器是 OkHttp 当中一个比较强大的机制,可以监视、重写和重试调用请求。

这是一个比较简单的 Interceptor 的实现,对请求的发送和响应进行了一些信息输出。

// 添加拦截器
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 来解析网络请求响应

Gson 是 Google 开源的一个 JSON 库,被广泛应用在 Android 开发中

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 转换为对象

val json ="{\"uid\":\"00001\",\"userName\":\"Freeman\",\"telNumber\":\"13000000000\"}";
val account = gson.fromJson(json, Account.class);
println(receiveAccount.toString());

# 将对象转换为 JSON

val gson = GSON()
val account = new Account()
println(gson.toJson(account));
输出结果===> {"uid":"00001","userName":"Freeman","telNumber":"13000000000"}

# 将集合转换成 JSON

val gson = GSON()
val accountList = ArrayList<Account>();
accountList.add(account);
println(gson.toJson(accountList));
输出结果===> [{"uid":"00001","userName":"Freeman","telNumber":"13000000000"}]

# 将 JSON 转换成集合

val json= "[{\"uid\":\"00001\",\"userName\":\"Freeman\",\"telNumber\":\"13000000000\"}]"
val accountList = gson.fromJson(json, TypeToken<List<Account>>(){}.getType());
println("accountList size:${accountList.size()}");

# JsonToKotlin 插件