Android 內存優化

前言

Random-access memory (RAM) is a valuable resource in any software development environment, but it's even more valuable on a mobile operating system where physical memory is often constrained. Although both the Android Runtime (ART) and Dalvik virtual machine perform routine garbage collection, this does not mean you can ignore when and where your app allocates and releases memory. You still need to avoid introducing memory leaks, usually caused by holding onto object references in static member variables, and release any Reference objects at the appropriate time as defined by lifecycle callbacks.

摘取自developer.android.google.cn

Andorid中的內存使用問題以及解決方案

在Android開發中內存優化是開發者需要永不停息去做的事情,隨著Android版本的變化和硬件的升級,系統對內存的大小限制也都有了變化,內存方面的問題主要有兩大問題:內存溢出,內存泄露。內存溢出是內存問題的最終因果。

內存溢出

內存泄露可以引發很多的問題:
  1. 程序卡頓,響應速度慢(內存占用高時JVM虛擬機會頻繁觸發GC)
  2. 莫名消失(當你的程序所占內存越大,它在后臺的時候就越可能被干掉。反之內存占用越小,在后臺存在的時間就越長)
  3. 直接崩潰(OutOfMemoryError)
發生內存溢出的條件:
  • Android 2.x系統中:GC LOG中的dalvik allocated + external allocated + 新分配的大小>= getMemoryClass()值的時候就會發生OOM。 例如,假設有這么一段Dalvik輸出的GC LOG:GC_FOR_MALLOC free 2K, 13% free 32586K/37455K, external 8989K/10356K, paused 20ms,那么32586+8989+(新分配23975)=65550>64M時,就會發生OOM。
  • Android 4.0以上的系統中: Android 4.x的系統廢除了external的計數器,類似bitmap的分配改到dalvik的java heap中申請,只要allocated + 新分配的內存 >= getMemoryClass()的時候就會發生OOM。

如何優化內存避免內存溢出?

1.申請大內存。
     android:largeHeap="true"

上面這句話可以寫在Android Manifest.xml中 請求擴大內存限制。這項選擇是提供給那種對內存特別需要的APP的,比如一些需要展示或編輯大量圖片視頻的應用。但是需要謹慎添加這句話,第一因為這種辦法本來就是治標不治本的,第二,如前言中Google官方文檔所說,移動端因設備尺寸的限制本身內存的容量就是非常有限的,占用了一些就會少一些。第三,如果你應用占用內存過大,估計當你的應用失去焦點時,java虛擬機或者尤其國內橫行的第三方加速軟件也是容不下你。

2.合理的使用、壓縮、緩存與復用圖片。

在Android 系統中圖片是使用內存的大戶,建議使用較為成熟的第三方圖片加載框架,比如Glide、Freso,Picasso(依次為推薦順序,下面也是以Glide為例),同時我們使用圖片時需要注意以下幾點:

  • 啟用RBG_565色彩模式。色彩模式Android中有四種,分別是:

ALPHA_8:每個像素占用1byte內存
ARGB_4444:每個像素占用2byte內存
ARGB_8888:每個像素占用4byte內存
RGB_565:每個像素占用2byte內存

Android默認的色彩模式為ARGB_8888,這個色彩模式色彩最細膩,顯示質量最高。但同樣的,占用的內存也最大。通常我們只需要使用RGB_565格式就好(Glide默認模式)。

  • 根據已知尺寸加載圖片縮略圖

      1 BitmapFactory.Options options = new BitmapFactory.Options();
      2 options.inSampleSize = 2;
      3 Bitmap img = BitmapFactory.decodeFile("/sdcard/1.png", options);
    

該段代碼便是讀取本地sd卡根目錄1.png的縮略圖,長度、寬度都只有原圖片的1/2。圖片大小削減,占用的內存自然也變小了。當然代價就是圖片質量變差了。加載網絡圖片時Glide的邏輯是可以指定圖片尺寸按需使用,緩存的時候默認也是按需緩存的,所以有些時候你會發現本來這張圖片已經在其他地方加載過但是換了個位置需要重新下載,這個時候可以針對性的設置緩存策略diskCacheStrategy(DiskCacheStrategy.ALL)。

  • 及時的回收內存,使用Glide時正確的賦予應有的生命周期。

Bitmap類的構造方法都是私有的,所以開發者不能直接new出一個Bitmap對象,只能通過BitmapFactory類的各種靜態方法來實例化一個Bitmap。
查看BitmapFactory的源代碼可以看到,生成Bitmap對象最終都是通過JNI調用方式實現的。所以,加載Bitmap到內存里以后,是包含兩部分內存區域的。簡單的說,一部分是Java部分的,一部分是C部分的。這個Bitmap對象是由Java部分分配的,不用的時候系統就會自動回收了,但是那個對應的C可用的內存區域,虛擬機是不能直接回收的,這個只能調用底層的功能釋放。所以需要調用recycle()方法來釋放C部分的內存。從Bitmap類的源代碼也可以看到,recycle()方法里也的確是調用了JNI方法了的。

  • 當使用Glide加載一張圖片時可以給指定特定的生命周期.

      Glide.with(NewsDetailActivity.this).load(url)into(ImageView);
    

Glide 的 with() 方法不光接受 Context,還接受 Activity 和 Fragment。此外,with() 方法還能自動地從你放入的各種東西里面提取出 Context,供它自己使用。將Activity/Fragment作為with()參數的好處是:圖片加載會和Activity/Fragment的生命周期保持一致,比如 Paused狀態在暫停加載,在Resumed的時候又自動重新加載。所以我建議傳參的時候傳遞Activity 和 Fragment給Glide,而不是Context。

  • 內存重用

充分使用BitmapFactory.Option inBitmap屬性,利用這種特性即使是上千張的圖片,也只會僅僅只需要占用屏幕所能夠顯示的圖片數量的內存大 小。在3.0-4.4系統中inBitmap要求新申請的Bitmap必須和可復用的Bitmap對象的大小完全相同,而在4.4系統以后要求有所放 松,只要新申請Bitmap對象小于或等于可復用的Bitmap對象即可復用

關于圖片內存優化的內容 Google 官方文檔上也有類似講解。Managing Bitmap Memory

3.響應釋放內存的事件

Android App 應該監聽系統的廣播信號且根據用戶不同行為釋放不同的資源,系統會在有內存壓力的時候,發出廣播告訴應用,讓它們適當調整內存使用情況。以下引用自Google 官方文檔

You can use the ComponentCallbacks2 API to listen for these signals and then adjust your memory usage in response to app lifecycle or device events. The onTrimMemory() method allows your app to listen for memory related events when the app runs in the foreground (is visible) and when it runs in the background.
To listen for these events, implement the onTrimMemory() callback in your Activity classes, as shown in the following code snippet.

import android.content.ComponentCallbacks2;
// Other import statements ...

public class MainActivity extends AppCompatActivity
implements ComponentCallbacks2 {

// Other activity code ...

/**
 * Release memory when the UI becomes hidden or when system resources become low.
 * @param level the memory-related event that was raised.
 */
public void onTrimMemory(int level) {

    // Determine which lifecycle or system event was raised.
    switch (level) {

        case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN:

            /*
               Release any UI objects that currently hold memory.

               The user interface has moved to the background.
            */

            break;

        case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE:
        case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:
        case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:

            /*
               Release any memory that your app doesn't need to run.

               The device is running low on memory while the app is running.
               The event raised indicates the severity of the memory-related event.
               If the event is TRIM_MEMORY_RUNNING_CRITICAL, then the system will
               begin killing background processes.
            */

            break;

        case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND:
        case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
        case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:

            /*
               Release as much memory as the process can.

               The app is on the LRU list and the system is running low on memory.
               The event raised indicates where the app sits within the LRU list.
               If the event is TRIM_MEMORY_COMPLETE, the process will be one of
               the first to be terminated.
            */

            break;

        default:
            /*
              Release any non-critical data structures.

              The app received an unrecognized memory level value
              from the system. Treat this as a generic low-memory message.
            */
            break;
    }
}
}

The onTrimMemory() callback was added in Android 4.0 (API level 14). For earlier versions, you can use the onLowMemory() callback as a fallback for older versions, which is roughly equivalent to the TRIM_MEMORY_COMPLETE event.

4.混淆你的代碼

ProGuard工具通過移除無用代碼,使用語意模糊來保留類,字段和方法來壓縮,優化和混淆代碼。可以使你的代碼更加完整,更少的RAM 映射頁。

5.內存抖動與避免

有些代碼并不造成內存泄露,但是資源沒有得到重用,例如for循環分配占內存的對象導致垃圾回收機制頻繁運行(短時間內產生大量對象,需要大量內存,而且還是頻繁抖動,就可能會需要回收內存以用于產生對象,垃圾回收機制就自然會頻繁運行了),頻繁的申請內存和銷毀內存,消耗CPU資源的同時,也引起內存忽高忽低,這就是內存抖動,反應給使用者的表現就是UI卡頓。

此種問題大多數出現在循環或者重復調用的回調里,所以我們應該避免這些問題,盡量在for循環體外創捷對象或者使用對象池等方法,但也需要注意對象池的內存管理和釋放。

6.TinyPNG 智能有損壓縮資源文件里的圖片(同樣適合ios)

TinyPNG采用了智能有損壓縮技術,以減少文件大小的PNG文件。通過選擇性地降低在圖像中的顏色的數量,需要較少字節來存儲該數據。其效果是幾乎看不見,但它使文件的大小非常大的差別!TinyPNG

7.其他優化點
  • 不使用枚舉型數據,Android官方培訓課程提到過

Enums often require more than twice as much memory as static constants. You should strictly avoid using enums on Android.
枚舉通常情況下所占用的內存是靜態常量的兩倍甚至更多,有興趣的同學可以建一個工程先打一個包看看dex有多大,然后分別加上這兩段代碼,再對比一下dex的大小。

  • Try catch 某些內存操作情況 OutOfMemoryError 是可以被catch的。
  • 使用軟引用,軟引用只有當內存空間不足了,才會回收這些對象的內存。弱引用,被垃圾回收器掃描到后即被回收。
  • 使用DDMS(Dalvik Debug Monitor Server)查看堆內存的分配情況,針對優化。
  • 使用square 公司出的內存監測工具 LeakCanary 監聽內存泄露。

內存泄露

內存泄露漏 (memory leak)所導致的內存問題會更加隱晦復雜,具體會在下一節詳細闡述。

技術博客 Wells'Note

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,829評論 25 708
  • 本文轉載來源 http://www.csdn.net/article/2015-09-18/2825737/1 (...
    yoosir閱讀 1,125評論 0 5
  • 如何避免OOM 一、減小對象的內存占用 1、使用更加輕量的數據結構 例如,我們可以考慮使用ArrayMap/Spa...
    呂侯爺閱讀 743評論 0 5
  • 前言 手機極大的方便了和豐富了我們的生活,隨著喬布斯改變世界的iOS操作系統的發展和android系統的扶搖直上,...
    平凡小天地閱讀 1,619評論 1 13
  • 感恩早上起床,老公已經刷碗熬粥,因為他知道二寶昨晚沒吃好,一直在鬧,我也沒休息好。 感恩我又感冒了,不停打噴嚏流鼻...
    米朵天天閱讀 197評論 0 0