私のアプリには、カメラのプレビューとして機能するアクティビティがあります。画面全体に広がるSurfaceViewと、その上にオーバーレイされた多数のアイテムを使用しますが、それほど複雑なものはありません。このアクティビティは他のアクティビティを起動して、カメラのプレビューに戻ることができます。
リソースのクリーンアップ、ビットマップのリサイクル、メモリリークの回避などに細心の注意を払っています。このアプリを実行して、狂ったようにテストできますが、スマートフォンがしばらくオンになっていて、他のアプリがメモリにあると、カメラプレビューを保持するアクティビティを再開または作成すると、サイレントシャットダウンが発生します。クラッシュを再現するための一般的なテストケースは、アプリの使用、写真のスナップ(処理をトリガーする)、サブアクティビティの起動などです。アプリを終了し、リソースやグラフィックが重いものを起動してから、アプリを再開します。
クラッシュ時のlogcat出力は次のとおりです。
03-29 14:20:02.109: ERROR/dalvikvm(6368): externalAllocPossible(): footprint 2756592 + extAlloc 15831356 + n 8640000 >= max 22409232 (space for 3821284)
03-29 14:20:02.109: ERROR/dalvikvm-heap(6368): 8640000-byte external allocation too large for this process.
03-29 14:20:02.109: ERROR/dalvikvm(6368): Out of memory: Heap Size=3835KB, Allocated=2835KB, Bitmap Size=15460KB, Limit=21884KB
03-29 14:20:02.109: ERROR/dalvikvm(6368): Trim info: Footprint=5383KB, Allowed Footprint=5383KB, Trimmed=1548KB
03-29 14:20:02.109: ERROR/GraphicsJNI(6368): VM won't let us allocate 8640000 bytes
私のアクティビティはすべてのステップでログに記録されるため、これは、super.onCreateを呼び出してからコンテキストビューをxmlレイアウトに設定するまでの間にActivity.onCreateで発生します。私が最初に考えたのは、SurfaceHolderを取得するプロセス、またはSurfaceHolderメソッドで発生するプロセスは、メモリが不足している状況では多すぎる可能性があるということでした。xmlレイアウトを解析し、Viewオブジェクトを構築しているときに、setContentViewで発生しているようです。
私のカメラプレビューコードは、本や記事で見つけた例から取られているので、surfaceDestroyedで行う必要のある追加のクリーンアップがあるかどうか疑問に思っていますか?その時点でガベージコレクションをトリガーする必要がありますか?この考え方の理由は、メモリ内のアプリが少ない状況でアプリを実行するのに十分なメモリがシステムにあるためです。これは、自分のアプリが十分にクリーンアップされていないか、システムが自分のアプリに十分な速度でメモリを再利用できないことに関係しています。私が理解していないのは、setContentView中に多くの新しいメモリを割り当てようとしている理由です。
これが私のSurfaceコールバックコードとアクティビティで行われていることの言い換えです
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.camera_preview);
// crash occurs here
// ...other stuff
initControls();
}
private void initControls()
{
previewHolder = preview.getHolder();
previewHolder.addCallback(surfaceCallback);
previewHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
// ...other stuff
}
SurfaceHolder.Callback surfaceCallback = new Callback() {
public void surfaceDestroyed(SurfaceHolder holder) {
Log.d(ApplicationEx.LogTag, "surfaceDestroyed");
camera.stopPreview();
camera.release();
camera = null;
isPreviewRunning = false;
}
public void surfaceCreated(SurfaceHolder holder) {
Log.d(ApplicationEx.LogTag, "surfaceCreated");
camera = Camera.open();
try
{
camera.setPreviewDisplay(previewHolder);
}
catch(Throwable t)
{
}
}
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
{
Log.d(ApplicationEx.LogTag, "surfaceChanged");
if (isPreviewRunning)
{
Log.d(ApplicationEx.LogTag, "preview is running, stop preview");
camera.stopPreview();
isPreviewRunning = false;
}
Camera.Parameters parameters = camera.getParameters();
setPreviewAndPictureSize(parameters, width, height);
parameters.setPictureFormat(PixelFormat.JPEG);
parameters.setJpegQuality(85);
camera.setParameters(parameters);
camera.startPreview();
isPreviewRunning = true;
Log.d(ApplicationEx.LogTag, "end surfaceChanged");
}
};