Android進階之光——網(wǎng)絡(luò)編程

網(wǎng)絡(luò)編程

網(wǎng)絡(luò)分層

網(wǎng)絡(luò)分層有不同的模型,有的分為7層,有的分為5層。


5層網(wǎng)絡(luò)分層
  • 物理層 該層負(fù)責(zé)比特流在節(jié)點間的傳輸,即負(fù)責(zé)物理傳輸。通俗來講就是把計算機連接起來的物理手段
  • 數(shù)據(jù)鏈路層 該層控制網(wǎng)絡(luò)層和物理層之間的通信
  • 網(wǎng)絡(luò)層 該層決定如何將數(shù)據(jù)從發(fā)送方路由到接收方
  • 傳輸層 該層為兩臺主機上的應(yīng)用程序提供端到端的通信。傳輸層有兩個傳輸協(xié)議:TCP/UDP
  • 應(yīng)用層 應(yīng)用層收到傳輸層數(shù)據(jù)后,對數(shù)據(jù)進行解讀。解讀必須事先規(guī)定好格式。應(yīng)用層是規(guī)定應(yīng)用程序的數(shù)據(jù)格式的,主要協(xié)議有HTTP、FTP、Telnet、SMTP、POP3等

TCP的三次握手與四次揮手

TCP傳輸

TCP三次握手與四次揮手的過程


三次握手與四次揮手

連接復(fù)用 keepalive connections

連接復(fù)用

HTTP協(xié)議原理

  • HTTP URL的格式
http://host[":"post][abs_path]

HTTP請求報文

請求報文的一般格式

HTTP響應(yīng)報文

響應(yīng)報文

源碼解析OkHttp

OkHttp的請求網(wǎng)絡(luò)流程

  • 當(dāng)我們要請求網(wǎng)絡(luò)的時候需要用OkHttpClient.newCall(request)進行execute或者enqueue操作。當(dāng)調(diào)用newCall方法時,我們從源碼中可以看出調(diào)用了如下代碼
@Override
public Call newCall(Request request) {
return new RealCall(this, request);
}

可以看到實際返回的是一個RealCall類。我們調(diào)用enqueue異步請求網(wǎng)絡(luò)實際上是調(diào)用RealCall的enqueue方法

void enqueue(Callback responseCallback, boolean forWebSocket) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
client.dispatcher().enqueue(new AsyncCall(responseCallback, forWebSocket));
}

從這可以看出最終的請求是dispatcher來完成的,我們來看看dispatcher

  • Dispatcher 任務(wù)調(diào)度
    Dispatcher主要用于控制并發(fā)的請求,它主要維護了以下變量
/** 最大并發(fā)請求數(shù)*/
private int maxRequests = 64;
/** 每個主機的最大請求數(shù)*/
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<>();

來看Dispatcher的構(gòu)造方法

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;
}

從dispathcer的構(gòu)造方法中可以看到,dispathcer中持有一個線程池,這個線程池可以使用自己設(shè)定的線程池。如果沒有設(shè)定線程池,則會在請求網(wǎng)絡(luò)前自己創(chuàng)建默認(rèn)線程池。這個線程池比較適合執(zhí)行大量的耗時比較少的任務(wù)。來看它的enqueue方法

synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call)
< maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}

當(dāng)正在運行的異步請求隊列中的數(shù)量小于64并且正在運行的請求主機數(shù)小于5時,把請求加載到runningAsyncCalls中并在線程池中執(zhí)行,否則就加入到readyAsyncCalls中進行緩存等待。
來看AsyncCall的execute方法

@Override
protected void execute() {
boolean signalledCallback = false;
try {
Response response = getResponseWithInterceptorChain(forWebSocket);//1
if (canceled) {
signalledCallback = true;
responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
} else {
signalledCallback = true;
responseCallback.onResponse(RealCall.this, response);
}
} catch (IOException e) {
if (signalledCallback) {
logger.log(Level.INFO, "Callback failure for " + toLoggableString(), e);
} else {
responseCallback.onFailure(RealCall.this, e);
}
} finally {
client.dispatcher().finished(this);
}
}

我們可以看到 getResponseWithInterceptorChain方法返回了Response,這說明正在請求網(wǎng)絡(luò)

  • Interceptor攔截器
    接下來我們再看看getResponseWithInterceptorChain方法
private Response getResponseWithInterceptorChain(boolean forWebSocket)
throws IOException {
Interceptor.Chain chain = new ApplicationInterceptorChain(0, originalRequest,
forWebSocket);
return chain.proceed(originalRequest);
}

在這個方法中創(chuàng)建了ApplicationInterceptorChain。這是一個攔截器鏈,這個類也是RealCall的內(nèi)部類,接下來執(zhí)行了它的proceed方法

public Response proceed(Request request) throws IOException {
if (index < client.interceptors().size()) {
Interceptor.Chain chain = new ApplicationInterceptorChain(index + 1,
request, forWebSocket);
//從攔截器列表中取出攔截器
Interceptor interceptor = client.interceptors().get(index);
Response interceptedResponse = interceptor.intercept(chain);//1
if (interceptedResponse == null) {
throw new NullPointerException("application interceptor " + interceptor
+ " returned null");
}
return interceptedResponse;
}
return getResponse(request, forWebSocket);
}

可以看到proceed方法每次從攔截器列表中取出攔截器。當(dāng)存在多個攔截器時都會//1處阻塞,并等待下一個攔截器的調(diào)用返回。


攔截器的使用場景

攔截器時一種能夠監(jiān)控、重寫、重試調(diào)用的機制。攔截器用來添加、移除、轉(zhuǎn)換請求和響應(yīng)的頭部信息。我們可以看到return getResponse(request,forWebSocket)如果沒有更多攔截器的話,就會執(zhí)行網(wǎng)絡(luò)請求。

我們看下getResponse方法

Response getResponse(Request request, boolean forWebSocket) throws
IOException {
...
engine = new HttpEngine(client, request, false, false, forWebSocket,
null, null, null);
int followUpCount = 0;
while (true) {
if (canceled) {
engine.releaseStreamAllocation();
throw new IOException("Canceled");
}
boolean releaseConnection = true;
try {
engine.sendRequest();
engine.readResponse();
releaseConnection = false;
} catch (RequestException e) {
throw e.getCause();
} catch (RouteException e) {
...
}
}

在獲取網(wǎng)絡(luò)響應(yīng)的方法中 我們可以看到創(chuàng)建了HttpEngine類,并調(diào)用了HttpEngine的sendRequest方法和readResponse方法

  • 緩存策略
    我們先來看HttpEngine的sendRequest方法
public void sendRequest() throws RequestException, RouteException, IOException {
if (cacheStrategy != null) return; // Already sent.
if (httpStream != null) throw new IllegalStateException();
Request request = networkRequest(userRequest);
//獲取 client 中的 Cache, 同時 Cache 在初始化時會讀取緩存目錄中曾經(jīng)請求過的所有信息
InternalCache responseCache = Internal.instance.internalCache(client);
Response cacheCandidate = responseCache != null
? responseCache.get(request): null;//1
long now = System.currentTimeMillis();
cacheStrategy = new CacheStrategy.Factory(now, request, cacheCandidate).
get();
//網(wǎng)絡(luò)請求
networkRequest = cacheStrategy.networkRequest;
//緩存的響應(yīng)
cacheResponse = cacheStrategy.cacheResponse;
if (responseCache != null) {
//記錄當(dāng)前請求是網(wǎng)絡(luò)發(fā)起還是緩存發(fā)起
responseCache.trackResponse(cacheStrategy);
}
if (cacheCandidate != null && cacheResponse == null) {
closeQuietly(cacheCandidate.body());
}
//不進行網(wǎng)絡(luò)請求并且緩存不存在或者過期, 則返回 504 錯誤
if (networkRequest == null && cacheResponse == null) {
userResponse = new Response.Builder()
.request(userRequest)
.priorResponse(stripBody(priorResponse))
.protocol(Protocol.HTTP_1_1)
.code(504)
.message("Unsatisfiable Request (only-if-cached)")
.body(EMPTY_BODY)
.build();
return;
}
// 不進行網(wǎng)絡(luò)請求而且緩存可以使用, 則直接返回緩存
if (networkRequest == null) {
userResponse = cacheResponse.newBuilder()
.request(userRequest)
.priorResponse(stripBody(priorResponse))
.cacheResponse(stripBody(cacheResponse))
.build();
userResponse = unzip(userResponse);
return;
}
//需要訪問網(wǎng)絡(luò)時
boolean success = false;
try {
httpStream = connect();
httpStream.setHttpEngine(this);
...
}
}

這里可以看出緩存是基于Map的 key就是請求中url的md5,value是在文件中查詢到的緩存,頁面置換基于LRU算法。

接著來看HttpEngine的readResponse方法

public void readResponse() throws IOException {
...
else{
//讀取網(wǎng)絡(luò)響應(yīng)
networkResponse = readNetworkResponse();
}
receiveHeaders(networkResponse.headers());
if (cacheResponse != null) {
//檢查緩存是否可用。 如果可用, 就用當(dāng)前緩存的 Response,關(guān)閉網(wǎng)絡(luò)連接,釋放連接
if (validate(cacheResponse, networkResponse)) {//1
userResponse = cacheResponse.newBuilder()
.request(userRequest)
.priorResponse(stripBody(priorResponse))
.headers(combine(cacheResponse.headers(), networkResponse.
headers()))
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
networkResponse.body().close();
releaseStreamAllocation();
InternalCache responseCache = Internal.instance.internalCache(client);
responseCache.trackConditionalCacheHit();
responseCache.update(cacheResponse, stripBody(userResponse));
userResponse = unzip(userResponse);
return;
} else {
closeQuietly(cacheResponse.body());
}
}
userResponse = networkResponse.newBuilder()
.request(userRequest)
.priorResponse(stripBody(priorResponse))
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
if (hasBody(userResponse)) {
maybeCache();
userResponse = unzip(cacheWritingResponse(storeRequest, userResponse));
}
}

這個方法主要用來解析HTTP響應(yīng)報頭,如果有緩存可用,則用緩存的數(shù)據(jù)并更新緩存,否則就用網(wǎng)絡(luò)請求返回的數(shù)據(jù)


OkHttp的請求流程圖

解析Retrofit

Retrofit是Square公司開發(fā)的一款針對Android網(wǎng)絡(luò)請求的框架。Retrofit的底層是基于OkHttp實現(xiàn)的,它更多使用運行時注解的方式提供功能
我們在使用Retrofit請求網(wǎng)絡(luò)時,首先需要寫請求接口

public interface IpService {
@GET("getIpInfo.php?ip=59.108.54.37")
Call<IpModel> getIpMsg();

接著我們創(chuàng)建Retrofit

Retrofit retrofit = new Retrofit.Builder()
.baseUrl(url)
.addConverterFactory(GsonConverterFactory.create())
.build();

可以看出來Retrofit是通過建造者模式構(gòu)建出來的,我們看一下Builder方法做了什么

public Builder() {
this(Platform.get());
}
private static final Platform PLATFORM = findPlatform();
static Platform get() {
return PLATFORM;
}
private static Platform findPlatform() {
try {
Class.forName("android.os.Build");
if (Build.VERSION.SDK_INT != 0) {
return new Android();
}
} catch (ClassNotFoundException ignored) {
}
try {
Class.forName("java.util.Optional");
return new Java8();
} catch (ClassNotFoundException ignored) {
}
try {
Class.forName("org.robovm.apple.foundation.NSObject");
return new IOS();
} catch (ClassNotFoundException ignored) {
}
return new Platform();
}

可以看到Platform的get方法最終調(diào)用的是findPlatform方法,會根據(jù)不同的運行平臺來提供不同的線程池,接下來看build方法

public Retrofit build() {
if (baseUrl == null) {//1
throw new IllegalStateException("Base URL required.");
}
okhttp3.Call.Factory callFactory = this.callFactory;//2
if (callFactory == null) {
callFactory = new OkHttpClient();//3
}
Executor callbackExecutor = this.callbackExecutor;
if (callbackExecutor == null) {
callbackExecutor = platform.defaultCallbackExecutor();//4
}
List<CallAdapter.Factory> adapterFactories = new ArrayList<>(this.
adapterFactories);//5
adapterFactories.add(platform.defaultCallAdapterFactory
(callbackExecutor));
List<Converter.Factory> converterFactories = new ArrayList<>(this.
converterFactories);//6
return new Retrofit(callFactory, baseUrl, converterFactories, adapterFactories,
callbackExecutor, validateEagerly);
}

這里1處可看出baseUrl是必須指定的

Call的創(chuàng)建過程

我們創(chuàng)建Retrofit實例并調(diào)用如下代碼來生成接口的動態(tài)代理對象

IpService ipService = retrofit.create(IpService.class);

我們看下Retrofit的create方法

public <T> T create(final Class<T> service) {
Utils.validateServiceInterface(service);
if (validateEagerly) {
eagerlyValidateMethods(service);
}
return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[]
{ service },
new InvocationHandler() {
private final Platform platform = Platform.get();
@Override
public Object invoke(Object proxy, Method method, Object... args)
throws Throwable {
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args);
}
if (platform.isDefaultMethod(method)) {
return platform.invokeDefaultMethod(method, service, proxy, args);
}
ServiceMethod serviceMethod = loadServiceMethod(method);//1
OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
return serviceMethod.callAdapter.adapt(okHttpCall);
}
});
}

我們可以看到create方法返回了一個動態(tài)代理對象。當(dāng)我們調(diào)用IpService的getIpMsg方法時,最終會調(diào)用InvocationHandler的invoke方法

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容