OkHttp介绍
OkHttp官网地址:http://square.github.io/okhttp/ OkHttp
GitHub地址:https://github.com/square/okhttp
okhhttp可实现:
1、联网请求文本数据
2、大文件下载
3、大文件上传
4、请求图片
添加依赖
implementation("com.squareup.okhttp3:okhttp:3.12.0")
implementation("com.squareup.okhttp3:okhttp:4.8.0")
implementation("com.squareup.okhttp3:logging-interceptor:4.8.0")
get请求
get同步请求
/**
* 放在子线程中执行
*/
private void getDataBySync() {
new Thread() {
@Override
public void run() {
super.run();
OkHttpClient okHttpClient = new OkHttpClient();
Request request = new Request.Builder()
.get() //默认就是GET请求,可以不写
.url(Constant.URL)
.build();
Call call = okHttpClient.newCall(request);
try {
Response response = call.execute();
if (response.isSuccessful()) {
String string = response.body().string();
Log.e("xyh", "getDataBySync " + string);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
}
get异步请求
private void getDataByAsync() {
OkHttpClient okHttpClient = new OkHttpClient();
Request request = new Request.Builder()
.get() 默认就是GET请求,可以不写
.url(Constant.URL)
.build();
Call call = okHttpClient.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 (Looper.getMainLooper().getThread() == Thread.currentThread()) {
Log.e("xyh", "onResponse: " + "运行在主线程");
} else {
Log.e("xyh", "onResponse: " + "运行在子线程");
}
if (response.isSuccessful()) {
Log.e("xyh", "onResponse: " + response.body().string());
}
}
});
}
添加请求头
如果你需要在request的的header添加参数,例如Cookie,User-Agent什么的,就是:
Request request = new Request.Builder()
.url(url)
.header("cookie", "JSESSIONID=EB36DE5E50E342D86C55DAE0CDDD4F6D")
.addHeader("content-type", "application/json;charset:utf-8")
.addHeader("Home", "china")// 自定义的header
.addHeader("user-agent", "android")
.build();
HTTP 头的数据结构是 Map<String, List>类型。也就是说,对于每个 HTTP 头,可能有多个值。但是大部分 HTTP 头都只有一个值,只有少部分 HTTP 头允许多个值。至于name的取值说明,可以查看这个请求头大全。
OkHttp的处理方式是:
- 使用header(name,value)来设置HTTP头的唯一值,如果请求中已经存在响应的信息那么直接替换掉。
- 使用addHeader(name,value)来补充新值,如果请求头中已经存在name的name-value,那么还会继续添加,请求头中便会存在多个name相同而value不同的“键值对”。
创建OkHttpClient实例
- 方式1:创建一个默认配置OkHttpClient,可以使用默认的构造函数。
OkHttpClient okHttpClient = new OkHttpClient();
- 方式2
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.callTimeout(10, TimeUnit.SECONDS)//完整请求超时时长,从发起到接收返回数据,默认值0,不限定,
.connectTimeout(10, TimeUnit.SECONDS)//与服务器建立连接的时长,默认10s
.readTimeout(10, TimeUnit.SECONDS)//读取服务器返回数据的时长
.writeTimeout(10, TimeUnit.SECONDS)//向服务器写入数据的时长,默认10s
.retryOnConnectionFailure(true)//失败重连
.followRedirects(false)//重定向
.build();
可以设置一堆参数。
推荐让 OkHttpClient 保持单例,用同一个 OkHttpClient 实例来执行你的所有请求,因为每一个 OkHttpClient 实例都拥有自己的连接池和线程池,重用这些资源可以减少延时和节省资源,如果为每个请求创建一个 OkHttpClient 实例,显然就是一种资源的浪费。
添加请求参数
HttpUrl.Builder newBuilder = HttpUrl.parse(url).newBuilder();
newBuilder.addQueryParameter("name","xyh");
newBuilder.addQueryParameter("name2","xyh2");
HttpUrl httpUrl = newBuilder.build();
Request request = new Request.Builder()
.get() //默认就是GET请求,可以不写
.addHeader("token", "xxx")
.url(httpUrl)
.build();
POST请求
POST方式提交json
/**
* POST方式提交json
*/
public void postJson() {
OkHttpClient okHttpClient = new OkHttpClient();
RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), "提交的json");
Request request = new Request.Builder()
.url("")
.post(requestBody)
.build();
okHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
}
});
}
RequestBody的数据格式都要指定Content-Type,代表提交内容的类型,常见的有:
- application/x-www-form-urlencoded 数据是个普通表单
- multipart/form-data 数据里有文件
- application/json 数据是个json
- text/x-markdown数据是字符串
POST方式提交String
/**
* POST方式提交String
*/
public void postString() {
OkHttpClient okHttpClient = new OkHttpClient();
RequestBody requestBody = RequestBody.create(MediaType.parse("text/x-markdown; charset=utf-8"), "提交的字符串");
Request request = new Request.Builder()
.url("")
.post(requestBody)
.build();
okHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
}
});
}
POST方式提交表单
/**
* POST方式提交表单
*/
private void postForm() {
OkHttpClient okHttpClient = new OkHttpClient();
//普通表单并没有指定Content-Type,这是因为FormBody继承了RequestBody,它已经指定了数据类型为application/x-www-form-urlencoded。
//表单提交
FormBody formBody = new FormBody.Builder()
.add("platform", "2")
.add("gifttype", "1")
.add("page", "1")
.build();
Request request = new Request.Builder()
.url(url)
.post(formBody)
.build();
Call call = okHttpClient.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.e("xyh", "onResponse: " + response.body().string());
}
}
});
}
普通表单并没有指定Content-Type,这是因为FormBody继承了RequestBody,它已经指定了数据类型为application/x-www-form-urlencoded。
ivate static final MediaType CONTENT_TYPE = MediaType.get("application/x-www-form-urlencoded");
POST方式提交流
/**
* POST方式提交流
* 重写RequestBody中的几个方法,将本地数据放入到Http协议的请求体中,然后发送到服务端。
*/
private void postInputStream() {
RequestBody requestBody = new RequestBody() {
@Nullable
@Override
public MediaType contentType() {
return MediaType.parse("text/x-markdown; charset=utf-8");
}
@Override
public void writeTo(BufferedSink sink) throws IOException {
sink.writeUtf8("I am Jdqm.");
}
};
OkHttpClient okHttpClient = new OkHttpClient();
Request request = new Request.Builder()
.url(url)
.post(requestBody)
.build();
Call call = okHttpClient.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.e("xyh", "onResponse: " + response.body().string());
}
}
});
}
post上传图片
/**
* 上传图片
*/
private void postImage() {
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 okHttpClient = builder.build();
File file = new File("");
File file2 = new File("");
//上传一张图片
MultipartBody multipartBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("image", file.getName(), RequestBody.create(MediaType.parse("image/png"), file))
.build();
//也可用这种方式:addPart()
/** MultipartBody.Part part = MultipartBody.Part.createFormData(
"image", file.getName(), RequestBody.create(MediaType.parse("image/png"), file));
MultipartBody multipartBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addPart(part)
.build();**/
//上传多张图片
List<File> fileList = new ArrayList<>();
fileList.add(file);
fileList.add(file2);
MultipartBody.Builder body= new MultipartBody.Builder().setType(MultipartBody.FORM);
for (int i = 0; i < fileList.size(); i++) {
body.addFormDataPart("image" + i, RequestBody.create(MediaType.parse("image/png"), fileList.get(i));
}
MultipartBody multipartBody2 = body.build();
//图文混传
MultipartBody multipartBody3 = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("username", "xxx")
.addFormDataPart("password", "xxx")
.addFormDataPart("image", file.getName(), RequestBody.create(MediaType.parse("image/png"), file))
.build();
Request request = new Request.Builder()
.url("")
.post(multipartBody)
.build();
okHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
}
});
}
MultipartBuilder可以构建复杂的请求体,与HTML文件上传形式兼容。
多块请求体中每块请求都是一个请求体,可以定义自己的请求头。这些请求头可以用来描述这块请求,例如他的Content-Disposition。如果Content-Length和Content-Type可用的话,他们会被自动添加到请求头中。
private static final String IMGUR_CLIENT_ID = "...";
private static final MediaType MEDIA_TYPE_PNG = MediaType.parse("image/png");
private void postMultipartBody() {
OkHttpClient client = new OkHttpClient();
MultipartBody body = new MultipartBody.Builder("AaB03x")
.setType(MultipartBody.FORM)
.addPart(Headers.of("Content-Disposition", "form-data; name=\"title\""),
RequestBody.create(null, "Square Logo"))
.addPart(Headers.of("Content-Disposition", "form-data; name=\"image\""),
RequestBody.create(MEDIA_TYPE_PNG, new File("website/static/logo-square.png")))
.build();
Request request = new Request.Builder()
.header("Authorization", "Client-ID " + IMGUR_CLIENT_ID)
.url("https://api.imgur.com/3/image")
.post(body)
.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 {
System.out.println(response.body().string());
}
});
}
OkHttp请求图片
/**
* 请求图片
*/
public class ImageActivity extends AppCompatActivity {
private static final String TAG = "ImageActivity";
private ImageView mImageView;
private String url = "http://gpcd.gtimg.cn/upload/qqtalk/news/201804/171441456233877_282.jpg";
private static final int SUCCESS_STATE = 287;
private static final int FAIL_STATE = 767;
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case SUCCESS_STATE:
byte[] result = (byte[]) msg.obj;
Bitmap bitmap = BitmapFactory.decodeByteArray(result, 0, result.length);
mImageView.setImageBitmap(bitmap);
break;
case FAIL_STATE:
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_image);
mImageView = (ImageView) findViewById(R.id.iv);
}
public void doloadImage(View view) {
OkHttpClient okHttpClient = new OkHttpClient();
final Request request = new Request.Builder()
.url(url)
.build();
okHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
// TODO: 2018/5/20/020 该方法运行在子线程
Log.e(TAG, "onFailure: " + e.getMessage());
}
@Override
public void onResponse(Call call, Response response) throws IOException {
// TODO: 2018/5/20/020 该方法运行在子线程
if (response.isSuccessful()) {
Message message = mHandler.obtainMessage();
message.what = SUCCESS_STATE;
message.obj = response.body().bytes();
mHandler.sendMessage(message);
} else {
mHandler.sendEmptyMessage(FAIL_STATE);
}
}
});
//使用封装类请求数据
OkHttpManager.getInstance().asyncBitmapByURL(url, new OkHttpManager.BitmapCallback() {
@Override
public void onResponse(Bitmap bitmap) {
mImageView.setImageBitmap(bitmap);
}
@Override
public void onFailure(IOException e) {
}
});
}
}
对OkHttp进行封装
/**
* 对okhttp的封装
* Created by xiaoyehai on 2018/5/20/020.
*/
public class OkHttpManager {
private OkHttpClient mOkHttpClient;
private volatile static OkHttpManager mOkHttpManager;
private static final String TAG = "OkHttpManager";
private Handler mHandler;
//json
public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
//字符串
public static final MediaType STRING = MediaType.parse("text/x-markdown; charset=utf-8");
private OkHttpManager() {
mOkHttpClient = new OkHttpClient();
mHandler = new Handler();
}
public static OkHttpManager getInstance() {
if (mOkHttpManager == null) {
synchronized (OkHttpManager.class) {
if (mOkHttpManager == null) {
mOkHttpManager = new OkHttpManager();
}
}
}
return mOkHttpManager;
}
/**
* get同步请求:不常用,会阻塞主线程
*
* @param url
* @return
*/
public String syncGetByURL(String url) {
Request request = new Request.Builder()
.url(url)
.build();
Response response = null;
try {
response = mOkHttpClient.newCall(request).execute();
if (response.isSuccessful()) {
return response.body().string();
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* get 异步请求返回json字符串
*
* @param url
* @param callBack
*/
public void asyncJsonStringByURL(String url, final StringCallback callBack) {
Request request = new Request.Builder().url(url).build();
mOkHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, final IOException e) {
mHandler.post(new Runnable() {
@Override
public void run() {
callBack.onFailure(e);
}
});
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if (response != null && response.isSuccessful()) {
onJsonString(response.body().string(), callBack);
}
}
});
}
/**
* get异步请求返回json对象
*
* @param url
* @param callBack
*/
public void asyncJsonObjectByURL(String url, final ObjectCallback callBack) {
Request request = new Request.Builder().url(url).build();
mOkHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, final IOException e) {
mHandler.post(new Runnable() {
@Override
public void run() {
callBack.onFailure(e);
}
});
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if (response != null && response.isSuccessful()) {
onJsonObject(response.body().string(), callBack);
}
}
});
}
/**
* get异步请求返回字节数组
*
* @param url
* @param callBack
*/
public void asyncByteArrayByURL(String url, final ByteArrayCallback callBack) {
Request request = new Request.Builder().url(url).build();
mOkHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, final IOException e) {
mHandler.post(new Runnable() {
@Override
public void run() {
callBack.onFailure(e);
}
});
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if (response != null && response.isSuccessful()) {
onByteArray(response.body().string(), callBack);
}
}
});
}
/**
* get异步请求返回字bitmap
*
* @param url
* @param callBack
*/
public void asyncBitmapByURL(String url, final BitmapCallback callBack) {
Request request = new Request.Builder().url(url).build();
mOkHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, final IOException e) {
mHandler.post(new Runnable() {
@Override
public void run() {
callBack.onFailure(e);
}
});
}
@Override
public void onResponse(Call call, final Response response) throws IOException {
if (response != null && response.isSuccessful()) {
byte[] result = response.body().bytes();
final Bitmap bitmap = BitmapFactory.decodeByteArray(result, 0, result.length);
if (callBack != null) {
mHandler.post(new Runnable() {
@Override
public void run() {
callBack.onResponse(bitmap);
}
});
}
}
}
});
}
/**
* post表单提交
*
* @param url
* @param params
* @param callback
*/
public void postByForm(String url, Map<String, String> params, final StringCallback callback) {
//表单对象
FormBody.Builder builder = new FormBody.Builder();
if (params != null && !params.isEmpty()) {
for (Map.Entry<String, String> entry : params.entrySet()) {
builder.add(entry.getKey(), entry.getValue());
}
}
Request request = new Request.Builder()
.url(url)
.post(builder.build())
.build();
mOkHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(final Call call, final IOException e) {
mHandler.post(new Runnable() {
@Override
public void run() {
callback.onFailure(e);
}
});
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if (response != null && response.isSuccessful()) {
onJsonString(response.body().string(), callback);
}
}
});
}
/**
* post提交数据是string
*
* @param url
* @param content
* @param callback
*/
public void postByString(String url, String content, final StringCallback callback) {
Request request = new Request.Builder()
.url(url)
.post(RequestBody.create(STRING, content))
.build();
mOkHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(final Call call, final IOException e) {
mHandler.post(new Runnable() {
@Override
public void run() {
callback.onFailure(e);
}
});
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if (response != null && response.isSuccessful()) {
onJsonString(response.body().string(), callback);
}
}
});
}
/**
* post提交数据是json
*
* @param url
* @param json
* @param callback
*/
public void postByjson(String url, String json, final StringCallback callback) {
Request request = new Request.Builder()
.url(url)
.post(RequestBody.create(JSON, json))
.build();
mOkHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(final Call call, final IOException e) {
mHandler.post(new Runnable() {
@Override
public void run() {
callback.onFailure(e);
}
});
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if (response != null && response.isSuccessful()) {
onJsonString(response.body().string(), callback);
}
}
});
}
/**
* post上传文件
*
* @param url
* @param key 上传文件的key
* @param fileList
* @param params
* @param callback
*/
public void uploadFile(String url, String key, List<File> fileList, Map<String, String> params, final StringCallback callback) {
MultipartBody.Builder builder = new MultipartBody.Builder();
builder.setType(MultipartBody.FORM);
//参数
if (params != null) {
for (String s : params.keySet()) {
builder.addFormDataPart(key, params.get(s));
}
}
if (fileList != null) {
for (int i = 0; i < fileList.size(); i++) {
builder.addFormDataPart(key + i, fileList.get(i).getName(),
RequestBody.create(MediaType.parse("application/octet-stream"), fileList.get(i)));
}
}
MultipartBody multipartBody = builder.build();
Request request = new Request.Builder()
.url(url)
.post(multipartBody)
.build();
mOkHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, final IOException e) {
mHandler.post(new Runnable() {
@Override
public void run() {
callback.onFailure(e);
}
});
}
@Override
public void onResponse(Call call, final Response response) throws IOException {
mHandler.post(new Runnable() {
@Override
public void run() {
if (callback != null) {
try {
callback.onResponse(response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}
}
});
}
});
}
/**
* 请求返回的结果是json字符串
*
* @param json
* @param callBack
*/
private void onJsonString(final String json, final StringCallback callBack) {
mHandler.post(new Runnable() {
@Override
public void run() {
if (callBack != null) {
try {
callBack.onResponse(json);
} catch (Exception e) {
e.printStackTrace();
}
}
}
});
}
/**
* 请求返回的结果是字节数组
*
* @param json
* @param callBack
*/
private void onByteArray(final String json, final ByteArrayCallback callBack) {
mHandler.post(new Runnable() {
@Override
public void run() {
if (callBack != null) {
try {
callBack.onResponse(json.getBytes());
} catch (Exception e) {
e.printStackTrace();
}
}
}
});
}
/**
* 请求返回的结果是json对象
*
* @param json
* @param callBack
*/
private void onJsonObject(final String json, final ObjectCallback callBack) {
mHandler.post(new Runnable() {
@Override
public void run() {
if (callBack != null) {
try {
callBack.onResponse(new JSONObject(json));
} catch (Exception e) {
e.printStackTrace();
}
}
}
});
}
public interface StringCallback {
void onResponse(String result);
void onFailure(IOException e);
}
public interface ByteArrayCallback {
void onResponse(byte[] result);
void onFailure(IOException e);
}
public interface BitmapCallback {
void onResponse(Bitmap bitmap);
void onFailure(IOException e);
}
public interface ObjectCallback {
void onResponse(JSONObject jsonObject);
void onFailure(IOException e);
}
}
okhttp缓存设置
OkHttp3中关于缓存的类,我们使用的最多的是:Cache类和ControlCache类,其中前者用于指定缓存的地址和大小,后者用于对缓存进行各种控制,ControlCache又有其构建者类Builder。
先利用Cache类定义缓存地址和缓存最大尺寸:
File cacheDir = null;
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
cacheDir = getExternalCacheDir();
} else {
cacheDir = getCacheDir();
}
int cacheSize = 64 * 1024 * 1024;
然后将其注入到OkHttpClient实例中:
//参数1:缓存的位置;参数2:缓存存储空间大小的最大值
Cache cache = new Cache(cacheDir, cacheSize);
OkHttpClient.Builder builder = new OkHttpClient.Builder()
.cache(cache); //缓存设置
OkHttpClient okHttpClient = builder.build();
//默认会有缓存加载缓存,没缓存加载网络
Request request = new Request.Builder()
.get()
.url(Constant.URL)
.build();
okHttpClient.newCall(request).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.e("xyh", "onResponse: " + response.body().string());
//在执行完请求之后,Response的Body需要关闭(Body的string()方法内嵌了关闭功能),否则缓存将不会如期生效
response.body().close();
}
}
});
控制缓存
OkHttp3控制缓存的类为CacheControl。
要对缓存进行控制,我们需要在创建Request实例的时候就为其注入相应的缓存控制机制。这个注入是通过Request的cacheControl(CacheControl )方法实现的。
1.不想存储缓存,而是直接从服务器拉取,并且不保存缓存:
Request request = new Request.Builder()
.url(Constant.URL)
.cacheControl(new CacheControl.Builder().noStore().build())
.build();
2.强制从网络获取资源:
Request request = new Request.Builder()
.url(Constant.URL)
.cacheControl(CacheControl.FORCE_NETWORK) //强制加载网络
.build();
3.强制加载缓存:
Request request2 = new Request.Builder()
.url(Constant.URL)
.cacheControl(CacheControl.FORCE_CACHE) //强制加载缓存
.build();
4.如果需要每次请求都进行再验证环节,如果验证通过还是使用缓存,那么可以使用
Request request = new Request.Builder()
.url(Constant.URL)
.cacheControl(new CacheControl.Builder().maxAge(0, TimeUnit.SECONDS).build())
.build();
Cookie的管理
Request经常都要携带Cookie,上面说过request创建时可以通过header设置参数,Cookie也是参数之一。就像下面这样:
Request request = new Request.Builder()
.url(url)
.header("cookie", "JSESSIONID=EB36DE5E50E342D86C55DAE0CDDD4F6D")
.build();
然后可以从返回的response里得到新的Cookie,你可能得想办法把Cookie保存起来。
但是OkHttp可以不用我们管理Cookie,自动携带,保存和更新Cookie。方法是在创建OkHttpClient设置管理Cookie的CookieJar:
CookieJar cookieJar = new CookieJar() {
//保存cookie的集合
private final HashMap<HttpUrl, List<Cookie>> map = new LinkedHashMap<>();
/**
* 网络服务器返回的cookie,需要存储
* 如果服务器返回的cookie已经过时,需要删除本地cookie
* @param url
* @param cookies
*/
@Override
public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
map.put(url, cookies);
Log.e("xyh", "url: " + url);
for (Cookie cookie : cookies) {
Log.e("xyh", "cookie: " + cookie);
}
// TODO: 2018/11/22 0022 最终建议cookie保存在数据库或者文件中
}
/**
* 当网络请求需要发送的时候,需要先加载cookie信息
* 如果加载的cookie已经过时了,就不需要传递给服务器
* @param url
* @return 不允许返回null, 可以返回空的或者有内容的
*/
@Override
public List<Cookie> loadForRequest(HttpUrl url) {
List<Cookie> list;
if (map.containsKey(url)) {
list = map.get(url);
} else {
list = new LinkedList<>();
}
return list;
}
};
OkHttpClient.Builder builder = new OkHttpClient.Builder()
.cookieJar(cookieJar); //设置cookie
OkHttpClient okHttpClient = builder.build();
Request request = new Request.Builder()
.url("http://www.baidu.com")
.build();
okHttpClient.newCall(request).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.e("xyh", "onResponse: " + response.body().string());
}
}
});
}
这样以后发送Request都不用管Cookie这个参数也不用去response获取新Cookie什么的了。
网络请求代理Proxy的功能
在OkHttp框架中,已经集成好了网络请求代理Proxy的功能,我们只需要调用如下API,即可实现使用代理地址访问目标服务器:
OkHttpClient.Builder builder = new OkHttpClient.Builder();
// 设置代理地址
SocketAddress sa = new InetSocketAddress("代理服地址", 代理端口);
builder.proxy(new Proxy(Proxy.Type.HTTP, sa));
OkHttpClient client = builder.build();
Request.Builder requestBuilder = new Request.Builder();
requestBuilder.url("目标服务器地址");
client.newCall(requestBuilder.build());
什么是代理?该如何实现代理呢?
为了从目标服务器取得内容,客户端向代理服务器发送一个请求并指定目标,然后代理服务器向目标服务器转交请求并将获得的内容返回给客户端。
这种代理其实在生活中是比较常见的,比如访问外国技术网站(Google、Medium等)。
拦截器
拦截器是OkHttp中提供一种强大机制,它可以实现网络监听、请求以及响应重写、请求失败重试等功能。
拦截器可以以application或者network两种方式注册,分别调用addInterceptor()以及addNetworkInterceptor方法进行注册。
日志拦截器
下面举一个简单打印日志的栗子,此拦截器可以打印出网络请求以及响应的信息。
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.addInterceptor(new LoggingInterceptor()) //添加拦截器
.build();
Request request = new Request.Builder()
.url("http://www.publicobject.com/helloworld.txt")
.header("User-Agent", "OkHttp Example")
.build();
okHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
Log.e("xyh", "onResponse: " + response.body().string());
}
});
/**
* 自定义拦截器
* 日志打印
* 此拦截器可以打印出网络请求以及响应的信息。
*/
class LoggingInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
// 拦截请求,获取到该次请求的request
Request request = chain.request();
long t1 = System.nanoTime();
// logger.info(String.format("Sending request %s on %s%n%s", request.url(), chain.connection(), request.headers()));
Log.e("xyh", "intercept: " + String.format("Sending request %s on %s%n%s",
request.url(), chain.connection(), request.headers()));
// 执行本次网络请求操作,返回response信息
Response response = chain.proceed(request);
long t2 = System.nanoTime();
//logger.info(String.format("Received response for %s in %.1fms%n%s", response.request().url(), (t2 - t1) / 1e6d, response.headers()));
Log.e("xyh", "intercept: " + String.format("Received response for %s in %.1fms%n%s",
response.request().url(), (t2 - t1) / 1e6d, response.headers()));
return response;
}
}
打印信息:
在没有本地缓存的情况下,每个拦截器都必须至少调用chain.proceed(request)一次,这个简单的方法实现了Http请求的发起以及从服务端获取响应。
自定义拦截器的使用情景通常是对所有网络请求作统一处理。如果下次你也碰到这种类似的需求,别忘记使用自定义拦截器哦!
拦截器可以添加、移除或者替换请求头。甚至在有请求主体时候,可以改变请求主体。
拦截器添加请求头
假设现在后台要求我们在请求 API 接口时,要在每一个接口的请求头上添加对应的 token ,可以用拦截器添加请求头。
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.addInterceptor(new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
//我们先拦截得到 originalRequest ,然后利用 originalRequest 生成新的 updateRequest ,再交给 chain 处理进行下一环。
Request originalRequest = chain.request();
Request updateRequest = originalRequest.newBuilder()
.addHeader("token", "你的token")
.build();
return chain.proceed(updateRequest);
}
})
.build();
拦截器改变请求体
假如:后台要求我们传过去的请求参数是要按照一定规则经过加密的。
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.addInterceptor(new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
RequestBody body = request.body();
//进行加密操作
//加密后的请求参数
RequestBody body2 = body;
request = request.newBuilder()
.post(body2)
.build();
return chain.proceed(request);
}
})
.build();
实际开发中的用途
1、对请求参数进行统一加密处理。
2、拦截不符合规则的URL。
3、对请求或者返回参数设置统一的编码方式
4、其它…。
OkHttp实现文件下载
public class DownloadManager {
private static DownloadManager mInstance;
private final OkHttpClient okHttpClient;
private final Handler mHandler;
public static DownloadManager getInstance() {
if (mInstance == null) {
mInstance = new DownloadManager();
}
return mInstance;
}
private DownloadManager() {
okHttpClient = new OkHttpClient();
mHandler = new Handler();
}
/**
* @param url 下载连接
* @param saveDir 储存下载文件的SDCard目录
* @param callback 下载监听
*/
public void downloadFile(final String url, final String saveDir, final FileCallback callback) {
Request request = new Request.Builder().url(url).build();
okHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(final Call call, final IOException e) {
// 下载失败
mHandler.post(new Runnable() {
@Override
public void run() {
callback.onError(call, e);
}
});
}
@Override
public void onResponse(final Call call, Response response) throws IOException {
InputStream is = null;
byte[] buf = new byte[2048];
int len = 0;
FileOutputStream fos = null;
// 储存下载文件的目录
String savePath = isExistDir(saveDir);
try {
is = response.body().byteStream();
final long total = response.body().contentLength(); //文件大小
final File file = new File(savePath, getNameFromUrl(url));
fos = new FileOutputStream(file);
long sum = 0;
while ((len = is.read(buf)) != -1) {
fos.write(buf, 0, len);
sum += len;
final int progress = (int) (sum * 1.0f / total * 100);
// 下载中
mHandler.post(new Runnable() {
@Override
public void run() {
callback.onProgress(progress, total);
}
});
}
fos.flush();
// 下载完成
mHandler.post(new Runnable() {
@Override
public void run() {
callback.onSuccess(file);
}
});
} catch (final Exception e) {
mHandler.post(new Runnable() {
@Override
public void run() {
callback.onError(call, e);
}
});
} finally {
try {
if (is != null)
is.close();
} catch (IOException e) {
}
try {
if (fos != null)
fos.close();
} catch (IOException e) {
}
}
}
});
}
/**
* @param saveDir
* @return
* @throws IOException 判断下载目录是否存在
*/
private String isExistDir(String saveDir) throws IOException {
// 下载位置
File downloadFile = new File(saveDir);
if (!downloadFile.mkdirs()) {
downloadFile.createNewFile();
}
String savePath = downloadFile.getAbsolutePath();
return savePath;
}
/**
* @param url
* @return 从下载连接中解析出文件名
*/
@NonNull
private String getNameFromUrl(String url) {
return url.substring(url.lastIndexOf("/") + 1);
}
public interface FileCallback {
/**
* 下载成功
*/
void onSuccess(File file);
/**
* @param progress 下载进度
*/
void onProgress(int progress, long total);
/**
* 下载失败
*/
void onError(Call call, Exception e);
}
}
使用:
DownloadManager.getInstance().downloadFile(url, fileDir, new DownloadManager.FileCallback() {
@Override
public void onSuccess(File file) {
Toast.makeText(Main8Activity.this, "下载成功", Toast.LENGTH_SHORT).show();
Log.e("xyh", "onSuccess: " + file.getAbsolutePath());
}
@Override
public void onProgress(int progress, long total) {
Log.e("xyh", "onProgress: " + progress);
}
@Override
public void onError(Call call, Exception e) {
Toast.makeText(Main8Activity.this, "下载失败==" + e.getMessage(), Toast.LENGTH_SHORT).show();
}
});
OkHttp 面试相关
OkHttp 之 网络请求耗时统计
先提问一个问题:
OkHttp如何进行各个请求环节的耗时统计呢?
OkHttp 3.11.0版本提供了EventListener接口,,可以让调用者接收一系列网络请求过程中的事件,例如DNS解析、TSL/SSL连接、Response接收等。通过继承此接口,调用者可以监视整个应用中网络请求次数、流量大小、耗时(比如dns解析时间,请求时间,响应时间等等)情况。
EventListener回调原理
为什么要使用OkHttp?
- OkHttp是一个相对成熟的解决方案,Android4.4的源码中可以看到HttpURLConnection已经替换成OkHttp实现了。所以我们更有理由相信OkHttp的强大。
- OkHttp是一个高效的HTTP客户端,它的横空出世,让其他的网络请求框架都变得黯然失色。
1. DNS
okhttp提供了自定义DNS解析的接口。
DNS劫持:DNS劫持一般指域名劫持,通过攻击域名解析服务器(DNS),或伪造域名解析服务器(DNS)的方法,把目标网站域名解析到错误的IP地址从而实现用户无法访问目标网站或者访问钓鱼网站的目的。
在2010年的时候,百度曾经被DNS劫持。腾讯、阿里等许多巨头也都曾经因为DNS劫持造成过重大的损失。
2. 实现了连接池
OkHttp实现了连接池的概念,即对于同一主机的多个请求,其实可以公用一个Socket连接,而不是每次发送完HTTP请求就关闭底层的Socket,这样就实现了连接池的概念。
3. 线程池怎么复用
线程池的线程复用:就是任务在并不只执行创建时指定的firstTask第一任务,还会从任务队列的中自己主动取任务执行,而且是有/无时间限定的阻塞等待,保证线程的存活。
4. 响应缓存减少重复请求
5. 支持GZIP压缩
6. OKIO
Retrofit
OkHttp是一个高效的HTTP客户端,它的横空出世,让其他的网络请求框架都变得黯然失色。
Retrofit是一个基于OkHttp的RESTful网络请求框架,功能强大、简洁易用及高可拓展性。Retrofit说起来相当简单,简单到源码只有37个文件,其中22个文件是注解,还都和HTTP有关,真正暴露给用户的类并不多。
Retrofit其实就是一个网络请求框架的封装。
为什么需要封装呢?说白了,就是为了解耦,为了方便日后切换到不同框架实现,而无需到处修改调用的地方。
比如我们项目当中经常会用到一些之前比较流行的的网络框架,后期这个框架停止维护或者功能无法满足业务需求,我们想切换到新的框架,可能调用的地方会非常多,如果不做封装,直接切换的话,改动量将非常非常大,而且还很有可能会有遗漏,风险度非常高。
OKHttp有哪些拦截器,分别起什么作用
OKHTTP的拦截器是把所有的拦截器放到一个list里,然后每次依次执行拦截器,并且在每个拦截器分成三部分:
- 预处理拦截器内容
- 通过proceed方法把请求交给下一个拦截器
- 下一个拦截器处理完成并返回,后续处理工作。
这样依次下去就形成了一个链式调用,看看源码,具体有哪些拦截器:
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
interceptors.addAll(client.interceptors());
interceptors.add(retryAndFollowUpInterceptor);
interceptors.add(new BridgeInterceptor(client.cookieJar()));
interceptors.add(new CacheInterceptor(client.internalCache()));
interceptors.add(new ConnectInterceptor(client));
if (!forWebSocket) {
interceptors.addAll(client.networkInterceptors());
}
interceptors.add(new CallServerInterceptor(forWebSocket));
Interceptor.Chain chain = new RealInterceptorChain(
interceptors, null, null, null, 0, originalRequest);
return chain.proceed(originalRequest);
}
根据源码可知,一共七个拦截器:
- addInterceptor(Interceptor),这是由开发者设置的,会按照开发者的要求,在所有的拦截器处理之前进行最早的拦截处理,比如一些公共参数,Header都可以在这里添加。
- RetryAndFollowUpInterceptor,这里会对连接做一些初始化工作,以及请求失败的充实工作,重定向的后续请求工作。跟他的名字一样,就是做重试工作还有一些连接跟踪工作。
- BridgeInterceptor,这里会为用户构建一个能够进行网络访问的请求,同时后续工作将网络请求回来的响应Response转化为用户可用的Response,比如添加文件类型,content-length计算添加,gzip解包。
- CacheInterceptor,这里主要是处理cache相关处理,会根据OkHttpClient对象的配置以及缓存策略对请求值进行缓存,而且如果本地有了可⽤的Cache,就可以在没有网络交互的情况下就返回缓存结果。
- ConnectInterceptor,这里主要就是负责建立连接了,会建立TCP连接或者TLS连接,以及负责编码解码的HttpCodec
- networkInterceptors,这里也是开发者自己设置的,所以本质上和第一个拦截器差不多,但是由于位置不同,所以用处也不同。这个位置添加的拦截器可以看到请求和响应的数据了,所以可以做一些网络调试。
- CallServerInterceptor,这里就是进行网络数据的请求和响应了,也就是实际的网络I/O操作,通过socket读写数据。
OkHttp怎么实现连接池
为什么需要连接池?
频繁的进行建立Sokcet连接和断开Socket是非常消耗网络资源和浪费时间的,所以HTTP中的keepalive连接对于降低延迟和提升速度有非常重要的作用。
keepalive机制是什么呢?也就是可以在一次TCP连接中可以持续发送多份数据而不会断开连接。所以连接的多次使用,也就是复用就变得格外重要了,而复用连接就需要对连接进行管理,于是就有了连接池的概念。
OkHttp中使用ConectionPool实现连接池,默认支持5个并发KeepAlive,默认链路生命为5分钟。
OKHttp线程池
public synchronized ExecutorService executorService() {
if (executorService == null) {
executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
}
return executorService;
}
OkHttp实现的是无边界限制的线程池。
- 0: 核心线程数。即使空闲也会被保留的线程数。这里设置为0,说明没有被保留的线程,所有空闲的线程都会被终止。
- Integer.MAX_VALUE:线程池可以容纳最大线程数量。Integer.MAX_VALUE是非常大的一个数,可以理解为OkHttp随时可以创建新的线程来满足需要。可以保证网络的I/O任务有线程来处理,不被阻塞。
- 60, TimeUnit.SECONDS:空闲线程被终止的等待时间,这里设置为60秒。
- new SynchronousQueue():阻塞队列。同步队列,按序排队,先来先服务。
- Util.threadFactory(“OkHttp Dispatcher”, false):用来创建线程的线程工厂。
在OKHttp中,创建了一个阀值是Integer.MAX_VALUE的线程池,它不保留任何最小线程,随时创建更多的线程数,而且如果线程空闲后,只能多活60秒。所以也就说如果收到20个并发请求,线程池会创建20个线程,当完成后的60秒后会自动关闭所有20个线程。他这样设计成不设上限的线程,以保证I/O任务中高阻塞低占用的过程,不会长时间卡在阻塞上。
SynchronousQueue每个插入操作必须等待另一个线程的移除操作,同样任何一个移除操作都等待另一个线程的插入操作。因此队列内部其实没有任何一个元素,或者说容量为0,严格说并不是一种容器,由于队列没有容量,因此不能调用peek等操作,因此只有移除元素才有元素,显然这是一种快速传递元素的方式,也就是说在这种情况下元素总是以最快的方式从插入者(生产者)传递给移除者(消费者),这在多任务队列中最快的处理任务方式。对于高频请求场景,无疑是最合适的。
SynchronousQueue是一个没有容量的队列。即里面不会放入等待线程,会直接创建线程。