使用一个框架要问自己你为什么选择这个框架,它的优势在哪里,以及它的基本原理是怎么样的?
OkHttp的优势
-
支持SPDY,那这个SPDY又是什么呢?
SPDY 是 Google 开发的基于传输控制协议 (TCP) 的应用层协议。SPDY 协议旨在通过压缩、多路复用和优先级来缩短网页的加载时间和提高安全性。(SPDY 是 Speedy 的昵音,意思是更快)SPDY 协议只是在性能上对 HTTP 做了很大的优化,其核心思想是尽量减少连接个数,而对于 HTTP 的语义并没有做太大的修 改。具体来说是,SPDY 使用了 HTTP 的方法和页眉,但是删除了一些头并重写了 HTTP 中管理连接和数据转移格式的部分,所以基本上是兼容 HTTP 的。
-
内置连接池,支持连接复用,减少延迟
-
支持透明的gzip压缩响应体
-
通过缓存避免重复的请求
-
请求失败时自动重试主机的其他ip,自动重定向
-
Api使用起来比较方便
OkHttp使用
get
public void get(View view) {
OkHttpClient okHttpClient = new OkHttpClient.Builder().build();
Request request = new Request.Builder().get().url("http:www.baidu.com").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 {
LogUtil.loge("获取是否成功:" + response.isSuccessful());
if (response.isSuccessful()) {
LogUtil.loge("get获取的数据" + response.body().string());
}
}
});
}
public void getWithParams(View view) {
Uri.Builder builder = Uri.parse("http://api.qmyg.weiduanxian.com:7000/ebuyApp/indexPage/getAppVersion.json").buildUpon();
builder.appendQueryParameter("platform", "android");
String address = builder.build().toString();
OkHttpClient okHttpClient = new OkHttpClient.Builder().build();
Request request = new Request.Builder().get().url(address).tag("get").build();
Call call = okHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
LogUtil.loge("onFailure" + e.getMessage());
}
@Override
public void onResponse(Call call, Response response) throws IOException {
LogUtil.loge("获取是否成功:" + response.isSuccessful());
if (response.isSuccessful()) {
LogUtil.loge("getWithParams获取的数据" + response.body().string());
}
}
});
}
加载图片
public void getImage(final OutputStream outputStream) {
String imageUrl = "http://www.itlanbao.com/img/islider/a.jpg";
OkHttpClient client = new OkHttpClient.Builder().build();
Request.Builder requestBuild = new Request.Builder();
Request request = requestBuild.get().url(imageUrl).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()) {
return;
}
final InputStream inputStream = response.body().byteStream();
ByteArrayOutputStream bios = new ByteArrayOutputStream();
byte[] bytes = new byte[512];
int len = -1;
while ( (len = inputStream.read(bytes)) != -1 ){
bios.write(bytes,0,len);
}
bios.flush();
final ByteArrayInputStream is = new ByteArrayInputStream(bios.toByteArray());
Log.e("result", "拿到数据了么" + inputStream);
imageView.post(new Runnable() {
@Override
public void run() {
Bitmap bitmap = BitmapFactory.decodeStream(is);
Log.e("result", "拿到Bitmap了吗:" + bitmap);
imageView.setImageBitmap(bitmap);
}
});
}
});
}
注意:这里使用OkHttp加载图片的时候,使用Response.body.byteStream()直接decodeStreame得到的bitmap是空的,为什么呢?因为: inputstream 被重复调用了,也就是被解析了多次,当你用response.getbody().byteStream() 时,其实已经被解析了,你这个时候获取获取的尾节点。怎么办?我们可以用 ByteArrayOutputStream 解析 response.getbody().byteStream() ,然后把它转成 inputstream 就可以解析了。
post
public void post(View view) {
OkHttpClient okHttpClient = new OkHttpClient();
RequestBody body = new FormBody.Builder().add("infoUpd", "2").build();
Request request = new Request.Builder().url(HOST + "indexPage/getMsgList.json").post(body).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 {
LogUtil.loge("获取是否成功:" + response.isSuccessful());
if (response.isSuccessful()) {
LogUtil.loge("post获取的数据" + response.body().string());
}
}
});
}
上传文件
public void uploadFile(View view) {
ImageCache.getImageCache(this).putBitmap("test", BitmapFactory.decodeResource(getResources(), R.drawable.img_empty_address));
File img = ImageCache.getImageCache(this).getImageFile("test");
if (!img.exists()) {
Toast.makeText(this, "文件不存在", Toast.LENGTH_SHORT).show();
return;
}
OkHttpClient client = new OkHttpClient();
RequestBody body = new MultipartBody.Builder()
.addFormDataPart("avatar", "app.jpg", RequestBody.create(MEDIA_TYPE_PNG, img))
.addFormDataPart("userId", "9394D4AE36244264871FA32CE1757149")
.build();
final Request request = new Request.Builder().post(body).url(HOST + "users/editAvatar.json").tag("").build();
Call call = client.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
LogUtil.loge("上传失败");
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if (response != null && response.isSuccessful()) {
LogUtil.loge("上传是否成功:" + response.body().string());
}
}
});
}
下载文件
public void downloadFile(View view) {
String absolutePath = Environment.getExternalStorageDirectory().getAbsolutePath();
final File file = new File(absolutePath + "/test.png");
OkHttpClient client = new OkHttpClient();
Call call = client.newCall(new Request.Builder().url("http://7xt07n.com2.z0.glb.qiniucdn.com/ebuy_user_avater_img_20170808142716_972_89573769").build());
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
InputStream inputStream = response.body().byteStream();
//内容总长度
final long totalLength = response.body().contentLength();
FileOutputStream fileOutputStream = null;
try {
fileOutputStream = new FileOutputStream(file);
//一次读取的buffer
byte[] buffer = new byte[1024];
int len = 0;
while ((len = inputStream.read(buffer)) != -1) {
//读取的进度
read += len;
tvProgress.post(new Runnable() {
@Override
public void run() {
tvProgress.setText(read + "/" + totalLength);
}
});
fileOutputStream.write(buffer, 0, len);
}
//写完刷新
fileOutputStream.flush();
} catch (Exception e) {
if (fileOutputStream != null) {
fileOutputStream.close();
}
}
}
});
}
使用缓存
public void cache(View view) {
Uri.Builder builder = Uri.parse("http://api.qmyg.weiduanxian.com:7000/ebuyApp/indexPage/getAppVersion.json").buildUpon();
builder.appendQueryParameter("platform", "android");
String address = builder.build().toString();
File file = new File(getCacheDir().getAbsolutePath());
if (!file.exists()) {
file.mkdirs();
}
Cache cache = new Cache(file, 1024 * 10 * 10);
OkHttpClient okHttpClient = new OkHttpClient.Builder().cache(cache).build();
Request request = new Request.Builder().url(address).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 {
String response1Body = response.body().string();
System.out.println("Response 1 response: " + response);
System.out.println("Response 1 cache response: " + response.cacheResponse());
System.out.println("Response 1 network response: " + response.networkResponse());
}
});
}
简单封装
- 保证OkHttpClient是单例模式的
- 其实不同的请求都是在构建不同的Request
- 写一个公共的Callback,根据传入的类型来进行不同的返回,比如StringCallback、BitmapCallback
代码如下:
public static AreYouOk getAreYouOk() {
if (areYouOk == null) {
synchronized (AreYouOk.class) {
if (areYouOk == null) {
areYouOk = new AreYouOk();
}
}
}
return areYouOk;
}
public Request buildGetRequest(String url) {
Request.Builder getBuilder = new Request.Builder();
return getBuilder.url(url).tag(url).build();
}
public void get(String url, AreYouCallback callback) {
if (TextUtils.isEmpty(url) || callback == null)
return;
Call call = okHttpClient.newCall(buildGetRequest(url));
execute(call, callback);
}
/**
* 执行异步操作
*/
public void execute(Call call, final AreYouCallback callback) {
if (callback == null || call == null)
return;
callback.onBefore();
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
loadFailure(call, e, callback);
}
@Override
public void onResponse(Call call, Response response) throws IOException {
try {
Object o = callback.parseResponse(response);
loadSuccess(o, response, callback);
} catch (Exception e) {
callback.onError(e);
}
}
});
}
private void loadSuccess(final Object o, final Response response, final AreYouCallback callback) {
if (response.isSuccessful()) {
handler.post(new Runnable() {
@Override
public void run() {
callback.onSusccess(o);
callback.onAfter();
}
});
}
}
private void loadFailure(Call call, final IOException e, final AreYouCallback areYouCallback) {
handler.post(new Runnable() {
@Override
public void run() {
areYouCallback.onError(e);
}
});
}
源码简析
OkHttp.newCall(request):内部调用了
@Override
public Call newCall(Request request) {
return new RealCall(this, request, false /* for web socket */);
}
所以Call.enqueue实际上就是RealCall执行的enqueue方法如下:
@Override
public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
可见最终是调用了Dispatcher(任务调度类)来执行enqueue,传入的这个参数AsyncCall是RealCall的内部类,RealCall实现了Runnable。
Dispatcher任务调度类
Dispatcher主要用于控制并发的请求,它主要维护了以下变量:
/** 最大并发请求数*/
private int maxRequests = 64;
/** 每个主机最大请求数*/
private int maxRequestsPerHost = 5;
/** 消费者线程池 */
private ExecutorService executorService;
/** 将要运行的异步请求队列 */
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
/**正在运行的异步请求队列 */
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
/** 正在运行的同步请求队列 */
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
构造方法
public Dispatcher(ExecutorService executorService) {
this.executorService = executorService;
}
public Dispatcher() {
}
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;
}
Dispatcher有两个构造函数,可以使用自己设定线程池,如果没有设定线程池则会在请求网络前自己创建线程池,这个线程池类似于CachedThreadPool比较适合执行大量的耗时比较少的任务。
到这里我们再看Disptacher是如何执行的:
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
只有当正在运行的任务数<64 && 同一个host访问数量 < 每个主机最大请求数5,就会将该任务添加到正在执行的任务中,并从线程池中开一个线程去执行,如果不满足条件就会添加到准备去执行的列表中去。
去执行的这个AsyncCall是RealCall的内部类,是一个Runnable,实现了execute()方法,如下:
@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);
}
}
}
看到其中:
Response response = getResponseWithInterceptorChain();
这里的response就是Callback中返回的Response,然后我们跟进去getResponseWithInterceptorChain()这个方法去看看里面具体怎么进行的网络请求:
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);
}
这里看到将多个拦截器添加到了拦截器集合中,然后作为构造参数初始化了RealInterceptorChin,主要看这个拦截器的proceed方法:
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
if (index >= interceptors.size()) throw new AssertionError();
calls++;
// If we already have a stream, confirm that the incoming request will use it.
if (this.httpCodec != null && !this.connection.supportsUrl(request.url())) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must retain the same host and port");
}
// If we already have a stream, confirm that this is the only call to chain.proceed().
if (this.httpCodec != null && calls > 1) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must call proceed() exactly once");
}
// Call the next interceptor in the chain.
RealInterceptorChain next = new RealInterceptorChain(
interceptors, streamAllocation, httpCodec, connection, index + 1, request);
//递归调用Chain.process()!!!!!!!!!!!!!!!!!!!!!!!!
Interceptor interceptor = interceptors.get(index);
Response response = interceptor.intercept(next);
// Confirm that the next interceptor made its required call to chain.proceed().
if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
throw new IllegalStateException("network interceptor " + interceptor
+ " must call proceed() exactly once");
}
// Confirm that the intercepted response isn't null.
if (response == null) {
throw new NullPointerException("interceptor " + interceptor + " returned null");
}
return response;
}
Chain与Interceptor会互相递归调用,直到链的尽头。
我们看到,通过职责链模式,清楚地切开了不同的逻辑,每个拦截器完成自己的职责,从而完成用户的网络请求。
大概流程是:
-
先经过用户拦截器
-
RetryAndFollowUpInterceptor负责自动重试和进行必要的重定向
-
BridgeIntercetor负责将用户Request转换成一个实际的网络请求的Request,再调用下层的拦截器获取Response,最后再将网络Response转换成用户的Reponse
-
CacheInterceptor负责控制缓存
-
ConnectInterceptor负责进行连接主机
-
网络拦截器进行拦截
-
CallServerInterceptor是真正和服务器通信,完成http请求
回到上面的RealCall的execute方法,执行到最后可以看到:在AsyncCall的run()方法中进行网络请求,不管网络请求是否成功:最终在finally中都会调用Dispatcher.finish(this),结束掉当前任务,并且去runningAsyncCalls和readyAsyncCalls中去找接下来要执行的任务,代码如下:
private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
int runningCallsCount;
Runnable idleCallback;
synchronized (this) {
if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
if (promoteCalls) promoteCalls();//去
runningCallsCount = runningCallsCount();
idleCallback = this.idleCallback;
}
if (runningCallsCount == 0 && idleCallback != null) {
idleCallback.run();
}
}
private void promoteCalls() {
if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.
for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
AsyncCall call = i.next();
if (runningCallsForHost(call) < maxRequestsPerHost) {
i.remove();
runningAsyncCalls.add(call);
executorService().execute(call);
}
if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
}
}
okhttp_full_process.png
一图了然。图是借用了下面链接大佬的图。
访问网络整体流程:
-
OkHttpClient.newCall()
-
实际上是初始化RealCall
-
RealCall.enqueue 其实是调用Dispatch这个任务调度类来执行任务即:调用了Dispatch的enqueue
-
在Dispatch方法中 只有当正在运行的任务数<64 && 同一个host访问数量 < 每个主机最大请求数5,就会将该任务添加到正在执行的任务总,并从线程池中开一个线程去执行,如果不满足条件就会添加到准备去执行的列表中去。
-
在Dispatch中执行的Call是RealCall的一个内部类,AsyncCall extends Runable,在AsyncCall的run()方法中进行网络请求,不管网络请求是否成功:最终都会调用Dispatcher.finish(this),结束掉当前任务
-
在Dispatcher的结束方法中会调用:promoteCalls()如下:
private void promoteCalls() { if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity. if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote. for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) { AsyncCall call = i.next(); if (runningCallsForHost(call) < maxRequestsPerHost) { i.remove(); runningAsyncCalls.add(call); executorService().execute(call); } if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity. } }
这个方法中呢,就是如果有等待的任务就去执行任务,如果正在执行的任务数>最大请求数和如果等待请求的数目为0就返回。
- 那是如何进行请求数据的呢?我们
网友评论