Slide 1

Slide 1 text

STEP BY STEP↑↑ - Okio & OkHttp Shun Sato@stsn_jp Shohei Kawano@shaunkawano

Slide 2

Slide 2 text

本日話すこと(ニッチかも) ● Okio, OkHttpの内部実装周り ○ Okio ■ 背景 ■ バッファリング周り ■ タイムアウト周り ○ OkHttp ■ Interceptor周り ■ Cache周り

Slide 3

Slide 3 text

Okio

Slide 4

Slide 4 text

JavaでのI/O処理つらい (例) InputStream

Slide 5

Slide 5 text

private static final int MAX_SKIP_BUFFER_SIZE = 2048; public abstract int read() throws IOException public int read(byte b[]) throws IOException public int read(byte b[], int off, int len) throws IOException public long skip(long n) throws IOException public int available() throws IOException public void close() throws IOException public synchronized void mark(int readlimit) public synchronized void reset() throws IOException public boolean markSupported() InputStream.java

Slide 6

Slide 6 text

APIの使い勝手 ■ 用意されているAPIの中に、バッファ処理に関するものまで入っている (本来、 BufferedInputStream内に用意されているべき APIなど) ■ 複数のread メソッド。独自拡張する場合には両方 overrideして処理を書く必要がある ■ byte[]を渡す、どれくらい読み込んで、末尾までどれくらいか、自前で whileで都度チェック パフォーマンスへの意識 ■ 渡したbyte[]が読み込んでいるデータ量より小さい場合は別途 byte[]を作成 ■ 必要に応じてbyte[]のコピー処理 ■ 不要になったbyte[]に対してGCが走ってしまう? InputStream

Slide 7

Slide 7 text

Okio

Slide 8

Slide 8 text

public interface Source extends Closeable { long read(Buffer sink, long byteCount) throws IOException; Timeout timeout(); @Override void close() throws IOException; } Source.java(InputStreamの補完)

Slide 9

Slide 9 text

Okio 概要 Okio.sink() Okio.source() といったstaticメソッドがある: ● Source: InputStreamの補完 - Source of Bytes 読み込み先を指定 ● Sink: OutputStreamの補完 - Sink for Bytes 書き込み先を指定 一般的なアプリで利用する場合にはBufferを利用する: ● Okio.buffer(Source source) => return BufferedSource ● Okio.buffer(Sink sink) => return BufferedSink

Slide 10

Slide 10 text

Okio 概要 Okio.sink() Okio.source() といったstaticメソッドがある: ● Source: InputStreamの補完 - Source of Bytes 読み込み先を指定 ● Sink: OutputStreamの補完 - Sink for Bytes 書き込み先を指定 一般的なアプリで利用する場合にはBufferを利用する: ● Okio.buffer(Source source) => return BufferedSource ● Okio.buffer(Sink sink) => return BufferedSink

Slide 11

Slide 11 text

public interface Source extends Closeable { long read(Buffer sink, long byteCount) throws IOException; Timeout timeout(); @Override void close() throws IOException; } Source.java

Slide 12

Slide 12 text

Okio: Buffer & Segment

Slide 13

Slide 13 text

Buffer ● メモリ上のバイトの集まり(=バッファ)を表すクラス ● BufferedSource, BufferedSink両方を実装(データの読み書きを行なう) ● 読み書きしたデータをSegmentクラス(Bufferの一部)のbyte[] data内に保存 Segment ● バッファの断片を表すクラス ● byte[] dataを保持している(MAX: 8KB) ● 自分の前(prev)のSegment, 次(next)のSegmentを知っている ● SegmentPoolによって生成・最大限プールされる

Slide 14

Slide 14 text

Okio: SegmentPool

Slide 15

Slide 15 text

SegmentPool ● Segmentの生成と、使われていないSegmentをプールするクラス ● MAX 64KBまでのSegmentをプールする ● プールにSegmentがない時と、書き込み先のSegmentに必要なデータ容量がない 場合にSegmentを新しく生成する ● (他にもタイミングはありますが、大きくはこの流れ)

Slide 16

Slide 16 text

No content

Slide 17

Slide 17 text

DroidKaigiRejectConferenceDroidKaigiRejectConferenceDr oidKaigiRejectConferenceDroidKaigiRejectConferenceDroi dKaigiRejectConferenceDroidKaigiRejectConferenceDroidK aigiRejectConferenceDroidKaigiRejectConferenceDroidKai giRejectConferenceDroidKaigiRejectConferenceDroidKaigi RejectConferenceDroidKaigiRejectConferenceDroidKaigiRe jectconConferenceDroidKaigiRejectConferenceDroidKaigiR ectconConferenceDroidKaigiRejectConferenceDroidKaigi…..

Slide 18

Slide 18 text

)); Okio.buffer(Okio.Source(

Slide 19

Slide 19 text

Buffer RealBufferedSource Source

Slide 20

Slide 20 text

BufferedSource#readUtf8(); ↓ DroidKaigiRejectConferenceDroidKaigi RejectConferenceDroidKaigiRejectConf erenceDroidKaigiRejectConferenceDroi dKaigiRejectConferenceDroidKaigiR….

Slide 21

Slide 21 text

BufferedSource#readUtf8();

Slide 22

Slide 22 text

BufferedSource#readUtf8(); SegmentPool

Slide 23

Slide 23 text

SegmentPool.take(); BufferedSource#readUtf8();

Slide 24

Slide 24 text

BufferedSource#readUtf8(); Segment

Slide 25

Slide 25 text

BufferedSource#readUtf8();

Slide 26

Slide 26 text

BufferedSource#readUtf8();

Slide 27

Slide 27 text

“DroidKaigiRejectConferenceDrodKai…. BufferedSource#readUtf8();

Slide 28

Slide 28 text

BufferedSource#readUtf8();

Slide 29

Slide 29 text

BufferedSource#readUtf8();

Slide 30

Slide 30 text

BufferedSource#readUtf8();

Slide 31

Slide 31 text

BufferedSource#readUtf8();

Slide 32

Slide 32 text

“DroidKaigiRejectConferenceDrodKai…. BufferedSource#readUtf8();

Slide 33

Slide 33 text

“DroidKaigiRejectConferenceDrodKai…. BufferedSource#readUtf8();

Slide 34

Slide 34 text

“DroidKaigiRejectConferenceDrodKai…. BufferedSource#readUtf8();

Slide 35

Slide 35 text

BufferedSource#readUtf8();

Slide 36

Slide 36 text

BufferedSource#readUtf8();

Slide 37

Slide 37 text

Buffer#readUtf8(); BufferedSource#readUtf8();

Slide 38

Slide 38 text

BufferedSource#readUtf8(); “DroidKaigiRejectConferenceDrodKai….

Slide 39

Slide 39 text

BufferedSource#readUtf8(); “DroidKaigiRejectConferenceDrodKai….

Slide 40

Slide 40 text

BufferedSource#readUtf8(); “DroidKaigiRejectConferenceDrodKai…. SegmentPool.recycle(segment);

Slide 41

Slide 41 text

BufferedSource#readUtf8(); “DroidKaigiRejectConferenceDrodKai….

Slide 42

Slide 42 text

BufferedSource#readUtf8(); “DroidKaigiRejectConferenceDrodKai…. ...

Slide 43

Slide 43 text

BufferedSource#readUtf8(); “DroidKaigiRejectConferenceDrodKai…. … ...

Slide 44

Slide 44 text

BufferedSource#readUtf8(); “DroidKaigiRejectConferenceDrodKai…. … … ...

Slide 45

Slide 45 text

BufferedSource#readUtf8(); “DroidKaigiRejectConferenceDrodKai…. … … … ...

Slide 46

Slide 46 text

BufferedSource#readUtf8(); “DroidKaigiRejectConferenceDrodKai…. … … … … ...

Slide 47

Slide 47 text

BufferedSource#readUtf8(); “DroidKaigiRejectConferenceDrodKai…. … … … … ...

Slide 48

Slide 48 text

DroidKaigiRejectConferenceDroidKaigiRejectConferenceDr oidKaigiRejectConferenceDroidKaigiRejectConferenceDroi dKaigiRejectConferenceDroidKaigiRejectConferenceDroidK aigiRejectConferenceDroidKaigiRejectConferenceDroidKai giRejectConferenceDroidKaigiRejectConferenceDroidKaigi RejectConferenceDroidKaigiRejectConferenceDroidKaigiRe jectconConferenceDroidKaigiRejectConferenceDroidKaigiR ectconConferenceDroidKaigiRejectConferenceDroidKaigi…..

Slide 49

Slide 49 text

No content

Slide 50

Slide 50 text

No content

Slide 51

Slide 51 text

No content

Slide 52

Slide 52 text

SegmentPool.take();

Slide 53

Slide 53 text

No content

Slide 54

Slide 54 text

✨Segment from the pool is reused.✨

Slide 55

Slide 55 text

Okio: Inside OkHttp

Slide 56

Slide 56 text

Okio inside OkHttp ● OkHttpはネットワーク通信のためのライブラリ ● 受け取ったレスポンスをSourceとして取り込み処理を行なう ● タイムアウト等の処理ももちろん行なう Sourceからデータを読み取る・Sinkにデータを書き込む時、タイムアウトを実現したい。 タイムアウトしたら、SourceやSinkをcloseする処理

Slide 57

Slide 57 text

Okio: Timeout

Slide 58

Slide 58 text

public static Source source(Socket socket) throws IOException { if (socket == null) throw new IllegalArgumentException("socket == null"); AsyncTimeout timeout = timeout(socket); Source source = source(socket.getInputStream(), timeout); return timeout.source(source); } Okio.source(Socket socket)

Slide 59

Slide 59 text

Set Timeout to OkHttpClient

Slide 60

Slide 60 text

Example: How Timeout is set? - OkHttpClient.Builder @Provides fun provideOkHttpClientBuilder(cache: Cache): OkHttpClient.Builder = OkHttpClient.Builder().cache(cache) .connectTimeout(10L, TimeUnit.SECONDS) .writeTimeout(10L, TimeUnit.SECONDS) .readTimeout(30L, TimeUnit.SECONDS)

Slide 61

Slide 61 text

Create Raw Call

Slide 62

Slide 62 text

Example: How Timeout is set? - OkHttpCall private okhttp3.Call createRawCall() throws IOException { Request request = serviceMethod.toRequest(args); okhttp3.Call call = serviceMethod.callFactory.newCall(request); if (call == null) { throw new NullPointerException("Call.Factory returned null."); } return call; }

Slide 63

Slide 63 text

Example: How Timeout is set? - OkHttpCall private okhttp3.Call createRawCall() throws IOException { Request request = serviceMethod.toRequest(args); okhttp3.Call call = serviceMethod.callFactory.newCall(request); if (call == null) { throw new NullPointerException("Call.Factory returned null."); } return call; }

Slide 64

Slide 64 text

Example: How Timeout is set? - OkHttpClient @Override public Call newCall(Request request) { return RealCall.newRealCall(this, request, false /* for web socket */); }

Slide 65

Slide 65 text

Example: How Timeout is set? - OkHttpClient @Override public Call newCall(Request request) { return RealCall.newRealCall(this, request, false /* for web socket */); } static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) { // Safely publish the Call instance to the EventListener. RealCall call = new RealCall(client, originalRequest, forWebSocket); call.eventListener = client.eventListenerFactory().create(call); return call; }

Slide 66

Slide 66 text

Example: How Timeout is set? - OkHttpClient @Override public Call newCall(Request request) { return RealCall.newRealCall(this, request, false /* for web socket */); } static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) { // Safely publish the Call instance to the EventListener. RealCall call = new RealCall(client, originalRequest, forWebSocket); call.eventListener = client.eventListenerFactory().create(call); return call; }

Slide 67

Slide 67 text

Example: How Timeout is set? - RealCall -> Chain final class RealCall implements Call { final OkHttpClient client; … Response getResponseWithInterceptorChain() throws IOException { Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0, originalRequest, this, eventListener, client.connectTimeoutMillis(), client.readTimeoutMillis(), client.writeTimeoutMillis()); return chain.proceed(originalRequest); } }

Slide 68

Slide 68 text

Example: How Timeout is set? - RealConnection public HttpCodec newCodec(OkHttpClient client, Interceptor.Chain chain, StreamAllocation streamAllocation) throws SocketException { if (http2Connection != null) { return new Http2Codec(client, chain, streamAllocation, http2Connection); } else { socket.setSoTimeout(chain.readTimeoutMillis()); source.timeout().timeout(chain.readTimeoutMillis(), MILLISECONDS); sink.timeout().timeout(chain.writeTimeoutMillis(), MILLISECONDS); return new Http1Codec(client, streamAllocation, source, sink); } }

Slide 69

Slide 69 text

Example: How Timeout is set? - RealConnection public HttpCodec newCodec(OkHttpClient client, Interceptor.Chain chain, StreamAllocation streamAllocation) throws SocketException { if (http2Connection != null) { return new Http2Codec(client, chain, streamAllocation, http2Connection); } else { socket.setSoTimeout(chain.readTimeoutMillis()); source.timeout().timeout(chain.readTimeoutMillis(), MILLISECONDS); sink.timeout().timeout(chain.writeTimeoutMillis(), MILLISECONDS); return new Http1Codec(client, streamAllocation, source, sink); } }

Slide 70

Slide 70 text

Example: How Timeout is set? - RealConnection private void connectSocket(int connectTimeout, int readTimeout, Call call, EventListener eventListener) throws IOException { … try { source = Okio.buffer(Okio.source(rawSocket)); sink = Okio.buffer(Okio.sink(rawSocket)); } catch (NullPointerException npe) { if (NPE_THROW_WITH_NULL.equals(npe.getMessage())) { throw new IOException(npe); } }

Slide 71

Slide 71 text

public static Source source(Socket socket) throws IOException { if (socket == null) throw new IllegalArgumentException("socket == null"); AsyncTimeout timeout = timeout(socket); Source source = source(socket.getInputStream(), timeout); return timeout.source(source); } Okio.source(Socket socket)

Slide 72

Slide 72 text

public static Source source(Socket socket) throws IOException { if (socket == null) throw new IllegalArgumentException("socket == null"); AsyncTimeout timeout = timeout(socket); Source source = source(socket.getInputStream(), timeout); return timeout.source(source); } Okio.source(Socket socket)

Slide 73

Slide 73 text

AsyncTimeout

Slide 74

Slide 74 text

● バックグランドスレッドを利用してタイムアウト検知・必要に応じた処理を行なうため のクラス ● enter()でタイムアウトしうる処理を開始、exit()で処理の終了(中断もありえる) → exit がtrueを返す=タイムアウト ● WatchdogというThreadを継承したstaticクラスによってタイムアウトが発生したかど うかを判定・監視している AsyncTimeout

Slide 75

Slide 75 text

public static Source source(Socket socket) throws IOException { if (socket == null) throw new IllegalArgumentException("socket == null"); AsyncTimeout timeout = timeout(socket); Source source = source(socket.getInputStream(), timeout); return timeout.source(source); } Okio.source(Socket socket)

Slide 76

Slide 76 text

public final Source source(final Source source) { return new Source() { @Override public long read(Buffer sink, long byteCount) throws IOException { boolean throwOnTimeout = false; enter(); try { long result = source.read(sink, byteCount); throwOnTimeout = true; return result; } catch (IOException e) { throw exit(e); } finally { exit(throwOnTimeout); } } AsyncTimeout.source

Slide 77

Slide 77 text

public final Source source(final Source source) { return new Source() { @Override public long read(Buffer sink, long byteCount) throws IOException { boolean throwOnTimeout = false; enter(); try { long result = source.read(sink, byteCount); throwOnTimeout = true; return result; } catch (IOException e) { throw exit(e); } finally { exit(throwOnTimeout); } } AsyncTimeout.source

Slide 78

Slide 78 text

public final Source source(final Source source) { return new Source() { @Override public long read(Buffer sink, long byteCount) throws IOException { boolean throwOnTimeout = false; enter(); try { long result = source.read(sink, byteCount); throwOnTimeout = true; return result; } catch (IOException e) { throw exit(e); } finally { exit(throwOnTimeout); } } AsyncTimeout.source

Slide 79

Slide 79 text

AsyncTimeout

Slide 80

Slide 80 text

static @Nullable AsyncTimeout head;

Slide 81

Slide 81 text

static @Nullable AsyncTimeout head; private @Nullable AsyncTimeout next;

Slide 82

Slide 82 text

static @Nullable AsyncTimeout head; private @Nullable AsyncTimeout next;

Slide 83

Slide 83 text

No content

Slide 84

Slide 84 text

enter()

Slide 85

Slide 85 text

WatchDog Thread

Slide 86

Slide 86 text

WatchDog Thread

Slide 87

Slide 87 text

WatchDog Thread

Slide 88

Slide 88 text

WatchDog Thread exit();

Slide 89

Slide 89 text

WatchDog Thread exit();

Slide 90

Slide 90 text

WatchDog Thread exit();

Slide 91

Slide 91 text

WatchDog Thread ✅ No more task for timeout!

Slide 92

Slide 92 text

WatchDog Thread wait() for timeout time

Slide 93

Slide 93 text

WatchDog Thread ❌ Nothing changes! Then Timeout!

Slide 94

Slide 94 text

OkHttp

Slide 95

Slide 95 text

OkHttp 概要 http://square.github.io/okhttp/ 効率的にHTTPなどのネットワーク通信をする - Connection Poolを使い、リクエストのレイテンシを減らす - GZIPなどの圧縮を透過的に行うことが出来る - Responseをキャッシュし、繰り返しのRequest時にネットワーク通信を避けることが 出来る InterceptorとCacheに絞って説明します。

Slide 96

Slide 96 text

Interceptorの概要/仕組み

Slide 97

Slide 97 text

RequestからResponseを取得する時 に、間に処理を差し込むための機能 例 Loggingをする User-Agentヘッダーを付ける Interceptor?

Slide 98

Slide 98 text

Interceptorがどのように実装されて いるか?

Slide 99

Slide 99 text

public interface Interceptor { Response intercept(Chain chain) throws IOException; } interface Chain { Response proceed(Request request) throws IOException; ... }

Slide 100

Slide 100 text

Chain? Interceptor間のつなぎ込みをするクラス chain0(interceptor0, chain1) chain1(interceptor1, chain2) chain2… … chain2… chain1(interceptor1, chain2) chain0(interceptor0, chain1) 各chainは次へのchainを持っているので、chainが連鎖していく

Slide 101

Slide 101 text

Chain? chain0(interceptor0, chain1) chain1(interceptor1, chain2) chain2… … chain2… chain1(interceptor1, chain2) chain0(interceptor0, chain1)

Slide 102

Slide 102 text

/** Chain.proceedメソッド内のコード */ // Call the next interceptor in the chain. RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec, connection, index + 1, request, call, eventListener, connectTimeout, readTimeout, writeTimeout); Interceptor interceptor = interceptors.get(index); Response response = interceptor.intercept(next);

Slide 103

Slide 103 text

/** 一般的なInterceptor.interceptメソッド内のコード */ @Override public Response intercept(Chain next) { ... Response response = next.proceed(request); ... return response; }

Slide 104

Slide 104 text

Cacheをどのように行うか

Slide 105

Slide 105 text

以前までのResponseを保存し、同じURLのときにshort-cutとして使用する CacheInterceptorクラスが存在し、このInterceptorでCache Responseを早期に返す か、それともNetworkリクエストをするかを決定する CacheInterceptorの概要 - 取得したResponseをファイルに書き込む - Requestに対応したCache Responseの読み込み Cache?

Slide 106

Slide 106 text

Responseをファイルに書き込む デフォルトではファイルにLRUアルゴリズムで書き込む(DiskLruCacheクラス) - Least Recently Used: 更新が古いものから削除(evict)する 同期的にファイルに書き込んでいては時間が掛かるので適切なタイミングでファイルに 書き込むことでパフォーマンスを向上させている ただ、タイミングによってはファイルへの書き込みが終わっていないので状態を保持する 必要がある - DIRTY: まだ完全に書き込みが終わっていない状態 - CLEAN: header, bodyが全て書き終わった状態 この状態をJournalファイルで管理している

Slide 107

Slide 107 text

適切なタイミング? ResponseBodyの段階ではまだSource(InputStream)なので読み込みが完了していな い。byte[]などに変換されていない状態 これを同期的に読みこんでしまうと処理に時間がかかってしまう。上手いことやりたい 具体的にいうとユーザがResponseBodyが必要になったと同時にファイルへの書き込 みを行いたい

Slide 108

Slide 108 text

Journalファイル? Responseの書き込みが完全に完了しているのか、キャッシュが削除されたなどを管理 するファイル。indexファイルをイメージすると良い 下の場合、f418cc9de6ab23b30744a5771f22154cがCLEANなので、完全に書き込み が完了している状態になる 再起動などでメモリがリフレッシュされても、このJournalファイルがあれば、どのcacheを 持っているかを再構築できる DIRTY f418cc9de6ab23b30744a5771f22154c CLEAN f418cc9de6ab23b30744a5771f22154c 5457 2305 READ f418cc9de6ab23b30744a5771f22154c DIRTY f418cc9de6ab23b30744a5771f22154c CLEAN f418cc9de6ab23b30744a5771f22154c 5465 2305

Slide 109

Slide 109 text

DiskLruCacheクラス Journal in memory

Slide 110

Slide 110 text

put レスポンス a put Journal in memory

Slide 111

Slide 111 text

put レスポンス a put Journal DIRTY a in memory DIRTY a

Slide 112

Slide 112 text

put レスポンス a put Journal DIRTY a in memory DIRTY a headers a hoge: fuga

Slide 113

Slide 113 text

put レスポンス a put headers a hoge: fuga body a Journal DIRTY a in memory DIRTY a

Slide 114

Slide 114 text

put レスポンス a オリジナルレ スポンス body a body a put Journal DIRTY a in memory DIRTY a headers a hoge: fuga

Slide 115

Slide 115 text

put レスポンス a put Journal DIRTY a in memory DIRTY a オリジナルレ スポンス body a body a headers a hoge: fuga

Slide 116

Slide 116 text

Source cacheWritingSource = new Source() { boolean cacheRequestClosed; @Override public long read(Buffer sink, long byteCount) { long bytesRead; try { bytesRead = source.read(sink, byteCount); // sourceはResponseBodyのstream } catch (IOException e) { // error handling throw e; } if (bytesRead == -1) { if (!cacheRequestClosed) { cacheRequestClosed = true; cacheBody.close(); } return -1; } sink.copyTo(cacheBody.buffer(), sink.size() - bytesRead, bytesRead); // ここでcacheに結果を読み込んだ結果をコピーする cacheBody.emitCompleteSegments(); return bytesRead; }

Slide 117

Slide 117 text

put レスポンス a put source.read() Journal DIRTY a in memory DIRTY a headers a hoge: fuga body a オリジナルレ スポンス body a

Slide 118

Slide 118 text

put body a Hello world レスポンス a source.read() 結果を返す put ついでに書き 込みも行う Journal DIRTY a in memory DIRTY a headers a hoge: fuga オリジナルレ スポンス body a

Slide 119

Slide 119 text

put body a Hello world レスポンス a オリジナルレ スポンス body a source.read() 結果を返す put Journal DIRTY a CLEAN a in memory CLEAN a 完了通知 headers a hoge: fuga

Slide 120

Slide 120 text

Journal DIRTY a CLEAN a in memory CLEAN a body a Hello world headers a hoge: fuga

Slide 121

Slide 121 text

再起動時

Slide 122

Slide 122 text

Journal DIRTY a CLEAN a ... in memory body a Hello world headers a hoge: fuga

Slide 123

Slide 123 text

Journal DIRTY a CLEAN a ... in memory CLEAN a body a Hello world headers a hoge: fuga ファイルからin memoryに読み込み

Slide 124

Slide 124 text

Journal DIRTY a CLEAN a ... in memory CLEAN a body a Hello world headers a hoge: fuga

Slide 125

Slide 125 text

Candidate Responseの取得 URL、Vary Headerに対応したResponseを先程のCLEANな状態のCacheから取得する Vary Header? - Vary: User-Agent のようにheader-nameを指定する 指定したheader-nameがmatchするかどうかでCacheを取得するかどうかを決定す る

Slide 126

Slide 126 text

Journal DIRTY a CLEAN a in memory CLEAN a body a Hello world headers a hoge: fuga リクエスト a get key (url, vary) Candidate Response

Slide 127

Slide 127 text

CacheStrategyでCandidateの妥当性を確認する 上記で取得したのはあくまでCandidate Response Cache-Controlなどを見て有効性を判定する これらのHeaderをもとに、本当にこのResponse Cacheが妥当かどうかを確認する。合 わせて304(Not Modified)のためのheaderが含まれるかも確認している

Slide 128

Slide 128 text

long now = System.currentTimeMillis(); CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get(); Request networkRequest = strategy.networkRequest; Response cacheResponse = strategy.cacheResponse;

Slide 129

Slide 129 text

CacheControl requestCaching = request.cacheControl(); if (requestCaching.noCache() || hasConditions(request)) { return new CacheStrategy(request, null); } CacheControl responseCaching = cacheResponse.cacheControl(); if (responseCaching.immutable()) { return new CacheStrategy(null, cacheResponse); } long ageMillis = cacheResponseAge(); long freshMillis = computeFreshnessLifetime(); if (requestCaching.maxAgeSeconds() != -1) { freshMillis = Math.min(freshMillis, SECONDS.toMillis(requestCaching.maxAgeSeconds())); } ...

Slide 130

Slide 130 text

String conditionName; String conditionValue; if (etag != null) { conditionName = "If-None-Match"; conditionValue = etag; } else if (lastModified != null) { conditionName = "If-Modified-Since"; conditionValue = lastModifiedString; } else if (servedDate != null) { conditionName = "If-Modified-Since"; conditionValue = servedDateString; } else { return new CacheStrategy(request, null); // No condition! Make a regular request. }

Slide 131

Slide 131 text

Journal DIRTY a CLEAN a in memory CLEAN a body a Hello world headers a hoge: fuga リクエスト get key (url, method, vary) Candidate Response Candidate Responseが妥当かど うかをCacheStrategyから判定

Slide 132

Slide 132 text

Journal DIRTY a CLEAN a in memory CLEAN a body a Hello world headers a hoge: fuga リクエスト get key (url, method, vary) Candidate Response Candidate Responseが妥当かど うかをCacheStrategyから判定 結果から、Network通信をするか、 Cache Responseを返すか決定す る Cacheが有効ならここから Responseを読み込む

Slide 133

Slide 133 text

Thank you!!!!! Happy Okio!! Happy OkHttp!!