一 OKHttp简介

OKHttp是一个处理网络请求的开源项目,Android 当前最火热网络框架,由移动支付Square公司贡献,用于替代HttpUrlConnection和Apache HttpClient(android API23 6.0里已移除HttpClient)。 
OKHttpGitHub地址

OKHttp优点

  1. 支持HTTP2/SPDY(SPDY是Google开发的基于TCP的传输层协议,用以最小化网络延迟,提升网络速度,优化用户的网络使用体验。)
  2. socket自动选择最好路线,并支持自动重连,拥有自动维护的socket连接池,减少握手次数,减少了请求延迟,共享Socket,减少对服务器的请求次数。
  3. 基于Headers的缓存策略减少重复的网络请求。
  4. 拥有Interceptors轻松处理请求与响应(自动处理GZip压缩)。

OKHttp的功能

  1. PUT,DELETE,POST,GET等请求
  2. 文件的上传下载
  3. 加载图片(内部会图片大小自动压缩)
  4. 支持请求回调,直接返回对象、对象集合
  5. 支持session的保持

二 OkHttp3使用

主要介绍 OkHttp3 的 Get 请求、 Post 请求、 上传下载文件 、 上传下载图片等功能 。

添加OkHttp3的依赖

  1. compile 'com.squareup.okhttp3:okhttp:3.7.0'
  2. compile 'com.squareup.okio:okio:1.12.0'

添加网络权限

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

添加请求头
    private Request.Builder addHeaders() {
Request.Builder builder = new Request.Builder()
//addHeader,可添加多个请求头 header,唯一,会覆盖
.addHeader("Connection", "keep-alive")
.addHeader("platform", "2")
.addHeader("phoneModel", Build.MODEL)
.addHeader("systemVersion", Build.VERSION.RELEASE)
.addHeader("appVersion", "3.2.0")
.header("sid", "eyJhZGRDaGFubmVsIjoiYXBwIiwiYWRkUHJvZHVjdCI6InFia3BsdXMiLCJhZGRUaW1lIjoxNTAzOTk1NDQxOTEzLCJyb2xlIjoiUk9MRV9VU0VSIiwidXBkYXRlVGltZSI6MTUwMzk5NTQ0MTkxMywidXNlcklkIjoxNjQxMTQ3fQ==.b0e5fd6266ab475919ee810a82028c0ddce3f5a0e1faf5b5e423fb2aaf05ffbf");
return builder;
}

1.异步GET请求

        //1.创建OkHttpClient对象
OkHttpClient okHttpClient = new OkHttpClient();
//2.创建Request对象,设置一个url地址(百度地址),设置请求方式。
Request request = new Request.Builder().url("http://www.baidu.com").method("GET",null).build();
//3.创建一个call对象,参数就是Request请求对象
Call call = okHttpClient.newCall(request);
//4.请求加入调度,重写回调方法
call.enqueue(new Callback() {
//请求失败执行的方法
@Override
public void onFailure(Call call, IOException e) {
}
//请求成功执行的方法
@Override
public void onResponse(Call call, Response response) throws IOException {
}
});

上面就是发送一个异步GET请求的4个步骤:

  1. 创建OkHttpClient对象
  2. 通过Builder模式创建Request对象,参数必须有个url参数,可以通过Request.Builder设置更多的参数比如:header、method等
  3. 通过request的对象去构造得到一个Call对象,Call对象有execute()和cancel()等方法。
  4. 以异步的方式去执行请求,调用的是call.enqueue,将call加入调度队列,任务执行完成会在Callback中得到结果。

注意事项:

  1. 异步调用的回调函数是在子线程,我们不能在子线程更新UI,需要借助于 runOnUiThread() 方法或者 Handler 来处理。
  2. onResponse回调有一个参数是response,如果我们想获得返回的是字符串,可以通过response.body().string()获取;如果希望获得返回的二进制字节数组,则调用response.body().bytes();如果你想拿到返回的inputStream,则调response.body().byteStream(),有inputStream我们就可以通过IO的方式写文件(后面会有例子)。

2.同步GET请求

        //1.创建OkHttpClient对象
OkHttpClient okHttpClient = new OkHttpClient();
//2.创建Request对象,设置一个url地址(百度地址),设置请求方式。
Request request = new Request.Builder().url("http://www.baidu.com").method("GET",null).build();
//3.创建一个call对象,参数就是Request请求对象
Call call = okHttpClient.newCall(request);
//4.同步调用会阻塞主线程,这边在子线程进行
new Thread(new Runnable() {
@Override
public void run() {
try {
//同步调用,返回Response,会抛出IO异常
Response response = call.execute();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();

同步GET请求和异步GET请求基本一样,不同地方是同步请求调用Call的execute()方法,而异步请求调用call.enqueue()方法(具体2个方法的不同点我下一遍具体源码详解再说)。

3.POST请求提交键值对

        //1.创建OkHttpClient对象
OkHttpClient okHttpClient = new OkHttpClient();
//2.通过new FormBody()调用build方法,创建一个RequestBody,可以用add添加键值对
RequestBody requestBody = new FormBody.Builder().add("name","zhangqilu").add("age","25").build();
//3.创建Request对象,设置URL地址,将RequestBody作为post方法的参数传入
Request request = new Request.Builder().url("url").post(requestBody).build();
//4.创建一个call对象,参数就是Request请求对象
Call call = okHttpClient.newCall(request);
//5.请求加入调度,重写回调方法
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
} @Override
public void onResponse(Call call, Response response) throws IOException {
}
});

上面就是一个异步POST请求提交键值对的5个步骤:

  1. 创建OkHttpClient对象。
  2. 通过new FormBody()调用build方法,创建一个RequestBody,可以用add添加键值对 ,FormBody 是 RequestBody 的子类。
  3. 创建Request对象,设置URL地址,将RequestBody作为post方法的参数传入。
  4. 创建一个call对象,参数就是Request请求对象。
  5. 请求加入调度,重写回调方法。

通过对比我们发现异步的POST请求和GET请求步骤很相似。

4.异步POST请求提交字符串

POST请求提交字符串和POST请求提交键值对非常相似,不同地方主要是RequestBody,下面我们来具体看一下。 
在有些情况下客户端需要向服务端传送字符串,我们该怎么做? 
我们需要用到另一种方式来构造一个 RequestBody 如下所示:

        MediaType mediaType = MediaType.parse("application/json; charset=utf-8");//"类型,字节码"
//字符串
String value = "{username:admin;password:admin}";
//1.创建OkHttpClient对象
OkHttpClient okHttpClient = new OkHttpClient();
//2.通过RequestBody.create 创建requestBody对象
RequestBody requestBody =RequestBody.create(mediaType, value);
//3.创建Request对象,设置URL地址,将RequestBody作为post方法的参数传入
Request request = new Request.Builder().url("url").post(requestBody).build();
//4.创建一个call对象,参数就是Request请求对象
Call call = okHttpClient.newCall(request);
//5.请求加入调度,重写回调方法
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
} @Override
public void onResponse(Call call, Response response) throws IOException {
}
});

5.异步POST请求上传文件

我们这里举一个上传图片的例子,也可以是其他文件如,TXT文档等,不同地方主要是RequestBody,首先我们要添加存储卡读写权限,在 AndroidManifest.xml 文件中添加如下代码:

  1. <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
  2. <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

下面我们具体看一下上传文件代码。

        //1.创建OkHttpClient对象
OkHttpClient okHttpClient = new OkHttpClient();
//上传的图片
File file = new File(Environment.getExternalStorageDirectory(), "zhuangqilu.png");
//2.通过RequestBody.create 创建requestBody对象,application/octet-stream 表示文件是任意二进制数据流
RequestBody requestBody =RequestBody.create(MediaType.parse("application/octet-stream"), file);
//3.创建Request对象,设置URL地址,将RequestBody作为post方法的参数传入
Request request = new Request.Builder().url("url").post(requestBody).build();
//4.创建一个call对象,参数就是Request请求对象
Call call = okHttpClient.newCall(request);
//5.请求加入调度,重写回调方法
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
} @Override
public void onResponse(Call call, Response response) throws IOException {
}
});

6.异步GET请求下载文件

下载文件也是我们经常用到的功能,我们就举个下载图片的例子吧

        //1.创建OkHttpClient对象
OkHttpClient okHttpClient = new OkHttpClient();
//2.创建Request对象,设置一个url地址(百度地址),设置请求方式。
Request request = new Request.Builder().url("https://www.baidu.com/img/bd_logo1.png").get().build();
//3.创建一个call对象,参数就是Request请求对象
Call call = okHttpClient.newCall(request);
//4.请求加入调度,重写回调方法
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.e(TAG, "onFailure: "+call.toString() );
} @Override
public void onResponse(Call call, Response response) throws IOException {
//拿到字节流
InputStream is = response.body().byteStream();
int len = 0;
//设置下载图片存储路径和名称
File file = new File(Environment.getExternalStorageDirectory(),"baidu.png");
FileOutputStream fos = new FileOutputStream(file);
byte[] buf = new byte[128];
while((len = is.read(buf))!= -1){
fos.write(buf,0,len);
Log.e(TAG, "onResponse: "+len );
}
fos.flush();
fos.close();
is.close();
}
});

Get请求下载文件还是比较简单,设置下载地址,在回调函数中拿到了图片的字节流,然后保存为了本地的一张图片。

从网络下载一张图片并直接设置到ImageView中。

@Override
public void onResponse(Call call, Response response) throws IOException {
InputStream is = response.body().byteStream();
//使用 BitmapFactory 的 decodeStream 将图片的输入流直接转换为 Bitmap
final Bitmap bitmap = BitmapFactory.decodeStream(is);
//在主线程中操作UI
runOnUiThread(new Runnable() {
@Override
public void run() {
//然后将Bitmap设置到 ImageView 中
imageView.setImageBitmap(bitmap);
}
}); is.close();

主要注释已在代码中了。

7.异步POST请求上传Multipart文件

我们在有些情况下既要上传文件还要上传其他类型字段。比如在个人中心我们可以修改名字,年龄,修改图像,这其实就是一个表单。这里我们用到MuiltipartBody ,它 是RequestBody 的一个子类,我们提交表单就是利用这个类来构建一个 RequestBody,我们来看一下具体代码。

        //1.创建OkHttpClient对象
OkHttpClient okHttpClient = new OkHttpClient();
//上传的图片
File file = new File(Environment.getExternalStorageDirectory(), "zhuangqilu.png");
//2.通过new MultipartBody build() 创建requestBody对象,
RequestBody requestBody = new MultipartBody.Builder()
//设置类型是表单
.setType(MultipartBody.FORM)
//添加数据
.addFormDataPart("username","zhangqilu")
.addFormDataPart("age","25")
.addFormDataPart("image","zhangqilu.png",
RequestBody.create(MediaType.parse("image/png"),file))
.build();
//3.创建Request对象,设置URL地址,将RequestBody作为post方法的参数传入
Request request = new Request.Builder().url("url").post(requestBody).build();
//4.创建一个call对象,参数就是Request请求对象
Call call = okHttpClient.newCall(request);
//5.请求加入调度,重写回调方法
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
} @Override
public void onResponse(Call call, Response response) throws IOException {
}
});

Post 表单

public void postForm(View view) {
OkHttpClient client = new OkHttpClient();
RequestBody requestBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("username", "叶应是叶")
.addFormDataPart("password", "叶应是叶")
.build();
final Request request = new Request.Builder()
.url("http://www.jianshu.com/")
.post(requestBody)
.build();
Call call = client.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
ToastUtil.showToast(PostFormActivity.this, "Post Form 失败");
} @Override
public void onResponse(Call call, Response response) throws IOException {
final String responseStr = response.body().string();
ToastUtil.showToast(PostFormActivity.this, "Code:" + String.valueOf(response.code()));
runOnUiThread(new Runnable() {
@Override
public void run() {
tv_result.setText(responseStr);
}
});
}
});
}

Post 流

public void postStreaming(View view) {
final MediaType MEDIA_TYPE_MARKDOWN = MediaType.parse("text/x-markdown; charset=utf-8");
File file = new File("README.md");
final FileInputStream fileInputStream1=new FileInputStream(file);
RequestBody requestBody1=new RequestBody() {
@Nullable
@Override
public MediaType contentType() {
return MEDIA_TYPE_MARKDOWN;
} @Override
public void writeTo(BufferedSink sink) throws IOException {
OutputStream outputStream=sink.outputStream();
int length;
byte[] buffer = new byte[1024];
while ((length = fileInputStream1.read(buffer)) != -1) {
outputStream.write(buffer, 0, length);
}
}
}; RequestBody requestBody2=new RequestBody() {
@Nullable
@Override
public MediaType contentType() {
return MEDIA_TYPE_MARKDOWN;
} @Override
public void writeTo(BufferedSink sink) throws IOException {
int length;
byte[] buffer = new byte[1024];
while ((length = fileInputStream1.read(buffer)) != -1) {
sink.write(buffer, 0, length);
}
}
}; Request request = new Request.Builder()
.url(url)
.post(requestBody1)
.build(); Call call = client.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
ToastUtil.showToast(PostStreamingActivity.this, "Post Streaming 失败");
} @Override
public void onResponse(Call call, Response response) throws IOException {
final String responseStr = response.body().string();
ToastUtil.showToast(PostStreamingActivity.this, "Code:" + String.valueOf(response.code()));
runOnUiThread(new Runnable() {
@Override
public void run() {
tv_result.setText(responseStr);
}
});
}
});
}

解析Json

这里来通过Gson将response的内容解析为Java Bean
首先需要先去将Gson.jar文件导入工程

这里来通过OkHttp访问接口“http://news-at.zhihu.com/api/4/themes”,获取Json数据然后将之解析为JavaBean实体

Response response = okHttpClient.newCall(request).execute();
if (response.isSuccessful()){
User user = new Gson().fromJson(response.body().charStream(), User.class);
}

设置超时时间和缓存

和OkHttp2.x有区别的是不能通过OkHttpClient直接设置超时时间和缓存了,而是通过OkHttpClient.Builder来设置,通过builder配置好OkHttpClient后用builder.build()来返回OkHttpClient,所以我们通常不会调用new OkHttpClient()来得到OkHttpClient,而是通过builder.build():

 File sdcache = getExternalCacheDir();
int cacheSize = 10 * 1024 * 1024;
OkHttpClient.Builder builder = new OkHttpClient.Builder()
.connectTimeout(15, TimeUnit.SECONDS)
.writeTimeout(20, TimeUnit.SECONDS)
.readTimeout(20, TimeUnit.SECONDS)
.cache(new Cache(sdcache.getAbsoluteFile(), cacheSize));
OkHttpClient mOkHttpClient=builder.build();

关于取消请求和封装

取消请求仍旧可以调用call.cancel(),这个没有变化,不明白的可以查看上一篇文章Android网络编程(五)OkHttp2.x用法全解析,这里就不赘述了,封装上一篇也讲过仍旧推荐OkHttpFinal,它目前是基于OkHttp3来进行封装的。

        call.cancel();//取消请求,不能取消已经准备完成的请求
okHttpClient.dispatcher().cancelAll();//取消所有请求

有时候网络条件不好的情况下,用户会主动关闭页面,这时候需要取消正在请求的http request, OkHttp提供了cancel方法,但是实际在使用过程中发现,如果调用cancel()方法,会回调到CallBack里面的 onFailure方法中,

 /**
* Called when the request could not be executed due to cancellation, a connectivity problem or
* timeout. Because networks can fail during an exchange, it is possible that the remote server
* accepted the request before the failure.
*/
void onFailure(Call call, IOException e);

可以看到注释,当取消一个请求,网络连接错误,或者超时都会回调到这个方法中来,但是我想对取消请求做一下单独处理,这个时候就需要区分不同的失败类型了

解决思路

测试发现不同的失败类型返回的IOException e 不一样,所以可以通过e.toString 中的关键字来区分不同的错误类型

自己主动取消的错误的 java.net.SocketException: Socket closed
超时的错误是 java.net.SocketTimeoutException
网络出错的错误是java.net.ConnectException: Failed to connect to xxxxx

代码

直贴了部分代码

 call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
if(e.toString().contains("closed")) {
//如果是主动取消的情况下
}else{
//其他情况下
}

拦截器

 添加Interceptor
// 配置一些信息进入OkHttpClient
mOkHttpClient = new OkHttpClient().newBuilder()
.connectTimeout(REQUEST_TIME, TimeUnit.SECONDS)
.readTimeout(REQUEST_TIME, TimeUnit.SECONDS)
.writeTimeout(REQUEST_TIME, TimeUnit.SECONDS)
.addInterceptor(new LoggerInterceptor())
.build();

只要利用addInterceptor方法就可以添加拦截器,而自定义的拦截器只需要实现 Interceptor 接口就行了,可以使用拦截器方便的打印网络请求时,需要查看的日志。如下所示:

public class LoggerInterceptor implements Interceptor {

  @Override
public Response intercept(@NonNull Chain chain) throws IOException {
// 拦截请求,获取到该次请求的request
Request request = chain.request();
// 执行本次网络请求操作,返回response信息
Response response = chain.proceed(request);
if (Configuration.DEBUG) {
for (String key : request.headers().toMultimap().keySet()) {
LogUtil.e("zp_test", "header: {" + key + " : " + request.headers().toMultimap().get(key) + "}");
}
LogUtil.e("zp_test", "url: " + request.url().uri().toString());
ResponseBody responseBody = response.body(); if (HttpHeaders.hasBody(response) && responseBody != null) {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(responseBody.byteStream(), "utf-8"));
String result;
while ((result = bufferedReader.readLine()) != null) {
LogUtil.e("zp_test", "response: " + result);
}
// 测试代码
responseBody.string();
}
}
// 注意,这样写,等于重新创建Request,获取新的Response,避免在执行以上代码时,
// 调用了responseBody.string()而不能在返回体中再次调用。
return response.newBuilder().build();
} }

注意事项

  1. 如果提交的是表单,一定要设置表单类型, setType(MultipartBody.FORM)
  2. 提交文件 addFormDataPart() 方法的第一个参数就是类似于键值对的键,是供服务端使用的,第二个参数是文件的本地的名字,第三个参数是 RequestBody,里面包含了我们要上传的文件的路径以及 MidiaType。

Android OkHttp3简介和使用详解的更多相关文章

  1. 【转】Android OkHttp3简介和使用详解

    一 OKHttp简介 OKHttp是一个处理网络请求的开源项目,Android 当前最火热网络框架,由移动支付Square公司贡献,用于替代HttpUrlConnection和Apache HttpC ...

  2. Android 之窗口小部件详解--App Widget

    Android 之窗口小部件详解--App Widget  版本号 说明 作者 日期  1.0  添加App Widge介绍和示例  Sky Wang 2013/06/27        1 App ...

  3. Android 之窗口小部件详解(三)  部分转载

    原文地址:http://blog.csdn.net/iefreer/article/details/4626274. (一) 应用程序窗口小部件App Widgets 应用程序窗口小部件(Widget ...

  4. Android进阶之路(2)-详解MVP

    ### MVP简介 >MVP 全称:Model-View-Presenter :MVP 是从经典的模式MVC演变而来,它们的基本思想有相通的[地方](https://baike.baidu.co ...

  5. Drawable实战解析:Android XML shape 标签使用详解(apk瘦身,减少内存好帮手)

    Android XML shape 标签使用详解   一个android开发者肯定懂得使用 xml 定义一个 Drawable,比如定义一个 rect 或者 circle 作为一个 View 的背景. ...

  6. Android图片缓存之Bitmap详解

    前言: 最近准备研究一下图片缓存框架,基于这个想法觉得还是先了解有关图片缓存的基础知识,今天重点学习一下Bitmap.BitmapFactory这两个类. 图片缓存相关博客地址: Android图片缓 ...

  7. Android Design Support Library使用详解

    Android Design Support Library使用详解 Google在2015的IO大会上,给我们带来了更加详细的Material Design设计规范,同时,也给我们带来了全新的And ...

  8. Android不规则点击区域详解

    Android不规则点击区域详解 摘要 今天要和大家分享的是Android不规则点击区域,准确说是在视觉上不规则的图像点击响应区域分发. 其实这个问题比较简单,对于很多人来说根本不值得做为一篇博文写出 ...

  9. [Android新手区] SQLite 操作详解--SQL语法

    该文章完全摘自转自:北大青鸟[Android新手区] SQLite 操作详解--SQL语法  :http://home.bdqn.cn/thread-49363-1-1.html SQLite库可以解 ...

随机推荐

  1. 【Java】 BIO与NIO以及AIO分析

    一.BIO与NIO以及AIO的概念 BIO是同步阻塞式的IO NIO是同步非阻塞的IO (NIO1.0,JDK1.4) AIO是非同步非阻塞的IO(NIO2.0,JDK1.7) 二.BIO简单分析 1 ...

  2. 记录-Intellij Idea下以Tomcat运行Web项目时的位置问题

    今天本来准备把原来的一个Web项目导入到Idea下,之前这个项目是用eclipse写的,容器用的tomcat,首先导入前我把一些没用的配置文件都给删了,像什么.eclipse..setting什么的, ...

  3. Active Directory participation features and security extensions

    Participation in the Active Directory Samba 3.0 series, as well as the OS since Windows 2000, is pos ...

  4. mybatis 动态sql 的笔记 以及标签

    MyBatis常用OGNL表达式 e1 or e2 e1 and e2 e1 == e2,e1 eq e2 e1 != e2,e1 neq e2 e1 lt e2:小于 e1 lte e2:小于等于, ...

  5. Scrapy爬取小说简单逻辑

    Scrapy爬取小说简单逻辑 一 准备工作 1)安装Python 2)安装PIP 3)安装scrapy 4)安装pywin32 5)安装VCForPython27.exe ........... 具体 ...

  6. swiper 使用心得

    首先,我在这次学习的最大收益是,学习新框架.或者技术,先找官方文档比较好,那里的很全,你想要的基本都有的,如果没有那就是不支持喽. 然后简单概括下是怎么用的(比较谦虚,大家勿怪) 一 .找他的官方文档 ...

  7. 【OI学习注意事项】

    1. 必备知识 普及组必学 1.模拟算法(暴力枚举),按照题目的要求,题目怎么说就怎么做,保证时间和正确性即可. 2.搜索与回溯,主要的是\(DFS\)(深度优先搜索)和\(BFS\)(宽度优先搜索) ...

  8. Linux/Centos查看进程占用内存大小的几种方法总结

    1.命令行输入top回车,然后按下大写M按照memory排序,按下大写P按照CPU排序. 2. ps -ef | grep "进程名"     ps -e -o 'pid,comm ...

  9. 解决Windows jmeter Non HTTP response message: Address already in use: connect 错误(转载)

    jMeter报错: Response code: Non HTTP response code: java.net.BindExceptionResponse message: Non HTTP re ...

  10. Hadoop-No.8之时间戳

    要获得良好的HBase的模式设计,要正确的理解和使用时间错.在HBase中,时间戳的作用如下所述. 时间戳决定了在put请求修改记录时那些记录更新 时间戳决定了一条记录的多个版本在返回时的排序 时间戳 ...