一.基本使用
1.依赖引入
implementation 'com.squareup.okhttp3:okhttp:3.5.0'
2.请求使用
public class OkHttpUtils {
private final static String url = "https://www.httpbin.org/";
private static OkHttpUtils instance;
private OkHttpClient okHttpClient = new OkHttpClient();
private OkHttpUtils() {
}
public static OkHttpUtils getInstance() {
if (null == instance) {
synchronized (OkHttpUtils.class) {
if (null == instance) {
instance = new OkHttpUtils();
}
}
}
return instance;
}
//同步get请求
public void synGet() {
Request request = new Request.Builder().url(url + "get?username=123&password=123").build();
try {
Response response = okHttpClient.newCall(request)
.execute();
System.out.println("threadName:" + Thread.currentThread().getName());
System.out.println(Objects.requireNonNull(response.body().string()));
} catch (IOException | NullPointerException e) {
e.printStackTrace();
}
}
//异步get请求
public void aSynGet() {
Request request = new Request.Builder().url(url + "get?username=123&password=123").get().build();
startCall(request);
}
// form表单 post请求 contentType application/x-www-form-urlencoded,multipart/form-data:需要在表单中进行文件上传时,就需要使用该格式
public void postForm() {
FormBody formBody = new FormBody.Builder().add("use", "abc")
.add("pwd", "123").build();
Request request = new Request.Builder().url(url + "post").post(formBody).build();
startCall(request);
}
//post请求上传二进制文件,只上传单个文件 contentType可查看菜鸟教程https://www.runoob.com/http/http-content-type.html
public void postOctStream() {
try {
RequestBody streamBody = RequestBody.create(MediaType.parse("application/octet-stream"), "123".getBytes("utf-8"));
UploadRequestBody uploadRequestBody = new UploadRequestBody(new UploadRequestBody.ProgressListener() {
@Override
public void progress(String progress, long contentLength, long currentWrite) {
System.out.println("progress:" + progress + "contentLength:" + contentLength + ",currentWrite:" + currentWrite);
}
}, streamBody);
Request request = new Request.Builder().url(url + "post").post(uploadRequestBody).build();
startCall(request);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
//post多类型上传
public void postMultiPart() {
try {
MultipartBody multipartBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("username", "abc")
.addFormDataPart("pwd", "123")
.addFormDataPart("file", "fileName",
RequestBody.create(MediaType.parse("application/octet-stream"), "123".getBytes("utf-8"))).build();
Request request = new Request.Builder().url(url + "post").post(multipartBody).build();
startCall(request);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
//下载
public void downloadImage(ImageView imageView) {
Request request = new Request.Builder().get().url("https://img0.baidu.com/it/u=1501084209,93021381&fm=253&fmt=auto&app=138&f=JPEG?w=889&h=500")
.build();
startCall(request, imageView);
}
/**
* 普通拦截器和网络拦截器
*/
public void addDownLoadInterceptor() {
okHttpClient=okHttpClient.newBuilder()
.addInterceptor(new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
//前置处理 添加请求头等等
Request request = chain.request().newBuilder()
.addHeader("os", "android")
.addHeader("version", "1.o")
.build();
Response response = chain.proceed(request);
//后置处理
return response;
}
}).addNetworkInterceptor(new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
//网路拦截器中配置下载拦截器
System.out.println("version>>>>>" + chain.request().header("version"));
Response response = chain.proceed(chain.request());
// return chain.proceed(chain.request());
return response.newBuilder().body(new ProgressResponseBody(response.body(), new ProgressResponseBody.ProgressListener() {
@Override
public void progress(String progress, long contentLength, long currentWrite) {
System.out.println("addInterceptor----progress:"+progress+",contentLength:"+contentLength+",currentWrite:"+currentWrite);
}
})).build();
}
}).build();
}
/**
* 文件上传拦截器
*/
public void addUpLoadInterceptor() {
okHttpClient=okHttpClient.newBuilder()
.addInterceptor(new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
//前置处理 添加请求头等等
Request request = chain.request().newBuilder()
.addHeader("os", "android")
.addHeader("version", "1.o")
.build();
Response response = chain.proceed(request);
//后置处理
return response;
}
}).addNetworkInterceptor(new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request.Builder builder = chain.request().newBuilder();
Request request=builder.post(new UploadRequestBody(new UploadRequestBody.ProgressListener() {
@Override
public void progress(String progress, long contentLength, long currentWrite) {
System.out.println("progress--->>>:" + progress + "contentLength:" + contentLength + ",currentWrite:" + currentWrite);
}
},chain.request().body())).build();
return chain.proceed(request);
}
}).build();
}
/**
* 这里设置了缓存的大小和路径 默认okHttp的缓存是关闭的,需要手动开启
*/
public void addCache() {
okHttpClient=okHttpClient.newBuilder()
.cache(new Cache(new File("path/cache"), 100))
.build();
}
/**
* okHttp默认不自动处理cookies
*/
public void addCookieJar() {
final Map<String, List<Cookie>> cookiesMap = new HashMap<>();
okHttpClient= okHttpClient.newBuilder()
.cookieJar(new CookieJar() {
@Override
public void saveFromResponse(HttpUrl httpUrl, List<Cookie> list) {
//返回服务器的cookie 存到本地
cookiesMap.put(httpUrl.host(), list);
}
@Override
public List<Cookie> loadForRequest(HttpUrl httpUrl) {
List<Cookie> cookies = cookiesMap.get(httpUrl.host());
return cookies == null ? new ArrayList<Cookie>() : cookies;
}
})
.build();
}
private void startCall(Request request) {
startCall(request, null);
}
//开始请求
private void startCall(final Request request, final ImageView imageView) {
try {
okHttpClient.newCall(request)
.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
System.out.println("onFailure:" + e.getMessage());
}
@Override
public void onResponse(Call call, Response response) throws IOException {
System.out.println("threadName:" + Thread.currentThread().getName());
if (null != imageView) {
InputStream inputStream = response.body().byteStream();
File file = new File(imageView.getContext().getFilesDir().getPath() + File.separator + "a.png");
if (!file.exists()) {
System.out.println("file:" + file.getPath());
file.createNewFile();
}
FileOutputStream fos = new FileOutputStream(file);
float sum = 0l;
long total = response.body().contentLength();
byte buff[] = new byte[128];
int length;
while ((length = inputStream.read(buff)) != -1) {
fos.write(buff, 0, length);
sum += length;
float speed = sum / total * 100;
String format = (new DecimalFormat("0.00")).format(speed);
String format1 = String.format("%.2f", speed);
System.out.println("speed:" + speed + ",sum:" + sum + ",total:" + total + ",format:" + format + ",format1:" + format1);
}
fos.flush();
final Bitmap bitmap = BitmapFactory.decodeFile(file.getAbsolutePath());
imageView.post(new Runnable() {
@Override
public void run() {
imageView.setImageBitmap(bitmap);
}
});
inputStream.close();
} else {
/**
* 如果先body的string方法后调用byteStream流已经被关闭了,会导致io异常
*/
System.out.println(Objects.requireNonNull(response.body().string()));
}
}
});
} catch (NullPointerException e) {
e.printStackTrace();
}
}
/**
* 下载进度拦截
*/
static class ProgressResponseBody extends ResponseBody {
private ResponseBody mResponseBody;
private ProgressResponseBody.ProgressListener mProgressListener;
private BufferedSource mBufferedSource;
public ProgressResponseBody(ResponseBody mResponseBody, ProgressListener mProgressListener) {
this.mResponseBody = mResponseBody;
this.mProgressListener = mProgressListener;
}
@Override
public MediaType contentType() {
return mResponseBody.contentType();
}
@Override
public long contentLength() {
return mResponseBody.contentLength();
}
@Override
public BufferedSource source() {
if (mBufferedSource == null) {
mBufferedSource = Okio.buffer(getSource(mResponseBody.source()));
}
return mBufferedSource;
}
private Source getSource(Source source) {
return new ForwardingSource(source) {
long totalSize = 0L;
long sum = 0L;
@Override
public long read(Buffer sink, long byteCount) throws IOException {
if (totalSize == 0) {
totalSize = contentLength();
}
long len = super.read(sink, byteCount);
System.out.println("ProgressResponseBody len:"+len);
sum += (len == -1 ? 0 : len);
System.out.println("ProgressResponseBody sum:"+sum);
double speed = (sum+0.0) / totalSize * 100;
System.out.println("ProgressResponseBody speed:"+speed);
String format = (new DecimalFormat("0.00")).format(speed);
mProgressListener.progress(format, totalSize, sum);
return len;
}
};
}
public interface ProgressListener {
void progress(String progress, long contentLength, long currentWrite);
}
}
//上传进度拦截器
private static class UploadRequestBody extends RequestBody {
private ProgressListener progressListener;
private RequestBody requestBody;
public UploadRequestBody(ProgressListener progressListener, RequestBody requestBody) {
this.progressListener = progressListener;
this.requestBody = requestBody;
}
@Override
public MediaType contentType() {
return requestBody.contentType();
}
@Override
public void writeTo(BufferedSink bufferedSink) throws IOException {
System.out.println(">>>>>");
CountSink countSink = new CountSink(bufferedSink);
BufferedSink buffer = Okio.buffer(countSink);
requestBody.writeTo(buffer);
buffer.flush();
}
@Override
public long contentLength() throws IOException {
return requestBody.contentLength();
}
class CountSink extends ForwardingSink {
long byteWrite;
public CountSink(Sink sink) {
super(sink);
}
/**
* @param source
* @param byteCount 缓冲区大小
* @throws IOException
*/
@Override
public void write(Buffer source, long byteCount) throws IOException {
super.write(source, byteCount);
System.out.println("write>>>>>");
byteWrite += byteCount;
float speed = byteWrite / contentLength() * 100;
String format = (new DecimalFormat("0.00")).format(speed);
progressListener.progress(format, contentLength(), byteWrite);
}
}
public interface ProgressListener {
void progress(String progress, long contentLength, long currentWrite);
}
}
}
二.原理分析
1.okHttp3中的构建器
构建者模式是设计模式中的创建者中的一种,当我们的对象的构建比较复杂,可以为对象添加不同的功能展示时,我们一般会使用构建者模式,我们的okhttp3的创建过程就是一个通过构建器模式的创建的,我们一般会new一个okHttpBuilder然后为其添加各种拦截器、超时等。当然,如果你不通过它来创建,okHttp3也会给你一个默认的builder参数:
public OkHttpClient() {
this(new Builder());
}
public Builder() {
dispatcher = new Dispatcher();
protocols = DEFAULT_PROTOCOLS;
connectionSpecs = DEFAULT_CONNECTION_SPECS;
proxySelector = ProxySelector.getDefault();
cookieJar = CookieJar.NO_COOKIES;
socketFactory = SocketFactory.getDefault();
hostnameVerifier = OkHostnameVerifier.INSTANCE;
certificatePinner = CertificatePinner.DEFAULT;
proxyAuthenticator = Authenticator.NONE;
authenticator = Authenticator.NONE;
connectionPool = new ConnectionPool();
dns = Dns.SYSTEM;
followSslRedirects = true;
followRedirects = true;
retryOnConnectionFailure = true;
connectTimeout = 10_000;
readTimeout = 10_000;
writeTimeout = 10_000;
pingInterval = 0;
}
我们通过一个简单的例子来表述下okHttpClient和其builder之间的关系:
static class Design {
House house;
Floor floor;
Window window;
public Design() {
this(new Builder()); //使用默认参数
}
//build的参数
Design(Builder builder) {
this.house = builder.house;
this.floor = builder.floor;
this.window = builder.window;
}
/**
* 获取对象的builder构建起对象
*
* @return
*/
public Builder newBuilder() {
return new Builder(this);
}
static class Builder {
House house;
Floor floor;
Window window;
//1.默认参数
public Builder() {
house = new House();
floor = new Floor();
window = new Window();
}
//2.外部传过来的
public Builder(Design design) {
house = design.house;
floor = design.floor;
window = design.window;
}
//3,修改图纸
public Builder buildWindow(Window window) {
this.window = window;
return this;
}
//4.通过该方法生成一个新的图纸
public Design builder() {
return new Design(this);
}
}
}
static class House {
}
static class Floor {
}
static class Window {
public String windowColor = "red";
}
@Test
public void testDefaultBuilder() {
//使用默认的构造器
Design design = new Design();
System.out.println(design);
System.out.println(design.window.windowColor);
Window window = new Window();
window.windowColor = "green";
Design design2 = design.newBuilder().buildWindow(window).builder();
System.out.println(design2.window.windowColor);
}
上面是一个设计师设计房子的图纸,由于设计时需要绘制房子、窗户、地板等,所以我们提供了房子的构建者,当然构建者构建出来的对象当然是设计图纸。所以构建者的作用有:1.生成默认图纸对象;2.支持外部传入设计对象;3.支持修改当前设计图纸,并通过构建后返回生成新的图纸;那设计师的作用是:提供默认图纸,当然是通过上面的默认构造器生成;2.支持获取当前图纸的构造器,这样就可以修改当前图纸并且生成新的图纸对象;所以我们的图纸的创建就完全依赖于builder来创建,我们新建的默认图纸或为图纸增加新的功能都需要依赖builder实现。
其实在okHttp中的源码也是这样,我们创建一个默认okHttpClient就是new,如果要通过builder创建,就需要先创建一个builder或者通过okHttpClient.newBuilder之后在创建一个新的okHttpClient.
2.okHttp3中的责任链
责任链模式是设计模式中的行为型设计模式,它是由多个对象都有机会处理请求,并且形成链条传递,直到有人处理请求。下面我们通过一个简单职场的例子来说明:比如小王请假,会向主管申请,主管有权限就批准了,但是他想升职,主管批复后,还得向经理提交,经理提交后才可以,如果要加薪经理提交后还得向老板申请,这样就形成了一个责任链,在链条上的每个人都可以处理请求,但是由于请求的类别不同,处理的权限不同,就会向上一级提交请求,最终顶层执行后会返回给用户一个提交结果。
那我们用代码的形式表述如下:
public void testChain() {
Boss boss = new Boss(null);
Manager manager = new Manager(boss);
Head head = new Head(manager);
Request request=new Request();
request.request="请假";
System.out.println(head.handlerRequest(request).response);
// head.handlerRequest("升职");
// head.handlerRequest("加薪");
}
//抽象处理请求
static abstract class Handler {
public abstract Response handlerRequest(Request request);//
Handler nextHandler;//上一级领导
public Handler(Handler nextHandler) {
this.nextHandler = nextHandler;
}
}
class Request {
public String request;
}
static class Response {
public String response;
}
/**
* 主管
*/
static class Head extends Handler {
public Head(Handler nextHandler) {
super(nextHandler);
}
@Override
public Response handlerRequest(Request request) {
if (request.request.equals("请假")) {
Response response = new Response();
response.response = "主管批复了";
return response;
} else {
//上一级领导处理
System.out.println("主管未知指令 转交给上级");
return nextHandler.handlerRequest(request);
}
}
}
static class Manager extends Handler {
public Manager(Handler nextHandler) {
super(nextHandler);
}
@Override
public Response handlerRequest(Request request) {
if (request.request.equals("升职")) {
Response response = new Response();
response.response = "经理批复了,成功";
return response;
} else {
System.out.println("经理未知指令 转交给上级");
return nextHandler.handlerRequest(request);
}
}
}
static class Boss extends Handler {
public Boss(Handler nextHandler) {
super(nextHandler);
}
@Override
public Response handlerRequest(Request request) {
Response response = new Response();
if (request.request.equals("加薪")) {
response.response = "老板批复了,成功";
} else {
response.response = "老板未知请求";
}
return response;
}
}
}
可以看到我们的链条的连接需要手动去关联下,在okHttp中,他使用了是一个接口Chain的接口,这个接口链条持有了请求和响应,拦截器则是返回了响应,类关系如下图:
拦截调用关系如下图所示:每个拦截器都可以直接返回结果,也可以通过chain.proceed(request)方法将请求传递给下一个拦截器,并获取处理接结果。
我们用代码模拟下okHttp中的责任链的变形与实现:
/**
* 类比拦截器
*/
public interface Interceptor {
String intercept(Chain chain);
/**
* 拦截器的链条,每个拦截器都在链条上,并且每个拦截器可以直接返回结果或者
* 通过下一个链条来获取结果
*/
interface Chain {
String request();
String proceed(String request);
}
}
/**
* 拦截器1
*/
class I1 implements Interceptor {
@Override
public String intercept(Chain chain) {
System.out.println("I1 intercept EXE");
String response=((RealInterceptorChain)chain).proceed(chain.request());//将当前请求传过去 让链条下端处理
System.out.println("I1 intercept EXE after");
return response;
}
}
/**
* 拦截器2
*/
class I2 implements Interceptor {
@Override
public String intercept(Chain chain) {
System.out.println("I2 intercept EXE");
// String response=((RealInterceptorChain)chain).proceed(chain.request());
System.out.println("I2 intercept EXE after");
return "response";
}
}
public final class RealInterceptorChain implements Interceptor.Chain {
private final int index; //当前的index
private final String request;
private final List<Interceptor> interceptors; //拦截器列表
public RealInterceptorChain(int index, String request, List<Interceptor> interceptors) {
this.index = index;
this.request = request;
this.interceptors = interceptors;
}
@Override
public String request() {
return request;
}
/**
* 执行流程
* 1.生成下一个拦截器(这里非常的巧妙的使用创建一个RealInterceptorChain作为下一个拦截器,
* 并且这个对象的index是index+1的,当前的拦截器如果传递链条,又会执行到这个对象的proceed方法时,index就会变成index+1,
* 依次往后又创建,这样index就会不停的+1直到等于列表大小)
* 2.获取当前拦截器
* 3.执行当前拦截器的拦截方法,并传入下一个拦截器;
* @param request
* @return
*/
@Override
public String proceed(String request) {
System.out.println("index:"+index);
if (index >= interceptors.size()) throw new AssertionError();
//通过它来绑定链条上的关系
RealInterceptorChain nextInterceptor = new RealInterceptorChain(index + 1, request, interceptors);
//调用当前端或者认为它是调用下一段,因为如果是上一段(index+1)调用的,就可以理解为下一段
Interceptor interceptor = interceptors.get(index);
String response = interceptor.intercept(nextInterceptor);
return response;
}
}
@Test
public void testChain() {
List<Interceptor> interceptors=new ArrayList<>();
interceptors.add(new I1());
interceptors.add(new I2());
RealInterceptorChain realInterceptorChain=new RealInterceptorChain(0,"request",interceptors);
realInterceptorChain.proceed("request");
}
当我们调用realInterceptorChain.proceed("request")时,它的执行流程是:
1.处理当前拦截器的拦截方法,传入(index+1)的RealInteceptorChain链条对象->2.当前拦截器的拦截方法会向链条的下一段传递,而这个下一段,并非时I2,而是我们的RealInteceptorChain,它的proceed的处理方法也是调用列表中的下一段(index+1)拦截方法而已,因为它的index是上传传递过来的index+1->(1-2)步骤循环直到拦截列表结束或者拦截器不通过RealInteceptorChain来传递到下端。
3.请求流程
1.Call的执行流程
Call是个抽象类,它的请求在实现类RealCall中执行,异步请求执行,将请求包装成AsyncCall,并添加到分发器的请求队列中,AsyncCall是RealCall的内部类,它继承自NameRunnable,当线程池执行这个AsyncCall时,会执行他的run方法,而它的run方法就是调用AsyncCall的execute方法,请求执行完后,调用dispatcher分发器的移除运行队列中的这个call。同步执行则是直接把他加入到运行队列中,并阻塞当前线程,直到有结果或异常。
@Override public Call newCall(Request request) {
return new RealCall(this, request, false /* for web socket */);
}
在调用call的execute()方法,我们跟下这个方法:
//同步执行
@Override public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
try {
client.dispatcher().executed(this);//放到队列中去
Response result = getResponseWithInterceptorChain();//执行操作
if (result == null) throw new IOException("Canceled");
return result;
} finally {
client.dispatcher().finished(this); //完成后移除
}
}
private void captureCallStackTrace() {
Object callStackTrace = Platform.get().getStackTraceForCloseable("response.body().close()");
retryAndFollowUpInterceptor.setCallStackTrace(callStackTrace);
}
//异步执行
@Override public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
//包裹新的异步call并放到分发器的队列中去 会执行Dispatcher中的enqueue的方法
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
final class AsyncCall extends NamedRunnable {
private final Callback responseCallback;
AsyncCall(Callback responseCallback) {
super("OkHttp %s", redactedUrl());
this.responseCallback = responseCallback;
}
String host() {
return originalRequest.url().host();
}
Request request() {
return originalRequest;
}
RealCall get() {
return RealCall.this;
}
@Override protected void execute() {
boolean signalledCallback = false;
try {
Response response = getResponseWithInterceptorChain();
if (retryAndFollowUpInterceptor.isCanceled()) {
signalledCallback = true;
responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
} else {
signalledCallback = true;
responseCallback.onResponse(RealCall.this, response);
}
} catch (IOException e) {
if (signalledCallback) {
// Do not signal the callback twice!
Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
} else {
responseCallback.onFailure(RealCall.this, e);
}
} finally {
client.dispatcher().finished(this);
}
}
}
public abstract class NamedRunnable implements Runnable {
protected final String name;
public NamedRunnable(String format, Object... args) {
this.name = Util.format(format, args);
}
@Override public final void run() {
String oldName = Thread.currentThread().getName();
Thread.currentThread().setName(name);
try {
execute();
} finally {
Thread.currentThread().setName(oldName);
}
}
protected abstract void execute();
}
2.任务分发器Dispatch
异步执行:根据当前call请求数量是否大于64和同一个host请求是否大于5来决定加入到异步等待队列还是执行队列
private int maxRequests = 64; //最大请求数量
private int maxRequestsPerHost = 5;
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
//线程池执行call,也就是会执行NamedRunnable的run方法-》AsyncCall的execute方法
executorService().execute(call);
} else {
//放到等待队列中去
readyAsyncCalls.add(call);
}
}
同步执行:直接将call加入到同步运行队列
synchronized void executed(RealCall call){
runningSyncCalls.add(call);
}
4.流程总结
下面我用一张图来对流程做个总结: