2020 Android 面試重難點(萬字篇)
-
onStop:變得不可見,被下一個Activity覆蓋了(onPause和onStop的區別是否可見)
-
onDestroy:這是Activity被幹掉前最後一個被呼叫方法了,可能是外面類呼叫finish方法或者是系統為了節省空間將它暫時性的幹掉,可以用isFinishing()來判斷它,如果你有一個ProgressDialog線上程中轉動,請在onDestroy裡把它cancel掉,不然等執行緒結束的時候,呼叫Dialog的cancel會丟擲異常的。
onPause,onstop,onDestroy,三種狀態下,Activity都有可能被系統幹掉。
啟動另一個Activity然後finish,先呼叫舊Activity的onPause方法,然後呼叫新的Activity和onCreate->onStart->onResume方法,然後呼叫舊Activity的onStop->onDestroy方法。
如果沒有呼叫finish那麼onDestroy方法不會被呼叫,而且在onStop之前還會呼叫onSavedInstanceState方法
onRestart方法執行完了之後還會呼叫onStart方法
fragment:[SupportFragmentManager,childFragment]
service:
[Android Service的生命週期]
[android-Service和Thread的區別]
Service和Intent Service:沒啥區別,只是IntentService在onCreate方法中開啟新的HandlerThread去執行。
Service執行的程序和執行緒:當它執行的時候如果是LocalService,那麼對應的Service是執行在主程序的main執行緒上的。如onCreate,onStart這些函式都是在系統呼叫的時候在主程序的main執行緒上執行的。如果是RemoteSevice,那麼對應的Service則是執行在獨立的main執行緒上。
-
服務不是單一的程序,服務沒有自己的程序,應用程式可以不同,服務執行在相同的程序中
-
服務不是執行緒,可以線上程中工作
-
在應用中,如果是長時間的在後臺執行,而且不需要互動的情況下,使用服務
-
同樣是在後臺執行,不需要互動的情況下,如果只是完成某個任務,之後就不需要執行,而且可能是多個任務,需要長時間執行的情況下使用執行緒
-
如果任務佔用CPU時間多,資源大的情況下,要使用執行緒
Thread的執行是獨立於Activity的,也就是說當一個Activity被finish之後,如果你沒有主動停止Thread或者Thread裡的run方法沒有執行完畢的話,Thread就會一直執行。
[](
)8.View繪畫機制
View的繪製主要涉及三個方法:onMeasure()、onLayout()、onDraw()
-
onMeasure主要用於計算view的大小,onLayout主要用於確定view在ContentView中的位置,onDraw主要是繪製View。
-
在執行onMeasure()、onLayout()方法時都會通過相應的標誌位或者對應的座標點來判斷是否需要執行對應的函式,如我們經常呼叫的invalidate方法就只會執行onDraw方法,因為此時的檢視大小和位置均未發生變化,除非呼叫requestLayout方法完整強制進行view的繪製,從而執行上面三個方法。
進度條元件:
[ProgressView]
[AnnotationView]
[](
)9.事件傳遞機制
[android 事件處理機制總結,ScrollView ViewPager ListView GridView巢狀小結]
當手指觸控到螢幕時,系統就會呼叫相應View的onTouchEvent,並傳入一系列的action。
dispatchTouchEvent的執行順序為:
-
首先觸發ACTIVITY的dispatchTouchEvent,然後觸發ACTIVITY的onUserInteraction
-
然後觸發LAYOUT的dispatchTouchEvent,然後觸發LAYOUT的onInterceptTouchEvent
這就解釋了重寫ViewGroup時必須呼叫super.dispatchTouchEvent();
(1)dispatchTouchEvent:
此方法一般用於初步處理事件,因為動作是由此分發,所以通常會呼叫super.dispatchTouchEvent。這樣就會繼續呼叫onInterceptTouchEvent,再由onInterceptTouchEvent決定事件流向。
(2)onInterceptTouchEvent:
若返回值為true事件會傳遞到自己的onTouchEvent();若返回值為false傳遞到下一個View的dispatchTouchEvent();
(3)onTouchEvent():
若返回值為true,事件由自己消耗,後續動作讓其處理;若返回值為false,自己不消耗事件了,向上返回讓其他的父View的onTouchEvent接受處理
三大方法關係的虛擬碼:如果當前View攔截事件,就交給自己的onTouchEvent去處理,否則就丟給子View繼續走相同的流程。
public boolean dispatchTouchEvent(MotionEvent ev)
{
boolean consume = false;
if(onInterceptTouchEvent(ev))
{
consume = onTouchEvent(ev);
}
else
{
consume = child.dispatchTouchEvent(ev);
}
return consume;
}
onTouchEvent的傳遞:
當有多個層級的View時,在父層級允許的情況下,這個action會一直傳遞直到遇到最深層的View。所以touch事件最先呼叫的是最底層View的onTouchEvent,如果View的onTouchEvent接收到某個touch action並做了相應處理,最後有兩種返回方式return true和return false;return true會告訴系統當前的View需要處理這次的touch事件,以後的系統發出的ACTION_MOVE,ACTION_UP還是需要繼續監聽並接收的,並且這次的action已經被處理掉了,父層的View是不可能觸發onTouchEvent的了。所以每一個action最多隻能有一個onTouchEvent介面返回true。如果返回false,便會通知系統,當前View不關心這一次的touch事件,此時這個action會傳向父級,呼叫父級View的onTouchEvent。但是這一次的touch事件之後發出任何action,該View都不在接受,onTouchEvent在這一次的touch事件中再也不會觸發,也就是說一旦View返回false,那麼之後的ACTION_MOVE,ACTION_UP等ACTION就不會在傳入這個View,但是下一次touch事件的action還是會傳進來的。
父層的onInterceptTouchEvent
前面說了底層的View能夠接收到這次的事件有一個前提條件:在父層允許的情況下。假設不改變父層級的dispatch方法,在系統呼叫底層onTouchEvent之前會呼叫父View的onInterceptTouchEvent方法判斷,父層View是否要截獲本次touch事件之後的action。如果onInterceptTouchEvent返回了true,那麼本次touch事件之後的所有action都不會向深層的View傳遞,統統都會傳給父層View的onTouchEvent,就是說父層已經截獲了這次touch事件,之後的action也不必詢問onInterceptTouchEvent,在這次的touch事件之後發出的action時onInterceptTouchEvent不會再被呼叫,直到下一次touch事件的來臨。如果onInterceptTouchEvent返回false,那麼本次action將傳送給更深層的View,並且之後的每一次action都會詢問父層的onInterceptTouchEvent需不需要截獲本次touch事件。只有ViewGroup才有onInterceptTouchEvent方法,因為一個普通的View肯定是位於最深層的View,只有ViewGroup才有onInterceptTouchEvent方法,因為一個普通的View肯定是位於最深層的View,touch能夠傳到這裡已經是最後一站了,肯定會呼叫View的onTouchEvent()。
底層View的getParent().requestDisallowInterceptTouchEvent(true)
對於底層的View來說,有一種方法可以阻止父層的View獲取touch事件,就是呼叫getParent().requestDisallowInterceptTouchEvent(true)方法。一旦底層View收到touch的action後呼叫這個方法那麼父層View就不會再呼叫onInterceptTouchEvent了,也無法截獲以後的action(如果父層ViewGroup和最底層View需要截獲不同焦點,或不同手勢的touch,不能使用這個寫死)。
曾經開發過程中遇到的兩個示例:左邊是處理ViewPager和ListView的衝突,紀錄水平和垂直方向的偏移量,如果水平方向的偏移更多的話就讓ViewPager處理pager滑動
右邊處理的ViewPager和ImageBanner的滑動衝突,同樣是紀錄偏移量,如果發生在ImageBanner上的水平偏移量大於垂直偏移量的話就讓banner滾動
想想為什麼右邊是重寫dispatchTouchEvent方法而不是onInterceptTouchEvent方法?
FixedViewPager
@Override
public boolean onInterceptTouchEvent(MotionEvent ev)
{
switch(ev.getAction() & MotionEvent.ACTION_MASK)
{
case MotionEvent.ACTION_DOWN:
mX = ev.getX();
mY = ev.getY();
break;
case MotionEvent.ACTION_MOVE:
float x = ev.getX();
float y = ev.getY();
float dX = x - mX;
float dY = y - mY;
float tmp = Math.abs(dX) / Math.abs(dY);
mX = x;
mY = y;
if(tmp > 1)
{
return true;
}
else
{
return super.omInterceptTouchEvent(ev);
}
}
}
FixedImageLoadBanner
@override
public boolean dispatchTouchEvent(MotionEvent ev)
{
if(mX != 0 || mY != 0)
{
float dY = ev.getRawY() - mY;
float dX = ev.getRawX() - mX;
if(Math.abs(dY) > Math.abs(dX))
{
requestDisallowInterceptTouchEvent(false);
}
else
{
requestDisallowInterceptTouchEvent(true);
}
}
mX = ev.getRawX();
mY = ev.getRawY();
return super.dispatchTouchEvent(ev);
}
[](
)10.ART和Dalvik區別
-----------------------------------------------------------------------------
art上應用啟動快,執行快,但是耗費更多儲存空間,安裝時間長,總的來說ART的功效就是”空間換時間”。
ART: Ahead of Time Dalvik: Just in Time
什麼是Dalvik:Dalvik是Google公司自己設計用於Android平臺的Java虛擬機器。Dalvik虛擬機器是Google等廠商合作開發的Android移動裝置平臺的核心組成部分之一,它可以支援已轉換為.dex(即Dalvik Executable)格式的Java應用程式的執行,.dex格式是專為Dalvik應用設計的一種壓縮格式,適合記憶體和處理器速度有限的系統。Dalvik經過優化,允許在有限的記憶體中同時執行多個虛擬機器的例項,並且每一個Dalvik應用作為獨立的Linux程序執行。獨立的程序可以防止在虛擬機器崩潰的時候所有程式都被關閉。
什麼是ART:Android作業系統已經成熟,Google的Android團隊開始將注意力轉向一些底層元件,其中之一是負責應用程式執行的Dalvik執行時。Google開發者已經花了兩年時間開發更快執行效率更高更省電的替代ART執行時。ART代表Android Runtime,其處理應用程式執行的方式完全不同於Dalvik,Dalvik是依靠一個Just-In-Time(JIT)編譯器去解釋位元組碼。開發者編譯後的應用程式碼需要通過一個直譯器在使用者的裝置上執行,這一機制並不高效,但讓應用能更容易在不同硬體和架構上執行。ART則完全改變了這套做法,在應用安裝的時候就預編譯位元組碼到機器語言,這一機制叫Ahead-Of-Time(AOT)編譯。在移除解釋程式碼這一過程後,應用程式執行將更有效率,啟動更快。
ART優點:
1. 系統性能的顯著提升
2. 應用啟動更快、執行更快、體驗更流暢、觸感反饋更及時。
3. 更長的電池續航能力
4. 支援更低的硬體
ART缺點:
1. 更大的儲存空間佔用,可能會增加10%-20%
2. 更長的應用安裝時間
Scroller執行流程裡面的三個核心方法
1. mScroller.startScroll()
2. mScroller.computeScrollOffset()
3. view.computeScroll()
1、在mScroller.startScroll()中為滑動做了一些初始化準備,比如:起始座標,滑動的距離和方向以及持續時間(有預設值),動畫開始時間等。
2、mScroller.computeScrollOffset()方法主要是根據當前已經消逝的時間來計算當前的座標點。因為在mScroller.startScroll()中設定了動畫時間,那麼在computeScrollOffset()方法中依據已經消逝的時間就很容易得到當前時刻應該所處的位置並將其儲存在變數mCurrX和mCurrY中。除此之外該方法還可判斷動畫是否已經結束。
![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy8yNDI0NDMxMy0zMjUxZDY1NzBhZmU1Y2I3LnBuZw?x-oss-process=image/format,png)
[](
)12.Activity Manager Service, ActivityThread
---------------------------------------------------------------------------------------------------------
[](
)13.Android幾種程序
----------------------------------------------------------------------------
1. 前臺程序: 即與使用者正在互動的Activity或者Activity用到的Service等,如果系統記憶體不足時前臺程序是最後被殺死的
2. 可見執行緒:可以是處於暫停狀態(onPause)的Activity或者繫結在其上的Service,即被使用者可見,但由於失去了焦點而不能與使用者互動
3. 服務程序:其中執行著使用startService方法啟動的Service,雖然不被使用者可見,但是卻是使用者關係的,例如使用者正在非音樂介面聽的音樂或者正在非下載頁面自己下載的檔案等,當系統要用空間執行前兩者程序時才會被終止
4. 後臺程序:其中執行著執行onStop方法而停止的程式,但是卻不是使用者當前關心的,例如後臺掛著的QQ,這樣的程序系統一旦沒有記憶體就首先被殺死。
5. 空程序:不包含任何應用程式的程式元件的程序,這樣的程序系統是一般不會讓他存在的。
如何避免後臺程序被殺死?
1. 呼叫startForegound,讓你的Service所在的程序成為前臺程序
2. Service的onStartCommand返回START\_STICKY或START\_REDELIVER\_INTENT
3. Service的onDestroy裡面重新啟動自己
[](
)14.Activity啟動模式
-----------------------------------------------------------------------------
standard:Activity的預設載入方式,該方法會通過跳轉到一個新的Activity,同時將該例項壓入到棧中(不管該Activity是否已經存在在Task棧中,都是採用new操作,生命週期從onCreate()開始)。例如:棧中順序是A B C D,此時D通過Intent跳轉到A,那麼棧中結構就變成A B C D A,點選返回按鈕的顯示順序是D C B A,依次摧毀。
singleTop:singleTop模式下,當前Activity D位於棧頂的時候,如果通過Intent跳轉到它本身的Activity(D),那麼不會重新建立一個新的D例項(走onNewIntent()),所以棧中的結構依次為A B C D,如果跳轉到B,那麼由於B不處於棧頂,所以會新建一個B例項並壓入到棧中,結構就變成了A B C D B。應用例項:三條推送,點進去都是一個Activity,這肯定用singletop
singleTask:singleTask模式下,Task棧中只能有一個對應的Activity例項。例如:Task棧1中結構為:A B C D。此時D通過Intent跳轉到B(走onNewIntent()),則棧的結構變成了:A,B。其中的C和D被棧彈出銷燬了,也就是說位於B之上的例項都被銷燬了。通常應用於首頁,首頁肯定在棧底部,也只能在棧底部。
singleInstance:singleInstance模式下,會將開啟的Activity壓入一個新的任務棧中。例如:Task棧1中結構為:A B C,C通過Intent跳轉到了D(D的模式為singleInstance),那麼則會新建一個Task,棧1中結構依舊為A B C,棧2中結構為D。此時螢幕顯示D,之後D通過Intent跳轉到D,棧2不會壓入新的D,所以兩個棧中的情況沒發生改變。如果D跳轉到了C,那麼就會根據C對應的launchMode在棧1中進行對應的操作,C如果為standard,那麼D跳轉到C,棧1的結構為A B C C ,此時點選返回按鈕,還是在C,棧1的結構變為A B C,而不會回到D。
launchMode為singleTask的時候,通過Intent啟動到一個Activity,如果系統已經存在一個例項,系統就會將請求傳送到這個例項上,但這個時候,系統就不會再呼叫通常情況下我們處理請求資料的onCreate方法,而不是呼叫onNewIntent方法。
onSavedInstanceState的呼叫遵循一個重要原則,即當系統”未經你許可”時銷燬了你的Activity,則onSavedInstanceState會被系統呼叫,這時系統的責任,因為它必須要提供一個機會讓你儲存你的資料,至於onRestoreInstanceState方法,需要注意的是,onSavedInstanceState方法和onRestoreInstanceState方法”不一定”是成對呼叫的。
onRestoreInstanceState被呼叫的前提是,Activity A確實被系統銷燬了,而如果僅僅是停留在有這種可能性的情況下,則該方法不會被呼叫,例如,當正在顯示Activity A的時候,使用者按下HOME鍵回到主介面,然後使用者緊接著又返回到Activity A,這種情況下Activity A一般不會因為記憶體的原因被銷燬,故Activity的onRestoreInstanceState方法不會被執行。
另外,onRestoreInstanceStated的bundle引數也會傳遞到onCreate方法中,你也可以選擇在onCreate方法中做資料還原。
onSavedInstanceState(Bundle bundle)通常和onRestoreInstanceState(Bundle bundle)不會成對出現,onRestoreInstanceState這玩意不太好觸發,給大家提個好辦法,橫豎屏切換的時候100%會觸發。然後儲存在onRestoreInstanceState bundle裡面的資料,就是onCreate的那個引數bundle啦,要怎麼恢復就看開發者了。
[](
)15.TMImageView圖片庫的設計
----------------------------------------------------------------------------------
Feature機制
[](
)16.ListView優化
---------------------------------------------------------------------------
1. 首先,雖然大家都知道,還是提一下,利用好convertView來重用View,切忌每次getView都新建。ListView的核心原理就是重用View。ListView有一個回收器,Item滑出介面的時候view就會回收這裡,需要顯示新的Item的時候,就儘量重用回收器裡面的View。
2. 利用好ViewType,例如你的ListView中有幾個型別的Item,需要給每個型別建立不同的View,這樣有利於ListView的回收,當然型別不能太多
3. 儘量讓ItemView的Layout層次結構簡單,這時所有Layout都必須遵守的
4. 善用自定義View,自定義View可以有效的減小Layout的層級,而且對繪製過程可以很好的控制
5. 儘量保證Adapter的hasStableIds()返回true,這樣在notifyDataSetChanged()的時候,如果id不變,listView將不會重新繪製這個View,達到優化的目的。
6. 每個item不能太高,特別是不要超出螢幕的高度,可以參考Facebook的優化方法,把特別複雜的Item分解為若干個小的Item.
7. 為了保證ListView滑動的流暢性,getView()中要做盡量少的事情,不要有耗時的操作。特別是滑動的時候不要載入圖片,停下來再載入。
8. 使用RecyclerView代替。ListView每次更新資料都要notifyDataSetChanged(),有些太暴力了。RecyclerView在效能和可定製性上都有很大的改善,推薦使用。
9. 有時候,需要從根本上考慮,是否真的要使用listView來實現你的需求,或者是否有其他選擇?
[](
)17.webView
------------------------------------------------------------------------
如何使用webview在js中呼叫java方法?
webView.addJavaScriptInterface(new Object(){xxx}, "xxx");
答案:可以使用WebView控制元件執行JavaScript指令碼,並且可以在JavaScript中執行Java程式碼。要想讓WebView控制元件執行JavaScript,需要呼叫WebSettings.setJavaScriptEnabled方法,程式碼如下:
WebView webView = (WebView)findViewById(R.id.webview)
WebSettings webSettings = webView.getSettings()
//設定WebView支援JavaScript
webSettings.setJavaScriptEnabled(true)
webView.setWebChromeClient(new WebChromeClient())
JavaScript呼叫Java方法需要使用WebView.addJavascriptInterface方法設定JavaScript呼叫的Java方法,程式碼如下:
webView.addJavascriptInterface(new Object()
{
public String process(String value)
{
return result;
}
}, "demo");
可以使用下面的JavaScript程式碼呼叫process方法,程式碼如下:
function search()
{
result.innerHTML = "" + window.demo.process('data') + "";
}
[](
)18.SurfaceView和View的最本質的區別
----------------------------------------------------------------------------------------
SurfaceView是在一個新起的單獨執行緒中可以重新繪製畫面,而view必須在UI的主執行緒中更新畫面。
在UI的主執行緒中更新畫面可能會引發問題,比如你更新的時間過長,那麼你的主UI執行緒就會被你正在畫的函式阻塞。那麼將無法響應按鍵、觸屏等訊息。當使用SurfaceView由於是在新的執行緒中更新畫面所以不會阻塞你的UI主執行緒。但這也帶來了另外一個問題,就是事件同步。比如你觸屏了一下,你需要SurfaceView中thread處理,一般就需要有一個event queue的設計來儲存touchevent,這會稍稍複雜一點,因為涉及到執行緒安全。
[](
)19.標籤
-------------------------------------------------------------------
\[merge和include\]
\[Android ViewStub的基本使用\]
簡言之,都是用來解決重複佈局的問題,但是標籤能夠在佈局重用的時候減少UI層級結構。
viewStub標籤是用來給其他的View事先佔據好位置,當需要的時候用inflater()或者是setVisible()方法顯示這些View。
[](
)20.ANR排錯
----------------------------------------------------------------------
1、ANR排錯一般有三種類型
1. KeyDispatchTimeout(5 seconds) –主要型別按鍵或觸控事件在特定時間內無響應
2. BroadcastTimeout(10 secends) –BroadcastReceiver在特定時間內無法處理完成
3. ServiceTimeout(20 secends) –小概率事件 Service在特定的時間內無法處理完成
2、如何避免
1. UI執行緒儘量只做跟UI相關的工作
2. 耗時的操作(比如資料庫操作,I/O,連線網路或者別的有可能阻塞UI執行緒的操作)把它放在單獨的執行緒處理
3. 儘量用Handler來處理UIthread和別的thread之間的互動
3、如何排查
1. 首先分析log
2. 從trace.txt檔案檢視呼叫stack,adb pull data/anr/traces.txt ./mytraces.txt
3. 看程式碼
4. 仔細檢視ANR的成因(iowait?block?memoryleak?)
4、監測ANR的Watchdog
[](
)21.fragment生命週期
-----------------------------------------------------------------------------
\[圖片上傳中…(image-4517e1-1597835878604-0)\]
常見面試問題\=========
**1、橫豎屏切換時候Activity的生命週期**
1. 不設定Activityd餓android:configChanges時,切屏會重新掉喲過各個生命週期,切橫屏時會執行一次,切豎屏時會執行兩次
2. 設定Activity的android:configChanges=”orientation”時,切屏還是會呼叫各個生命週期,切換橫豎屏只會執行一次
3. 設定Activity的android:configChanges=”orientation|keyboardHidden”時,切屏不會重新呼叫各個生命週期,只會執行onConfigurationChanged方法
**2、(圖片相關的)OOM怎麼處理**
\[Android 記憶體溢位解決方案(OOM) 整理總結\]
1. 在記憶體引用上做些處理,常用的有軟引用、弱引用
2. 在記憶體中載入圖片時直接在記憶體中作處理,如:邊界壓縮
3. 動態回收記憶體
4. 優化Dalvik虛擬機器的堆記憶體分配
5. 自定義堆記憶體大小
**3、介面優化**
\[Android開發——效能優化之如何防止過度繪製\]
太多重疊的背景(overdraw)
這個問題其實最容易解決,建議就是檢查你在佈局和程式碼中設定的背景,有些背景是隱藏在底下的,它永遠不可能顯示出來,這種沒必要的背景一定要移除,因為它很可能會嚴重影響到app的效能。如果採用的是selector的背景,將normal狀態的color設定為”@android:color/transparent”,也同樣可以解決問題。
太多重疊的View
第一個建議是 :是喲過ViewStub來載入一些不常用的佈局,它是一個輕量級且預設是不可見的檢視,可以動態的載入一個佈局,只要你用到這個重疊著的View的時候才載入,推遲載入的時間。
第二個建議是:如果使用了類似Viewpager+Fragment這樣的組合或者有多個Fragment在一個介面上,需要控制Fragment的顯示和隱藏,儘量使用動態的Inflation view,它的效能要比SetVisibility好。
複雜的Layout層級
這裡的建議比較多一些,首先推薦使用Android提供的佈局工具Hierarchy Viewer來檢查和優化佈局。第一個建議是:如果巢狀的線性佈局加深了佈局層次,可以使用相對佈局來取代。第二個建議是:用標籤來合併佈局。第三個建議是:用標籤來重用佈局,抽取通用的佈局可以讓佈局的邏輯更清晰明瞭。記住,這些建議的最終目的都是使得你的Layout在Hierarchy Viewer裡變得寬而淺,而不是窄而深。
**4、移動端獲取網路資料優化的幾個點**
1. 連線複用:節省連線建立時間,如開啟 keep-alive。
對於Android來說預設情況下HttpURLConnection和HttpClient都開啟了keep-alive。只是2.2之前HttpURLConnection存在影響連線池的Bug,具體可見:Android HttpURLConnection及HttpClient選擇
2. 請求合併:即將多個請求合併為一個進行請求,比較常見的就是網頁中的CSS Image Sprites。如果某個頁面內請求過多,也可以考慮做一定的請求合併。
3. 減少請求資料的大小:對於post請求,body可以做gzip壓縮的,header也可以做資料壓縮(不過只支援http 2.0)。
4. 返回資料的body也可以做gzip壓縮,body資料體積可以縮小到原來的30%左右。(也可以考慮壓縮返回的json資料的key資料的體積,尤其是針對返回資料格式變化不大的情況,支付寶聊天返回的資料用到了)
5. 根據使用者的當前的網路質量來判斷下載什麼質量的圖片(電商用的比較多)