Android SDK:写真用のメモリサイズの制限との戦い

描画アプリケーションは、SurfaceViewといくつかの画面サイズのビットマップを使用します(たとえば、本のページのページングをスムーズに表示したい)。



画面解像度が高い多くのデバイスで、アプリケーションがエラーでクラッシュする

AndroidRuntime:java.lang.OutOfMemoryError:ビットマップサイズがVMの予算を超えています



問題は、ビットマップおよびSurfaceViewのメモリが共通のプロセスヒープから予約されていることです。 ヒープサイズの制限は小さく、通常は10MB強です。 そして、システムを組み立てるときにこの制限が設定されます。



ピクセル形式を32ビットから16ビットに削減して状況を改善しようとしても、あまり役に立ちません。 この問題は、後で表面化するだけです-たとえば、SurfaceViewの上にウィンドウを開くと(明らかに、画面サイズの別のビットマップが作成されます)。



プログラムのグラフィックバッファのサイズを3〜4画面に制限するのは、残念なことです。 そのような不正を修正してみましょう。



実際、ビットマップの機能のほとんどはネイティブコード(JNI)で実装されており、バッファはJavaヒープではなく、通常のシステムmallocを使用して割り当てられます。 ヒープサイズの制限が課されるのはなぜですか?



ソースコードを吸います。



trackExternalAllocation / trackExternalFreeメソッドを使用して、Bitmapの各メモリ割り当てがdalvik.system.VMRuntimeに登録されていることがわかります。 制限を超えてメモリを割り当てようとすると、例外をスローするのはtrackExternalAllocationメソッドです。



愚かなロボットをだまそうとしたらどうなるでしょうか? 画像を配置した後、使用したばかりの外部メモリがすでに解放されていると言います。 そして、画像をリリースする前に、対応するメモリサイズが占有されていることを真似てください。



小さな問題を克服するために残っています-trackExternalAllocationおよびtrackExternalFreeメソッドは表示されません。 Reflectionを使用して、「ハッカー」メソッドで呼び出す必要があります。



このアイデアを実装しようとしています。

空のAndroidアプリケーションプロジェクトを作成します。



便宜上、VMRuntimeへのアクセスは別のクラスに実装されています。



static class VMRuntimeHack { private Object runtime = null; private Method trackAllocation = null; private Method trackFree = null; public boolean trackAlloc(long size) { if (runtime == null) return false; try { Object res = trackAllocation.invoke(runtime, Long.valueOf(size)); return (res instanceof Boolean) ? (Boolean)res : true; } catch (IllegalArgumentException e) { return false; } catch (IllegalAccessException e) { return false; } catch (InvocationTargetException e) { return false; } } public boolean trackFree(long size) { if (runtime == null) return false; try { Object res = trackFree.invoke(runtime, Long.valueOf(size)); return (res instanceof Boolean) ? (Boolean)res : true; } catch (IllegalArgumentException e) { return false; } catch (IllegalAccessException e) { return false; } catch (InvocationTargetException e) { return false; } } public VMRuntimeHack() { boolean success = false; try { Class cl = Class.forName("dalvik.system.VMRuntime"); Method getRt = cl.getMethod("getRuntime", new Class[0]); runtime = getRt.invoke(null, new Object[0]); trackAllocation = cl.getMethod("trackExternalAllocation", new Class[] {long.class}); trackFree = cl.getMethod("trackExternalFree", new Class[] {long.class}); success = true; } catch (ClassNotFoundException e) { } catch (SecurityException e) { } catch (NoSuchMethodException e) { } catch (IllegalArgumentException e) { } catch (IllegalAccessException e) { } catch (InvocationTargetException e) { } if (!success) { Log.i(TAG, "VMRuntime hack does not work!"); runtime = null; trackAllocation = null; trackFree = null; } } } private static final VMRuntimeHack runtime = new VMRuntimeHack();
      
      







別個のクラス-ファクトリーの形式でビットマップを作成/リリースします。

ここでは、使用中のメモリに関する情報を修正したビットマップを記憶します-リリース時に戻ることを忘れないようにします。



  static class BitmapFactory { public BitmapFactory(boolean useHack) { this.useHack = useHack; } //   public Bitmap alloc(int dx, int dy) { Bitmap bmp = Bitmap.createBitmap(dx, dy, Bitmap.Config.RGB_565); if (useHack) { runtime.trackFree(bmp.getRowBytes() * bmp.getHeight()); hackedBitmaps.add(bmp); } allocatedBitmaps.add(bmp); return bmp; } //   public void free(Bitmap bmp) { bmp.recycle(); if (hackedBitmaps.contains(bmp)) { runtime.trackAlloc(bmp.getRowBytes() * bmp.getHeight()); hackedBitmaps.remove(bmp); } allocatedBitmaps.remove(bmp); } //    (  ) public void freeAll() { for (Bitmap bmp : new LinkedList<Bitmap>(allocatedBitmaps)) free(bmp); } private final boolean useHack; private Set<Bitmap> allocatedBitmaps = new HashSet<Bitmap>(); private Set<Bitmap> hackedBitmaps = new HashSet<Bitmap>(); }
      
      







次に、このメソッドが機能するかどうかを確認するテストを作成します。 testAllocation()メソッドは、最大数のメガバイトイメージを作成しようとします(OutOfMemoryがクラッシュするか、指定された制限に達するまで)。 UseHackフラグは、テストをハックありまたはハックなしで実行するように設定します。 このメソッドは、写真の下に占めることができた量を返します。



  public int testAllocation(boolean useHack, int maxAlloc) { System.gc(); BitmapFactory factory = new BitmapFactory(useHack); int allocated = 0; // AndroidRuntime: java.lang.OutOfMemoryError: bitmap size exceeds VM budget while (allocated < maxAlloc) { try { Bitmap bmp = factory.alloc(1024, 512); allocated += bmp.getRowBytes() * bmp.getHeight(); Log.i(TAG, "Bitmap bytes allocated " + allocated); } catch (OutOfMemoryError e) { Log.e(TAG, "Exception while allocation of bitmap, total size = " + allocated, e); break; } } factory.freeAll(); return allocated; }
      
      







ハックの有無にかかわらず、Activity.onCreate()でテストを呼び出します。

結果を画面とログに表示します。 (レイアウト「メイン」を修正する必要があります-テキストを変更するTextView IDに追加します)。



  public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // perform test int allocatedNormally = testAllocation(false, 48 * MB); int allocatedWithHack = testAllocation(true, 48 * MB); String msg = "normally: " + (allocatedNormally / MB) + " MB allocated " + "\nwith hack: " + (allocatedWithHack / MB) + " MB allocated"; Log.i(TAG, msg); // display results LayoutInflater inflater = LayoutInflater.from(this); View main = (View)inflater.inflate(R.layout.main, null); TextView text = (TextView)main.findViewById(R.id.text); text.setText(msg); setContentView(main); }
      
      







したがって、実行して結果を確認します。

03-07 09:43:37.233: I/bmphack(17873): normally: 10 MB allocated

03-07 09:43:37.233: I/bmphack(17873): with hack: 48 MB allocated









ハックなしで、それぞれ1メガバイトの写真を10枚だけ作成できました。 ハックあり-指定した最大数(48メガバイト)。

サイズが大きい場合、テストはまだシミュレーターでハングしました-58Mb後。



誰かがこの記事を参考にするといいのですが。



All Articles