# 高性能网络框架 OKHttp
# 出现背景
在 okhttp 出现以前,android 上发起网络请求要么使用系统自带的 HttpClient 、 HttpURLConnection 、要么使用 google 开源的 Volley 、要么使用第三方开源的 AsyncHttpClient , 随着互联网的发展,APP 的业务发展也越来越复杂,APP 的网络请求数量急剧增加,但是上述的网络请求框架均存在难以性能和并发数量的限制
OkHttp 流行得益于它的良好的架构设计,强大的 拦截器(intercepts) 使得操纵网络十分方便;OkHttp 现在已经得到 Google 官方认可,大量的 app 都采用 OkHttp 做网络请求,其源码详见 OkHttp Github。
也得益于强大的生态,大量的流行库都以 OkHttp 作为底层网络框架或提供支持,比如 Retrofit 、 Glide 、 Fresco 、 Moshi 、 Picasso 等。
当 OKhttp 面世之后,瞬间成为各个公司的开发者的新宠,常年霸占 github star 榜单,okhttp 可以说是为高效而生,迎合了互联网高速发展的需要
# 特点
1. 同时支持 HTTP1.1 与支持 HTTP2.0;
2. 同时支持同步与异步请求;
3. 同时具备 HTTP 与 WebSocket 功能;
4. 拥有自动维护的 socket 连接池,减少握手次数;
5. 拥有队列线程池,轻松写并发;
6. 拥有 Interceptors (拦截器),轻松处理请求与响应额外需求 (例:请求失败重试、响应内容重定向等等);

# 开始使用
在 AndroidManifest.xml 添加网络访问权限
| <uses-permission android:name="android.permission.INTERNET" /> | 
# 添加依赖
在 app/build.gradle 的 dependencies 中添加下面的依赖
| 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 请求很简单:
- 创建 OkHttpClient实例client
- 通过 Request.Builder构建一个Request请求实例request
- 通过 client.newCall(request)创建一个Call的实例
- Call的实例调用- execute方法发送同步请求
- 请求返回的 response转换为String类型返回
# 异步 GET 请求
异步 GET 是指在另外的工作线程中执行 http 请求,请求时不会阻塞当前的线程,所以可以在 Android 主线程中使用.
onFailure , onResponse 的回调是在子线程中的,我们需要切换到主线程才能操作 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}") | |
|         } | |
| }) | |
| } | 
异步请求的步骤和同步请求类似,只是调用了 Call 的 enqueue 方法异步请求,结果通过回调 Callback 的 onResponse 方法及 onFailure 方法处理。
看了两种不同的 Get 请求,基本流程都是先创建一个 OkHttpClient 对象,然后通过 Request.Builder() 创建一个 Request 对象, OkHttpClient 对象调用 newCall() 并传入 Request 对象就能获得一个 Call 对象。
而同步和异步不同的地方在于 execute() 和 enqueue() 方法的调用,
调用 execute() 为同步请求并返回 Response 对象;
调用 enqueue() 方法测试通过 callback 的形式返回 Response 对象。
注意:无论是同步还是异步请求,接收到
Response对象时均在子线程中,onFailure,onResponse的回调是在子线程中的,我们需要切换到主线程才能操作 UI 控件
# POST 请求
POST 请求与 GET 请求不同的地方在于 Request.Builder 的 post() 方法, 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()}"); | 
