Android性能相關--經驗篇

##基礎知識

  • 底層觸發回收機制時機:

GC_FOR_MALLOC 堆上分配對象時內存不足觸發
GC_CONCURRENT 堆內存達到一定量(即快滿了)時觸發
GC_EXPLICIT 主動觸發,System.gc、VMRuntime.gc或收到SIGUSR1信號
~
GC日誌如下
D/dalvikvm( 7030) : GC_CONCURRENT feed 1049K,60% free 2341K/9351K ,external 3502K/6261K,paused 3ms 3ms
~
GC_CONCURRENT是GC類型,此外還有GC_FOR_MALLOC、GC_EXTERNAL_ALLOC、GC_HPROF_DUMO_HEAP、GC_EXPLICIT
feed 1049K 表示這次GC回收了多少內存
60% free 2341K/9351K 描述Heap內存的信息,本次回收後60%的Heap可用,存活對象大小爲2341K,Heap大小爲9351K
external 3502K/6261K 描述Native Momory信息(存放位圖數據或堆外內存), 表示已經分配了3502K內存,當分配到6261K的時候會觸發下一次GC
paused 3ms 3ms 表示GC暫停時間

  • UI卡頓:60fps=1000ms / 16ms
    0~100 毫秒的延遲會讓用戶感知到瞬時的卡頓;
    100~300 毫秒的延遲會讓用戶感覺遲緩;
    300~1000 毫秒的延遲讓用戶感覺“手機卡死了”;
    1000 毫秒以上的延遲會讓用戶想去幹別的事情

  • 硬件加速(GPU)的繪製效率遠高於軟件繪製(CPU),但存在以下缺陷:耗電高、某些接口不兼容、內存佔用大(Open GL至少需要8M內存)

  • 引用(強軟弱虛)
    軟引用:垃圾收集器運行時可能會也可能不會釋放軟引用,但在保證虛擬機內存不足之時會清理它
    弱引用:與軟引用的區別是,弱引用生命週期更短,且無論虛擬機內存是否不足都會被清理
    虛引用:只能用於跟蹤即將對被引用對象進行的收集,在任何時候都可能被垃圾回收器回收

  • ANR:
    1.主線程5s內沒有處理完輸入事件
    2.主線程10s沒處理完BroadCastReceiver.onReceive
    3.主線程在Service各生命週期函數時20s沒處理完

ANR IN :發生ANR的具體類
PID:發生ANR的進程,系統會在此時生成trace文件
Reason:當前ANR的類型以及導致ANR的原因
CPU usage:CPU的使用情況,在ANR發生前後都會產生一個CPU usage一共打印兩次CPU使用情況

  • OOM:試圖申請的內存+已分配的內存>虛擬機允許的最大內存
    內存泄漏:無用對象持續佔用內存或得不到及時釋放

案例:https://tech.meituan.com/oom_analysis.html

  • App閃退常見原因:NullPointer、OOM、數組越界、類型轉換異常、數字轉換異常、Activity/Service找不到、實體對象沒有序列化等

  • Force Close常見原因:Error、OOM(內存溢出)、StackOverFlowError、RuntimeException空指針異常

  • Native 層Crash
    SIGILL 執行了非法指令、可執行文件本身出錯、堆棧溢出等情況
    SIGABRT 調用abort函數生成
    SIGBUS 訪問非法地址,包括內存地址對齊出錯
    SIGFPE 算法運算錯誤,如溢出及除0等
    SIGSEGV 訪問不屬於自己的存儲空間或訪問只讀存儲空間
    SIGPIPE 管道異常,這個信號通常在進程間通信時產生


磁盤性能問題

性能瓶頸:磁盤的順序讀寫基本速度都非常快了,問題基本都在隨機讀寫

  1. 隨機讀因爲沒有預讀(read-ahead)的優化性能差很多
  2. 隨機寫會造成大量的失效頁和寫入放大問題
  3. 數據庫的性能問題,本質上也是IO性能問題

常見問題:

  • 卡頓
  • ANR
    • IO Wait很高的時候,很可能主線程執行了耗時IO操作
    • 大量的數據讀寫、數據庫操作、硬件操作也可能造成ANR

Tips:寫入放大情景:我們知道磁盤一塊有512k,塊中每頁4k,當需要寫入4k時系統恰好找到一個512k塊中有一頁4k的無效頁。那麼系統會將這512k塊數據讀出再擦除這512k塊,之後再把讀出的數據+要寫入的4k數據寫入這個512k的塊中。這樣原本的寫入4k變成了【閃存讀512k】-【緩存改4k】-【閃存擦除512k】-【閃存寫入512k】。寫入放大實驗:1、正常寫入1M文件,記錄耗時。2、先用6k的小文件寫滿存儲,再刪除(系統標記爲無效頁,並未真正刪除類似垃圾桶功能),這樣保證存儲中沒有乾淨的存儲塊,最好寫入1M的文件,記錄耗時,兩相對比。

工具推薦:

  1. Systrace:IO操作耗時過長問題
  2. Strict Mode:主線程IO問題
  3. IO Monitor:主線程IO問題、多餘IO操作、Buffer過小問題
  4. SQL IO Monitor:主線程IO問題、數據庫IO問題(全表掃描、不合理事務等造成)

優化建議:

  1. 通過緩存避免重複讀寫次數(變量保存、延遲寫入等)
  2. IO操作、數據庫操作儘量放入子線程
  3. ByteArrayOutputStream > ObjectOutputStream
  4. 合理設置Buffer大小
  5. 加解壓 ZipFile + BufferedOutputStream;ZipInputStream適合網絡數據解壓、損壞壓縮包解壓、大量的小文件加解壓
  6. 避免頻繁開關數據庫,數據庫打開後等到真正不需要再關閉或應用退出再關閉
  7. Bitmap的加載,decodeStream + BufferedInputStream > decodeFile;decodeResource > decodeResourceStream
  8. SP的commit都是一次文件的打開讀寫關閉,儘量在必要時在進行;同時用apply代替commit
  9. 索引能加快查詢速度,但同樣能導致插入更新緩慢,需合理使用
  10. Buffer的讀寫每次的緩存數據使用8K,可以提供一定的效率

內存性能問題

性能瓶頸:

  1. OOM、Low Memory Killer會殺死進程,GC會導致App卡頓
  2. Activity泄露問題極爲嚴重,通常有mContext間接引用造成、this$0間接引用造成、Activity直接引用造成

常見問題:

  • 內存泄露 OOM(Crash)
    • 隨着功能的反覆執行,Heap內存一直在持續增長。通常是內存泄露,適合用LeakCannary工具進行白盒測試分析
    • 每次啓動應用後,Heap內存相比以前版本穩定增長。通常出現在啓動後待機或使用某功能後,可能是由新功能及代碼改動引入固定內存增長。使用用Heap Dump進行多版本或功能使用前後的對比
    • Heap Alloc變化不大,但進程的Dalvik Heap Pss內存明顯增加。通常由於分配了大量小對象造成的內存碎片。
  • GC引起的卡頓
    • 大量的new臨時變量或new Bitmap等大內存等操作需要注意
    • 代碼執行時出現頻繁GC,Heap Alloc內存大幅度波動。即內存抖動,通常是分配了許多臨時變量或數組還有AsyncTask,隨後被迅速回收。適合使用Heap Viewer/Allocation Tracker等工具查看具體分配的對象

工具推薦:

  1. top/procrank :內存佔用過大、內存泄
  2. meminfo : Native內存泄漏、是否存在Activity/ApplicationContext泄漏、數據庫緩存命中率低
  3. MAT/Finder/JHAT : Java層的重複內存、不合理圖片解碼、內存泄漏
  4. libc_malloc_deBug_leak.so : Ntive內存泄漏
  5. LeakCannary/StrictMode/LeakInspector : Activity內存泄漏
  6. 騰訊APT : 內存佔用過大、內存泄漏
  7. GC Log from Logcat/GC Log 生成圖表 : 人工觸發GC for Explicit而導致的卡頓,Heap內存不足觸發GC for Alloc 而導致的卡頓
  8. Systrace : GC導致的卡頓
  9. Allocation Tracer : 申請內存次數過多和過大,輔助定位GC Log發現的問題
  10. chrome devtool : HS的內存問題

優化建議:

  1. 使用輕量級的數據結構 ArrayMap、SparseArray
  2. 儘量避免使用Enum枚舉(枚舉最大優勢是類型安全,但佔用內存比普通定義常量大得多,可用註解來檢查類型安全);儘量避免基本類型的拆裝箱(Integer、Long 、Float…)
  3. String的拼接使用StringBuilder
  4. 內存對象的重複利用:對象池;利用系統自帶的資源;StringBuilder、StringBuffer
  5. 儘量不要再循環中創建大量的臨時變量–內存抖動
  6. 引入SDK庫和調用新的系統API需要考慮成本
  7. dex文件有很多優化空間,如仔細統計並調整dex文件的順序往往能節約1M以上的dex mmap內存

【內存泄露】

  1. this$0 間接引用 導致 Activity內存泄漏;
    解決方案:在Activity關閉時在onDestroy中,解除內部類和外部類的引用關係或使用弱引用
  2. Thread 直接引用 導致Activity內存泄漏
    解決方案:使用完解除Thread和Activity的引用關係,即Thread使用完移除Activity或使用弱引用
  3. mContext 間接引用 導致Activity內存泄漏
    解決方案:使用後解除mContext引用或使用弱引用
  4. this$0 間接引用 導致Activity內存泄漏
    解決方案:用static來截斷匿名類引用,或使用弱引用
  5. 匿名內部類/非靜態內部類(尤其是放在Activity中的):非靜態內部類會持有外部類的引用,若非靜態內部類的實例是靜態的則其生命週期也是跟app一樣長,導致外部類(尤其爲Activity)無法釋放
    解決方案:可將內部類置爲靜態類則不會持有外部類引用,或內部類設計成單例,傳入資源用弱引用包裹,Context用ApplicationContext或用後置空
  6. 單例、Application的生命週期跟app一樣長,要注意其所持有的對象無法釋放。eg:單例對象持有Activity的mContext對象導致Activity無法釋放。
  7. 非View類Context儘量使用ApplicationContext替代(startActivity、Layout.Inflation、Dialog必須使用Activity的Context)
  8. Timer、Handler、AsyncTask等異步操作,導致 Activity內存泄漏
    解決方案:onDestroy進行關閉,清除。handler爲內部類會持有外部類(尤其Activity)的引用,而activity結束時若handler的消息還沒處理完則導致內存泄漏【handler置爲靜態類,且內部若持有外部對象則置爲弱引用WeakReference】
  9. 常駐內存問題,相同的圖片保存一份,去重
  10. 避免靜態變量持有大量數據引用。靜態變量不會被回收,長期維持對象的引用阻止垃圾回收,如果持有對象是Bitmap等大數據對象就很容造成內存不足
  11. 注意資源/監聽器/Cursor對象/Recerver對象/Sensor對象/File對象等的關閉、註銷、移除,臨時資源主動回收(加載後忘記釋放的Bitmap、臨時生成的Byte數組和文件緩衝區)。注意需要close後置空,並且注意try-catch中使用的資源在finally中進行關閉處理。

內部類的正確用法(Handler爲例)

private static Class InnerHandler extends Handler{
	private final WeakReference<HandlerActivity> mActivity;
	public InnerHandler(HandlerActivity activity){
		mActivity = activity;
	}
	@Override
	public void handleMessage(){
		HandlerActivity activity = mActivity.get();
		if(activity != null){...}
	}
}

單例中使用Context的正確方式

public class SingleInstance{
	private Context mContext;
	private static SingleInstance sInstance;
	private SingleInstance(Context context){
		mContext = context;
	}
	public static SingleInsatance getInstance(Context context){
		if(sInstance == null){
			sInstance = new SingleInstance(context.getApplicationContext());
		}
		return sInstance;
	}
}

【圖片優化】

  • 格式:WebP(Android4.0+才支持) > JPEG(不支持透明) > PNG
  • 圖片壓縮:ImageAlpha有損壓縮、TinyPNG有損壓縮、ImageOptim無損壓縮
  • ARGB565/ARGB4444/ALPHA_8的使用
  • 改變圖片大小:inSampleSize指數冪的縮放,inScaled、inDensity、inTargetDensity更爲細緻
BitmapFactory.Options options = new BitmapFractory.Options();
//inSample縮放
options.inSampleSize = 4;
//inScaled、inDensity、inTargetDensity組合
options.inScaled = true;
options.inDensity = srcWidth;
options.inTargetDensity = dstWidth;
BitmapFactory.decodeStream(is,null,optins);
  • inBitmap複用內存
  • 不用的使用recycle
  • 三級緩存

【WebView內存問題】
WebView一經加載其內存不可釋放,除非進程死亡;而且WebView有很多坑可能導致應用崩潰。可將WebView放在一個單獨的進程中,通過AIDL與主進程進行通信。WebView進程關閉即可銷燬內存,但不宜每次退出都進行關閉,因爲進程的啓動和銷燬也是耗時操作,每次進出都進行新建銷燬進程會導致卡頓問題,需要有一個策略來處理。

CPU性能問題

性能瓶頸:

  1. CPU資源冗餘使用,由於算法問題、重複查、排序、刪除等浪費CPU資源
  2. CPU資源爭搶,搶主線程的CPU資源,搶音視頻的CPU資源,多線程平等互爭資源
  3. CPU資源利用率低,主要在於磁盤IO、網絡IO、鎖操作、sleep操作等導致CPU無法得到有效利用,空跑。

常見問題:

  • 卡頓
    • 檢查UI線程是否有可能有耗時的操作
    • overdraw 佈局層數是否太多?
    • 動畫是否層數太多?是否過於複雜?是否onDraw調用次數不合理頻繁觸發measure、layout、onDraw
  • ANR(Crash)
    • 查看CPU是否很忙,是否因爲其他進程或子線程佔用CPU時間片過多導致主線程時間片過少
    • 死鎖、其他線程持有鎖,導致主線程等待超時

工具推薦:

  1. adb命令

    adb shell top 列出進程的各種數據
    adb shell ps 處理進程的身份標識
    cat /proc/pid/stat 查看CPU信息
    adb shell dumpsys cpuinfo 獲取PCU信息

  2. PerfBox : FPS、Activity啓動速度
  3. Systrace:分析繪製時流程導致的卡頓,能大約定位是GC、IO、貼圖太大,還是沒用ViewHolder的問題
  4. Traceview:能深入定位分析各種流暢度與時延問題,但時只能初步定位XML佈局和OpenGL繪製的性能問題
  5. Trepn
  6. Gfxinfo/Slickr : 定位硬件加速下的性能問題
  7. Hierarchy Viewer : 定位XML佈局導致的性能問題
  8. Tracer for Open GL/Adreno/UXTune : 具體定位繪製性能問題
  9. Chrome DevTool : 定位具體H5卡頓問題

優化建議:

  1. CPU資源爭搶,減少非核心需求的CPU消耗,或降低非核心線程的優先級
  2. 優化算法,避免重複浪費CPU資源
  3. 將CPU消耗轉換爲GPU消耗(使用RenderScript、OpenGL來進行復雜的繪製)
  4. 使用合適的數據類型(int、float、long),使用合適的容器(ArrayMap、SparseArray、ConcurrentHashMap)
  5. 使用緩存和預處理來提升算法效率,(緩存提升效率其實是內存空間換時間)
  6. 使用多線程提高效率的時候注意選擇合適的線程數

【流暢性–佈局優化】

  • 儘量使用RelativeLayout和LinearLayout佈局,不用AbsoluteLayout。考慮用RelativeLayout代替LinearLayout減少佈局層次,同層級則建議用LinearLayout(性能略高)
  • include 重用佈局,include使用其他屬性的時候需要設置width、height屬性
  • ViewStub view的延遲加載,推遲佈局加載。當佈局需要顯示的時候纔會去加載佈局(佈局的顯隱我們經常用setVisible來搞定,其實用ViewStub的inflation來控制顯示性能會好一些,但不適合需要進行顯隱切換的佈局)
  • merge 減少不必要的層級,多用來替代FrameLayout
  • 去掉多餘的背景顏色(包括Activity背景燈),減少過度繪製
<item name="android:windowNoTitle">true</item>
<item name="android:windowBackground">@null</item>
  • 圖標+文字的佈局可以考慮TextView的compound drawables來實現,會比TextView+ImageView更高效一些
  • 使用layout_weight屬性會導致LinearLayout繪製的時候需要被測量兩次,影響性能,尤其在ListView/GridView的子Item中尤其明顯。此外weight+weight_sum屬性可以用來減少佈局層數的情況下可以考慮使用。
  • 文字有多種樣式可以考慮Html.fromHtml()和Spannable來實現,而不是寫多個TextView拼接
  • 佈局複雜導致層次多可以考慮用自定義ViewGroup來實現,可以考慮用LayoutAnimationController實現佈局動畫

【流暢性–ListView/RecyclerView優化】

  • 複用convertView,即HoldView的使用
  • 異步加載圖片
  • 快速滑動時SCROLL_STATE_FLING不顯示/加載圖片或消耗資源較大的view,停止後處於SCROLL_STATE_IDLE和SCROLL_STATE_TOUCH_SCROLL再進行加載顯示
  • item儘可能地減少使用控件和佈局層次,儘可能地複用控件,這樣可以減少ListView的內存使用,減少滑動時gc次數。ListView的背景色與cacheColorHint設置相同顏色可以提高滑動時的渲染性能
  • getView不做複雜的邏輯計算

網絡性能問題

網絡有三種狀態 FullPower全力運行、Low Power低狀態、Standby等待狀態,特點:升到Full快,降下來慢
這裏寫圖片描述

性能瓶頸:

  1. 業務成功率
  2. 業務網絡延遲。DNS部分可以採用IP直連、域名重用、HttpDNS等方式;TCP短連接會導致每次連接都發生三次握手,可利用長連接
  3. 業務帶寬成本。減少用戶使用app的帶寬成本,不必要的網絡請求放在wifi環境下等策略

工具推薦:

  1. Wireshark – 網絡性能問題分析定位
  2. fiddler – 主要針對Http,能模擬Http的錯誤和延時返回
  3. SPDY/HTTP2.0/QUIC – 網絡協議,利用FastTcpOpen減少握手次數,利用UDP更好地適應網絡抖動
  4. WebPageTest.org – 做web應用的數據上報,提供LoadTime、StartRender、SpeedIndex、DOM Elements等耗時
  5. ATC – 最專業的弱網絡模擬工具,能模擬窄帶寬、延時、丟包、損壞包、包亂序等情況

優化建議:

  1. Webview或一些實時性不強的網絡採用網絡緩存;
    解決方案:設置HTTP的Cache-control使得其保存資源文件;public/private/no-cache/no-store/max-age/max-stale
    然後設置HTTP請求時的緩存模式;LOAD_CACHE_ONLY/LOAD_DEFAULT/LOAD_CACHE_NORMAL/LOAD_NO_CACHE/LOAD_CACHE_ELSE_NETWORK
  2. 大數據網絡傳輸可先進行壓縮下載後再進行解壓使用
  3. 調整服務器RcvBuffer的MSS優化【分片策略】,緩解網絡延遲
  4. 超時和重試,單個請求設置超時時間,適當考慮失敗重試。分片下載,下載失敗只重新獲取失敗部分
  5. 可考慮避免DNS解析直接用IP
  6. 根據業務合併網絡請求或預先獲取網絡數據
  7. CDN的使用
  8. 儘量合併非必要實時請求,控制請求的頻率。
  9. 數據壓縮:POST的Body採用GZip;請求頭壓縮採用SPDY和HTTP2.0直接 壓縮

電量

組成App電量消耗各部分定義:
● CPU的電量消耗
● wake lock的電量消耗
● 數據傳輸的電量消耗
● WIFI運行的電量消耗
● GPS的電量消耗
● other sensors的電量消耗
● 蜂窩通信電量消耗
● 屏幕電量消耗
● 信號電量消耗
● WIfi電量消耗
● CPU空閒時電量消耗
● 藍牙模塊電量消耗

推薦工具:

  • adb shell dumpsys batteryinfo
  • PowerStat2.0(騰訊)
  • Battery Historian 2.0(Google 需要Android5.0以上系統)
    獲取方式:
    adb shell dumpsys batterystats --enable full-wake-history
    adb shell dumpsys batterystats --reset
    adb bugreport > bugreport.txt
    將txt轉成html可視化,historian.py從https://github.com/google/battery-historian下載
    python historian.py -a bugreport.txt > bugreport.html

優化建議:
1、網絡方面
a、發起網絡請求時機,把可以延遲執行的網絡請求延後合併發起,減少網絡Http建立次數
b、減少移動網絡被激活時間和次數:發現返回數據相同時,延遲下次請求 時間。避免頻繁間隔請求,採用批處理一次性請求。使用預存取技術提取獲取一些數據避免後面頻繁再次發起網絡請求
c、數據處理:網絡 傳輸進行壓縮。進行大數據下載用GZIP下載。使用高效率的數據格式和解析方法,推薦JSON和Protobuf
d、慎用輪訓方式去執行網絡請求
e、減少推送消息的次數和頻率
f、網絡狀態,處理具體業務前,養成判斷當前網絡 狀態的習慣
2、界面相關
a、離開界面後停止對應的耗電活動
b、應用進入後臺禁止異常耗電
3、定位相關
a、使用GPS後記得關閉
b、定位要求不高時用網絡定位代替GPS定位
c、慎用持續定位
d、慎用被動定位

Android6.0之後的兩種省電模式Doze 和 App Standby
Doze模式進入:未連接電源 + 屏幕關閉
Doze模式的五種狀態:

  1. ACTIVE:手機處於激活狀態
  2. INACTIVE:手機剛脫離激活狀態
  3. IDLE_PENDING:IDLE預備態
  4. IDLE:空閒狀態
  5. IDLE_MAINTENANCE:處理掛起任務
    IDLE狀態下的限制:
    ● 斷開網絡連接
    ● 忽略Wake Lock
    ● 標準鬧鐘AlarmManager定時任務延遲
    ● 系統不會掃描熱點WIFI
    ● 禁止同步工作
    ● 停止JobScheduler任務調度

Apk瘦身

Analyze Apk:將APK拖入Android Studio即可觀察當前包的結構及各部分佔用的大小
這裏寫圖片描述

這裏寫圖片描述

瘦身建議:

  • 資源壓縮,字體壓縮FontZip、解壓庫AndrodiUn7Zip
  • 利用Proguard來做代碼混淆、除去冗餘代碼、無用代碼冗餘資源
  • 一般圖片資源只做一套高清圖,個別有問題再用9png解決,最後纔會根據不同分辨率做不同的圖的方案
  • 圖片格式的選擇以及壓縮
  • redex 字節碼優化,可以在優化包Size的同時提高字節碼加載性能,提示App速度。主要優化項目有減少和壓縮字符串、消除冗餘代碼、內聯函數、Interdex(一種冷啓動方法)
  • 對最終的Apk包進行7Zip極限壓縮

啓動速度

冷啓動:App進程還沒有創建的情況下啓動App
熱啓動:App啓動時,後臺已有App進程(後臺掛起),eg:按Home鍵退出App
溫啓動:介於冷啓動和溫啓動之間,eg:系統由於某種原因回收App,用戶重新啓動App等

啓動時間度量:
adb shell am start -W pkg_name/activity 打印【TotalTime】 應用啓動耗時 ,包括 新進程啓動、Applicaton初始化、Activity啓動時間;【ThisTime】一般和Total Time相同,除非應用啓動時開了一個透明的Activity做預處理再加載主Activity;【WaitTime】總的耗時,包括啓動耗時和前一個應用Activity pause時間

優化建議:

  1. 做的初始化 工作儘量減輕,採用延遲加載、異步加載或懶加載(按需加載),attachBaseContext、onCreate中不做耗時操作
  2. Activity要進行 佈局 優化;避免加載 或編碼Bitmap,依賴Bitmap的View延遲更新 ;避免硬盤或網絡操作 阻塞UI繪製;避免在UI線程 中進行資源初始化,可延遲或異步處理
  3. 爲Activity設置windowBackground,在View加載完成前可以顯示這張 背景logo

Android性能優化典範
http://hukai.me/android-performance-patterns/
http://hukai.me/android-performance-patterns-season-2/
http://hukai.me/android-performance-patterns-season-3/
http://hukai.me/android-performance-patterns-season-4/
http://hukai.me/android-performance-patterns-season-5/
http://hukai.me/android-performance-patterns-season-6/
Android內存優化之OOM
http://hukai.me/android-performance-oom/
Android性能優化之電量篇
http://hukai.me/android-performance-battery/
Android性能優化之內存篇
http://hukai.me/android-performance-memory/
Android性能優化之運算篇
http://hukai.me/android-performance-compute/
Android性能優化之渲染篇
http://hukai.me/android-performance-render/

發佈了48 篇原創文章 · 獲贊 8 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章