[Android 性能優化系列]內存之終極篇–降低你的內存消耗

大傢如果喜歡我的博客,請關註一下我的微博,請點擊這裡(http://weibo.com/kifile),謝謝

轉載請標明出處(http://blog.csdn.net/kifile),再次感謝

原文地址:http://developer.android.com/training/articles/memory.html

在接下來的一段時間裡,我會每天翻譯一部分關於性能提升的Android官方文檔給大傢

建議大傢在看本文之前先去我的博客看看

[Android
性能優化系列]內存之基礎篇–Android如何管理內存

[Android
性能優化系列]內存之提升篇–應用應該如何管理內存

題外話:

這一篇文章是Android官網內存部分的最後一節,看完瞭這個內存文檔之後,發現很多東西自己以前從來沒有差覺過,比如對象和類所消耗的內存空間,依賴庫的使用,多進程的使用等等。希望大傢能夠好好看看,多理解一下Android的內存機制,如果覺得我翻譯的不夠到位,自己希望看看官網的原文,可以點擊上面的鏈接去查看。

下面是本次的正文:

################

拒絕在 Bitmap 上浪費你的內存

當你加載一張 Bitmap 的時候,你可以選擇隻在你的確需要在屏幕上顯示的時候才將它加載到內存裡,或者通過縮放原圖的尺寸來減小內存占用。請記住隨著 Bitmap 尺寸的增長,圖片所消耗的內存會成平方量級的增長,因為 Bitmap 的 x 軸和 y 軸都在增長。

註意:在 Android2.3及以下的平臺,Bitmap 對象在你應用堆中的大小總是一樣的(因為他實際的內存被單獨存放在瞭本地內存中)。這會使得我們分析 Bitmap 消耗的內存資源變得非常困難,因為大多數分析工具隻收集你應用的 Dalvik 堆信息。但是,從 Android3.0開始,為瞭提升垃圾收集和調試的能力,Bitmap 的像素數據被放在瞭你的 Dalvik 堆裡。因此如果你的應用使用瞭 Bitmap 並且你在老設備上無法發現應用的內存消耗問題,那麼請你在
Android3.0或者更高的機型上調試他。

對於更多使用 Bitmap 的幫助,你可以去查看Manage Bitmap Memory(我強烈建議可以去看看,對於降低Bitmap的內存占用很有幫助)

使用優化後的數據容器

請使用 Andorid 框架中優化過的數據容器,例如 SparseArray,SparseBooleanArray 和 LongSparseArray。類似於 HashMap 這一類的容器的效率不是很高,因為在每個 Map 中對於每一次的存放數據,他都需要獨立一個單獨的 Entry 對象進行傳芳。而 SparseArray 由於禁止系統自動封裝鍵值對,因此他更加有效率。並且你不需要擔心丟失掉原有信息

小心內存花銷

請對你正在使用的語言和依賴包擁有一定的瞭解,並且在你設計應用的整個階段,都不要忽視它。通常大多數看起來無害的東西都可能讓你花費大量的內存,比如說一下的幾個:

1.枚舉與靜態常量相比,通常會消耗兩倍的內存資源,因此你應該盡量避免在 Android 中使用枚舉類型

2.Java 中的每一個類(包括匿名內部類),都會消耗大約500比特內存

3.每一個類對象都會消耗12-16比特內存

4.把單個 Entry 放入 HashMap 需要多消耗32比特的內存(原因請參看上一小節,使用優化後的數據容器)

雖然這裡的消耗看起來比較少,但是他們累計起來就很大瞭,應用中設計那些重量級的類就很可能承受這些內存花銷。這會使你的堆分析變得困難起來,你很難發現你的問題其實是因為很多小的對象正在占用你的內存。

小心抽象代碼

通常,開發者都會將抽象作為一種好的編程習慣,因為抽象可以提升代碼的靈活性和可維護性。但是,抽象方法可能帶來很多的額外花費,例如當他們執行的時候,他們擁有大量的代碼,並且他們會被多次映射到內存中占用更多的內存,因此如果抽象的效果不是很好,那麼最好放棄他

對於序列化數據使用 Protobufs

Protocol buffers 是谷歌一種跨語言,跨平臺的結構化序列數據,相比於 XML,他更小,更快,更簡單。如果你決定讓你的數據使用 Protobufs,你應用總是在你的客戶端使用納米級的Protobufs。規則的Protobufs會產生極其冗餘的代碼,這可能會導致應用產生各種問題:增加內存使用,APK包體增加,執行效率變慢,打破Dex的符號限制。

更多的信息,請參看protobuf readme

避免依賴註入框架

使用類似於 Guice 和 RoboGuice 的依賴註射框架,或許會使你的代碼變得更加漂亮,因為他們能夠減少你需要寫的代碼,並且為測試或者在其他條件改變的情況下,提供一種自適應的環境。但是,這些框架在初始化的時候會因為註釋而消耗大量的工作在掃描你的代碼上,這會讓你的代碼在進行內存映射的時候花費更多的資源。雖然這些內存能夠被 Android 進行回收,但是等待整個分頁被釋放需要很長一段時間。

小心使用外部依賴包

很多依賴包都不是專門為瞭移動環境或者移動客戶端寫的。如果你決定使用一個外部依賴包,你應該提前明白你需要為瞭將它移植到移動端而消耗花費大量的時間和工作量。請在使用外部依賴包得時候提前分析他的代碼和內存占用

即使依賴包是為瞭 Android 而設計的,但是這也有潛伏的危險,因為每一個包都做著不同的工作。例如,有一個依賴包使用納米級的 protobufs 但是別的包使用微米級的 protobufs.那麼現在在你的應用中就有兩套 protobuf 的標準瞭。這會在你記錄數據,分析數據,加載圖像,緩存,或者其他任何可能的情況下發生你不希望發生的事情。ProGuard 無法在這裡幫助你,因為他們都是你所依賴包的底層實現,。當你使用從別的依賴包(他可能繼承瞭很多的依賴包)裡繼承的
Activity 時,這個問題變得尤其嚴重,當你使用反射以及幹別的事情的時候

請註意不要落入一個依賴包的陷阱,你不希望引入一大片你根本不會使用到的代碼。如果你無法找到一種已經實現的邏輯來完全滿足你的需求,那麼你盡量創建一個自己的實現方式。

優化整體性能

很多關於優化應用的整體性能的信息被放到瞭 Best
Practices for Performance,這裡的很多文章介紹瞭如何優化 CPU 性能,但是很多小提示能夠幫助你優化你的應用的內存使用,比如說通過減少你ui 的佈局元素。

你應該讀一下優化你的 ui,並使用佈局調試工具來進行優化,另外可以通過 lint 工具來進行優化

使用 ProGuard 來剔除你不用的代碼

ProGuard 工具能夠通過移除不用的代碼以及對類,方法和標量進行無意義的重命名來起到回收,優化和混淆代碼的作用,使用 ProGuard 能夠使你的代碼變得更緊湊,而且減低內存消耗

使用 Zipalign 來優化你的 Apk

如果你對你生成的 APK 文件做瞭後期優化,那麼你必須要使用 Zipalign 來讓他對齊字節。不這樣做可能會導致你的應用因為從 APK 裡的資源不能被很好的映射到內存裡而消耗更多的內存。

註意:現在 Google 市場不接受沒有通過 Zipalign 處理過的 APK 文件

分析你的內存使用

一旦你已經擁有一個穩定版本的應用,那麼就從他的整個生命周期開始分析你應用的使用內存。更多關於你應用的內存分析,請查看Investigating
Your RAM Usage.

使用多進程

你應該在恰當的時候將你的應用組件分佈到多個進程中,這能夠幫助你管理你的應用內存。你應該被小心的使用這個技術,而且絕大部分應用都沒有必要在多線程中運行。如果錯誤的使用,反而可能導致你的應用消耗更多的內存,而不是減少。

舉個例而言,你可以在構建一個音樂播放器的時候使用多進程,因為他的後臺服務會持續很長一段時間,當整個應用運行在一個進程中,他的 activity 界面上的那些資源在播放音樂的時候還會被保存,即使這個時候用戶已經去到瞭另外一個應用,但是你的服務還在播放音樂。這樣的一個應用應該獨立的使用兩個進程,一個用於 ui 線程,另一個用於執行後臺服務。

你可以通過在 manifest 文件中為組件定義 android:process 屬性來為他們區分所屬的進程。例如,你可以讓你的某個服務運行在獨立於主線程的進程中,通過定義一個進程明教“backgroud”,這個名字可以隨便定義


你的進程的名稱應該以一個逗號開始,以確保這個進程是你應用私有的

在你決定創建一個新進程之前,你需要理解內存的消耗情況。為瞭說明每個進程的重要性,我們來看看一個空進程,即使他什麼事情也不做,他也會消耗1.4M 的空間,就像下面的內存信息裡顯示的那樣:

adb shell dumpsys meminfo com.example.android.apis:empty

** MEMINFO in pid 10172 [com.example.android.apis:empty] **
                Pss     Pss  Shared Private  Shared Private    Heap    Heap    Heap
              Total   Clean   Dirty   Dirty   Clean   Clean    Size   Alloc    Free
             ------  ------  ------  ------  ------  ------  ------  ------  ------
  Native Heap     0       0       0       0       0       0    1864    1800      63
  Dalvik Heap   764       0    5228     316       0       0    5584    5499      85
 Dalvik Other   619       0    3784     448       0       0
        Stack    28       0       8      28       0       0
    Other dev     4       0      12       0       0       4
     .so mmap   287       0    2840     212     972       0
    .apk mmap    54       0       0       0     136       0
    .dex mmap   250     148       0       0    3704     148
   Other mmap     8       0       8       8      20       0
      Unknown   403       0     600     380       0       0
        TOTAL  2417     148   12480    1392    4832     152    7448    7299     148

註意,關於如何閱讀這些輸出的信息,可以查看 Investigating
Your RAM Usage.,這裡主要的內容是 Private Dirty 和 Private Clean 這兩塊,他們分別顯示瞭這個進程使用瞭大約1.4M 的未分頁內存(分別分佈在 Dalvik 堆,本地分配空間,類庫加載),此外還有大約150k
的內存被映射到瞭內存執行。

這個空進程的內存輸出情況是相當清楚的。當你在進程中進行操作時,進程的內存還會繼續增大。舉個例子,這裡是一個隻在 activity 上顯示瞭一些文本的進程的內存數據:

** MEMINFO in pid 10226 [com.example.android.helloactivity] **
                Pss     Pss  Shared Private  Shared Private    Heap    Heap    Heap
              Total   Clean   Dirty   Dirty   Clean   Clean    Size   Alloc    Free
             ------  ------  ------  ------  ------  ------  ------  ------  ------
  Native Heap     0       0       0       0       0       0    3000    2951      48
  Dalvik Heap  1074       0    4928     776       0       0    5744    5658      86
 Dalvik Other   802       0    3612     664       0       0
        Stack    28       0       8      28       0       0
       Ashmem     6       0      16       0       0       0
    Other dev   108       0      24     104       0       4
     .so mmap  2166       0    2824    1828    3756       0
    .apk mmap    48       0       0       0     632       0
    .ttf mmap     3       0       0       0      24       0
    .dex mmap   292       4       0       0    5672       4
   Other mmap    10       0       8       8      68       0
      Unknown   632       0     412     624       0       0
        TOTAL  5169       4   11832    4032   10152       8    8744    8609     134

這個進程現在的大小差不多有4M,但是他真的隻在 ui 界面上顯示瞭一些文本。通過這個,我們可以得出結論,如果你打算將你的應用分割到多進程中,那麼隻能讓一個進程響應 ui,其他進程應該避免響應 ui,否則會極大的提升內存占用,尤其是當你加載瞭 bimap 或者其他一些資源的時候。這會導致 ui 繪制完成之後,我們也很難甚至於說不可能降低內存消耗。

除此之外,當運行超過一個進程的時候,你應該盡可能讓你的代碼保持簡潔。因為那些不必要的內存。例如說,如果你使用瞭枚舉(我們在之前討論過枚舉的內存消耗大約是靜態常量的兩倍),那麼所有的進程中 RAM 都需要創建並且初始化這些常量,而且你在局部區域或者適配器中使用過的抽象方法也會被反復復制。

多進程的另一個核心是兩者之間的依賴關系,比如說,如果你的應用中有一個運行在默認進程上的內容提供者,那麼在後臺進程中的代碼如果想要訪問這個內容提供者,就需要在 ui 線程中。如果你的目標是避免在繁重的 ui 線程中執行操作,那麼你就不應該依賴處於 ui 線程中的內容提供者或者服務。

##########我是分隔符#######################

Ok,關於Android的內存,就翻譯到這裡瞭,之後我會將更多的官網文章翻譯過來,希望大傢 喜歡,如果點贊就更好瞭

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。