最基本的接口只有两个:Sink(writer)、Source(Read),大概相当于OutputStream和InputStream在原生接口中的地位。这两个接口中只定义了一些最基础的IO操作方法
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链表结构)
通过一段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 [email protected]
*/
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 [email protected]
*/
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() {
@Override
public 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;
}
}
|---RealCall
| |--- Response 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);
}
}
}
getResponseWithInterceptorChain()
方法把请求变成了一个响应。conditionName = "If-Modified-Since"
findhealthyConnection()
找一个连接,首先判断有没有健康的,没有就创建(建立Socket,握手连接,)连接缓存。OkHttp基于原生的Socket.RealConnection
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() {
@Override
public 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();
拦截器的调用顺序
自己写一个基于okhttp的缓存,有网络10s内可以读取缓存,没网每次请求读取缓存。
首先来看一段基础的代码
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(){
@Override
public 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() {
@Override
public void onFailure(Call call, IOException e) {
e.printStackTrace();
}
@Override
public void onResponse(Call call, Response response) throws IOException {
Log.e("TAG", response.body().string());
}
});
}
分析流程
|---RealCall
| |---CallServerInterceptor
| |--- MultipartBody
| | |---writeTo(BufferedSink sink)
contentlength
以及currentLength
这两个参数之后,通过编写一个借口回调,就可以拿到值,然后在主线程中进行一些操作就可以完成。|---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) //执行下载的线程
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);
}
}
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 5
public void startDownload(final String url, final DownloadCallback callback){
// 获取文件的大小
Call call = OkHttpManager.getManager().asyncCall(url);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
callback.onFailure(e);
}
@Override
public 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){
// 这个停止的是不是正在下载的
}
// 开个单独的线程去执行 所有下载的回调
}
真正执行断点下载的管理类,为执行断点下载的线程分发任务。这个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
@Nullable
ExecutorService 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() {
@Override
public 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() {
@Override
public void onFailure(IOException e) {
// 一个apk 下载里面有一个线程异常了,处理异常,把其他线程停止掉
mCallback.onFailure(e);
}
@Override
public 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();
}
}
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 - 2M
this.end = end;
mCallback = callback;
this.mProgress = progress;
this.mDownloadEntity = downloadEntity;
}
@Override
public void run() {
// 只读写我自己的内容,Range
RandomAccessFile 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;
// 保存进度,做断点 , 100kb
mProgress += 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);
}
}
@Override
public String toString() {
return "DownloadRunnable{" +
"start=" + start +
", end=" + end +
", threadId=" + threadId +
", url='" + url + '\'' +
'}';
}
public void stop() {
mStatus = STATUS_STOP;
}