【モバイル開発】Androidで画像が大きすぎてメモリが溢れ、OOM(OutOfMemory)異常解決方法

7737 ワード

私たちがプロジェクトをしている間に、画像を表示するときは、画像の大きさ、メモリの大きさを考慮しなければなりません.なぜなら、AndroidがBitmapに割り当てた大きさは8 Mしかないからです.携帯電話で写真を撮ると、普通の1枚の写真でも1 M以上かかるのではないかと思います.そのため、androidが画像を処理するときは、画像が大きすぎることによるメモリ異常を考慮しなければなりません.
その時は単純に画像をローカルにキャッシュして圧縮するだけでしたが、この問題はうまく解決できず、発生確率を減らしただけだと感じました.
ここでは、先輩たちが解決した方法をもう一度整理して、自分で後で使うのに便利にします.
   1.メモリリファレンスで処理します.よく使われるのはソフトリファレンス、強化リファレンス、弱いリファレンスです(このブログを参照できます:http://smallwoniu.blog.51cto.com/blog/3911954/1248751)
import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.reflect.Field;
public class Test {
    public static boolean isRun = true;
    public static void main(String[] args) throws Exception {
        String abc = new String("abc");
        System.out.println(abc.getClass() + "@" + abc.hashCode());
                                                                                                                                                                                                                                                                                                                                                                                                               
        final ReferenceQueue referenceQueue = new ReferenceQueue();
        new Thread() {
            public void run() {
                while (isRun) {
                    Object o = referenceQueue.poll();
                    if (o != null) {
                        try {
                            Field rereferent = Reference.class
                                    .getDeclaredField("referent");
                            rereferent.setAccessible(true);
                            Object result = rereferent.get(o);
                            System.out.println("gc will collect:"
                                    + result.getClass() + "@"
                                    + result.hashCode());
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }.start();
        PhantomReference abcWeakRef = new PhantomReference(abc,
                referenceQueue);
        abc = null;
        Thread.currentThread().sleep(3000);
        System.gc();
        Thread.currentThread().sleep(3000);
        isRun = false;
    }
}

結果:
class java.lang.String@96354 
gc will collect:class java.lang.String@96354

2.メモリに画像をロードするときに直接メモリに処理する
A.境界圧縮
@SuppressWarnings("unused")
private Bitmap copressImage(String imgPath){
    File picture = new File(imgPath);
    Options bitmapFactoryOptions = new BitmapFactory.Options();
    //                     
    bitmapFactoryOptions.inJustDecodeBounds = true;
    bitmapFactoryOptions.inSampleSize = 2;
    int outWidth  = bitmapFactoryOptions.outWidth;
    int outHeight = bitmapFactoryOptions.outHeight;
    bmap = BitmapFactory.decodeFile(picture.getAbsolutePath(),
         bitmapFactoryOptions);
    float p_w_picpathw = 150;
    float p_w_picpathh = 150;
    int yRatio = (int) Math.ceil(bitmapFactoryOptions.outHeight
            / p_w_picpathh);
    int xRatio = (int) Math
            .ceil(bitmapFactoryOptions.outWidth / p_w_picpathw);
    if (yRatio > 1 || xRatio > 1) {
        if (yRatio > xRatio) {
            bitmapFactoryOptions.inSampleSize = yRatio;
        } else {
            bitmapFactoryOptions.inSampleSize = xRatio;
        }
                                                                                                                                                                                                                                                   
    } 
    bitmapFactoryOptions.inJustDecodeBounds = false;//false --- allowing the caller to query the bitmap without having to allocate the memory for its pixels.
    bmap = BitmapFactory.decodeFile(picture.getAbsolutePath(),
            bitmapFactoryOptions);
    if(bmap != null){               
        //ivwCouponImage.setImageBitmap(bmap);
        return bmap;
    }
    return null;
}

B.境界圧縮の場合、間接的にソフトリードを用いてOOMを避ける
/*    Adapter     */
        public View getView(int position, View convertView, ViewGroup parent) {
            File file = new File(it.get(position));
            SoftReference srf = p_w_picpathCache.get(file.getName());
            Bitmap bit = srf.get();
            ImageView i = new ImageView(mContext);
            i.setImageBitmap(bit);
            i.setScaleType(ImageView.ScaleType.FIT_XY);
            i.setLayoutParams( new Gallery.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT,
                    WindowManager.LayoutParams.WRAP_CONTENT));
            return i;
        }

 
しかし、これらの関数はdecodeを完了した後、最終的にjava層のcreateBitmapによって完成され、より多くのメモリを消費する必要があります.画像が多くて大きい場合、この方法はOOM異常を参照するので、さらに処理する必要があります.
A.第一の方式
InputStream is = this.getResources().openRawResource(R.drawable.pic1);
     BitmapFactory.Options options=new BitmapFactory.Options();
     options.inJustDecodeBounds = false;
     options.inSampleSize = 10;   //width,hight        
     Bitmap btp =BitmapFactory.decodeStream(is,null,options);
 if(!bmp.isRecycle() ){
         bmp.recycle()   //         
         system.gc()  //        
}

 
B.第二中方式
/**
*                  
* */  
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);  
   }

   
C.適切な時期にゴミ回収
if(bitmapObject.isRecycled()==false) //        
         bitmapObject.recycle();

D.Dalvik仮想マシンのスタックメモリ割り当ての最適化
Androidプラットフォームにとって、その管理層が使用するDalvik JavaVMは現在の表現から処理を最適化できるところが多く、eg私たちはいくつかの大規模なゲームや資源を消費するアプリケーションを開発する中で、手動でGC処理に干渉し、dalvikを使用することを考慮する可能性がある.system.VMRuntimeクラスが提供するsetTargetHeapUtilizationメソッドは、プログラムスタックメモリの処理効率を向上させることができます.
private final static floatTARGET_HEAP_UTILIZATION = 0.75f; 
//   onCreate      
VMRuntime.getRuntime().setTargetHeapUtilization(TARGET_HEAP_UTILIZATION);
  

上記が0.75である理由は、ヒープ(HEAP)がVMの中で最もメモリを消費する部分であり、通常は動的に割り当てられているからである.スタックのサイズは一定ではなく、通常、そのサイズを制御するための割り当てメカニズムがあります.例えば、初期のHEAPは4 Mであり、4 Mの空間が75%を超えると、再分配スタックは8 Mである.8 Mが75%を超えると、分配スタックは16 Mの大きさになる.逆に,16 Mのスタック利用が30%未満の場合,その大きさを8 Mに縮小する.スタックのサイズを再設定します.特に圧縮では、メモリのコピーが一般的に発生するため、スタックのサイズを変更することは効率に悪影響を及ぼします.
E.アプリケーションをカスタマイズするにはどれだけのメモリが必要ですか.
private final static int CWJ_HEAP_SIZE = 6* 1024* 1024 ;
 //    heap   6MB  
VMRuntime.getRuntime().setMinimumHeapSize(CWJ_HEAP_SIZE);

以上、私がまとめたOOMの異常を解決する方法です.
参考ブログ:http://blog.sina.com.cn/s/blog_7501670601014dcj.html