73

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

  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");
    }
}

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

4

5 に答える 5

59

編集: targetSDKが10より大きい場合、アプリをスリープ状態にonPause onStopます。ソース

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

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

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

原則として、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も破棄および再作成されます。

于 2012-12-02T16:47:17.573 に答える
23

正常に機能するもう1つの簡単なソリューションは、プレビューサーフェスの可視性を変更することです。

private SurfaceView preview;

プレビューはonCreateメソッドの初期化です。プレビューサーフェスに設定された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
}
于 2014-05-21T14:44:28.110 に答える
3

以前のすべての回答のおかげで、バックグラウンドまたはロック画面から戻っている間、カメラのプレビューをわかりやすく機能させることができました。

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

したがって、@ validcatがアドバイスしたように、それぞれonPause()とonResume()を呼び出すと、サーフェスビューがそれ自体をリレーアウトし、コールバックと呼ばれますpreview.setVisibility(View.VISIBLE);preview.setVisibility(View.GONE);

それまでに、@ emrys57のソリューションに加えて、上記の2つの可視性メソッドの呼び出しにより、カメラのプレビューがわかりやすく機能します:)

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

于 2015-03-10T14:29:09.063 に答える
1

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

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

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

于 2012-11-28T02:44:19.430 に答える
-2

これは、すべてのコールバックメソッドの代替ソリューションです。これらはすべて、アクティビティサイクルで同じ未定義のイベント順序の動作の対象となる可能性があります。オリジントリガーを決定するために使用する各コールバックのすべてのAndroidコードを検査し、実装を制御し、コードベースが将来変更されないことを期待しない限り、コールバック間のイベントの順序を実際に述べることができますアクティビティのライフサイクルイベントが保証される可能性があります。

現在、これらの順序の相互作用は、通常、開発目的では未定義動作と呼ばれます。

したがって、順序が定義された動作であることを確認することにより、最初から問題にならないように、この未定義の動作を常に正しく処理することが最善です。

たとえば、私のSony Xperiaは、スリープ時に、アプリを破棄してから再起動し、信じられないかもしれませんが一時停止状態にすることで、現在のアプリを循環させます。

ホスト環境の特別なテストビルドとしてGoogleがSDKで提供するイベント順序の動作テストの量はわかりませんが、イベントの順序の動作はすべて、かなり厳密にすることでロックダウンされるように努力する必要があります。案件。

https://code.google.com/p/android/issues/detail?id=214171&sort=-opened&colspec=ID%20Status%20Priority%20Owner%20Summary%20Stars%20Reporter%20Opened

import android.util.Log; import android.util.SparseArray;

/ ***2016/06/24にwoliverによって作成されました。* * Androidホスト環境は、OnCreate、onStart、onResume、onPause、onStop、onDestoryのアクティビティライフサイクルを指示します。*他のアプリケーションが使用するためにメモリとハンドルを解放する必要があります。*再開時に、これらのアイテムを他のオブジェクトと再バインドしてアクティブ化する必要がある場合があります。*通常、これらの他のオブジェクトは、onCreatedおよびonDestroyを提供するホスト環境からのコールバックメソッドを提供します。このメソッドでは、OnCreatedからのみこのオブジェクトにバインドでき、*onDestoryのバインドを解除します。*これらのタイプのコールバックメソッド、実行時間はホスト環境によって制御されます*。これらは、アクティビティライフサイクルとこれらのコールバックメソッドの動作/実行順序が一貫していることを保証するものではありません。*開発の目的で、相互作用と実行の順序は、ホスト実装の実装者であるsamsung、sony、htc次第であるため、技術的には未定義と呼ぶことができます。**次の開発者向けドキュメントを参照してください。https://developer.android.com/reference/android/app/Activity.html *見積もり:*アクティビティが別のアクティビティによって完全に隠されている場合、そのアクティビティは停止されます。それでもすべての状態とメンバー情報が保持されますが、ユーザーには表示されなくなるため、ウィンドウは非表示になり、他の場所でメモリが必要になるとシステムによって強制終了されることがよくあります。* EndQuato:* *アクティビティが非表示になっていない場合、OnCreateメソッドやOnDestoryメソッドインターフェイスのSurfaceViewコールバックなど、ホストシステムによって呼び出されると予想されるコールバックは呼び出されません。*これは、カメラなどのSurfaceViewにバインドされたオブジェクトを一時停止する必要があり、OnCreateコールバックが呼び出されないため、オブジェクトを再バインドしないことを意味します。* * /

public abstract class WaitAllActiveExecuter<Size>
{
     private SparseArray<Boolean> mReferancesState = null;

// Use a dictionary and not just a counter, as hosted code
// environment implementer may make a mistake and then may double executes things.
private int mAllActiveCount = 0;
private String mContextStr;

public WaitAllActiveExecuter(String contextStr, int... identifiers)
{
    mReferancesState = new SparseArray<Boolean>(identifiers.length);

    mContextStr = contextStr;

    for (int i  = 0; i < identifiers.length; i++)
        mReferancesState.put(identifiers[i], false);
}

public void ActiveState(int identifier)
{
    Boolean state = mReferancesState.get(identifier);

    if (state == null)
    {
        // Typically panic here referance was not registered here.
        throw new IllegalStateException(mContextStr + "ActiveState: Identifier not found '" + identifier + "'");
    }
    else if(state == false){

        mReferancesState.put(identifier, true);
        mAllActiveCount++;

        if (mAllActiveCount == mReferancesState.size())
            RunActive();
    }
    else
    {
        Log.e(mContextStr, "ActivateState: called to many times for identifier '" + identifier + "'");
        // Typically panic here and output a log message.
    }
}

public void DeactiveState(int identifier)
{
    Boolean state = mReferancesState.get(identifier);

    if (state == null)
    {
        // Typically panic here referance was not registered here.
        throw new IllegalStateException(mContextStr + "DeActiveState: Identifier not found '" + identifier + "'");
    }
    else if(state == true){

        if (mAllActiveCount == mReferancesState.size())
            RunDeActive();

        mReferancesState.put(identifier, false);
        mAllActiveCount--;
    }
    else
    {
        Log.e(mContextStr,"DeActiveState: State called to many times for identifier'" + identifier + "'");
        // Typically panic here and output a log message.
    }
}

private void RunActive()
{
    Log.v(mContextStr, "Executing Activate");

    ExecuterActive();
}

private void RunDeActive()
{
    Log.v(mContextStr, "Executing DeActivate");

    ExecuterDeActive();
}


abstract public void ExecuterActive();

abstract public void ExecuterDeActive();
}

実装とクラスの使用の例。これは、Androidホスト環境の実装者の未定義の動作を処理します。

private final int mBCTSV_SurfaceViewIdentifier = 1;
private final int mBCTSV_CameraIdentifier = 2;

private WaitAllActiveExecuter mBindCameraToSurfaceView =
        new WaitAllActiveExecuter("BindCameraToSurfaceViewe", new int[]{mBCTSV_SurfaceViewIdentifier, mBCTSV_CameraIdentifier})
{
    @Override
    public void ExecuterActive() {

        // Open a handle to the camera, if not open yet and the SurfaceView is already intialized.
        if (mCamera == null)
        {
            mCamera = Camera.open(mCameraIDUsed);

            if (mCamera == null)
                throw new RuntimeException("Camera could not open");

            // Look at reducing the calls in the following two methods, some this is unessary.
            setDefaultCameraParameters(mCamera);
            setPreviewSizesForCameraFromSurfaceHolder(getSurfaceHolderForCameraPreview());
        }

        // Bind the Camera to the SurfaceView.
        try {
            mCamera.startPreview();
            mCamera.setPreviewDisplay(getSurfaceHolderForCameraPreview());
        } catch (IOException e) {

            e.printStackTrace();
            ExecuterDeActive();

            throw new RuntimeException("Camera preview could not be set");
        }
    }

    @Override
    public void ExecuterDeActive() {

        if ( mCamera != null )
        {
            mCamera.stopPreview();

            mCamera.release();
            mCamera = null;
        }
    }
};

@Override
protected void onPause() {


    mBindCameraToSurfaceView.DeactiveState(mBCTSV_CameraIdentifier);

    Log.v(LOG_TAG, "Activity Paused - After Super");
}

@Override
public void  onResume() {

    mBindCameraToSurfaceView.ActiveState(mBCTSV_CameraIdentifier);
}

private class SurfaceHolderCallback implements SurfaceHolder.Callback
{
    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
    Log.v(LOG_TAG, "Surface Changed");

    }

    public void surfaceCreated(SurfaceHolder surfaceHolder) {

        Log.v(LOG_TAG, "Surface Created");
        mBindCameraToSurfaceView.ActiveState(mBCTSV_SurfaceViewIdentifier);
    }

    public void surfaceDestroyed(SurfaceHolder arg0) {

        Log.v(LOG_TAG, "Surface Destoryed");
        mBindCameraToSurfaceView.DeactiveState(mBCTSV_SurfaceViewIdentifier);
    }
}
于 2016-06-25T07:43:56.727 に答える