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

关于在 Activity 暂停后为什么表面仍未被破坏的任何想法?此外,在这种情况下,您如何处理相机生命周期?

4

5 回答 5

59

编辑:如果 targetSDK 大于 10,则将应用置于睡眠调用onPause onStop. 资源

我在姜饼手机上的一个微型相机应用程序中查看了 Activity 和 SurfaceView 的生命周期。你是完全正确的;按下电源按钮使手机进入睡眠状态时,表面不会被破坏。当手机进入睡眠状态时,Activity 会onPause。(并且不这样做onStop。)onResume当手机唤醒时它会这样做,而且,正如你所指出的,它会在锁定屏幕仍然可见并接受输入时这样做,这有点奇怪。当我通过按 Home 按钮使 Activity 不可见时,Activity 会同时执行onPauseonStop。在这种情况下,有些东西会导致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();
}

这对我来说似乎没问题。旋转事件导致 Activity 被销毁并重新创建,这导致 SurfaceView 也被销毁和重新创建。

于 2012-12-02T16:47:17.573 回答
23

另一个工作正常的简单解决方案 - 更改预览表面的可见性。

private SurfaceView preview;

预览是 init inonCreate方法。在预览表面的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 的回调在屏幕锁定时不会被调用,因为表面视图对系统仍然可见。

因此,正如@validcat 建议的那样,分别调用preview.setVisibility(View.VISIBLE);onPause preview.setVisibility(View.GONE);() 和 onResume() 将强制表面视图重新布局自身并将其称为回调。

届时,@emrys57 的解决方案加上上面这两个可见性方法调用将使您的相机预览工作简单:)

所以我只能给你们每个人+1,因为你们都应得的;)

于 2015-03-10T14:29:09.063 回答
1

SurfaceHolder.Callback 与它的 Surface 有关。

活动在屏幕上吗?如果是这样,就不会有SurfaceHolder.Callback,因为Surface还在屏幕上。

要控制任何 SurfaceView,您只能在 onPause/onResume 中处理它。对于SurfaceHolder.Callback,可以在Surface发生变化(created、sizechanged、destroy)时使用,比如surfaceCreated时初始化openGL,surfaceDestroyed时销毁openGL等。

于 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

导入android.util.Log;导入 android.util.SparseArray;

/** * 由 woliver 于 2016/06/24 创建。* * Android 主机环境,规定了 OnCreate、onStart、onResume、onPause、onStop、onDestory 的 Activity 生命周期,* 我们需要释放内存和句柄以供其他应用程序使用。* 恢复时,我们有时需要重新绑定并激活这些项目与其他对象。* 通常,这些其他对象提供来自宿主环境的回调方法,这些方法提供 * 一个 onCreated 和 onDestroy,其中我们只能从 OnCreated 绑定到该对象,并松开 * 绑定 onDestory。*这些类型的回调方法,运行时间是我们主机环境的控制器*并且它们不能保证活动生命周期的行为/执行顺序和这些回调方法*保持一致。*出于开发的目的,交互和执行顺序在技术上可以称为未定义 *因为它取决于主机实现实施者,三星,索尼,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 回答