文章首发于个人我的博客:wildma的博客,这里有更好的阅读体验,欢迎关注。css
最近有个想法——就是把 Android 主流开源框架进行深刻分析,而后写成一系列文章,包括该框架的详细使用与源码解析。目的是经过鉴赏大神的源码来了解框架底层的原理,也就是作到不只要知其然,还要知其因此然。html
这里我说下本身阅读源码的经验,我通常都是按照平时使用某个框架或者某个系统源码的使用流程入手的,首先要知道怎么使用,而后再去深究每一步底层作了什么,用了哪些好的设计模式,为何要这么设计。java
系列文章:android
更多干货请关注 AndroidNotesgit
上一篇介绍了 HttpClient 与 HttpURLConnection,咱们知道 Google 在 Android 6.0 版本已经删除了 HttpClient 的相关代码,HttpURLConnection 用起来也比较麻烦,因此网络框架 OkHttp 也就诞生了。github
OkHttp 是 Square 公司开源的网络框架,能够说是当前 Android 界最好用的网络框架了,它有以下特色:json
<uses-permission android:name="android.permission.INTERNET"/>
复制代码
implementation 'com.squareup.okhttp3:okhttp:3.11.0'
复制代码
同步 GET 请求的步骤:设计模式
须要注意的是:api
具体代码以下:数组
private void syncGetRequestByOkHttp() throws Exception {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url("https://www.baidu.com")
.build();
Call call = client.newCall(request);
Response response = call.execute();
if (response.isSuccessful()) {
Log.i(TAG, "syncGetRequestByOkHttp data-->" + response.body().string());
} else {
throw new IOException("Unexpected code " + response);
}
}
复制代码
打印结果:
OkHttpActivity: syncGetRequestByOkHttp data-->
<!DOCTYPE html>
<!--STATUS OK--><html> <head><meta http-equiv=content-type content=text/html;charset=utf-8><meta http-equiv=X-UA-Compatible content=IE=Edge><meta content=always name=referrer><link rel=stylesheet type=text/css href=http://s1.bdstatic.com/r/www/cache/bdorz/baidu.min.css>
<title>百度一下,你就知道</title>
</head> <body link=#0000cc> 省略部分日志...</body>
</html>
复制代码
异步 GET 请求与同步 GET 请求的代码差很少,区别是:
具体代码以下:
private void asyncGetRequestByOkHttp() {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url("https://www.baidu.com")
.build();
Call call = client.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if (response.isSuccessful()) {
Log.i(TAG, "onResponse data-->" + response.body().string());
} else {
throw new IOException("Unexpected code " + response);
}
}
});
}
复制代码
打印结果:与同步 GET 请求同样。
POST 请求与 GET 请求的区别是 POST 请求须要构建一个 RequestBody 来存放请求参数,而后在 Request.Builder 中调用 post 方法,并传入 RequestBody 对象。具体代码以下:
private void asyncPostRequestByOkHttp() {
OkHttpClient client = new OkHttpClient();
RequestBody formBody = new FormBody.Builder()
.add("username", "wildma")
.add("password", "123456")
.build();
Request request = new Request.Builder()
.url("https://postman-echo.com/post")
.post(formBody)
.build();
Call call = client.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if (response.isSuccessful()) {
Log.i(TAG, "onResponse data-->" + response.body().string());
} else {
throw new IOException("Unexpected code " + response);
}
}
});
}
复制代码
打印结果:
OkHttpActivity: onResponse data-->
{
"args": {},
"data": "",
"files": {},
"form": {
"username": "wildma",
"password": "123456"
},
"headers": {
"x-forwarded-proto": "https",
"host": "postman-echo.com",
"content-length": "31",
"content-type": "application/x-www-form-urlencoded",
"user-agent": "Apache-HttpClient/UNAVAILABLE (java 1.4)",
"x-forwarded-port": "443"
},
"json": {
"username": "wildma",
"password": "123456"
},
"url": "https://postman-echo.com/post"
}
复制代码
上传文件首先须要定义上传文件的类型 MediaType,而后构建一个 File 的 RequestBody,其余与普通 POST 请求相似。
其中 test.txt 为 SD 卡跟目录下的文件,内容为“test post file”,须要提早放好。具体代码以下:
private void asyncPostFileByOkHttp() {
final MediaType MEDIA_TYPE_MARKDOWN = MediaType.parse("text/x-markdown; charset=utf-8");
//test.txt 为 SD 卡跟目录下的文件,须要提早放好
File file = new File(Environment.getExternalStorageDirectory(), "test.txt");
OkHttpClient client = new OkHttpClient();
RequestBody requestBody = RequestBody.create(MEDIA_TYPE_MARKDOWN, file);
Request request = new Request.Builder()
.url("https://api.github.com/markdown/raw")
.post(requestBody)
.build();
Call call = client.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.i(TAG, "onFailure IOException-->" + e);
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if (response.isSuccessful()) {
Log.i(TAG, "onResponse data-->" + response.body().string());
} else {
throw new IOException("Unexpected code " + response);
}
}
});
}
复制代码
注意,因为须要操做 SD 卡数据,因此须要在 AndroidManifest.xml 文件添加读写权限,以下:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
复制代码
若是是 6.0 以上还须要动态申请权限。
打印结果:
OkHttpActivity: onResponse data-->test post file
复制代码
MultipartBody.Builder 能够构建与 HTML 文件上传表单兼容的复杂请求体。multipart 请求体的每一部分自己就是一个请求体,能够定义本身的请求头。也就是说一个接口可能须要同时上传文件和其余参数,这时候就可使用 MultipartBody.Builder 来构建复杂的请求体。具体代码以下:
private void asyncPostMultipartRequestByOkHttp() {
final String IMGUR_CLIENT_ID = "...";
final MediaType MEDIA_TYPE_PNG = MediaType.parse("image/png");
//test.png 为 SD 卡跟目录下的文件,须要提早放好
File file = new File(Environment.getExternalStorageDirectory(), "test.png");
OkHttpClient client = new OkHttpClient();
RequestBody requestBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("title", "test")
.addFormDataPart("image", "test.png",
RequestBody.create(MEDIA_TYPE_PNG, file))
.build();
Request request = new Request.Builder()
.header("Authorization", "Client-ID " + IMGUR_CLIENT_ID)
.url("https://api.imgur.com/3/image")
.post(requestBody)
.build();
Call call = client.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.i(TAG, "onFailure IOException-->" + e);
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if (response.isSuccessful()) {
Log.i(TAG, "onResponse data-->" + response.body().string());
} else {
throw new IOException("Unexpected code " + response);
}
}
});
}
复制代码
打印结果: 因为这里没有合适的测试服务器,因此请求会走到 onFailure(),如需测试请换成本身的服务器。
发送一个请求若是没有响应则会使用超时结束 call,没有响应多是客户端或服务器问题,例如网络慢致使请求失败,OkHttp 能够设置链接、读取和写入的超时时间。 以下请求 url 的延迟时间为 2 秒,这时候我设置读取的超时时间为 1 秒,最终则会请求失败走到 onFailure() 方法。具体代码以下:
private void asyncGetRequestByOkHttpAndSetTimeout() {
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.readTimeout(1, TimeUnit.SECONDS)
.build();
Request request = new Request.Builder()
.url("http://httpbin.org/delay/2") //该 url 的延迟时间为 2 秒
.build();
Call call = client.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.i(TAG, "onFailure IOException-->" + e);
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if (response.isSuccessful()) {
Log.i(TAG, "onResponse data-->" + response.body().string());
} else {
throw new IOException("Unexpected code " + response);
}
}
});
}
复制代码
打印结果:
OkHttpActivity: onFailure IOException-->java.net.SocketTimeoutException: timeout
复制代码
说明 socket 超时了。
经过 Call.cancel() 能够当即中止正在进行的 Call。若是一个线程正在写请求或读响应,它还将收到一个 IOException 异常。当一个 Call 不须要时,可使用 Call.cancel() 节约网络资源,例如用户离开一个界面时。同步和异步调用均可以被取消。 以下请求 url 的延迟时间为 2 秒,这时候我在请求的同时立刻取消请求。具体代码以下:
private void asyncGetRequestByOkHttpAndCancelRequest() {
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build();
Request request = new Request.Builder()
.url("http://httpbin.org/delay/2") //该 url 的延迟时间为 2 秒
.build();
mCall = client.newCall(request);
mCall.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.i(TAG, "onFailure IOException-->" + e);
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if (response.isSuccessful()) {
Log.i(TAG, "onResponse data-->" + response.body().string());
} else {
throw new IOException("Unexpected code " + response);
}
}
});
//请求的同时立刻取消请求
mCall.cancel();
Log.i(TAG, "asyncGetRequestByOkHttpAndCancelRequest isCanceled-->" + mCall.isCanceled());
}
复制代码
打印结果:
OkHttpActivity: asyncGetRequestByOkHttpAndCancelRequest isCanceled-->true
OkHttpActivity: onFailure IOException-->java.io.IOException: Canceled
复制代码
第一行说明取消成功了,第二行说明一个线程正在写请求或读响应,这时候会走到 onFailure() 方法并收到一个 IOException 异常。