1. 原理

1. okio

最基本的接口只有两个:Sink(writer)、Source(Read),大概相当于OutputStream和InputStream在原生接口中的地位。这两个接口中只定义了一些最基础的IO操作方法

1. 图解okio


okio的骨架。下面是几个核心的类:
Okio:提供生成Sink和Source的方法
Sink : 接口类,功能上对应OutputStream
Source :接口类,功能上对应InputStream
BufferedSink:接口类继承自Sink,内部有一个Buffer的Sink
BufferedSource:接口类继承自Source,内部有一个Buffer的Source
Buffer:BufferedSink和BufferedSource的最终实现实现类, 实现缓存功能,内部有一个Segment链表
Segment:里面有个byte数组,通过pos,limit控制读写的位置(从byte[]哪里开始读,哪里开始写入),next, prev实现导航到前面或后面的Segment(实现Segment链表结构)

相关连接
  1. csdn

通过一段socket编程来演示:

package com.example.disignmode.myhttp.myokio;import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.nio.charset.Charset;import okio.BufferedSink;
import okio.BufferedSource;
import okio.Okio;/*** describe :* date on 2019/5/2* author linghailong* email 105354999@qq.com*/
public class OkioClient {public static void main(String[] args) {Socket socket = null;try {socket = new Socket("127.0.0.1", 8080);InputStream inputStream = socket.getInputStream();OutputStream outputStream = socket.getOutputStream();BufferedSink bufferedSink = Okio.buffer(Okio.sink(outputStream));BufferedSource bufferedSource = Okio.buffer(Okio.source(inputStream));writeMsg(bufferedSink,"hello");while (true){int length=bufferedSource.readInt();String message=bufferedSource.readString(length,Charset.forName("utf-8"));System.out.println("length is: "+length+" , message is : "+message); if ("error exit".equals(message)) {break;}String respMsg = getResponseAccordMsg(message);writeMsg(bufferedSink, respMsg);if ("error exit".equals(respMsg)) {break;}}} catch (IOException e) {e.printStackTrace();} finally {try {if (socket != null) {socket.close();}} catch (IOException e) {e.printStackTrace();}}}private static void writeMsg(BufferedSink sink, String msg) {try {int msgLength = msg.getBytes().length;sink.writeInt(msgLength);sink.writeString(msg, Charset.forName("utf-8"));sink.flush();} catch (IOException e) {e.printStackTrace();}}private static String getResponseAccordMsg(String msg) {String result = "";if (msg != null && msg.length() > 0) {if (msg.equals("hello")) {result = "nice to meet you";} else if (msg.equals("nice to meet you too")) {result = "see you";}}if (result.length() == 0) {result = "error exit";}return result;}
}

Server端代码

package com.example.disignmode.myhttp.myokio;import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.Charset;import okio.BufferedSink;
import okio.BufferedSource;
import okio.Okio;/*** describe :* date on 2019/5/2* author linghailong* email 105354999@qq.com*/
public class OkioServer {public static void main(String[] args) {ServerSocket serverSocket = null;try {serverSocket = new ServerSocket(8080);while (true) {Socket connection = null;try {connection = serverSocket.accept();handleClientSocket(connection);}catch (IOException ex){ex.printStackTrace();}}} catch (IOException ex) {ex.printStackTrace();} finally {if (serverSocket != null) {try {serverSocket.close();} catch (IOException e) {e.printStackTrace();}}}}private static void handleClientSocket(Socket socket) {Thread thread = new Thread(new Runnable() {@Overridepublic void run() {try {while (true) {BufferedSource source = Okio.buffer(Okio.source(socket));BufferedSink sink = Okio.buffer(Okio.sink(socket));int length = source.readInt();String message = source.readString(length, Charset.forName("utf-8"));System.out.println("length is: " + length + " , message is : " + message);if ("error exit".equals(message)) {break;}String responseMsg = getResponseAccordMsg(message);if (responseMsg != null) {int respLength = responseMsg.getBytes().length;sink.writeInt(respLength);sink.writeString(responseMsg, Charset.forName("utf-8"));sink.flush();}if ("error exit".equals(responseMsg)) {break;}}} catch (IOException e) {e.printStackTrace();} finally {if (socket != null) {try {socket.close();} catch (IOException e) {e.printStackTrace();}}}}});thread.start();}private static String getResponseAccordMsg(String msg) {String result = "";if (msg != null && msg.length() > 0) {if (msg.equals("hello")) {result = "hello";} else if (msg.equals("nice to meet you")) {result = "nice to meet you too";} else if (msg.equals("see you")) {result = "see you next time";}}if (result.length() == 0) {result = "error exit";}return result;}}

2. 拦截器流程

|---RealCall
|   |--- Response getResponseWithInterceptorChain()
  1. getResponseWithInterceptorChain()
Response getResponseWithInterceptorChain() throws IOException {// Build a full stack of interceptors.List<Interceptor> interceptors = new ArrayList<>();// 客户端的所有拦截器// 使用了责任链的设计模式 每一个拦截器只处理与他相关的拦截器interceptors.addAll(client.interceptors());interceptors.add(new RetryAndFollowUpInterceptor(client));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, transmitter, null, 0,originalRequest, this, client.connectTimeoutMillis(),client.readTimeoutMillis(), client.writeTimeoutMillis());boolean calledNoMoreExchanges = false;try {Response response = chain.proceed(originalRequest);if (transmitter.isCanceled()) {closeQuietly(response);throw new IOException("Canceled");}return response;} catch (IOException e) {calledNoMoreExchanges = true;throw transmitter.noMoreExchanges(e);} finally {if (!calledNoMoreExchanges) {transmitter.noMoreExchanges(null);}}}
  1. 请求完成之后,通过getResponseWithInterceptorChain()方法把请求变成了一个响应。
  2. 分析几个拦截器的作用:
    • RetryAndFollowUpInterceptor: 处理重试的拦截器,首先会处理一些异常,只要不是一些致命的异常,它就会continue,重新发起一次请求(把request给下级),如果是致命的异常,抛给上一层。会处理一些重定向
    • BridgeInterceptor 设置一些通用的请求头:Content-Type,Content-Length,Cookie,做一些返回的处理:如果返回得数据被压缩了,那木采用ZipSource,保存Cookie
    • CacheInterceptor (重点) 在缓存可用的情况下,读取本地的缓存的数据,如果有首先判断有没有缓存陈列,然后判断有没有过期,如果没有过期直接拿缓存,如果过期了需要添加一些之前头部信息,如conditionName = "If-Modified-Since"
    • ConnectInterceptor
      findhealthyConnection()找一个连接,首先判断有没有健康的,没有就创建(建立Socket,握手连接,)连接缓存。OkHttp基于原生的Socket.RealConnection
    • CallServerInterceptor :从服务器读数据以及写数据。写头部信息,写body信息
  3. 重定向:
    • 返回码是307
    • 然后从response的Header中获取location
    • 重新请求
  4. Http缓存
    1. Cache-Control(缓存策略)public private no-cache maxage no-sotre
    2. Expires (缓存的过期策略)指明了缓存数据有效的绝对时间,告诉客户端到了这个时间点(比照客户端时间)后本地就作废了
  5. 连接三个核心类:连接复用
    • RealConnection
    • ConnectionPool
  6. no-cache, no-store以及max-age=0辨析
    • no-cache: 相当于是重载
    • no-store相当于是不缓存
    • max-age=0; 相当于是刷新
OkHttpClient okHttpClient = new OkHttpClient();
OkHttpClient newClient = okHttpClient.newBuilder().cache(new Cache(mContext.getCacheDir(), 10240*1024)).connectTimeout(20, TimeUnit.SECONDS).readTimeout(20, TimeUnit.SECONDS).build();

缓存相关使用实例

/*** 一、无论有无网路都添加缓存。* 目前的情况是我们这个要addNetworkInterceptor* 这样才有效。经过本人测试(chan)测试有效.* 60S后如果没有网络将获取不到数据,显示连接失败*/static Interceptor netInterceptor = new Interceptor() {@Overridepublic Response intercept(Chain chain) throws IOException {Request request = chain.request();Response response = chain.proceed(request);/*String cacheControl = request.header("Cache-Control");if (TextUtils.isEmpty(cacheControl)) {cacheControl = "public, max-age=60";}*/int maxAge = 60;return response.newBuilder().removeHeader("Pragma")// 清除头信息,因为服务器如果不支持,会返回一些干扰信息,不清除下面无法生效.removeHeader("Cache-Control").header("Cache-Control", "public, max-age=" + maxAge).build();}};File cacheFile = new File(BaseApp.getInstance().getCacheDir(), "caheData");//设置缓存大小Cache cache = new Cache(cacheFile, DEFAULT_DIR_CACHE);//google建议放到这里OkHttpClient client = new OkHttpClient.Builder().retryOnConnectionFailure(true)//连接失败后是否重新连接.connectTimeout(15, TimeUnit.SECONDS)//超时时间15S.addNetworkInterceptor(cacheInterceptor)//这里大家一定要注意了是addNetworkOnterceptor别搞错了啊。.cache(cache).build();

拦截器的调用顺序

  1. 自己的
  2. retry
  3. Bridge
  4. Cache
  5. ConnectInterceptor
  6. CallServer
题目

自己写一个基于okhttp的缓存,有网络10s内可以读取缓存,没网每次请求读取缓存。

参考文献
  1. 简书上的分享

2. socket

文件上传进度监听

  1. 首先需要找到okhttp中是怎样进行上传的。

首先来看一段基础的代码

 private void uploadFile() {// 这个是 Okhttp 上传文件的用法String url = "https://api.baidu.com/api/upload";File file = new File(Environment.getExternalStorageDirectory(), "test.apk");OkHttpClient httpClient = new OkHttpClient();// 构建请求 Body , 这个我们之前自己动手写过MultipartBody.Builder builder = new MultipartBody.Builder().setType(MultipartBody.FORM);builder.addFormDataPart("platform", "android");builder.addFormDataPart("file", file.getName(),RequestBody.create(MediaType.parse(guessMimeType(file.getAbsolutePath())), file));ExMultipartBody exMultipartBody = new ExMultipartBody(builder.build(),new UploadProgressListener(){@Overridepublic void onProgress(long total, long current) {showToast(total,current);}});// 怎么监听上传文件的进度?// 构建一个请求final Request request = new Request.Builder().url(url).post(exMultipartBody).build();// new RealCall 发起请求Call call = httpClient.newCall(request);call.enqueue(new Callback() {@Overridepublic void onFailure(Call call, IOException e) {e.printStackTrace();}@Overridepublic void onResponse(Call call, Response response) throws IOException {Log.e("TAG", response.body().string());}});}

分析流程

|---RealCall
|   |---CallServerInterceptor
|   |--- MultipartBody
|   |   |---writeTo(BufferedSink sink)
  1. 由于MultipartBody 是final类型的方法,我们无法直接拿到这个方法,那木我们就可以考虑静态代理的形式,编写一个扩展类,传入代理。监听到contentlength以及currentLength这两个参数之后,通过编写一个借口回调,就可以拿到值,然后在主线程中进行一些操作就可以完成。
  2. ForwardingSink 这是Sink中的一个代理类。

断点下载

流程讲解:


大概的流程图如上所示:

|---MainActivity()
|   |---DownloadFacade(Context context) //用于 管理
|   |   |---startDownload(String url,DownloadCallback callback)
|   |   |   |---DownloadDispatcher // okhttp执行请求的类
|   |   |   |   |---startDownload(final String url, final DownloadCallback callback)  //真正执行
|   |   |   |   |   |---DownloadTask // download 线程池的分发类
|   |   |   |   |   |   |---init() //实例化,开始计算哪个线程下载哪部分
|   |   |   |   |   |   |   |---DownloadRunnable(String url, int threadId, long start, long end, long progress, DownloadEntity downloadEntity,DownloadCallback callback) //执行下载的线程

代码流程分析

1. MainActivity 在这里仅仅做实例化以及传入需要下载的对象的链接。
2. DownloadFacade 做一些实例化的准备,以及下载入口
public class DownloadFacade {private static final DownloadFacade sFacade = new DownloadFacade();private DownloadFacade(){}public static DownloadFacade getFacade() {return sFacade;}public void init(Context context){FileManager.manager().init(context);DaoManagerHelper.getManager().init(context);}public void startDownload(String url,DownloadCallback callback){DownloadDispatcher.getDispatcher().startDownload(url,callback);}public void startDownload(String url){// DownloadDispatcher.getDispatcher().startDownload(url);}
}
DownloadDispatcher

okhttp的执行类,同时也是下载任务的管理类,在这里开始执行下载

package com.darren.architect_day28.download;import com.darren.architect_day28.OkHttpManager;import java.io.IOException;
import java.util.ArrayDeque;
import java.util.Deque;import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.Response;final class DownloadDispatcher {private static final DownloadDispatcher sDispatcher = new DownloadDispatcher();private DownloadDispatcher(){}public static DownloadDispatcher getDispatcher() {return sDispatcher;}/** Ready async calls in the order they'll be run. */private final Deque<DownloadTask> readyTasks = new ArrayDeque<>();/** Running asynchronous calls. Includes canceled calls that haven't finished yet. */private final Deque<DownloadTask> runningTasks = new ArrayDeque<>();/** Running synchronous calls. Includes canceled calls that haven't finished yet. */private final Deque<DownloadTask> stopTasks = new ArrayDeque<>();// 最大只能下载多少个 3 5public void startDownload(final String url, final DownloadCallback callback){// 获取文件的大小Call call = OkHttpManager.getManager().asyncCall(url);call.enqueue(new Callback() {@Overridepublic void onFailure(Call call, IOException e) {callback.onFailure(e);}@Overridepublic void onResponse(Call call, Response response) throws IOException {// 获取文件的大小long contentLength = response.body().contentLength();if(contentLength <= -1){// 没有获取到文件的大小,// 1. 跟后台商量// 2. 只能采用单线程去下载return;}// 计算每个线程负责哪一块?DownloadTask downloadTask = new DownloadTask(url,contentLength,callback);downloadTask.init();runningTasks.add(downloadTask);}});}public void recyclerTask(DownloadTask downloadTask) {runningTasks.remove(downloadTask);// 参考 OkHttp 的 Dispatcher 的源码,如果还有需要下载的开始下一个的下载}public void stopDownload(String url){// 这个停止的是不是正在下载的}// 开个单独的线程去执行 所有下载的回调}
DownloadTask

真正执行断点下载的管理类,为执行断点下载的线程分发任务。这个task执行完毕后从downloaddispatcher中的list中移除任务。


public class DownloadTask {private String mUrl;private long mContentLength;private List<DownloadRunnable> mRunnables;// OkHttp 为什么搞一个能被回收的线程池?OkHttpClient client = new OkHttpClient();/*** Executes calls. Created lazily.*/private@NullableExecutorService executorService;private volatile int mSucceedNumber;private DownloadCallback mCallback;private static final ThreadFactory sThreadFactory = new ThreadFactory() {private final AtomicInteger mCount = new AtomicInteger(1);public Thread newThread(Runnable r) {return new Thread(r, "DownThread #" + mCount.getAndIncrement());}};public synchronized ExecutorService executorService() {if (executorService == null) {executorService = new ThreadPoolExecutor(0, THREAD_SIZE, 30, TimeUnit.SECONDS,new SynchronousQueue<Runnable>(), new ThreadFactory() {@Overridepublic Thread newThread(@NonNull Runnable r) {Thread thread = new Thread(r, "DownloadTask");thread.setDaemon(false);return thread;}});}return executorService;}private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();private static final int THREAD_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));public DownloadTask(String url, long contentLength, DownloadCallback callback) {this.mUrl = url;this.mContentLength = contentLength;mRunnables = new ArrayList<>();this.mCallback = callback;}/*** 初始化*/public void init() {for (int i = 0; i < THREAD_SIZE; i++) {// 计算出每个线程要下载的内容long threadSize = mContentLength / THREAD_SIZE;// 初始化的时候 这里要去读取数据库long start = i * threadSize;long end = (i + threadSize) - 1;if (i == THREAD_SIZE - 1) {end = mContentLength - 1;}List<DownloadEntity> entities = DaoManagerHelper.getManager().queryAll(mUrl);DownloadEntity downloadEntity = getEntity(i, entities);if (downloadEntity == null) {downloadEntity = new DownloadEntity(start, end, mUrl, i, 0, mContentLength);}DownloadRunnable downloadRunnable = new DownloadRunnable(mUrl, i, start, end,downloadEntity.getProgress(), downloadEntity, new DownloadCallback() {@Overridepublic void onFailure(IOException e) {// 一个apk 下载里面有一个线程异常了,处理异常,把其他线程停止掉mCallback.onFailure(e);}@Overridepublic void onSucceed(File file) {// 线程同步一下,synchronized (DownloadTask.this) {mSucceedNumber += 1;if (mSucceedNumber == THREAD_SIZE) {mCallback.onSucceed(file);DownloadDispatcher.getDispatcher().recyclerTask(DownloadTask.this);// 清楚数据库的这个文件下载存储}}}});// 通过线程池去执行executorService().execute(downloadRunnable);}}private DownloadEntity getEntity(int threadId, List<DownloadEntity> entities) {for (DownloadEntity entity : entities) {if (threadId == entity.getThreadId()) {return entity;}}return null;}public void stop() {for (DownloadRunnable runnable : mRunnables) {runnable.stop();}}
DownloadRunnable 执行断点下载的线程
public class DownloadRunnable implements Runnable{private static final int STATUS_DOWNLOADING = 1;private static final int STATUS_STOP = 2;private final long start;private final long end;private final int threadId;private final String url;private final DownloadCallback mCallback;private int mStatus = STATUS_DOWNLOADING;private long mProgress = 0;private DownloadEntity mDownloadEntity;public DownloadRunnable(String url, int threadId, long start, long end, long progress, DownloadEntity downloadEntity,DownloadCallback callback) {this.threadId = threadId;this.url = url;this.start = start + progress;// 1M-2M 0.5M  1.5M - 2Mthis.end = end;mCallback = callback;this.mProgress = progress;this.mDownloadEntity = downloadEntity;}@Overridepublic void run() {// 只读写我自己的内容,RangeRandomAccessFile accessFile = null;InputStream inputStream = null;try {Response response = OkHttpManager.getManager().syncResponse(url,start,end);Log.e("TAG",this.toString());inputStream = response.body().byteStream();// 写数据File file = FileManager.manager().getFile(url);// 从这里开始accessFile.seek(start);int len = 0;byte[] buffer = new byte[1024*10];while ((len = inputStream.read(buffer))!=-1){if(mStatus == STATUS_STOP)break;// 保存进度,做断点 , 100kbmProgress += len;accessFile.write(buffer,0,len);}mCallback.onSucceed(file);} catch (IOException e) {mCallback.onFailure(e);}finally {Utils.close(inputStream);Utils.close(accessFile);// 存到数据库,数据库怎么存?mDownloadEntity.setProgress(mProgress);DaoManagerHelper.getManager().addEntity(mDownloadEntity);}}@Overridepublic String toString() {return "DownloadRunnable{" +"start=" + start +", end=" + end +", threadId=" + threadId +", url='" + url + '\'' +'}';}public void stop() {mStatus = STATUS_STOP;}

参考文献

  1. hongyang
  2. okhttp的使用教程

okhttp原理分析(持续更新),包含okio了解,拦截器以及断点下载的使用相关推荐

  1. Web安全—脚本木马工作原理(持续更新)

    Web安全-脚本木马工作原理(持续更新) Webshell分类: 一句话木马 大马 小马 打包马 脱裤马 拿到网站Webshell意义(Webshell的权限取决于Web容器运行的权限,通常为网络服务 ...

  2. 持续更新!最新FCPX插件模板合集下载,Final Cut Pro X插件大全 效果/转场/字幕/发生器!

    使用FCPX软件的朋友们,是否还在为到处找插件烦恼呢?或者想要的效果不知道是用的什么插件制作的,为此我整理了一些FCPX软件专用的插件,做个列表让大家了解下! 一,先认识下FCPX软件的界面,这样才能 ...

  3. OkHttp原理分析总结

    OkHttp原理解析 OkHttp 3.10.0版本,最新OkHttp为:4.0.1逻辑与3版本并没有太大变化,但是改为kotlin实现. OkHttp介绍 OkHttp是当下Android使用最频繁 ...

  4. OKHttp原理分析

    最近生活上出了一些问题,对自己的人生也思考了很多,做安卓开发的程序员确实需要许多努力,可能你稍微有些懈怠就会被别人超过,甚至被行业淘汰,所以大家一定要多努力,但是同时也要注意自己的身体,身体出问题了其 ...

  5. Mybatis源码分析之(六)mybatis拦截器(Interceptor)的实现原理

    文章目录 前言 InterceptorChain保存所有的Interceptor 创建四大对象都走Configuration InterceptorChain增强对象方法 Plugin封装动态代理,让 ...

  6. 持续更新!福昕阅读器那些超好用的隐藏功能

    「福昕阅读器 专业版」是新功能首发版本,不仅可以阅读批注PDF文档,还支持划词翻译.PDF云文档等,但还有一些超级好用的功能,很多老用户都不知道哦~ 今天,我们就来跟大家揭秘这些好用的隐藏功能. 下载 ...

  7. OkHttp 3.x 源码解析之Interceptor 拦截器

    拦截器 Java里的拦截器是动态拦截Action调用的对象.它提供了一种机制可以使开发者可以定义在一个action执行的前后执行的代码,也可以在一个action执行前阻止其执行,同时也提供了一种可以提 ...

  8. OKHttp 可能你从来没用过这样的拦截器

    码个蛋(codeegg) 第 835 次推文 作者:北斗星_And 博客:https://juejin.im/post/5ddddd2a6fb9a07161483fb2 码妞看世界 世界比树枝错综复杂 ...

  9. Android OkHttp实现HttpDns的最佳实践(非拦截器)

    之前写过一篇文章 Android 使用OkHttp支持HttpDNS,该文章中使用的是OkHttp的拦截器来实现HttpDNS.在请求发出去之前,将URL中的域名替换成ip,再往Header中添加Ho ...

最新文章

  1. 北斗时钟在国内各行业的应用前景
  2. NET Framework 2.0中的数据访问新特性
  3. mysql as 后面字段_mysql 字段as详解及实例代码
  4. oracle 体系结构认识,Oracle数据库体系结构简单认识一
  5. VHDL读写txt文件
  6. PHP使用swoole来实现实时异步任务队列
  7. jQueryMobile引入文件后样式无法正常显示
  8. 如何使用ABAP code inspector找出所有在LOOP里访问database的操作
  9. 【渝粤题库】陕西师范大学500006 算法语言 作业
  10. pandas处理excel文件和csv文件
  11. BMVC 2020 各奖项公布!最佳论文可能就是你要的涨点神器
  12. echarts曲线太多卡顿怎么优化_光刻胶旋涂曲线如何获得?
  13. 开奖|八大福利,康康你中奖了没?
  14. javaee字符缓冲输出流
  15. 如何恢复删除的文件?4种常用方法教你恢复被删除的文件
  16. 列表解析式与生成器表达式
  17. 如何通过微信与手机连接到服务器,本地服务器和微信服务器的具体通信过程是怎样的...
  18. 经典车间生产调度问题模型及其算法 目录
  19. MATLAB希尔伯特黄变换HHT
  20. 10个月接私单赚了60多万,加工资年入百万,同学:你在做灰产吗?

热门文章

  1. 微信转发或分享朋友圈带缩略图、描述的实现方法
  2. 信号满格网速却很卡顿?别再被骗了,三个方法让你手机流畅一倍
  3. 阿里巴巴马云关于创新
  4. 计算机毕业设计asp.net酒店客房管理系统VS开发sqlserver数据库web结构c#编程计算机网页源码项目
  5. Go exec 包执行命令超时失效问题分析及解决方案
  6. 计算机ppt要求,计算机信息化培训总要求.ppt
  7. 模型参数量(Params)/模型大小 Pytorch统计模型参数量
  8. virsh查询虚拟机列表
  9. Django+vue 分页展示
  10. 2021-2027全球与中国工业RFID特种电子标签市场现状及未来发展趋势