web-dev-qa-db-ja.com

Android:ギャラリーのメモリ不足の例外

私のアプリは9つのカテゴリのリストを表示し、各カテゴリには、選択したカテゴリの画像を含むギャラリーベースのカバーフロー(Neil Davies here が提供)が表示されます。

画像はそれぞれ300Kから500KのサイズのWebからフェッチされ、DrawableのarrayListに格納されます。このデータは、BaseAdapter(以下のコード)を使用してCoverflowにバインドされます。
coverflowを終了してカテゴリのリストに戻るたびに、arrayListをクリアします(ここでも、以下のコード)。

シナリオ1では、arrayListに5つのDrawableが含まれています。このシナリオでは、すべてのカテゴリを自由に閲覧して、それらの画像を表示できます。テスト中、すべてのカテゴリを5回循環しました。これは、問題がないと判断するのに十分なようです。

シナリオ2では、arrayListに10個のドローアブルが含まれています。このシナリオでは、5番目または6番目のカテゴリ内の画像を処理しているときにOutOfMemoryError例外が発生します。

 07-13 08:38:21.266:エラー/ dalvikvm-heap(2133):このプロセスには819840バイトの外部割り当てが大きすぎます。
 07-13 08:38:21.266:エラー/ (2133):VM 819840バイトを割り当てられません
 07-13 08:38:21.277:DEBUG/skia(2133):---デコーダー->デコードfalseを返しました
 07-13 08:38:21.287:WARN/dalvikvm(2133):threadid = 25:キャッチされない例外でスレッドが終了します(group = 0x4001b188)
 07-13 08:38:21.296 :ERROR/AndroidRuntime(2133):キャッチされないハンドラー:スレッドThread-64がキャッチされない例外のために終了します
 07-13 08:38:21.308:ERROR/AndroidRuntime(2133):Java.lang.OutOfMemoryError:ビットマップサイズが超過していますVM予算
 07-13 08:38:21.308:エラー/ AndroidRuntime(2133):Android.graphics.BitmapFactory.nativeDecodeStream(ネイティブメソッド)
 07 -13 08:38:21.308:エラー/ AndroidRuntime(2133):Android.graphics.BitmapFactory.decodeStream(BitmapFactory.Java:459)
 07-13 08:38:21.308:エラー/ AndroidRuntime(2133) :Android.graphics.BitmapFactory.deでcodeResourceStream(BitmapFactory.Java:323)
 07-13 08:38:21.308:ERROR/AndroidRuntime(2133):at Android.graphics.drawable.Drawable.createFromResourceStream(Drawable.Java:697)
 07-13 08:38:21.308:エラー/ AndroidRuntime(2133):Android.graphics.drawable.Drawable.createFromStream(Drawable.Java:657)

これは私には意味がありません。メモリリークが発生した場合、シナリオ1のある時点でクラッシュすると予想されていましたが、すべてのカテゴリをかなりの回数通過し、クラッシュしませんでした。また、Eclipse用のMemory Analyzerプラグインを使用しましたが、潜在的な原因はありませんでした。

シナリオ2のように、システムが10枚の画像を処理できなかった場合、最初のカテゴリでクラッシュすると予想されていましたが、5つまたは6つのカテゴリの後でのみクラッシュします。

Coverflowのアダプター機能:

public int getCount() {
     return DataManager.getInstance().getImageBufferInstance().getImageArraySize(); 
}

public Object getItem(int position) {    
     return DataManager.getInstance().getImagesBuffer().get(position);
}

public long getItemId(int position) {
     return position;
}

public View getView(int position, View convertView, ViewGroup parent) {      
         ImageView i;
         if (convertView == null)
             i = new ImageView(mContext);
         else
             i = (ImageView)convertView;
         Drawable bufferedImage = (Drawable)getItem(position);
         Log.v("getView", "position: " + position);
         i.setImageDrawable(bufferedImage);

         i.setLayoutParams(new CoverFlow.LayoutParams(Utils.getInstance().getScreenWidth() / 2,
                 Utils.getInstance().getScreenHeight() / 2));
         i.setScaleType(ImageView.ScaleType.CENTER_INSIDE); 

         try{
         //Make sure we set anti-aliasing otherwise we get jaggies
         BitmapDrawable drawable = (BitmapDrawable) i.getDrawable();
         drawable.setAntiAlias(true);
         }
         catch (Exception e)
         {
             Log.v("getView", "Exception: " + e.toString());
         }
         return i;      
     }

カテゴリへの入力時にデータソースにデータを入力します。

for (int i = 0; i < ImageBuffer.getInstance().getImageArraySize(); i++)  
{  
  String imageUrl = ImageBuffer.getInstance().getImageUrl(i);  
  Log.v("Initial", imageUrl);  
  Drawable fullImage = AsyncImageLoader.getInstance().loadImageByUrl(imageUrl);  
  ImageBuffer.getInstance().getImages().add(i, fullImage);  

}

カテゴリを終了するときにデータソースをクリアする(finish()内):

for (int i = 0; i < ImageBuffer.getInstance().getImageArraySize(); i++)  
{  
  if (ImageBuffer.getInstance().images.get(i) != null)  
            {  
                ImageBuffer.getInstance().images.get(i).setCallback(null);  
                ImageBuffer.getInstance().images.set(i, null);  
            }    

}

編集:

OK、MathiasのLogHeap関数をCoverflowに適用しました。ここにいくつかの出力があります。最初のギャラリーをロードする前に:

 DEBUG/Application(5221):デバッグ。 ================================= 
 DEBUG/Application(5221):debug.heap native: [com.example.Coverflow] 
 DEBUG/Application(5221)に6.20MBの6.28MB(0.07MB空き)を割り当て:debug.memory:割り当て:4.00MBの24.00MB(0.00MB空き)
 DEBUG/dalvikvm(5221):GCは84msで4558オブジェクト/ 638152バイトを解放しました
 DEBUG/dalvikvm(5221):GCは67msで17オブジェクト/ 808バイトを解放しました

最初のギャラリーに入った後:

 DEBUG/Application(5221):デバッグ。 ================================= 
 DEBUG/Application(5221):debug.heap native: [com.example.Coverflow] 
 DEBUG/Application(5221)に14.90MBの16.89MB(0.07MB空き)を割り当て:debug.memory:割り当て:4.00MBの24.00MB(1.00MB空き)
 DEBUG/dalvikvm(5221):GCが357オブジェクトを解放/ 68msで50080バイト
 DEBUG/dalvikvm(5221):GCが353オブジェクト/ 27312バイトを67msで解放

最初のギャラリーが存在した後:

 DEBUG/Application(5221):デバッグ。 ================================= 
 DEBUG/Application(5221):debug.heap native: [com.example.Coverflow] 
 DEBUG/Application(5221)に16.89MB(0.11MB空き)の14.83MBを割り当て:debug.memory:割り当て:24.00MB(1.00MB空き)の4.00MB 
 DEBUG/dalvikvm(5221):GCは77msで330オブジェクト/ 17920バイトを解放しました
 DEBUG/dalvikvm(5221):GCは67msで13オブジェクト/ 760バイトを解放しました

5番目のギャラリーに入った後:

 DEBUG/Application(5221):デバッグ。 ================================= 
 DEBUG/Application(5221):debug.heap native: [com.example.Coverflow] 
 DEBUG/Application(5221)に23.32MB(0.08MB空き)の16.80MBを割り当て:debug.memory:割り当て:24.00MB(1.00MB空き)の4.00MB 
 DEBUG/dalvikvm(5221):GCが842オブジェクトを解放/ 73msで99256バイト
 DEBUG/dalvikvm(5221):GCが306オブジェクトを解放/ 69msで24896バイト

5番目のギャラリーを終了した後:

 DEBUG/Application(5221):デバッグ。 ================================= 
 DEBUG/Application(5221):debug.heap native: [com.example.Coverlow] 
 DEBUG/Application(5221)に23.32MB(0.11MB空き)の16.74MBを割り当て:debug.memory:割り当て:24.00MB(1.00MB空き)の4.00MB 
 DEBUG/dalvikvm(5221):GCが331オブジェクト/ 68msで18184バイトを解放しました
 DEBUG/dalvikvm(5221):GCが60オブジェクト/ 68msで3128バイトを解放しました

ギャラリーに入るときはますます多くのメモリが割り当てられているようですが、出るとほとんど解放されません。ドローアブルを適切にクリアしていませんか?ドローアブルのarrayList内の要素ごとに、setCallBack(null)を呼び出し、要素をnullに設定します。それだけでは不十分ですか?
どんな洞察にも必死です。
ありがとう

21
Rob

画像はそれぞれ300Kから500KのサイズのWebからフェッチされ、DrawableのarrayListに格納されます。

Webからロードする画像のkbファイルサイズは直接関係ありません。それらはビットマップに変換されるため、通常のARGB画像の場合、画像ごとに幅*高さ* 4バイトを計算する必要があります。 (幅と高さ(ピクセル単位))。

ビットマップはネイティブヒープを消費しますが、これは通常hprofには表示されません。 hprofは、残っているオブジェクトの数、つまりBitmapDrawablesまたはBitmapsのみを表示する必要があります。

アプリでこのコードを使用して、アプリとネイティブヒープで現在使用されているメモリを出力します。

public static void logHeap(Class clazz) {
    Double allocated = new Double(Debug.getNativeHeapAllocatedSize())/new Double((1048576));
    Double available = new Double(Debug.getNativeHeapSize())/1048576.0);
    Double free = new Double(Debug.getNativeHeapFreeSize())/1048576.0);
    DecimalFormat df = new DecimalFormat();
    df.setMaximumFractionDigits(2);
    df.setMinimumFractionDigits(2);

    Log.d(APP, "debug. =================================");
    Log.d(APP, "debug.heap native: allocated " + df.format(allocated) + "MB of " + df.format(available) + "MB (" + df.format(free) + "MB free) in [" + clazz.getName().replaceAll("com.myapp.Android.","") + "]");
    Log.d(APP, "debug.memory: allocated: " + df.format(new Double(Runtime.getRuntime().totalMemory()/1048576)) + "MB of " + df.format(new Double(Runtime.getRuntime().maxMemory()/1048576))+ "MB (" + df.format(new Double(Runtime.getRuntime().freeMemory()/1048576)) +"MB free)");
    System.gc();
    System.gc();

    // don't need to add the following lines, it's just an app specific handling in my app        
    if (allocated>=(new Double(Runtime.getRuntime().maxMemory())/new Double((1048576))-MEMORY_BUFFER_LIMIT_FOR_RESTART)) {
        Android.os.Process.killProcess(Android.os.Process.myPid());
    }
}

これは、開発中にアクティビティを開始または終了するときに呼び出します。

logHeap(this.getClass());

ここにいくつかの有益なリンクがあります-一般的にここにはこのトピックに関するたくさんのスレッドがあります。

また、ソフト参照、弱参照、単純なキャッシュ、画像処理に関するRomain Guy(Android Frameworkエンジニア)による便利なスライドもあります。 http://docs.huihoo.com/google/io/2009/Th_0230_TurboChargeYourUI-HowtomakeyourAndroidUIfastandefficient。 pdf

37
Mathias Conradt

ここにいくつかのアドバイスがあります:

  1. InSampleSizeオプションを使用していますか?画像を拡大縮小すると、メモリ消費量が削減されます。 ビットマップオブジェクトに画像をロードする際のメモリ不足の問題

  2. 画像が不要になった場合は、Bitmap.recycle()を呼び出す必要があります。あなたの場合、それは重要だと思います。 Android:OutofMemoryError:ビットマップサイズがVM予算を超えていますが、理由はわかりません

4
Fedor

convertViewのパラメータリストのgetViewは常にnullであることに注意してください。つまり、ギャラリーは内部の古いビューを再利用しません。

0
user2046063

ギャラリー5または6にロードしている画像は大きすぎてロードできない可能性があり、VMで許可されている最大サイズを超えています。

0
Ryan Conrad