內存優化(二) ---- Android APP性能優化

由于Android應用的沙箱機制,每個應用所分配的內存大小是有限度的,因此內存會顯得非常珍貴,如果我們的內存占用超過了一定的水平就會出現OutOfMemory錯誤

內存概述

RAM(random access memory)隨機存取存儲器.(通俗的說就是內存)

  • Java的內存分配策略:
Java內存分配時會涉及到以下區域:
棧(Stack):一些基本類型的變量和對象的引用都是在棧內存中分配,當超過變量的作用域后,java會自動釋放該變量分配的內存(對象本身不存放在棧中,而是存放在堆中)
堆(Heap): 通常用來存放new出來的對象和數組,由java垃圾回收器回收.
靜態存儲區(static field): 編譯時就分配好,在程序整個運行期間都存在.它主要存放靜態數據和常量

還一個CPU存儲區:
寄存器(Registers): 速度最快的存儲場所,因為寄存器位于處理器內部,我們在程序中無法控制
  • 堆棧的特點:

棧:

定義一個變量時,Java在棧中為這個變量分配內存空間,當該變量退出該作用域后,Java會自動釋放為該變量所分配的內存空間.

棧的存取速度比堆要快,僅次于寄存器.但是存在棧中的數據大小與生存期必須是確定的,缺乏靈活性

棧中的數據可以共享,它是由編譯器完成的,有利于節省空間

例如:需要定義兩個變量int a = 3;int b = 3;
編譯器先處理int a = 3;首先它會在棧中創建一個變量為a的引用,然后查找棧中是否有3這個值,如果沒有,就將3存放進來再將a指向3.
接著處理int b = 3,創建完b的引用變量后在棧中已經有3這個值,便將b直接指向3.這樣,就出現了a與b同時均指向3的情況.
這時,如果再讓a=4,那么編譯器會重新搜索棧中是否有4值,如果沒有,則將4存放進來,并讓a指向4.
如果已經有了,則直接將a指向這個地址.因此a值的改變不會影響到b的值。

堆:

當堆中通過new產生數組和對象超出其作用域后,它們不會被釋放,只有在沒有引用變量指向它們的時候才變成垃圾,不能再被使用,并且只有等被垃圾回收器回收才回釋放內存.這也是Java比較占內存的原因.

堆是一個運行時數據區,可以動態地分配內存大小,因此存取速度較慢.

如上例子,棧中a的修改并不會影響到b,而在堆中一個對象引用變量修改了這個對象的內部狀態,會影響到另一個對象引用變量

  • APP內存占用信息查詢
    float max = Runtime.getRuntime().maxMemory() * 1.0f / (1024 * 1024);
    float total = Runtime.getRuntime().totalMemory() * 1.0f / (1024 * 1024);
    float free = Runtime.getRuntime().freeMemory() * 1.0f / (1024 * 1024);
查看系統設置單個進程的內存上限
C:\Users\Administrator>adb shell
sagit:/ $ getprop|grep heapgrowthlimit
[dalvik.vm.heapgrowthlimit]: [256m]
  • java中四種引用類型:
強引用(StrongReference):強引用是使用最普遍的引用(如:Object object=new Object(),object就是一個強引用了),
如果一個對象具有強引用內存不足時,寧拋異常OOM導致程序終止也不回收,也就是JVM停止時才終止

軟引用(SoftReference):如果內存空間不足時,才會被回收(當內存達到一個閥值,GC就會去回收它)

弱引用(WeakReference):不管當前內存空間是否足夠,在GC 時都會回收

虛引用(PhantomReference):顧名思義,就是形同虛設,任何時候都可能被GC回收(已經不用)
image

軟引用實例:

    // 例子1:
    private Map<String, SoftReference<Bitmap>> imageCache = new HashMap<String, SoftReference<Bitmap>>();
    
    public void add(String path) {
        Bitmap bitmap = BitmapFactory.decodeFile(path); // 這里的bitmap屬于強引用
        SoftReference<Bitmap> softBitmap = new SoftReference<Bitmap>(bitmap);   // 軟引用的Bitmap對象
        imageCache.put(path, softBitmap);
    }
    
    public Bitmap get(String path) {
        SoftReference<Bitmap> softBitmap = imageCache.get(path);
        if (softBitmap == null) {
            return null;
        }
        return softBitmap.get();    // 取出軟引用的Bitmap,如果內存不足被回收,獲取為NUll  
    }
    
    public static Bitmap readBitmap(Context context, int resId) {
        BitmapFactory.Options opt = new BitmapFactory.Options();
        opt.inPreferredConfig = Bitmap.Config.RGB_565;
        opt.inPurgeable = true;
        opt.inInputShareable = true;
        InputStream is = context.getResources().openRawResource(resId);
        return BitmapFactory.decodeStream(is, null, opt);
    }
    
    // 例子2:
    static class MyHandler extends Handler {
        private SoftReference<Activity> reference;
    
        public MyHandler(Activity activity) {
            // 持有 Activity 的軟引用
            reference = new SoftReference<Activity>(activity);
        }
    
        @Override
        public void handleMessage(Message msg) {
            Activity activity = reference.get();
            if (activity != null && !activity.isFinishing()) {
                switch (msg.what) {
                    // 處理消息
                }
            }
        }
    }
  • 垃圾回收機制:
垃圾回收是指清理在內存中不再需要的數據對象,以便大塊內存可以重新分配給新的對
象。一般來說,一旦某個對象在 App 中沒有一個活動的引用,就可以作為垃圾被回收了。
垃圾回收器會先從根部的對象開始(它知道這些對象是活動的并且正被進程所使用),并
且沿著每個引用去查找它們的關聯。如果一個對象不在這個有效引用的列表中,那么它肯
定不會再被使用,就可以被回收了。此時,分配給這個對象的內存空間也可以回收了

內存優化

  • 內存泄漏

    內存泄漏是內存優化中最重要的部分

    Android內存泄露OOM的原因及解決方案

  • IntentService的使用

    IntentService是一種特殊的Service,繼承自Service;用于在后臺執行耗時的異步任務,當任務完成后會自動停止

    為什么使用IntentService?

    我們通常Service用法如下,也是標準用法:

    public class MyService extends Service {
    
        @Nullable
        @Override
        public IBinder onBind(Intent intent) {
            return null;
        }
    
        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            new Thread() {
                @Override
                public void run() {
                    // 處理耗時邏輯
                    stopSelf(); // 如需實現處理完自動停止功能,可這樣做
                }
            }.start();
            return super.onStartCommand(intent, flags, startId);
        }
    }
如上寫法并沒有什么錯誤,但是需要寫如上額外代碼,同時 當業務邏輯復雜后會有Service停止失敗導致內存泄漏的風險,Android官方推薦的最佳解決方案就是使用IntentService
    public class MyIntentService extends IntentService {
    
        public MyIntentService(String name) {
            super(name);
        }
    
        @Override
        protected void onHandleIntent(@Nullable Intent intent) {
            // 處理耗時邏輯,處理完自動停止
        }
    }
內部通過HandlerThread和Handler實現異步操作,創建IntentService時,只需實現onHandleIntent和構造方法.onHandleIntent為異步方法,可執行耗時操作.
  • Bitmap優化

    Bitmap是內存消耗大戶,是導致OMM最常見的原因之一

圖片顯示:

我們可以根據場景需求去加載圖片的大小,例如列表中的小圖我們可以只加載縮略圖(thumbnails)

等比例壓縮圖片:

直接使用圖片(bitmap)會占用較多資源,特別是圖片較大的時候,可能導致崩潰,這時,我們可以使用BitmapFactory.Options設置inSampleSize.inSampleSize表示縮略圖大小為原始圖片大小的幾分之一,即如果這個值為2,則獲取圖片的寬和高都是原始圖片的1/2,圖片大小就為原始大小的1/4.
    BitmapFactory.Options options = new BitmapFactory.Options();
    // 該值設為true后將不返回實際的bitmap,也不給其分配內存空間.但允許我們查詢圖片的信息,計算出原始圖片的長和寬
    options.inJustDecodeBounds = true; 
    //縮放的倍數,圖片寬高都為原來的二分之一,即圖片為原來的四分之一,SDK中建議該值為2,值越大會導致圖片不清晰
    options.inSampleSize = 2;  
    options.inJustDecodeBounds = false;  
    Bitmap bmp = BitmapFactory.decodeFile(sourceBitmap, options);  

圖片像素:

Android中圖片有四種屬性,分別是:
ALPHA_8:每個像素占用1byte內存 
ARGB_4444:每個像素占用2byte內存 
ARGB_8888:每個像素占用4byte內存 (默認)
RGB_565:每個像素占用2byte內存 

Android默認的顏色模式為ARGB_8888,這個顏色模式色彩最細膩,顯示質量最高.同時占用的內存也最大 所以對圖片效果不是特別高的情況下可以使用RGB_565(565沒有透明度屬性)
    public static Bitmap readBitmap(Context context, int resId) {
        BitmapFactory.Options opt = new BitmapFactory.Options();
        opt.inPreferredConfig = Bitmap.Config.RGB_565;
        opt.inPurgeable = true;
        opt.inInputShareable = true;
        InputStream is = context.getResources().openRawResource(resId);
        return BitmapFactory.decodeStream(is, null, opt);
    }

圖片回收:

使用Bitmap過后及時的調用Bitmap.recycle()方法來釋放內存,不要等Android系統來進行釋放
    if (bitmap != null && !bitmap.isRecycled()) {
        // 回收并且置為null
        bitmap.recycle();
        bitmap = null;
    }
    System.gc();

對圖片采用軟引用

SoftReference<Bitmap> bitmap = new SoftReference<Bitmap>(pBitmap);

捕獲異常:

最壞的情況下不能導致程序崩潰,捕獲OOM異常
    Bitmap bitmap = null;
    try {
        bitmap = BitmapFactory.decodeFile(path);
    } catch (OutOfMemoryError e) {
        e.printStackTrace();
    }
    if (bitmap == null) {
        return defaultBitmap;
    }

相關鏈接直達:

Android APP性能優化之 ---- 布局優化(一)

Android APP性能優化之 ---- 內存優化(二)

Android APP性能優化之 ---- 代碼優化(三)

Android APP性能優化之 ---- 優化監測工具(四)

Android APP性能優化之 ---- APK瘦身 App啟動優化

Android內存泄露OOM異常處理優化

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

推薦閱讀更多精彩內容