第2章 Android網(wǎng)絡(luò)底層框架設(shè)計

本章介紹Android網(wǎng)絡(luò)底層的封裝。很多公司、很多團隊都只是把網(wǎng)絡(luò)底層封裝成一個好用的方法印衔,而我接下來要介紹的內(nèi)容將覆蓋的范圍很廣:

  • 拋棄AsyncTask,自定義一套網(wǎng)絡(luò)底層的封裝框架姥敛。
  • 設(shè)計一套App緩存策略。
  • 設(shè)計一套MockService的機制瞎暑,在沒有MobileAPI的時候彤敛,也能假裝獲取到了網(wǎng)絡(luò)返回的數(shù)據(jù)。
  • 封裝了用戶Cookie的邏輯了赌。

2.1 使用原生的ThreadPoolExecutor+Runnable+Handler

先說說AsyncTask的致命缺點:
那就是不能靈活控制其內(nèi)部的線程池墨榄,線程池里面的每個線程存放的都是MobileAPI的調(diào)用請求,而AsyncTask中又沒有暴露出取消這些請求的方法勿她,也就是我們熟知的CancelRequest方法袄秩,所以,一旦從A頁面跳轉(zhuǎn)到B頁面逢并,那么在A頁面發(fā)起的MobileAPI請求之剧,如果還沒有返回,并不會被取消砍聊。

圖中只列出了8個背稼,還有1個RemoteService類,位于YoungHeart項目的engine包中玻蝌。下面分別介紹蟹肘。

2.1.1 網(wǎng)絡(luò)請求的格式

1. Request格式
2. Response格式

JSON數(shù)據(jù)格式1:

{  "code" : 1,    "message" : "網(wǎng)絡(luò)異常",    "result" : ""}

可以定義code為0為成功,其他為異常情況许饿。

3. UrlConfigManager和URLData

我們把App所要調(diào)用的所有MobileAPI接口的信息都放在url.xml文件中阳欲,如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<url>    
    <Node       
          Key="getWeatherInfo"        
          Expires="300"        
          NetType="get"         
          Url="http://www.weather.com.cn/data/sk/101010100.html" />     
    <Node       
          Key="login"        
          Expires="0"        
          NetType="post"        
          Url="http://www.weather.com.cn/data/login.api" />
</url>

在使用上,通過UrlConfigManager的findURL方法,在上述xml文件中找到當(dāng)前MobileAPI調(diào)用的節(jié)點胸完,其中每一個MobileAPI接口都對應(yīng)一個URLData實體书释,如下所示:

public class URLData {
        private String key;
        private long expires;
        private String netType;
        private String url;
    }
4. RemoteService和RequestCallback、RequestParameter
  • RequestCallback是回調(diào)赊窥,目前有onSuccess和onFail兩種爆惧。
  • RequestParameter是用來傳遞調(diào)用MobileAPI接口所需參數(shù)的鍵值對的。我們原本可以使用HashMap<String,String>這樣的數(shù)據(jù)結(jié)構(gòu)锨能,但是HashMap比較耗費內(nèi)存扯再,雖然它的查找速度是o(1),而對于MobileAPI接口的參數(shù)而言址遇,數(shù)據(jù)一般不會太多熄阻,查找速度快體現(xiàn)不出優(yōu)勢來,所以我們使用ArrayList<RequestParameter>這樣的數(shù)據(jù)結(jié)構(gòu)倔约。
  • RemoteService這個單例是用來發(fā)起請求的秃殉,它會創(chuàng)建一個request,并將其添加到RequestManager中浸剩,然后放到DefaultThreadPool的一個線程中去執(zhí)行這個request钾军。
5. RequestManager

RequestManager這個集合類是用于取消請求(cancelRe-quest)的。因為每次發(fā)起請求绢要,都會把為此創(chuàng)建的request添加到RequestManager中吏恭,所以RequestManager保存了全部re-quest。

6. DefaultThreadPool

DefaultThreadPool只是對ThreadPoolExecutor和Array-BlockingQueue的簡單封裝重罪。我們可以認為它就是一個線程池樱哼,每發(fā)起一次請求(runnable),就由線程池分配一個新的線程來執(zhí)行該請求剿配。

7. HttpRequest

HttpRequest是發(fā)起Http請求的地方搅幅,它實現(xiàn)了Runnable,從而讓DefaultThreadPool可以分配新的線程來執(zhí)行它惨篱,所以盏筐,所有的請求邏輯都在Runnable接口的run方法中,其中:

  • 對于get形式的MobileAPI接口砸讳,它會把從上層傳遞進來的ArrayList<RequestParameter>琢融,解析為urlk1=v1&k2=v2這樣的形式。
  • 對于post格式的MobileAPI接口簿寂,它會把從上層傳遞進來的ArrayList<RequestParameter>漾抬,轉(zhuǎn)為BasicNameVal-uePair的形式,放到表單中進行提交常遂。

2.1.2 網(wǎng)絡(luò)底層的一些優(yōu)化工作

我們的網(wǎng)絡(luò)底層越來越強大了纳令,是否有意猶未盡的感受?接下來將完善這個框架,修復(fù)其中的一些瑕疵平绩,如onFail的統(tǒng)一處理機制圈匆、UrlConfigManager的優(yōu)化、ProgressBar的處理等捏雌。

1. onFail的統(tǒng)一處理機制

統(tǒng)一處理異常跃赚,Toast提示或者對話框

2. UrlConfigManager的優(yōu)化

在App啟動時,一次性將url.xml文件都讀取到內(nèi)存性湿,把所有的UrlData實體保存在一個集合中纬傲,然后每次調(diào)用MobileAPI接口,直接從內(nèi)存的這個集合中查找肤频√纠ǎ考慮到內(nèi)存中的數(shù)據(jù)會被回收,所以上述這個集合一旦為空宵荒,我們要從url.xml中再次讀取汁雷。

3. 不是每個請求都需要回調(diào)的

2.2 App數(shù)據(jù)緩存設(shè)計

  • 對于App而言,它是感受不到取的是緩存數(shù)據(jù)還是調(diào)用MobileAPI报咳。具體工作由網(wǎng)絡(luò)底層完成摔竿。
  • 在url.xml中為每一個MobileAPI接口配置緩存時間Ex-pired。對于post少孝,一律設(shè)置為0,因為post不需要緩存熬苍。
  • 在HttpRequest類中的run方法中稍走,改動3個地方:
    • 寫一個排序算法sortKeys,對URL中的key進行排序柴底。
    • 將newUrl作為key婿脸,檢查緩存中是否有數(shù)據(jù),有則直接返回柄驻;否則狐树,繼續(xù)調(diào)用MobileAPI接口。
    • MobileAPI接口返回數(shù)據(jù)后鸿脓,將數(shù)據(jù)存入緩存抑钟。
  • CacheManager用于操作讀寫緩存數(shù)據(jù),并判斷緩存數(shù)據(jù)是否過期野哭。緩存中存放的實體就是CacheItem在塔。
  • 在App項目中,創(chuàng)建YoungHeartApplication這個Ap-plication級別的類拨黔,在程序啟動時蛔溃,初始化緩存的目錄,如果不存在則創(chuàng)建之。

2.3 強制更新

  • 如果對于某個接口的數(shù)據(jù)贺待,MobileAPI緩存了5分鐘徽曲,App緩存了3分鐘,那么最極端的情況是麸塞,用戶在8分鐘內(nèi)是看不到數(shù)據(jù)更新的秃臣。因此,我們需要在頁面上提供一個強制更新的按鈕喘垂。
  • 我們可以讓RemoteService多暴露一個boolean類型的參數(shù)甜刻,用于判斷是否要遵守App端緩存策略,如果是正勒,則在從url.xml中取出UrlData實體后得院,將其expired強制設(shè)置為0,這樣就不會執(zhí)行緩存策略了章贞。
  • 數(shù)據(jù)緩存是一把雙刃劍祥绞,設(shè)置時間長了,數(shù)據(jù)長期不更新鸭限,用戶體驗就會不好蜕径。因此我們需要為那些強迫癥類型的用戶提供一個強制刷新的按鈕,點擊按鈕后败京,頁面會重新調(diào)用MobileAPI加載數(shù)據(jù)兜喻,無論緩存是否到期。

2.4 MockService

在App團隊與MobileAPI團隊協(xié)同開發(fā)的過程中赡麦,經(jīng)常會遇到因為MobileAPI接口還沒好而App又急等著用的情況朴皆。

設(shè)計App端MockService包括如下幾個關(guān)鍵點:

  1. 對需要Mock數(shù)據(jù)的MobileAPI接口,通過在url.xml中配置Node節(jié)點MockClass屬性泛粹,來指定要使用那個Mock子類生成的數(shù)據(jù):
<Node    
  Key="getWeatherInfo"    
  Expires="300"    
  NetType="get"    
  MockClass="com.youngheart.mockdata.MockWeatherInfo"           
  Url="http://www.weather.com.cn/data/sk/101010100.html" />

這里將使用com.mockdata.mockdata包下的MockWeath-erInfo子類來解析遂铡。

  1. 我使用了反射工廠來設(shè)計MockService。MockService類是基類晶姊,它有一個抽象方法getJsonData扒接,用于返回手動生成的Mock數(shù)據(jù)。
  2. 接下來介紹如何實現(xiàn)反射機制们衙。
  • 主要的改造工作在RemoteService類的invoke方法中钾怔,根據(jù)是否在url.xml中指定了MockClass值來決定,是調(diào)用線上MobileAPI還是從本地MockService直接取假數(shù)據(jù)砍艾。
  • 如果MockClass有值蒂教,就把這個值反射為一個具體的類,比如MockWeatherInfo脆荷,然后調(diào)用它的getJsonData方法凝垛。
  • 有了MockService這個利器懊悯,對于作者來說,本書接下來的內(nèi)容將會輕松很多梦皮,因為不需要搭建自己的服務(wù)器炭分,全都用MockService在本地編寫假數(shù)據(jù)即可。

2.5 用戶登錄

首先剑肯,貫穿App的捧毛,應(yīng)該有一個User全局變量,在每次登錄成功后让网,會將其isLogin屬性設(shè)置為true呀忧,在退出登錄后,則將該屬性設(shè)置為false溃睹。這個User全局變量要支持序列化到本地的功能而账,這樣數(shù)據(jù)才不會因內(nèi)存回收而丟失。
其次因篇,登錄分為3種情形:

  1. 點擊登錄按鈕泞辐,進入登錄頁面LoginActivity,登錄成功后竞滓,直接進入個人中心PersonCenterActivity咐吼。這種情況最直截了當(dāng),一路執(zhí)行startActivity(intent)就能達到目的商佑。
  2. 在頁面A锯茄,想要跳轉(zhuǎn)到頁面B,并攜帶一些參數(shù)茶没,卻發(fā)現(xiàn)沒有登錄撇吞,于是先跳轉(zhuǎn)到登錄頁,登錄成功后礁叔,再跳轉(zhuǎn)到B頁面,同時仍然帶著那些參數(shù)迄薄。
  3. 在頁面A琅关,執(zhí)行某個操作,卻發(fā)現(xiàn)沒有登錄讥蔽,于是跳轉(zhuǎn)到登錄頁涣易,登錄成功后,再回到頁面A冶伞,繼續(xù)執(zhí)行該操作新症。

這里,我的操作是在父類寫一個startActivityForLogin响禽,入?yún)閕ntent

2.6 自動登錄

  • 我們將cookie取出來徒爹,不用關(guān)心它是什么荚醒,只要把它存放在本地文件中即可。每次發(fā)起MobileAPI請求時隆嗅,都要把本地保存的Cookie取出來界阁,放到HttpRequest的header中。
  • SD卡以及內(nèi)存中一份用戶信息

判斷用戶是否過期需要服務(wù)器返回標(biāo)識

2.7 HTTP頭中的奧妙

對于HTTP頭胖喳,我們并不陌生泡躯。我們在上一節(jié)中成功運用到了HTTP頭中的Cookie屬性。接下來丽焊,我們將繼續(xù)發(fā)揮它的威力较剃,看看它還能為我們做些什么。我們先學(xué)習(xí)一下HTTP請求的定義技健。

2.7.1 HTTP請求

HTTP請求分為HTTPRequest和HTTPResponse兩種写穴。但無論哪種請求,都由header和body兩部分組成凫乖。

  1. HTTP Body:Body部分就是存放數(shù)據(jù)的地方
  2. HTTP Header:它由很多鍵值對(key-value)組成确垫,其中有些key是標(biāo)準(zhǔn)的,兼容于各大瀏覽器帽芽,比如:
  • accept
  • accept-language
  • referrer
  • user-agent
  • accept-encoding
    我們還可以在MobileAPI端自定義一些鍵值對删掀,然后要求App在調(diào)用MobileAPI時把這些信息傳遞過來。比如MobileAPI可以定義一個check-value這樣的key导街,然后要求App將AppId(同一公司的不同App編號)披泪、ClientType(Android還是iPhone、iPad)這些值拼接在一起經(jīng)過MD5加密后搬瑰,作為這個key的值傳遞給MobileAPI款票,然后由MobileAPI再去分析這些數(shù)據(jù)。
2.7.2 時間校準(zhǔn)
  • 對于手機系統(tǒng)時間不準(zhǔn)的問題泽论,本文給出了比較好的解決方案艾少,即通過每次調(diào)用MobileAPI來計算時間差,然后每次本地獲取時間就加上這個時間差翼悴。
  • 對于用戶身處不同時區(qū)的問題缚够,App仍然返回同一個時間,只是要在App上注明這些時間都是北京時間鹦赎,而不能是北京用戶顯示飛機9點起飛而日本用戶顯示10點起飛谍椅。另一方面,這兩個時區(qū)的用戶在一起聊天是個麻煩的事情古话,即使有人在日本時區(qū)10點說句話雏吭,對于北京用戶而言,看到的也應(yīng)該是9點發(fā)的消息陪踩,反之亦然杖们。而服務(wù)器則要使用格林威治一套時間悉抵,具體怎么顯示,那是App的事情胀莹。有些App就存在這樣的bug基跑,出國旅游收不到即時聊天消息,到了晚上會莫名其妙冒出來幾百條消息描焰,就是因為這個時區(qū)問題沒有處理好導(dǎo)致的媳否。
2.7.3 開啟gzip壓縮

接下來要介紹的內(nèi)容和gzip有關(guān)。HTTP協(xié)議上的gzip編碼是一種用來改進Web應(yīng)用程序性能的技術(shù)荆秦。大流量的Web站點常常使用gzip壓縮技術(shù)來減少傳輸量的大小篱竭,減少傳輸量大小有兩個明顯的好處,一是可以減少存儲空間步绸,二是通過網(wǎng)絡(luò)傳輸時掺逼,可以減少傳輸?shù)臅r間。

2.8 本章小結(jié)

本章介紹如何對網(wǎng)絡(luò)底層進行封裝瓤介,其中包括:新寫了一個網(wǎng)絡(luò)調(diào)用框架用以代替AsyncTask吕喘;設(shè)計了App的緩存機制;設(shè)計了MockService的機制刑桑,以后即使沒有MobileAPI接口也能開發(fā)新功能了氯质。介紹用戶Cookie的設(shè)計方法;巧妙運用Http頭中的數(shù)據(jù)等祠斧。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末闻察,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子琢锋,更是在濱河造成了極大的恐慌辕漂,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件吴超,死亡現(xiàn)場離奇詭異钉嘹,居然都是意外死亡,警方通過查閱死者的電腦和手機鲸阻,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進店門隧期,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人赘娄,你說我怎么就攤上這事『牝龋” “怎么了遣臼?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長拾并。 經(jīng)常有香客問我揍堰,道長鹏浅,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任屏歹,我火速辦了婚禮隐砸,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蝙眶。我一直安慰自己季希,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布幽纷。 她就那樣靜靜地躺著式塌,像睡著了一般。 火紅的嫁衣襯著肌膚如雪友浸。 梳的紋絲不亂的頭發(fā)上峰尝,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天,我揣著相機與錄音收恢,去河邊找鬼武学。 笑死,一個胖子當(dāng)著我的面吹牛伦意,可吹牛的內(nèi)容都是我干的火窒。 我是一名探鬼主播,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼默赂,長吁一口氣:“原來是場噩夢啊……” “哼沛鸵!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起缆八,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤曲掰,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后奈辰,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體栏妖,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年奖恰,在試婚紗的時候發(fā)現(xiàn)自己被綠了吊趾。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡瑟啃,死狀恐怖论泛,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蛹屿,我是刑警寧澤屁奏,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站错负,受9級特大地震影響坟瓢,放射性物質(zhì)發(fā)生泄漏勇边。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一折联、第九天 我趴在偏房一處隱蔽的房頂上張望粒褒。 院中可真熱鬧,春花似錦诚镰、人聲如沸奕坟。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽执赡。三九已至,卻和暖如春函筋,著一層夾襖步出監(jiān)牢的瞬間沙合,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工跌帐, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留首懈,地道東北人。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓谨敛,卻偏偏與公主長得像究履,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子脸狸,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,033評論 2 355

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

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理最仑,服務(wù)發(fā)現(xiàn),斷路器炊甲,智...
    卡卡羅2017閱讀 134,659評論 18 139
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,166評論 25 707
  • 同樣是使用Java語言泥彤,為什么做MobileAPI的開發(fā)人員寫不了Android程序,反之亦然卿啡。我想大概是各行有各...
    lookid閱讀 812評論 1 2
  • 濕鞋趟過那斷橋的河 魚兒劃過我的腳邊 想要知道河的盡頭 沿途的荷花 偶遇的青蛙 潛伏著的半翅目 河邊洗衣的大娘們 ...
    柒禾頁閱讀 233評論 8 2
  • 增一阿含經(jīng)卷三十四載吟吝,劫初之時,光音天子至此世間食地肥颈娜,食少者剑逃,其體不重,且不失神足官辽,故亦能于虛空飛行蛹磺;然食多...
    山海花開閱讀 400評論 0 0