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

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

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

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


5層網(wǎng)絡(luò)分層
  • 物理層 該層負(fù)責(zé)比特流在節(jié)點(diǎn)間的傳輸畸写,即負(fù)責(zé)物理傳輸。通俗來講就是把計(jì)算機(jī)連接起來的物理手段
  • 數(shù)據(jù)鏈路層 該層控制網(wǎng)絡(luò)層和物理層之間的通信
  • 網(wǎng)絡(luò)層 該層決定如何將數(shù)據(jù)從發(fā)送方路由到接收方
  • 傳輸層 該層為兩臺(tái)主機(jī)上的應(yīng)用程序提供端到端的通信氓扛。傳輸層有兩個(gè)傳輸協(xié)議:TCP/UDP
  • 應(yīng)用層 應(yīng)用層收到傳輸層數(shù)據(jù)后枯芬,對數(shù)據(jù)進(jìn)行解讀。解讀必須事先規(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請求報(bào)文

請求報(bào)文的一般格式

HTTP響應(yīng)報(bào)文

響應(yīng)報(bào)文

源碼解析OkHttp

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

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

可以看到實(shí)際返回的是一個(gè)RealCall類。我們調(diào)用enqueue異步請求網(wǎng)絡(luò)實(shí)際上是調(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ā)的請求胎挎,它主要維護(hù)了以下變量
/** 最大并發(fā)請求數(shù)*/
private int maxRequests = 64;
/** 每個(gè)主機(jī)的最大請求數(shù)*/
private int maxRequestsPerHost = 5忆家;
/** 消費(fèi)者線程池 */
private ExecutorService executorService犹菇;
/** 將要運(yùn)行的異步請求隊(duì)列 */
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
/**正在運(yùn)行的異步請求隊(duì)列 */
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>()芽卿;
/** 正在運(yùn)行的同步請求隊(duì)列 */
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中持有一個(gè)線程池筷转,這個(gè)線程池可以使用自己設(shè)定的線程池姑原。如果沒有設(shè)定線程池,則會(huì)在請求網(wǎng)絡(luò)前自己創(chuàng)建默認(rèn)線程池呜舒。這個(gè)線程池比較適合執(zhí)行大量的耗時(shí)比較少的任務(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)正在運(yùn)行的異步請求隊(duì)列中的數(shù)量小于64并且正在運(yùn)行的請求主機(jī)數(shù)小于5時(shí),把請求加載到runningAsyncCalls中并在線程池中執(zhí)行到腥,否則就加入到readyAsyncCalls中進(jìn)行緩存等待朵逝。
來看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)索赏;
}

在這個(gè)方法中創(chuàng)建了ApplicationInterceptorChain盼玄。這是一個(gè)攔截器鏈,這個(gè)類也是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)存在多個(gè)攔截器時(shí)都會(huì)//1處阻塞,并等待下一個(gè)攔截器的調(diào)用返回忽你。


攔截器的使用場景

攔截器時(shí)一種能夠監(jiān)控幼东、重寫、重試調(diào)用的機(jī)制科雳。攔截器用來添加根蟹、移除、轉(zhuǎn)換請求和響應(yīng)的頭部信息糟秘。我們可以看到return getResponse(request,forWebSocket)如果沒有更多攔截器的話简逮,就會(huì)執(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, 同時(shí) Cache 在初始化時(shí)會(huì)讀取緩存目錄中曾經(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())贪庙;
}
//不進(jìn)行網(wǎng)絡(luò)請求并且緩存不存在或者過期蜘犁, 則返回 504 錯(cuò)誤
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止邮;
}
// 不進(jìn)行網(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ò)時(shí)
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))澄峰;
}
}

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


OkHttp的請求流程圖

解析Retrofit

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

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方法,會(huì)根據(jù)不同的運(yùn)行平臺(tái)來提供不同的線程池墩剖,接下來看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實(shí)例并調(diào)用如下代碼來生成接口的動(dòng)態(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方法返回了一個(gè)動(dòng)態(tài)代理對象蝇刀。當(dāng)我們調(diào)用IpService的getIpMsg方法時(shí)螟加,最終會(huì)調(diào)用InvocationHandler的invoke方法

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子捆探,更是在濱河造成了極大的恐慌然爆,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,544評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件黍图,死亡現(xiàn)場離奇詭異曾雕,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)助被,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門剖张,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人揩环,你說我怎么就攤上這事搔弄。” “怎么了丰滑?”我有些...
    開封第一講書人閱讀 162,764評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵顾犹,是天一觀的道長。 經(jīng)常有香客問我褒墨,道長蹦渣,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,193評(píng)論 1 292
  • 正文 為了忘掉前任貌亭,我火速辦了婚禮柬唯,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘圃庭。我一直安慰自己锄奢,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評(píng)論 6 388
  • 文/花漫 我一把揭開白布剧腻。 她就那樣靜靜地躺著拘央,像睡著了一般。 火紅的嫁衣襯著肌膚如雪书在。 梳的紋絲不亂的頭發(fā)上灰伟,一...
    開封第一講書人閱讀 51,182評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音儒旬,去河邊找鬼栏账。 笑死,一個(gè)胖子當(dāng)著我的面吹牛栈源,可吹牛的內(nèi)容都是我干的挡爵。 我是一名探鬼主播,決...
    沈念sama閱讀 40,063評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼甚垦,長吁一口氣:“原來是場噩夢啊……” “哼茶鹃!你這毒婦竟也來了涣雕?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,917評(píng)論 0 274
  • 序言:老撾萬榮一對情侶失蹤闭翩,失蹤者是張志新(化名)和其女友劉穎挣郭,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體疗韵,經(jīng)...
    沈念sama閱讀 45,329評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡兑障,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了伶棒。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片旺垒。...
    茶點(diǎn)故事閱讀 39,722評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖肤无,靈堂內(nèi)的尸體忽然破棺而出先蒋,到底是詐尸還是另有隱情,我是刑警寧澤宛渐,帶...
    沈念sama閱讀 35,425評(píng)論 5 343
  • 正文 年R本政府宣布竞漾,位于F島的核電站,受9級(jí)特大地震影響窥翩,放射性物質(zhì)發(fā)生泄漏业岁。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評(píng)論 3 326
  • 文/蒙蒙 一寇蚊、第九天 我趴在偏房一處隱蔽的房頂上張望笔时。 院中可真熱鬧,春花似錦仗岸、人聲如沸允耿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽较锡。三九已至,卻和暖如春盗痒,著一層夾襖步出監(jiān)牢的瞬間蚂蕴,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評(píng)論 1 269
  • 我被黑心中介騙來泰國打工俯邓, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留骡楼,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,729評(píng)論 2 368
  • 正文 我出身青樓看成,卻偏偏與公主長得像君编,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子川慌,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容