淘宝系App图片为什么在北京电信网络加载这么慢?
歡迎訪問我的個人網站:https://coderyuan.com
文章目錄
- 先講講怎么回事
- 不能抓包
- 初步判斷是DNS或IPv6問題(其實不是)
- 反饋
- 被逼無奈,只好搞逆向
- 利用LayoutInspector找到ImageView
- 探究一下TNodeImageView的源碼
- 嘗試修改Smali——放棄
- 編寫Xposed插件,抓取圖片URL
- 利用Xposed進一步研究淘寶的圖片加載流程
- 發現ImageLoader
- 分析PhenixCreator
- 捕獲圖片網絡流
- 發現淘寶的網絡框架
- 分析ANetwork網絡框架
- 首先要把圖片的URL抓出來
- 嘗試為ANetwork添加一個代理
- 不能抓包,打一些日志總是可以的
- 重點來了,分析ALog日志
- 為什么北京電信慢?
- 總結
- 后記
先講講怎么回事
不知道怎么的,大概是從19年雙十一前,我在家里刷淘寶(天貓、閑魚等)的時候,圖片經常加載的特別慢,家里是北京電信的100M寬帶,另外還有一張電信的手機卡,無論寬帶還是4G,圖片都刷的很慢,網速正常,SpeedTest測試速度都是正常的,其他App也都OK
部分家里使用電信寬帶的同事也遇到了類似的問題,但都以為是家里網速的問題,所以也沒有向淘寶進行反饋,只是在需要的時候,切成4G使用罷了
今年5月寬帶到期,由于淘寶的問題,差點就換了聯通的,但由于聯通略貴(500M,166/月),電信(200M,600+/年),而且疫情期間,懶得折騰了,直接續費吧
于是就想著怎么解決一下淘寶圖片的問題
不能抓包
作為一名Android碼農,首先想到的辦法就是開Charles/Fiddler去抓包看看,但是在掛上代理以后,會發現抓不到大部分的淘寶圖片請求,于是猜測是淘寶使用了自己的網絡庫,或者是做了防抓包處理,所以看來不能直接通過抓包來判斷問題所在
后來在反編譯淘寶代碼的時候,也驗證了這一問題,其底層采用的并非OKHttp或者HttpURLConnection的方法去實現連接,而是使用了基于NDK實現的連接層(后面會講到)
初步判斷是DNS或IPv6問題(其實不是)
-
DNS:這種問題非常讓人首先懷疑DNS出了問題,畢竟這種大廠,都會根據當地網絡和服務器負載的實際情況,動態解析域名,選擇最優的CDN
-
IPv6:另外就是回想起19年雙十一前,貌似家里網絡對IPv6支持進行了升級,會不會是淘寶的CDN對IPv6兼容有問題
于是保證試試看的心態,改了一下路由器的DNS,嘗試過的DNS有:百度DNS(180.76.76.76)、阿里云DNS(223.5.5.5)、114(114.114.114.114)
然而發現并沒有什么卵用,還是一樣的慢……
想想也有道理,這種大廠App一般都會集成HttpDNS能力,隨你怎么改DNS,都不會影響人家的解析
然后就是關掉了IPv6功能,發現還是沒用,由于無意間通過Charles抓到幾張圖片的URL,ping了一下域名,發現其實淘寶的CDN還沒有支持IPv6……
所以圖片問題,和DNS、IPv6,根本沒有任何關系!!!
反饋
-
首先是給淘寶客服反饋:
不知道其他路徑能不能有效,反正淘寶App里面的反饋界面就跟擺設一樣,沒有任何作用!!
-
反饋給在阿里工作的同學:
問我要了一些信息以后,然后說給相關人員反饋一下,后來也沒有動靜了,據說很難推動去解決。。。
另外,我覺得,對于這種比較詭異的技術問題,還是得有一些有力的證據,可能才好推
被逼無奈,只好搞逆向
既然通過抓包、調試網絡環境,以及正規渠道都無法解決問題,那我只好自己排查了
于是準備好這些工具,準備開干:apktool、dex2jar、VSCode、Android Studio、Xposed,另外就是需要一臺ROOT后的Android手機,最好是Nexus或者Pixel
當然,也可以考慮使用VirtualXposed(https://github.com/android-hacker/VirtualXposed),這個工具的優點是不需要ROOT就可以使用Xposed的功能,且每次更新Hook不需要重啟手機,但是部分場景不太穩定,個人還是喜歡用ROOT以后的手機去裝原版的Xposed,不過需要Android 8.1及以下的系統
上述工具的使用,我就不再多說了,直接尋找突破口
利用LayoutInspector找到ImageView
在一臺ROOT后的手機上,是可以利用LayoutInspector抓取任何一個App的View的
因為經常刷微淘,而且圖片也比較多,加載比較慢,這里以淘寶的微淘模塊為例,看看它用的類名是什么
感謝淘寶沒有做更深入的類名混淆,微信的部分場景,混淆就做的非常狠,幾乎沒法看了
于是可以發現這個用于圖片顯示的類就是TNodeImageView
探究一下TNodeImageView的源碼
既然已經定位圖片控件,那么可以直接搜Smali文件或者使用Android Studio,在加載使用dex2jar轉換而來的Jar包以后,雙擊Shift搜索(和平時使用Android Studio搜索項目中的類一樣)
個人建議首先使用Android Studio或IDEA看Jar包里的源碼,如果發現不能被反編譯,或者某些App做了防dex2jar轉換,再考慮去看Smali,效率高的多,不太推薦用JD-GUI,不如IDEA系列IDE方便
使用VSCode直接搜索Smali文件:
如果使用Android Studio/IDEA,需要把jar包先引入到工程當中:
這里可以發現兩個方法:
-
第一個是setImageUrl:
很明顯是一個外部調用,用來設置圖片URL的方法,斷定這里可以獲取到圖片的URL
-
第二個是onImageLoaded:
其參數為一個BitmapDrawable對象,猜測是圖片加載框架的回調方法,用來設置解碼后的圖片
嘗試修改Smali——放棄
由于找到了圖片路徑的首個突破口,于是想著修改一下Smali,能夠讓淘寶直接打印圖片URL,然后直接把apk裝在手機上測試
經過apktool二次打包和jarsigner的一番折騰,apk是可以裝上了
但是發現,很多頁面出現白屏,登錄也無法使用,并且Logcat會打出一下Security相關的Log
這就說明淘寶做了簽名校驗,這個方法不可行……
編寫Xposed插件,抓取圖片URL
由于以上修改源碼的辦法行不通,那么只能考慮Hook的辦法,目前最流行的Hook技術無非就是Xposed了
關于Xposed,我就不再多說了,自行搜索即可,不過自我推薦一下自己寫的一個小工具,可以簡化Xposed Hook的開發,https://github.com/yuanguozheng/coderyuan-xposed-hook,如有需要,可以自行拿去使用
以下代碼即為利用coderyuan-xposed-hook實現對淘寶App「微淘」模塊圖片組件(TNodeImageView)的setImageUrl方法Hook
@XPHook(pkgName = "com.taobao.taobao",applicationName = "com.taobao.tao.TaobaoApplication",isUseMultiDex = true ) class TaobaoHookImpl {@HookMethod(className = "com.taobao.tao.flexbox.layoutmanager.view.TNodeImageView",methodName = "setImageUrl",isHookBefore = false,paramsCls = [String::class])fun hookTNodeImageView(param: XC_MethodHook.MethodHookParam?) {param?.args?.forEach {if (it != null && it is String) {log("TNodeUrl", it.toString())}}} }跑起來以后,可以在Logcat中看到下面這些URL
于是我把這些圖片的URL抓出來,heic的圖片,去掉了.heic,即可獲取jpg或者png格式的數據,經過測試(PC瀏覽器、Android使用Glide等框架加載),結果發現如果我直接去訪問這些圖片,無論使用什么網絡,速度都是正常的
下圖是我的測試情況,網絡是中國電信4G,可以看到左上角的速度,也都基本保存3MB/s以上(圖片首次加載會比較慢。。。)
以上測試的App,使用如下代碼進行圖片加載
override fun onBindViewHolder(holder: ImgVH, position: Int) {Glide.with(this@MainActivity).load(jsonArray[position]).skipMemoryCache(true).diskCacheStrategy(DiskCacheStrategy.NONE) // 強制不使用緩存.into(holder.imageView)}這就說明如果使用較為常規的模式去加載淘寶上的圖片,其實是沒問題的,也進一步驗證了淘寶實際上是對網絡庫進行了修改,只是不知道具體做了什么事
利用Xposed進一步研究淘寶的圖片加載流程
發現ImageLoader
在TNodeImageView中,可以發現一個名為imageLoader,類型為p的類成員變量,猜測為淘寶自己的圖片加載框架
詳細查看p這個類,可以看到是一個abstract class
繼續查看p的實現類,可以發現一個名為j的類
于是研究j的源碼,再發現內部一段代碼,很明顯是用來加載圖片的功能,其內部的回調(onImageLoaded、onImageLoadFailed)、調用方式和命名,都和圖片有關,其中關鍵點就是PhenixCreator
分析PhenixCreator
根據PhenixCreator的包名,猜測這個框架的名字為:Phenix,暫且這么叫它
經過我對其反編譯后代碼的一番查看,可以發現以下兩個特點:
-
其內部結構設計和接口調用方式,和Glide略微類似,畢竟大家都是ImageLoader,或多或少有一些共性
-
看上去和Glide一樣,都可以比較方便的把網絡層組件換掉,同時會內置的類似HttpURLConnection的默認網絡層實現
其中,可以看到名為mImageRequest的變量,內部調用比較頻繁,而且其名字也基本能夠猜出作用,那就是做圖片請求
mImageRequest的類型為b(com.taobao.phenix.request.b)所以可以肯定b一定是用于在Phenix中管理圖片請求
然而此時,來回翻看b的源碼,難以發現有比較明顯的網絡請求痕跡,所以果斷嘗試另外一種思路,去尋找b的調用
這里我推薦使用Smali來搜索,但要注意相關的語法
可以發現名為com.taobao.phenix.loader.network.c的類使用了b,而且其包名包含network字樣,猜測是和網絡請求相關,果斷查看
從內部打出的Log,可以發現,這個類確實和網絡請求有一定關系,比如以下幾行代碼:
htr.a("Phenix", "received cancellation.", var1); htr.a("Phenix", "Network Read Started.", var9); htr.a("Phenix", "Network Connect Started.", var8);其中不乏有很多Trace相關的操作,應該是進行統計打點相關的操作,用來收集性能相關信息的數據內容
捕獲圖片網絡流
仔細觀察代碼和Log,其中以下這段代碼吸引了我的注意:
看上去是對網絡請求的響應數據進行讀取,其中htv.a很有可能就是獲取網絡請求數據的方法,于是果斷點進去分析,這里再看到下面的代碼,很明顯是對一種InputStream進行操作,斷定是網絡流
那么htq.a這個方法又顯得格外耀眼,進去看了以后,htq類的內部結構如下,看樣子是一個對輸入流InputStream操作的工具類
public class htq {public static htv a(InputStream var0, a var1, int[] var2) throws Exception {...}public static void a(InputStream var0, a var1, huk var2) throws Exception {...// 內部包含很多對Stream的操作,以及OOM的檢測及異常拋出} }很有可能第一a方法,其作用就是對圖片網絡流的讀取,這里沒有比較明顯的痕跡來判斷a方法的參數到底都是什么,所以只好借助Xposed來分析了
利用coderyuan-xposed-hook來Hook一下這個方法
@HookMethod(className = "tb.htq",methodName = "a",isHookBefore = false,paramsClsStr = ["java.io.InputStream", "com.taobao.tcommon.core.a", "tb.huk"])fun hookHTQ(param: XC_MethodHook.MethodHookParam?) {// 遍歷該方法的參數,由于是靜態方法,所以不要考慮首個參數是自己所在類的實例對象param?.args?.forEach {if (it != null) {log("YYY-htq", it.toString())}}}運行起來以后查看Logcat,于是有了以下Log:
2020-05-25 21:10:40.284 29080-29908/com.taobao.taobao I/Xposed: YYY-htq: com.taobao.phenix.compat.mtop.b@ef09a67 2020-05-25 21:10:40.284 29080-29908/com.taobao.taobao I/Xposed: YYY-htq: tb.hth@96d165b 2020-05-25 21:10:40.284 29080-29908/com.taobao.taobao I/Xposed: YYY-htq: tb.huk@e275014此時com.taobao.phenix.compat.mtop.b這個類映入眼簾,果斷查找,于是發現這個類確實為一個輸入流,且內部還包含一個ParcelableInputStream類型的看似是輸入流的變量a
b類內部實際是對ParcelableInputStream變量a操作的封裝,包含InputStream的常規操作,如:read、close
發現淘寶的網絡框架
由于找到了ParcelableInputStream,出于習慣地點進去看看,果斷發現新大陸,這個類位于一個非常醒目的類名下:anetwork.channel.aidl
于是對這個包名底下的類都查看一番
這還有啥說的!!!赤裸裸的網絡請求框架啊!
分析ANetwork網絡框架
首先要把圖片的URL抓出來
為了能夠驗證圖片請求確實走到這里了,第一步就是把ANetwork每次做請求的URL打印出來,看看有沒有圖片的URL
ANetwork的代碼混淆程度比較低,另外就是它還引用了anet.channel包中的很多內容,看樣子是一塊兒的,所以如果要比較全面地理解這個庫的流程,需要較為詳細的閱讀
我也是花了2-3天的時間去詳細地看了一把這個網絡庫的一些代碼,這里就沒有什么投機取巧的辦法,好好看看,基本能夠理解
經過研究以后,發現anet.channel.request.Request這個類是用來保存每次請求的基本信息的,內部還包含一個Builder類,所以可以Hook它的build()方法,然后把Builder內部的URL成員變量打印出來
@HookMethod(className = "anet.channel.request.Request\$Builder",methodName = "build",isHookBefore = true)fun hookRequest(param: XC_MethodHook.MethodHookParam?) {val instance = param?.thisObject ?: returnval oriUrlProp = instance::class.memberProperties.find { it.name == "originUrl" }oriUrlProp?.isAccessible = trueval formatUrlProp = instance::class.memberProperties.find { it.name == "formattedUrl" }formatUrlProp?.isAccessible = trueval ori = oriUrlProp?.getter?.call(instance)?.toString()val format = formatUrlProp?.getter?.call(instance)?.toString()val targetUrl = if (!format.isNullOrEmpty()) {format} else if (!ori.isNullOrEmpty()) {ori} else {null}if (targetUrl?.contains("heic.alicdn.com") == true) {log("YYY-Request-Url", targetUrl)}}這里過濾了其他請求的URL,專門來看看圖片請求的URL,下面是Logcat的輸出:
2020-05-25 21:10:24.117 29080-29674/com.taobao.taobao I/Xposed: YYY-Request-Url: https://heic.alicdn.com/tps/TB1hPVmisKfxu4jSZPfXXb3dXXa.jpg_200x200q90.jpg_.heic 2020-05-25 21:10:26.589 29080-29909/com.taobao.taobao I/Xposed: YYY-Request-Url: https://heic.alicdn.com/tps/i2/O1CN011EAZ5L1gJ0OScCpUv_!!0-juitemmedia.jpg_200x200q90.jpg_.heic 2020-05-25 21:10:26.743 29080-29898/com.taobao.taobao I/Xposed: YYY-Request-Url: https://heic.alicdn.com/imgextra/i1/128/TB24Xy8l5OYBuNjSsD4XXbSkFXa_!!128-0-luban.jpg_200x200q90.jpg_.heic 2020-05-25 21:10:27.219 29080-29676/com.taobao.taobao I/Xposed: YYY-Request-Url: https://heic.alicdn.com/tps/i2/O1CN011EAZ5L1gJ0OScCpUv_!!0-juitemmedia.jpg_200x200q90.jpg_.heic 2020-05-25 21:10:27.370 29080-29908/com.taobao.taobao I/Xposed: YYY-Request-Url: https://heic.alicdn.com/imgextra/i4/170/O1CN01XXyKpn1D7tt9CAYT3_!!170-0-lubanu.jpg_200x200q90.jpg_.heic 2020-05-25 21:10:27.803 29080-29674/com.taobao.taobao I/Xposed: YYY-Request-Url: https://heic.alicdn.com/imgextra/i1/128/TB24Xy8l5OYBuNjSsD4XXbSkFXa_!!128-0-luban.jpg_200x200q90.jpg_.heic 2020-05-25 21:10:28.186 29080-29912/com.taobao.taobao I/Xposed: YYY-Request-Url: https://heic.alicdn.com/imgextra/i2/34/O1CN01bMPrqD1C7c4FGfAwy_!!34-0-lubanu.jpg_960x960q90.jpg_.heic 2020-05-25 21:10:28.388 29080-29674/com.taobao.taobao I/Xposed: YYY-Request-Url: https://heic.alicdn.com/imgextra/i4/170/O1CN01XXyKpn1D7tt9CAYT3_!!170-0-lubanu.jpg_200x200q90.jpg_.heic 2020-05-25 21:10:28.978 29080-29676/com.taobao.taobao I/Xposed: YYY-Request-Url: https://heic.alicdn.com/imgextra/i2/34/O1CN01bMPrqD1C7c4FGfAwy_!!34-0-lubanu.jpg_960x960q90.jpg_.heic 2020-05-25 21:10:39.375 29080-29898/com.taobao.taobao I/Xposed: YYY-Request-Url: https://heic.alicdn.com/imgextra/i2/164/O1CN015yuRLo1D59YXcfmvo_!!164-0-lubanu.jpg_960x960q90.jpg_.heic 2020-05-25 21:10:39.380 29080-29674/com.taobao.taobao I/Xposed: YYY-Request-Url: https://heic.alicdn.com/imgextra/i2/164/O1CN015yuRLo1D59YXcfmvo_!!164-0-lubanu.jpg_960x960q90.jpg_.heic果不其然,圖片請求都是從這里構建的URL,也印證了上面的分析過程正確
嘗試為ANetwork添加一個代理
由于ANetwork的設計,導致無法直接使用Charles這種工具進行抓包,所以本想考慮使用Xposed,強制設置代理,但是經過一番嘗試,發現各類操作都沒有效果,這里就不詳細說了
而中途還發現其實這些請求并沒有走普通的HTTP連接,其連接過程是首先使用Spdy進行連接,而HTTP連接只是作為一種降級方案來兜底,那么就基本不能按照常規的方式去調試了
并且ANetwork的Spdy底層還是基于NDK實現(如上圖所示),并不像OKHttp這樣的網絡庫使用純Java來開發,所以看來,很難通過基本的逆向手段來添加代理,從而實現抓包了!
另外,還有一種可能就是:設置代理的地方,我還沒找到……
不能抓包,打一些日志總是可以的
在ANetwork的內部,有很多打Log的代碼,如下圖所示:
通讀ANetwork的代碼,發現ALog的調用確實很多,基本能夠確定ALog就是ANetwork專用的Log工具類,在很多關鍵位置,都使用了ALog輸出了相對詳細的日志信息,只是因為打正式包的時候,關掉了Logcat開關,所以無法在Logcat中打印出來
于是Hook ALog的所有靜態方法,讓ANetwork的日志,都能夠展現在Logcat當中
@HookMethod(className = "anet.channel.util.ALog",methodName = "e",paramsClsStr = ["java.lang.String", "java.lang.String", "java.lang.String", "java.lang.Throwable", "java.lang.Object[]"],isHookBefore = false)// 由于方法比較多,所以這里只列舉其中一個,即ALog.e的Hookfun hookLogcat2(param: XC_MethodHook.MethodHookParam?) {val logStr = getLogParamsStr(param?.args) ?: returnlog("YYY-ALog-E", logStr)}/*** 獲取Log參數,返回拼接后的字符串*/fun getLogParamsStr(objs: Array<Any>?, separator: String = "---"): String? {objs ?: return nullreturn StringBuilder().apply {objs.forEachIndexed { i, item ->if (item is String) {this.append(item)} else {try {this.append(gson.toJson(item))} catch (e: Exception) {}}if (i != objs.size - 1) {this.append(separator)}}}.toString()}再次運行Hook,觀察ALog相關的日志,各類請求相關的信息一目了然
由于日志包含各類網絡請求的信息,在翻看日志的時候,發現有以下類似內容,并且在圖片加載不出來的時候,比較頻繁
YYY-ALog-E: awcn.SessionCenter---[Get]timeout exception---21646297---{"stackTrace":[],"suppressedExceptions":[]}---["url","https://img.alicdn.com/bao/uploaded/i1/TB1TXTMdKP2gK0jSZFoSuuuIVXa.jpg"]于是過濾TB1TXTMdKP2gK0jSZFoSuuuIVXa字樣的Log,專門查看該圖片URL的相關信息,得到以下內容:
2020-05-26 10:28:40.376 29080-536/com.taobao.taobao I/Xposed: YYY-ALog-E: anet.UnifiedRequestTask---[traceId:Xr65l7NnDDkDAKlUmeifke5K15904601190960813129080]start---DGRD306---["bizId",null,"url","https://img.alicdn.com/bao/uploaded/i1/TB1TXTMdKP2gK0jSZFoSuuuIVXa.jpg"] 2020-05-26 10:28:40.378 29080-484/com.taobao.taobao I/Xposed: YYY-ALog-D: awcn.SessionCenter---getInternal---21646297---["u","https://img.alicdn.com/bao/uploaded/i1/TB1TXTMdKP2gK0jSZFoSuuuIVXa.jpg","sessionType","LongLink","timeout",0] 2020-05-26 10:28:40.385 29080-881/com.taobao.taobao I/Xposed: YYY-ALog-D: awcn.SessionCenter---getInternal---21646297---["u","https://img.alicdn.com/bao/uploaded/i1/TB1TXTMdKP2gK0jSZFoSuuuIVXa.jpg","sessionType","LongLink","timeout",3000] 2020-05-26 10:28:43.391 29080-881/com.taobao.taobao I/Xposed: YYY-ALog-E: awcn.SessionCenter---[Get]timeout exception---21646297---{"stackTrace":[],"suppressedExceptions":[]}---["url","https://img.alicdn.com/bao/uploaded/i1/TB1TXTMdKP2gK0jSZFoSuuuIVXa.jpg"] 2020-05-26 10:28:43.391 29080-881/com.taobao.taobao I/Xposed: YYY-ALog-D: awcn.SessionCenter---getInternal---21646297---["u","https://img.alicdn.com/bao/uploaded/i1/TB1TXTMdKP2gK0jSZFoSuuuIVXa.jpg","sessionType","ShortLink","timeout",0] 2020-05-26 10:28:44.377 29080-29994/com.taobao.taobao I/Xposed: YYY-ALog-E: anet.Repeater---[traceId:Xr65l7NnDDkDAKlUmeifke5K15904601190960813129080]end, [RequestStatistic]ret=1,statusCode=200,msg=SUCCESS,bizId=null,host=img.alicdn.com,ip=103.15.99.106,port=443,protocolType=https,retryTime=0,retryCostTime=0,processTime=3528,connWaitTime=3007,cacheTime=0,sendDataTime=191,firstDataTime=149,recDataTime=134,lastProcessTime=0,oneWayTime=4003,callbackTime=0,serverRT=0,sendSize=0,recDataSize=220008,originalDataSize=220008,extra={"firstIp":"103.15.99.106"},isReqSync=false,isReqMain=false,url=https://img.alicdn.com/bao/uploaded/i1/TB1TXTMdKP2gK0jSZFoSuuuIVXa.jpg---DGRD306---[] 2020-05-26 10:28:44.430 29080-475/com.taobao.taobao I/Xposed: YYY-ALog-E: analysis.FullTraceAnalysis---FullTraceStatistic|Xr65l7NnDDkDAKlUmeifke5K15904601190960813129080|https://img.alicdn.com/bao/uploaded/i1/TB1TXTMdKP2gK0jSZFoSuuuIVXa.jpg|img.alicdn.com|picture|null|wifi|https|0|1||1|0|0|0|0|1590412176629|1590411949467|1590412200433|3|com.taobao.tao.TBMainActivity|1590460108261|0||1590460119095|1590460119096|1590460120366|1590460120368|1590460123884|1590460123896|1590460124371|1590460124371|1590460124371|1590460124371|1590460124238|1590460124424|1590460124425|1590460124426|0|0|220008|220008|0|191|149|134|42|null|0|0|{}|200|---null---[]重點來了,分析ALog日志
分析ALog的日志可以發現,其基本的請求過程是:
-
建立一個UnifiedRequestTask任務,其內部是對一系列網絡請求、性能統計等操作的封裝
-
接著交給SessionCenter建立或獲取可用的Spdy連接
-
最后調用Repeater,生成TraceId,并進行數據上報
根據上面的Log,可以發現建立Spdy連接的過程,又分為以下幾個:
首先會建立長連接,超時時間為0
建立超時時間為3秒的長連接
建立短連接
然而出現這種情況的原因,看樣子是長連接無法建立,導致了TimeoutException,從而走了兜底邏輯,觸發建立了短連接,最終還是能把圖片顯示出來
由于長連接的超時時間定義為3秒,所以在Repeater的數據中,如果發生了TimeoutException,那么connWaitTime基本保持在3000ms以上
而正常的情況,大多數應該為0,或者1000ms之內
所以一開始白圖,等一會兒能看到圖的時候,基本都在3s以后了,再加上一些排隊策略和解碼,10s以后都是有可能的……
那么為什么不直接都走短鏈接?做過網絡性能優化的,這個問題肯定不難解釋
最關鍵的原因還是為了加速
眾所周知TCP連接的建立過程,需要經過3握手,短連接頻繁建立、斷開TCP連接,增加了很多額外的開銷,影響客戶端請求速度的同時,會額外增加服務器的壓力
然而不建立固定連接的Quic,目前來說,并不成熟,使用長連接Spdy,對阿里這種公司來說,無非是目前最優的一種解決方案
在ANetwork庫中,也可以看到Quic的相關實現,可以說淘寶在這方面還是緊跟時代潮流的,且幾種連接機制都可以比較靈活地切換,最終還有普通的HTTP作為兜底策略,可以兼顧性能和穩定
為什么北京電信慢?
在測試過程中,我使用了電信、聯通、移動的網絡,其中只有北京電信的寬帶、蜂窩能夠出現TimeoutException,其他家的網絡都是正常的,且connWaitTime值始終處于較低的水平
所以推測出以下幾點原因:
北京電信網絡對長連接支持較差,可能會導致無法連接的情況
淘寶的圖片CDN服務(包括heic.alicdn.com,img.alicdn.com,gw.alicdn.com等域名),北京電信線路優化上可能出現了問題
淘寶App內部的連接池可能需要改進,處理一些類似的極端Case
總結
通過這次對淘寶App的逆向,不但讓我對淘寶App加載圖片的過程有了一定了解和認識,也讓我學習了不少優秀的技術思想,這里列舉一些
-
InstantPatcher
猜測為淘寶的熱修復方案,對Java采取所有方法插樁的方式,App內每個方法第一行代碼都時一個IpChange對象,用來做熱修復
-
完善的統計機制
圖片框架Phenix、網絡框架ANetwork,其內部都有完善、詳細的性能及業務錯誤等信息的統計方法,便于進行性能評估和錯誤信息追蹤
-
統一任務調度管理器
不知道猜的對不對,淘寶內部包含一個名為rxm的庫,猜測是類似RxJava的實現,用來實現異步任務、UI回調、優先級調度等功能,可以實現統一的調度管理
-
HEIC圖片
目前淘寶的內部很多圖片都使用了HEIC格式,并且App內部還內置了NDK解碼庫,HEIC比WebP更省流,在省流方面,淘寶一直做的都比較先進,很久之前就應用了WebP,且CDN可以非常方便地支持各類格式轉碼、壓縮等
-
ARM64支持
Google Play現在正在推進64位so支持,而很多App還停留在v7,甚至v5的級別,淘寶則是增加了ARMv8a的so支持,雖然so的體積幾乎大了一倍,但是在如今64位手機CPU已經普及的情況下,換來的性能和體驗提升,這么看來還是非常值得的
最后,感謝淘寶沒有做過分的混淆和防Hook,可以讓我豐富視野、學習進步!
后記
目前,我已經聯系在淘寶工作的同學進行反饋,并把我抓到的日志發給了他們,據說他們內部的同學也可以復現類似的情況,也正在跟進處理中
但是截止我寫這篇文章時,這個異常還是沒有完全得到解決,使用北京電信網絡刷淘寶時,白圖的情況時好時壞,部分CDN資源還是有超時現象,希望淘寶的同學們加把勁,早日解決這一問題,讓我們北京電信用戶能夠更愉快地剁手!哈哈哈
總結
以上是生活随笔為你收集整理的淘宝系App图片为什么在北京电信网络加载这么慢?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 杭电LCY-ACM算法入门习题(01-0
- 下一篇: 磊科路由器信号按键_磊科无线路由器参数设