Android 內(nèi)存泄漏和OOM分析(二)

接下來我們來討論OOM的問題。

在做美食生活的項(xiàng)目的時(shí)候,有個(gè)功能是拍照上傳圖片的功能,沒有對(duì)圖片進(jìn)行壓縮,直接進(jìn)行了上傳,
11-21 22:05:28.774: E/dalvikvm-heap(13216): Out of memory on a 10485476-byte allocation.
11-21 22:05:28.784: E/AndroidRuntime(13216): java.lang.OutOfMemoryError

好了,出現(xiàn)了我們的OOM了!

一般造成OOM的最大的原因是Bitmap,Bitmap,Bitmap重要的事情說三遍!

一般的OOM經(jīng)常出現(xiàn)在加載很多圖片或者是加載高清大圖的時(shí)候!當(dāng)一個(gè)app啟動(dòng)后,虛擬機(jī)不停的申請(qǐng)內(nèi)存資源來裝載圖片,當(dāng)超過內(nèi)存上限時(shí)就出現(xiàn)OOM。這個(gè)現(xiàn)象如果你在跑monkey進(jìn)行測(cè)試的時(shí)候更容易出現(xiàn)

Android的APP內(nèi)存組成:

APP內(nèi)存由 dalvik內(nèi)存 和 native內(nèi)存2部分組成,dalvik也就是java堆,創(chuàng)建的對(duì)象就是就是在這里分配的,而native是通過c/c++方式申請(qǐng)的內(nèi)存,Bitmap就是以這種方式分配的。(android3.0以后,系統(tǒng)都默認(rèn)通過dalvik分配的,native作為堆來管理)。這2部分加起來不能超過android對(duì)單個(gè)進(jìn)程,虛擬機(jī)的內(nèi)存限制。

從代碼中獲取每個(gè)手機(jī)的內(nèi)存限制大?。?/p>

   ActivityManager activityManager =       (ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE)
    activityManager.getMemoryClass();

而對(duì)于head堆的大小限制,可以查看/system/build.prop文件。

    dalvik.vm.heapstartsize  =  5m
    dalvik.vm.heapgrowthlimit = 48m
    dalvik.vm.heapsize = 256m

注: heapsize參數(shù)表示單個(gè)進(jìn)程heap可用的最大內(nèi)存,但如果存在以下參數(shù)"dalvik.vm.headgrowthlimit =48m"表示單個(gè)進(jìn)程heap內(nèi)存被限定在48m,即程序運(yùn)行過程實(shí)際只能使用48m內(nèi)存。

怎么查看APP內(nèi)存分配情況?

1 在我們的AS中可以通過Monitor監(jiān)控得到

2 在App里面我們可以通過totalMemory與freeMemory:

    Runtime.getRuntime().freeMemory()
    RUntime.getRuntime().totalMemory()

3 adb shell dumpsys meminfo com.android.demo

這個(gè)是adb命令了,com.android.demo是包名。不會(huì)的可以去看我的另一篇博客:ADB 常用命令及詳解

常見避免OOM的幾個(gè)注意點(diǎn):

適當(dāng)調(diào)整圖像大小 。因?yàn)槭謾C(jī)屏幕尺寸有限,分配給圖像的顯示區(qū)域有限,尤其對(duì)于超大圖片,加載自網(wǎng)絡(luò)或者sd卡,圖片文件提及達(dá)到幾M或者十幾個(gè)M的:
加載到內(nèi)存前,先算出該bitmap的大小,然后通過適當(dāng)調(diào)節(jié)采樣率使得加載的圖片剛好,或稍大在手機(jī)屏幕上顯示就滿意了:

    BimtapFactory.Option opts =  new   BitampFactory.Option();
        opts.inJustDecodeBounds =  true ;
        opts.inSampleSize=computeSample(opts, minSideLength, maxNumOfPixels);  // Android 提供了一種動(dòng)態(tài)計(jì)算的方法 computeSampleSize
        opts.inJustDecodeBounds =  false ;
         try {
                return  BitmapFactory.decodeFile(imageFile, opts);
        }  catch (OutOfMemoryError err){

圖像緩存

在listview或Gallery等控件中一次性加載大量圖片時(shí),只加載屏幕顯示的資源,尚未顯示的不加載,移出屏幕的資源及時(shí)釋放,采用強(qiáng)引用+軟引用2級(jí)緩存,提高加載性能。緩存圖像到內(nèi)存,采用軟引用緩存到內(nèi)存,而不是在每次使用的時(shí)候都從新加載到內(nèi)存。

及時(shí)回收?qǐng)D像 。

如果引用了大量的Bitmap對(duì)象,而應(yīng)用又不需要同時(shí)顯示所有圖片??梢詫簳r(shí)不用到的Bitmap對(duì)象及時(shí)回收掉。對(duì)于一些明確直到圖片使用情況的場(chǎng)景可以主動(dòng)recycle回收
App的啟動(dòng)splash畫面上的圖片資源,使用完就recycle。對(duì)于幀動(dòng)畫,可以加載一張,畫一張,釋放一張。

不要在循環(huán)中創(chuàng)建過多的本地變量 。

慎用static,用static來修飾成員變量時(shí),該變量就屬于該類,而不是該類實(shí)例,它的生命周期是很長(zhǎng)的。如果用它來引用一些內(nèi)存占用太多的實(shí)例,這時(shí)候就要謹(jǐn)慎對(duì)待了。

重點(diǎn)要來了?。?!

對(duì)于圖App使用圖片時(shí)避免OOM的幾種方式有三四種,我只講一種:

LruCache + sd的緩存方式 :##

很多圖片處理框架和網(wǎng)絡(luò)請(qǐng)求框架都已經(jīng)做了三級(jí)緩存技術(shù),所以在使用框架加載圖片時(shí),利用好他們給的API和方法就行了。

如果就是簡(jiǎn)單的保存幾張圖片,簡(jiǎn)單的緩存,不想引入框架,那么可以看看這個(gè)!

    /**
     * Bitmap緩存,簡(jiǎn)單緩存的設(shè)置類.
     * Created by ChangMingShan on 2016/12/26.
      */
    public class BitmapMemoryCache {

    private static final String TAG = "BitmapMemoryCache";

    private static BitmapMemoryCache sInstance = new BitmapMemoryCache();

    private LruCache<String, Bitmap> mMemoryCache;

    /**
     * 單例模式.
     */
    public static BitmapMemoryCache getInstance() {
        return BitmapMemoryCache.sInstance;
    }

    private BitmapMemoryCache() {
        int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
        int cacheSize = maxMemory / 8;
        mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap bitmap) {
                // 重寫此方法來衡量每張圖片的大小,默認(rèn)返回圖片數(shù)量。
                return bitmap.getByteCount() / 1024;
            }
        };
    }
    public synchronized void addBitmapToMemoryCache(String key, Bitmap bitmap) {
        if (mMemoryCache.get(key) == null) {
            if (key != null && bitmap != null)
                mMemoryCache.put(key, bitmap);
        } else
            Log.w(TAG, "the res is aready exits");
    }

     public synchronized Bitmap getBitmapFromMemCache(String key) {
         Bitmap bm = mMemoryCache.get(key);
         if (key != null) {
            return bm;
        }
          return null;
     }

       /**
       * 移除緩存
       *
       * @param key
       */
      public synchronized void removeImageCache(String key) {
        if (key != null) {
            if (mMemoryCache != null) {
                Bitmap bm = mMemoryCache.remove(key);
                if (bm != null)
                    bm.recycle();
             }
         }
     }
        /**
        * 移除緩存
        */
       public synchronized void clearImageCache() {

        if (mMemoryCache != null) {
            if (mMemoryCache.size() > 0) {
                Log.d("CacheUtils",
                        "mMemoryCache.size() " + mMemoryCache.size());
                mMemoryCache.evictAll();
                Log.d("CacheUtils", "mMemoryCache.size()" + mMemoryCache.size());
            }
            mMemoryCache = null;
        }
    }


   
       public void clearCache() {
        if (mMemoryCache != null) {
            if (mMemoryCache.size() > 0) {
                Log.d("CacheUtils",
                        "mMemoryCache.size() " + mMemoryCache.size());
                mMemoryCache.evictAll();
                Log.d("CacheUtils", "mMemoryCache.size()" + mMemoryCache.size());
              }
            mMemoryCache = null;
           }
       }
            public Bitmap loadLocal(String path) {/*這是加載本地的圖片的步驟*/

            Bitmap bitmap=BitmapFactory.decodeFile(path);

            addBitmapToMemoryCache(path, bitmap);
  
            return getBitmapFromMemCache(path);
    }


        
      /*
      將圖片進(jìn)行壓縮代碼,本次未用到

       BitmapFactory.Options options = new BitmapFactory.Options();
       options.inJustDecodeBounds = false; // 設(shè)置了此屬性一定要記得將值設(shè)置為false
       Bitmap bitmap = null;
       bitmap = BitmapFactory.decodeFile(url, options);
       int be = (int) ((options.outHeight > options.outWidth ? options.outHeight / 150
            : options.outWidth / 200));
       if (be <= 0) // 判斷200是否超過原始圖片高度
       be = 1; // 如果超過,則不進(jìn)行縮放
       options.inSampleSize = be;
       options.inPreferredConfig = Bitmap.Config.ARGB_4444;
       options.inPurgeable = true;
       options.inInputShareable = true;
       options.inJustDecodeBounds = false;
       try {
           bitmap = BitmapFactory.decodeFile(url, options);
       } catch (OutOfMemoryError e) {
           System.gc();
           Log.e(TAG, "OutOfMemoryError");
       }
       */
    }

我們可以這樣定義:map里面的鍵是用來放圖片地址的,既可以是網(wǎng)絡(luò)上的圖片地址,也可以SDcard上的圖片地址,
map里面的值里面放的是持有軟引用的Bitmap

    /**
     * Bitmap緩存網(wǎng)絡(luò)圖片或者是本地圖片,簡(jiǎn)單緩存.
     * Created by ChangMingShan on 2016/12/26.
     */
    public class SaveBitmapMemoryCache {
    
    private Map<String, SoftReference<Bitmap>> imageMap = new HashMap<String, SoftReference<Bitmap>>();

    private static final String TAG = "NetBitmapMemoryCache";

    public Bitmap loadBitmap(final String imageUrl,final ImageCallBack imageCallBack) {
        SoftReference<Bitmap> reference = imageMap.get(imageUrl);
        if(reference != null) {
            if(reference.get() != null) {
                return reference.get();
            }
        }
        final Handler handler = new Handler() {
            public void handleMessage(final android.os.Message msg) {
                //加入到緩存中
                Bitmap bitmap = (Bitmap)msg.obj;
                imageMap.put(imageUrl, new SoftReference<Bitmap>(bitmap));
                if(imageCallBack != null) {
                    imageCallBack.getBitmap(bitmap);
                }
            }
        };
        new Thread(){
            public void run() {
                Message message = handler.obtainMessage();
                message.obj = downloadBitmap(imageUrl);
                handler.sendMessage(message);
            }
        }.start();
        return null ;
    }

    // 從網(wǎng)上下載圖片
    private Bitmap downloadBitmap (String imageUrl) {
        Bitmap bitmap = null;
        try {
            bitmap = BitmapFactory.decodeStream(new URL(imageUrl).openStream());
            return bitmap ;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

              /* public static boolean saveUrlAs(String fileUrl, String savePath)/*fileUrl網(wǎng)絡(luò)資源地址*/
                {//這是將圖片緩存到本地的代碼,此次也未用到,用到改一下就ok

            try
            {
               URL url = new URL(fileUrl);/*將網(wǎng)絡(luò)資源地址傳給,即賦值給url*/
               
               HttpURLConnection connection = (HttpURLConnection)url.openConnection();
               DataInputStream in = new DataInputStream(connection.getInputStream());
               
              DataOutputStream out = new DataOutputStream(new FileOutputStream(savePath));
             
              byte[] buffer = new byte[4096];
                int count = 0;
              while ((count = in.read(buffer)) > 0)/*將輸入流以字節(jié)的形式讀取并寫入buffer中*/
               {
                out.write(buffer, 0, count);
               }
               out.close();/*前額安吉的將流關(guān)閉*/
               in.close();
               connection.disconnect();

               return true;    /*網(wǎng)絡(luò)資源截取并存儲(chǔ)本地成功返回true*/

             }
           catch (Exception e)
           {
      
                return false;
           }*/
        }

       public interface ImageCallBack{
        void getBitmap(Bitmap bitmap);
         }
      }

如果能將Bitmap這個(gè)東西解決好,你的OOM基本不怎么會(huì)出現(xiàn)了,不建議修改系統(tǒng)或者是dalvike里面的東西。

還有一些OOM情況出現(xiàn)在加載非常多條目信息的listview,gridview等的情況,在這里推薦使用Recyclerview或者在這些adapter的getView函數(shù)里有個(gè)convertView參數(shù)。

本篇的使用要結(jié)合我的上一篇文章使用,避免不必要的麻煩??偟膩碚f來好的代碼規(guī)范可以幫你省去和解決很多不必要的麻煩!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容