web-dev-qa-db-ja.com

SurfaceHolderコールバックはアクティビティライフサイクルとどのように関連していますか?

サーフェス上でカメラプレビューを必要とするアプリケーションを実装しようとしています。私が物事を見ているように、活動と表面ライフサイクルの両方は、次の状態で構成されています。

  1. アクティビティを初めて起動したとき:onResume()->onSurfaceCreated()->onSurfaceChanged()
  2. アクティビティを離れるとき:onPause()->onSurfaceDestroyed()

このスキームでは、_onPause/onResume_およびonSurfaceCreated()/onSurfaceDestroyed()でカメラのオープン/リリースやプレビューの開始/停止などの対応する呼び出しを行うことができます。

画面をロックしない限り、問題なく動作します。アプリを起動し、画面をロックして後でロックを解除すると、次のように表示されます。

onPause()-そして、画面がロックされた後は他に何もありません-そしてロック解除後はonResume()-そしてその後は表面のコールバックはありません。実際、onResume()は、電源ボタンが押されて画面がオンになった後に呼び出されますが、ロック画面はまだアクティブであるため、アクティビティが表示される前です。

このスキームでは、ロック解除後に黒い画面が表示され、表面のコールバックは呼び出されません。

以下は、カメラでの実際の作業を含まないコードフラグメントですが、SurfaceHolderコールバックです。上記の問題は、私の電話でこのコードを使用しても再現されます(コールバックは、[戻る]ボタンを押すと通常の順序で呼び出されますが、画面をロックすると失われます)。

_class Preview extends SurfaceView implements SurfaceHolder.Callback {

    private static final String tag= "Preview";

    public Preview(Context context) {
        super(context);
        Log.d(tag, "Preview()");
        SurfaceHolder holder = getHolder();
        holder.addCallback(this);
        holder.setType(SurfaceHolder.SURFACE_TYPE_Push_BUFFERS);
    }

    public void surfaceCreated(SurfaceHolder holder) {
        Log.d(tag, "surfaceCreated");
    }

    public void surfaceDestroyed(SurfaceHolder holder) {
        Log.d(tag, "surfaceDestroyed");
    }

    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
        Log.d(tag, "surfaceChanged");
    }
}
_

アクティビティが一時停止された後に表面が破壊されない理由についてのアイデアはありますか?また、そのような場合のカメラのライフサイクルをどのように処理しますか?

67
krugloid

Edit:targetSDKが10を超える場合、アプリをスリープ状態にしますonPauseandonStopソース

Gingerbread電話の小さなカメラアプリで、アクティビティとSurfaceViewの両方のライフサイクルを調べました。あなたは完全に正しいです。電源ボタンを押して電話機をスリープ状態にしても、表面は破壊されません。電話がスリープ状態になると、アクティビティはonPauseを実行します。 (そしてonStopはしません。)電話機が起動したときにonResumeを行い、あなたが指摘するように、ロック画面がまだ表示され、入力を受け入れている間にこれを行います。少し奇妙です。ホームボタンを押してアクティビティを非表示にすると、アクティビティはonPauseonStopの両方を実行します。この場合、surfaceDestroyedの終わりとonPauseの始まりの間で、何かがonStopへのコールバックを引き起こします。それはあまり明白ではありませんが、非常に一貫しているようです。

電源ボタンを押して電話をスリープさせると、電話を停止するための明示的な操作がない限り、カメラは動作し続けます!プレビューフレームごとにカメラに画像ごとのコールバックを実行させ、そこにLog.d()を含めると、電話がスリープするふりをしている間、ログステートメントが表示され続けます。これはVery Sneakyと思います。

別の混乱として、surfaceCreatedsurfaceChangedへのコールバックは、サーフェスが作成されている場合、アクティビティでafteronResumeが発生します。

原則として、SurfaceHolderコールバックを実装するクラスでカメラを管理します。

class Preview extends SurfaceView implements SurfaceHolder.Callback {
    private boolean previewIsRunning;
    private Camera camera;

    public void surfaceCreated(SurfaceHolder holder) {
        camera = Camera.open();
        // ...
        // but do not start the preview here!
    }

    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
        // set preview size etc here ... then
        myStartPreview();
    }

    public void surfaceDestroyed(SurfaceHolder holder) {
        myStopPreview();
        camera.release();
        camera = null;
    }

   // safe call to start the preview
   // if this is called in onResume, the surface might not have been created yet
   // so check that the camera has been set up too.
   public void myStartPreview() {
       if (!previewIsRunning && (camera != null)) {
           camera.startPreview();
           previewIsRunning = true;
       }
   }

   // same for stopping the preview
   public void myStopPreview() {
       if (previewIsRunning && (camera != null)) {
           camera.stopPreview();
           previewIsRunning = false;
       }
   }
}

そしてアクティビティで:

@Override public void onResume() {
    preview.myStartPreview();  // restart preview after awake from phone sleeping
    super.onResume();
}
@Override public void onPause() {
    preview.myStopPreview();  // stop preview in case phone is going to sleep
    super.onPause();
}

それは私にとってはうまくいくようです。回転イベントにより、アクティビティが破棄および再作成されます。これにより、SurfaceViewも破棄および再作成されます。

56
emrys57

正常に動作する別のシンプルなソリューション-プレビューサーフェスの可視性を変更します。

private SurfaceView preview;

previewはonCreateメソッドのinitです。 onResumeメソッドでView.VISIBLEプレビューサーフェスの場合:

@Override
public void onResume() {
    preview.setVisibility(View.VISIBLE);
    super.onResume();
}

onPauseでそれぞれ可視性を設定View.GONE

@Override
public void onPause() {
    super.onPause();
    preview.setVisibility(View.GONE);
    stopPreviewAndFreeCamera(); //stop and release camera
}
20
validcat

これまでのすべての回答のおかげで、バックグラウンドまたはロック画面から戻るときに、カメラのプレビューを簡単に機能させることができました。

@ e7fendyが言及したように、システムの表面ビューはまだ表示されているため、スクリーンロック中にSurfaceViewのコールバックは呼び出されません。

したがって、@ validcatが推奨したように、onPause()およびonResume()でそれぞれpreview.setVisibility(View.VISIBLE);およびpreview.setVisibility(View.GONE);を呼び出すと、表面ビューが強制的に再レイアウトされ、コールバックが呼び出されます。

それまでに、@ emrys57のソリューションと上記の2つの可視性メソッドの呼び出しにより、カメラのプレビューが簡単に機能するようになります:)

だから、私はあなたがすべてに値するので、あなたのそれぞれに+1を与えることができます;)

2
ptitvinou

SurfaceHolder.Callbackは、そのSurfaceに関連しています。

画面にアクティビティがありますか?その場合、Surfaceはまだ画面上にあるため、SurfaceHolder.Callbackはありません。

SurfaceViewを制御するには、onPause/onResumeでのみ処理できます。 SurfaceHolder.Callbackの場合、SurfaceCreatedの場合はopenGLを初期化し、surfaceDestroyedの場合はopenGLを破棄するなど、Surfaceが変更(作成、サイズ変更、および破棄)された場合に使用できます。

1
e7fendy