Android性能優化以內存優化

導語

智能手機發展到今天已經有十幾個年頭,手機的軟硬件都已經發生了翻天覆地的變化,特別是Android陣營,從一開始的一兩百M到今天動輒4G,6G內存。然而大部分的開發者觀看下本身的異常上報系統,仍是會發現各類內存問題仍然層出不窮,各類OOM爲crash率貢獻很多。Android開發發展到今天也是已經比較成熟,各類新框架,新技術也是層出不窮,而內存優化一直都是Android開發過程一個不可避免的話題。 剛好最近作了內存優化相關的工做,這裏也對Android內存優化相關的知識作下總結。php

在開始文章以前推薦下公司同事翻譯整理版本《Android性能優化典範 - 第6季》,由於篇幅有限這裏我對一些內容只作簡單總結,同時若是有不正確內容也麻煩幫忙指正。html

本文將會對Android內存優化相關的知識進行總結以及最後案例分析(一二部分是理論知識總結,你也能夠直接跳到第三部分看案例):java

1、 Android內存分配回收機制
二 、Android常見內存問題和對應檢測,解決方式。
3、 JOOX內存優化案例
四 、總結android

工欲善其事必先利其器,想要優化App的內存佔用,那麼仍是須要先了解Android系統的內存分配和回收機制。git

一 ,Android內存分配回收機制

參考Android 操做系統的內存回收機制[1],這裏簡單作下總結:程序員

從宏觀角度上來看Android系統能夠分爲三個層次
1. Application Framework,
2. Dalvik 虛擬機
3. Linux內核。github

這三個層次都有各自內存相關工做:面試

1. Application Framework

Anroid基於進程中運行的組件及其狀態規定了默認的五個回收優先級:算法

  • Empty process(空進程)
  • Background process(後臺進程)
  • Service process(服務進程)
  • Visible process(可見進程)
  • Foreground process(前臺進程)

系統須要進行內存回收時最早回收空進程,而後是後臺進程,以此類推最後纔會回收前臺進程(通常狀況下前臺進程就是與用戶交互的進程了,若是連前臺進程都須要回收那麼此時系統幾乎不可用了)。數據庫

由此也衍生了不少進程保活的方法(提升優先級,互相喚醒,native保活等等),出現了國內各類全家桶,甚至各類殺不死的進程。

Android中由ActivityManagerService 集中管理全部進程的內存資源分配。

2. Linux內核

參考QCon大會上阿里巴巴的Android內存優化分享[2],這裏最簡單的理解就是ActivityManagerService會對全部進程進行評分(存放在變量adj中),而後再講這個評分更新到內核,由內核去完成真正的內存回收(lowmemorykiller, Oom_killer)。這裏只是大概的流程,中間過程仍是很複雜的,有興趣的同窗能夠一塊兒研究,代碼在系統源碼ActivityManagerService.java中。

3. Dalvik虛擬機

Android進程的內存管理分析[3],對Android中進程內存的管理作了分析。

Android中有Native Heap和Dalvik Heap。Android的Native Heap言理論上可分配的空間取決了硬件RAM,而對於每一個進程的Dalvik Heap都是有大小限制的,具體策略能夠看看android dalvik heap 淺析[4]。

Android App爲何會OOM呢?其實就是申請的內存超過了Dalvik Heap的最大值。這裏也誕生了一些比較」黑科技」的內存優化方案,好比將耗內存的操做放到Native層,或者使用分進程的方式突破每一個進程的Dalvik Heap內存限制。

Android Dalvik Heap與原生Java同樣,將堆的內存空間分爲三個區域,Young Generation,Old Generation, Permanent Generation。

最近分配的對象會存放在Young Generation區域,當這個對象在這個區域停留的時間達到必定程度,它會被移動到Old Generation,最後累積必定時間再移動到Permanent Generation區域。系統會根據內存中不一樣的內存數據類型分別執行不一樣的gc操做。

GC發生的時候,全部的線程都是會被暫停的。執行GC所佔用的時間和它發生在哪個Generation也有關係,Young Generation中的每次GC操做時間是最短的,Old Generation其次,Permanent Generation最長。

GC時會致使線程暫停,致使卡頓,Google在新版本的Android中優化了這個問題, 在ART中對GC過程作了優化揭祕 ART 細節 —— Garbage collection[5],聽說內存分配的效率提升了10倍,GC的效率提升了2-3倍(可見原來效率有多低),不過主要仍是優化中斷和阻塞的時間,頻繁的GC仍是會致使卡頓。

上面就是Android系統內存分配和回收相關知識,回過頭來看,如今各類手機廠商鼓吹人工智能手機,號稱18個月不卡頓,越用越快,其實很大一部分Android系統的內存優化有關,無非就是利用一些比較成熟的基於統計,機器學習的算法定時清理數據,清理內存,甚至提早加載數據到內存。

二 ,Android常見內存問題和對應檢測,解決方式

1. 內存泄露

不止Android程序員,內存泄露應該是大部分程序員都遇到過的問題,能夠說大部分的內存問題都是內存泄露致使的,Android裏也有一些很常見的內存泄露問題[6],這裏簡單羅列下:

  • 單例(主要緣由仍是由於通常狀況下單例都是全局的,有時候會引用一些實際生命週期比較短的變量,致使其沒法釋放)
  • 靜態變量(一樣也是由於生命週期比較長)
  • Handler內存泄露[7]
  • 匿名內部類(匿名內部類會引用外部類,致使沒法釋放,好比各類回調)
  • 資源使用完未關閉(BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap)

對Android內存泄露業界已經有不少優秀的組件其中LeakCanary最爲知名(Square出品,Square可謂Android開源界中的業界良心,開源的項目包括okhttp, retrofit,otto, picasso, Android開發大神Jake Wharton就在Square),其原理是監控每一個activity,在activity ondestory後,在後臺線程檢測引用,而後過一段時間進行gc,gc後若是引用還在,那麼dump出內存堆棧,並解析進行可視化顯示。使用LeakCanary能夠快速地檢測出Android中的內存泄露。

正常狀況下,解決大部份內存泄露問題後,App穩定性應該會有很大提高,可是有時候App自己就是有一些比較耗內存的功能,好比直播,視頻播放,音樂播放,那麼咱們還有什麼能作的能夠下降內存使用,減小OOM呢?

2. 圖片分辨率相關

分辨率適配問題。不少狀況下圖片所佔的內存在整個App內存佔用中會佔大部分。咱們知道能夠經過將圖片放到hdpi/xhdpi/xxhdpi等不一樣文件夾進行適配,經過xml android:background設置背景圖片,或者經過BitmapFactory.decodeResource()方法,圖片實際上默認狀況下是會進行縮放的。在Java層實際調用的函數都是或者經過BitmapFactory裏的decodeResourceStream函數

public static Bitmap decodeResourceStream(Resources res, TypedValue value,
        InputStream is, Rect pad, Options opts) {

    if (opts == null) {
        opts = new Options();
    }

    if (opts.inDensity == 0 && value != null) {
        final int density = value.density;
        if (density == TypedValue.DENSITY_DEFAULT) {
            opts.inDensity = DisplayMetrics.DENSITY_DEFAULT;
        } else if (density != TypedValue.DENSITY_NONE) {
            opts.inDensity = density;
        }
    }

    if (opts.inTargetDensity == 0 && res != null) {
        opts.inTargetDensity = res.getDisplayMetrics().densityDpi;
    }

    return decodeStream(is, pad, opts);
}

decodeResource在解析時會對Bitmap根據當前設備屏幕像素密度densityDpi的值進行縮放適配操做,使得解析出來的Bitmap與當前設備的分辨率匹配,達到一個最佳的顯示效果,而且Bitmap的大小將比原始的大,能夠參考下騰訊Bugly的詳細分析Android 開發繞不過的坑:你的 Bitmap 究竟佔多大內存?。

關於Density、分辨率、-hdpi等res目錄之間的關係:

舉個例子,對於一張1280×720的圖片,若是放在xhdpi,那麼xhdpi的設備拿到的大小仍是1280×720而xxhpi的設備拿到的多是1920×1080,這兩種狀況在內存裏的大小分別爲:3.68M和8.29M,相差4.61M,在移動設備來講這幾M的差距仍是很大的。

儘管如今已經有比較先進的圖片加載組件相似Glide,Facebook Freso, 或者老牌Universal-Image-Loader,可是有時就是須要手動拿到一個bitmap或者drawable,特別是在一些可能會頻繁調用的場景(好比ListView的getView),怎樣儘量對bitmap進行復用呢?這裏首先須要明確的是對一樣的圖片,要 儘量複用,咱們能夠簡單本身用WeakReference作一個bitmap緩存池,也能夠用相似圖片加載庫寫一個通用的bitmap緩存池,能夠參考GlideBitmapPool[8]的實現。

咱們也來看看系統是怎麼作的,對於相似在xml裏面直接經過android:background或者android:src設置的背景圖片,以ImageView爲例,最終會調用Resource.java裏的loadDrawable:

Drawable loadDrawable(TypedValue value, int id, Theme theme) throws NotFoundException {

    // Next, check preloaded drawables. These may contain unresolved theme
    // attributes.
    final ConstantState cs;
    if (isColorDrawable) {
        cs = sPreloadedColorDrawables.get(key);
    } else {
        cs = sPreloadedDrawables[mConfiguration.getLayoutDirection()].get(key);
    }

    Drawable dr;
    if (cs != null) {
        dr = cs.newDrawable(this);
    } else if (isColorDrawable) {
        dr = new ColorDrawable(value.data);
    } else {
        dr = loadDrawableForCookie(value, id, null);
    }

    ...

    return dr;
}

能夠看到實際上系統也是有一份全局的緩存,sPreloadedDrawables, 對於不一樣的drawable,若是圖片時同樣的,那麼最終只會有一份bitmap(享元模式),存放於BitmapState中,獲取drawable時,系統會從緩存中取出這個bitmap而後構造drawable。而經過BitmapFactory.decodeResource()則每次都會從新解碼返回bitmap。因此其實咱們能夠經過context.getResources().getDrawable再從drawable裏獲取bitmap,從而複用bitmap,然而這裏也有一些坑,好比咱們獲取到的這份bitmap,假如咱們執行了recycle之類的操做,可是假如在其餘地方再使用它是那麼就會有」Canvas: trying to use a recycled bitmap android.graphics.Bitmap」異常。

3. 圖片壓縮

BitmapFactory 在解碼圖片時,能夠帶一個Options,有一些比較有用的功能,好比:

  • inTargetDensity 表示要被畫出來時的目標像素密度
  • inSampleSize 這個值是一個int,當它小於1的時候,將會被當作1處理,若是大於1,那麼就會按照比例(1 / inSampleSize)縮小bitmap的寬和高、下降分辨率,大於1時這個值將會被處置爲2的倍數。例如,width=100,height=100,inSampleSize=2,那麼就會將bitmap處理爲,width=50,height=50,寬高降爲1 / 2,像素數降爲1 / 4
  • inJustDecodeBounds 字面意思就能夠理解就是隻解析圖片的邊界,有時若是隻是爲了獲取圖片的大小就能夠用這個,而沒必要直接加載整張圖片。
  • inPreferredConfig 默認會使用ARGB_8888,在這個模式下一個像素點將會佔用4個byte,而對一些沒有透明度要求或者圖片質量要求不高的圖片,可使用RGB_565,一個像素只會佔用2個byte,一下能夠省下50%內存。
  • inPurgeableinInputShareable 這兩個須要一塊兒使用,BitmapFactory.java的源碼裏面有註釋,大體意思是表示在系統內存不足時是否能夠回收這個bitmap,有點相似軟引用,可是實際在5.0之後這兩個屬性已經被忽略,由於系統認爲回收後再解碼實際會反而可能致使性能問題
  • inBitmap 官方推薦使用的參數,表示重複利用圖片內存,減小內存分配,在4.4之前只有相同大小的圖片內存區域能夠複用,4.4之後只要原有的圖片比將要解碼的圖片大既能夠複用了。

4. 緩存池大小

如今不少圖片加載組件都不只僅是使用軟引用或者弱引用了,實際上相似Glide 默認使用的事LruCache,由於軟引用 弱引用都比較難以控制,使用LruCache能夠實現比較精細的控制,而默認緩存池設置太大了會致使浪費內存,設置小了又會致使圖片常常被回收,因此須要根據每一個App的狀況,以及設備的分辨率,內存計算出一個比較合理的初始值,能夠參考Glide的作法。

5. 內存抖動

什麼是內存抖動呢?Android裏內存抖動是指內存頻繁地分配和回收,而頻繁的gc會致使卡頓,嚴重時還會致使OOM。

一個很經典的案例是string拼接建立大量小的對象(好比在一些頻繁調用的地方打字符串拼接的log的時候), 見Android優化之String篇[9]。

而內存抖動爲何會引發OOM呢?

主要緣由仍是有由於大量小的對象頻繁建立,致使內存碎片,從而當須要分配內存時,雖然整體上仍是有剩餘內存可分配,而因爲這些內存不連續,致使沒法分配,系統直接就返回OOM了。

好比咱們坐地鐵的時候,假設你沒帶公交卡去坐地鐵,地鐵的售票機就只支持5元,10元,而哪怕你這個時候身上有1萬張1塊的都沒用(是否是以爲很反人類..)。固然你能夠去兌換5元,10元,而在Android系統裏就沒那麼幸運了,系統會直接拒絕爲你分配內存,並扔一個OOM給你(有人說Android系統並不會對Heap中空閒內存區域作碎片整理,待驗證)。

其餘

經常使用數據結構優化,ArrayMap及SparseArray是android的系統API,是專門爲移動設備而定製的。用於在必定狀況下取代HashMap而達到節省內存的目的,具體性能見HashMap,ArrayMap,SparseArray源碼分析及性能對比[10],對於key爲int的HashMap儘可能使用SparceArray替代,大概能夠省30%的內存,而對於其餘類型,ArrayMap對內存的節省實際並不明顯,10%左右,可是數據量在1000以上時,查找速度可能會變慢。

枚舉,Android平臺上枚舉是比較爭議的,在較早的Android版本,使用枚舉會致使包過大,在個例子裏面,使用枚舉甚至比直接使用int包的size大了10多倍 在stackoverflow上也有不少的討論, 大體意思是隨着虛擬機的優化,目前枚舉變量在Android平臺性能問題已經不大,而目前Android官方建議,使用枚舉變量仍是須要謹慎,由於枚舉變量可能比直接用int多使用2倍的內存。

ListView複用,這個你們都知道,getView裏儘可能複用conertView,同時由於getView會頻繁調用,要避免頻繁地生成對象

謹慎使用多進程,如今不少App都不是單進程,爲了保活,或者提升穩定性都會進行一些進程拆分,而實際上即便是空進程也會佔用內存(1M左右),對於使用完的進程,服務都要及時進行回收。

儘可能使用系統資源,系統組件,圖片甚至控件的id

減小view的層級,對於能夠 延遲初始化的頁面,使用viewstub

數據相關:序列化數據使用protobuf能夠比xml省30%內存,慎用shareprefercnce,由於對於同一個sp,會將整個xml文件載入內存,有時候爲了讀一個配置,就會將幾百k的數據讀進內存,數據庫字段儘可能精簡,只讀取所需字段。

dex優化,代碼優化,謹慎使用外部庫, 有人以爲代碼多少於內存沒有關係,實際會有那麼點關係,如今稍微大一點的項目動輒就是百萬行代碼以上,多dex也是常態,不只佔用rom空間,實際上運行的時候須要加載dex也是會佔用內存的(幾M),有時候爲了使用一些庫裏的某個功能函數就引入了整個龐大的庫,此時能夠考慮抽取必要部分,開啓proguard優化代碼,使用Facebook redex使用優化dex(好像有很多坑)。

三 案例

JOOX是IBG一個核心產品,2014年發佈以來已經成爲5個國家和地區排名第一的音樂App。東南亞是JOOX的主要發行地區,實際上這些地區仍是有不少的低端機型,對App的進行內存優化勢在必行。

上面介紹了Android系統內存分配和回收機制,同時也列舉了常見的內存問題,可是當咱們接到一個內存優化的任務時,咱們應該從何開始?下面是一次內存優化的分享。

1. 首先是解決大部份內存泄露。

無論目前App內存佔用怎樣,理論上不須要的東西最好回收,避免浪費用戶內存,減小OOM。實際上自JOOX接入LeakCanary後,每一個版本都會作內存泄露檢測,通過幾個版本的迭代,JOOX已經修復了幾十處內存泄露。

2. 經過MAT查看內存佔用,優化佔用內存較大的地方。

JOOX修復了一系列內存泄露後,內存佔用仍是居高不下,只能經過MAT查看究竟是哪裏佔用了內存。關於MAT的使用,網上教程無數,簡單推薦兩篇MAT使用教程[11],MAT - Memory Analyzer Tool 使用進階[12]。

點擊Android Studio這裏能夠dump當前的內存快照,由於直接經過Android Sutdio dump出來的hprof文件與標準hprof文件有些差別,咱們須要手動進行轉換,利用sdk目錄/platform-tools/hprof-conv.exe能夠直接進行轉換,用法:hprof-conv 原文件.hprof 新文件.hprof。只須要輸入原文件名還有目標文件名就能夠進行轉換,轉換完就能夠直接用MAT打開。

下面就是JOOX打開App,手動進行屢次gc的hprof文件。

這裏咱們看的是Dominator Tree(即內存裏佔用內存最多的對象列表)。

  • Shallo Heap:對象自己佔用內存的大小,不包含其引用的對象內存。
  • Retained Heap: Retained heap值的計算方式是將retained set中的全部對象大小疊加。或者說,因爲X被釋放,致使其它全部被釋放對象(包括被遞歸釋放的)所佔的heap大小。

第一眼看去 竟然有3個8M的對象,加起來就是24M啊 這究竟是什麼鬼?

咱們經過List objects->with incoming references查看(這裏with incoming references表示查看誰引用了這個對象,with outgoing references表示這個對象引用了誰)

經過這個方式咱們看到這三張圖分別是閃屏,App主背景,App抽屜背景。

這裏其實有兩個問題:

  • 這幾張圖原圖實際都是1280x720,而在1080p手機上實測這幾張圖都縮放到了1920x1080
  • 閃屏頁面,其實這張圖在閃屏顯示事後應該能夠回收,可是由於歷史緣由(和JOOX的退出機制有關),這張圖被常駐在後臺,致使無謂的內存佔用。

優化方式:咱們經過將這三張圖從xhdpi挪動到xxhdpi(固然這裏須要看下圖片顯示效果有沒很大的影響),以及在閃屏顯示事後回收閃屏圖片。
優化結果:

從原來的8.29x3=24.87M 到 3.68x2=7.36M 優化了17M(有沒一種萬馬奔騰的感受。。可能有時費大力氣優化不少代碼也優化不了幾百K,因此不少狀況下內存優化時優化圖片仍是比較立竿見影的)。

一樣方式咱們發現對於一些默認圖,實際要求的顯示要求並不高(圖片相對簡單,同時大部分狀況下圖片加載會成功),好比下面這張banner的背景圖:

優化前1.6M左右,優化後700K左右。

同時咱們也發現了默認圖片一個其餘問題,由於歷史緣由,咱們使用的圖片加載庫,設置默認圖片的接口是須要一個bitmap,致使咱們原來幾乎每一個adapter都用BitmapFactory decode了一個bitmap,對同一張默認圖片,不但沒有複用,還保存了多份,不只會形成內存浪費,並且致使滑動偶爾會卡頓。這裏咱們也對默認圖片使用全局的bitmap緩存池,App全局只要使用同一張bitmap,都複用了同一份。

另外對於從MAT裏看到的圖片,有時候由於看不到在項目裏面對應的ID,會比較難確認究竟是哪一張圖,這裏stackoverflow上有一種方法,直接用原始數據經過GIM還原這張圖片。

這裏其實也看到JOOX比較吃虧一個地方,JOOX很多地方都是使用比較複雜的圖片,同時有些地方還須要模糊,動畫這些都是比較耗內存的操做,Material Design出來後,不少App都遵循MD設計進行改版,一般默認背景,默認圖片通常都是純色,不只App看起來比較明亮輕快,實際上也省了不少的內存,對此,JOOX後面對低端機型作了對應的優化。

3. 咱們也對Bugly上的OOM進行了分析,發現其實有些OOM是能夠避免的。

下面這個crash就是上面提到的在LsitView的adapter裏不停建立bitmap,這個地方是咱們的首頁banner位,理論上App一打開就會緩存這張默認背景圖片了,而實際在使用過一段時間後,才由於爲了解碼這張背景圖而OOM, 改成用全局緩存解決。

下面這個就是傳說中的內存抖動

實際代碼以下,由於打Log而進行了字符串拼接,一旦這個函數被比較頻繁地調用,那麼就頗有可能會發生內存抖動。這裏咱們新版本已經改成使用stringbuilder進行優化。

還有一些比較奇怪的狀況,這裏是咱們掃描歌曲文件頭的時候發生的,有些文件頭竟然有幾百M大,致使一次申請了過大的內存,直接OOM,這裏暫時也沒法修復,直接catch住out of memory error。

4. 同時咱們對一些邏輯代碼進行調整,好比咱們的App主頁的第三個tab(Live tab)進行了數據延遲加載,和定時回收。

這裏由於這個頁面除了有大圖還有輪播banner,實際強引用的圖片會有多張,若是這個時候切到其餘頁面進行聽歌等行爲,這個頁面一直在後臺緩存,實際是很浪費耗內存的,同時爲優化體驗,咱們又不能直接經過設置主頁的viewpager的緩存頁數,由於這樣常常都會回收,致使影響體驗,因此咱們在頁面不可見後過一段時間,清理掉adapter數據(只是清空adapter裏的數據,實際從網絡加載回來的數據還在,這裏只是爲了去掉界面對圖片的引用),當頁面再次顯示時再用已經加載的數據顯示,即減小了不少狀況下圖片的引用,也不影響體驗。

5. 最後咱們也遇到一個比較奇葩的問題,在咱們的Bugly上報上有這樣一條上報

咱們在stackoverflow上看到了相關的討論,大體意思是有些狀況下好比息屏,或者一些省電模式下,頻繁地調System.gc()可能會由於內核狀態切換超時的異常。這個問題貌似沒有比較好的解決方法,只能是優化內存,儘可能減小手動調用System.gc()

優化結果

咱們經過啓動App後,切換到個人音樂界面,停留1分鐘,屢次gc後,獲取App內存佔用

優化前:

優化後:

屢次試驗結果都差很少,這裏只截取了其中一次,有28M的優化效果。
固然不一樣的場景內存佔用不一樣,同時上面試驗結果是經過屢次手動觸發gc穩定後的結果。對於使用其餘第三方工具不手動gc的狀況下,試驗結果可能會差別比較大。

對於上面提到的JOOX裏各類圖片背景等問題,咱們作了動態的優化,對不一樣的機型進行優化,對特別低端的機型設置爲純色背景等方式,最終優化效果以下:

平均內存下降41M。

本次總結主要仍是從圖片方面下手,還有一點邏輯優化,已經基本達到優化目標。

四 總結

上面寫了不少,咱們能夠簡單總結,目前Andorid內存優化仍是比較重要一個話題,咱們能夠經過各類內存泄露檢測組件,MAT查看內存佔用,Memory Monitor跟蹤整個App的內存變化狀況, Heap Viewer查看當前內存快照, Allocation Tracker追蹤內存對象的來源,以及利用崩潰上報平臺從多個方面對App內存進行監控和優化。上面只是列舉了一些常見的狀況,固然每一個App功能,邏輯,架構也都不同,形成內存問題也是不盡相同,掌握好工具的使用,發現問題所在,才能對症下藥。

參考連接

1.Android 操做系統的內存回收機制
https://www.ibm.com/developerworks/cn/opensource/os-cn-android-mmry-rcycl/

2.阿里巴巴的Android內存優化分享
http://www.infoq.com/cn/presentations/android-memory-optimization

3.Android進程的內存管理分析
http://blog.csdn.net/gemmem/article/details/8920039

4.android dalvik heap 淺析
http://blog.csdn.net/cqupt_chen/article/details/11068129

5.揭祕 ART 細節 —— Garbage collection
http://www.cnblogs.com/jinkeep/p/3818180.html

6.Android性能優化之常見的內存泄漏
http://blog.csdn.net/u010687392/article/details/49909477

7.Android App 內存泄露之Handler
http://blog.csdn.net/zhuanglonghai/article/details/38233069

8.GlideBitmapPool
https://github.com/amitshekhariitbhu/GlideBitmapPool

9.Android 性能優化之String篇
http://blog.csdn.net/vfush/article/details/53038437

10.HashMap,ArrayMap,SparseArray源碼分析及性能對比
http://www.jianshu.com/p/7b9a1b386265

11.MAT使用教程
http://blog.csdn.net/itomge/article/details/48719527

12.MAT - Memory Analyzer Tool 使用進階
http://www.lightskystreet.com/2015/09/01/mat_usage/

相關文章
相關標籤/搜索