Androidのアプリで頻繁に描画を更新したい時、カメラを使う時などに、SurfaceViewを使いますよね。例えばカメラを使う時だと、surfaceCreatedかsurfaceChangedでカメラをopenしてsurfaceDestroyedでreleaseしたりしますよね。
よくあるサンプルだと、例えばこんな感じ(CameraのプレビューをSurfaceViewへ描画するときのSurfaceHolder.Callback)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
private Camera mCamera; private final SurfaceHolder.Callback mSurfaceHolderCallback = new SurfaceHolder.Callback() { public void surfaceCreated(SurfaceHolder holder) { mCamera = Camera.open(); try { mCamera.setPreviewDisplay(holder); } catch (Exception e) { e.printStackTrace(); } } public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { final Camera.Parameters params = mCamera.getParameters(); // 良い子のみんなはgetSupportedPreviewSizesを使ってちゃんと設定しましょう(^_^)/ // 機種によっては、setPreviewSizeにViewのwidth/heightを渡すだけだと // RuntimeException等で正常に動かないことが有ります。 // とりあえず動かすだけなら下の2行(setPreviewSizeとsetParameters)を // コメントアウトすると初期値のままで動くはずです params.setPreviewSize(width, height); // 動かない時はこの行と mCamera.setParameters(params); // この行をコメントアウト mCamera.startPreview(); } public void surfaceDestroyed(SurfaceHolder holder) { mCamera.release(); mCamera = null; } }; |
ところがですよ、surfaceDestroyedを思ったタイミングで呼んでもらえない時が有るんです。
ふつ〜にアプリを立ち上げて、バックキーで終了してとかであれば、Activityのライフサイクルに合わせてsurfaceCreated→ surfaceChanged→ surfaceDestroyedと呼ばれるので何の問題もありません。
でも、例えば電源ボタンを押して画面を消すとActivityは通常通りにonPause→onStopまではイベントが発生するものの、surfaceは残ったままでsurfaceDestroyedも呼ばれません。
カメラを使っていればそのまま裏で動きっぱなし、SurfaceViewの描画更新のためにスレッドを生成してたり、Runnableで周期的にオートフォーカス等の処理を行っているとそれも動きっぱなしのお行儀の悪いアプリになってしまいます。ガ━━(;゚Д゚)━━ン!!
対応策ですが、まずはSurfaceが存在しているかどうかを保持するためのprivateなフィールドを設けてsurfaceCreatedでtrueにセット、surfaceDestroyedでクリアします。
その上で
1.ActivityやFragmentのonPause/onStopとonResume/onStartでSurfaceが存在しているかどうかに合わせて処理を停止・復帰処理を行ってあげる。
例えばこんな感じ(SurfaceViewを使ってカメラのプレビューを表示するActivityのコードを抜粋)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
private boolean mHasSurface; private Camera mCamera; private final SurfaceHolder.Callback mSurfaceHolderCallback = new SurfaceHolder.Callback() { public final void surfaceCreated(SurfaceHolder holder) { mHasSurface = true; openCamera(holder); } public final void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { setupCamera(width, height); } public final void surfaceDestroyed(SurfaceHolder holder) { mHasSurface = false; onPause(); } }; @Override public void onResume() { if (mHasSurace) { openCamera(getHolder()); setupCameraParams(getWidth(), getHeight()); } } @Override public void onPause() { if (mCamera != null) { mCamera.release(); mCamera = null; } } /** * カメラを使用中でなければopenする */ private final void openCamera(SurfaceHolder holder) { if (mCamera == null) { mCamera = Camera.open(); try { mCamera.setPreviewDisplay(holder); } catch (Exception e) { e.printStackTrace(); } } } /** * カメラのパラメータを設定する */ private final void setupCamera(int width, int height) { if (mCamera != null) { final Camera.Parameters params = mCamera.getParameters(); // 良い子のみんなはgetSupportedPreviewSizesを使ってちゃんと設定しましょう(^_^)/ // 機種によっては、setPreviewSizeにViewのwidth/heightを渡すだけだと // RuntimeException等で正常に動かないことが有ります。 // とりあえず動かすだけなら下の2行(setPreviewSizeとsetParameters)を // コメントアウトすると初期値のままで動くはずです params.setPreviewSize(width, height); // 動かない時はこの行と mCamera.setParameters(params); // この行をコメントアウト mCamera.startPreview(); } } |
1 2