SurfaceHolder回调与Activity生命周期如何相关?


73

我一直在尝试实现需要在表面上预览相机的应用程序。正如我所看到的,活动和表面生命周期都包含以下状态:

  1. 首次启动活动时: onResume()->onSurfaceCreated()->onSurfaceChanged()
  2. 当我离开活动时: onPause()->onSurfaceDestroyed()

在此方案中,我可以做相应的像在开/释放摄像头和启动/停止预览电话onPause/onResumeonSurfaceCreated()/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");
    }
}

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


您正在开发哪个Android Plaform / API级别?
FerranB

Answers:


59

编辑:如果targetSDK大于10,则使应用程序进入睡眠状态,onPause 然后按 onStop资源

我在姜饼手机上的一个微型相机应用程序中查看了Activity和SurfaceView的生命周期。你是完全正确的;按下电源按钮使手机进入睡眠状态后,表面不会被破坏。当手机进入睡眠状态时,“活动”就会进行睡眠onPause。(并且不这样做onStop。)它会onResume在手机唤醒时执行,并且如您所指出的,它会在锁定屏幕仍然可见并接受输入时执行此操作,这有点奇怪。通过按“主页”按钮使“活动”不可见时,“活动”同时执行onPauseonStopsurfaceDestroyed在这种情况下,会导致在的结束onPause和开始之间回调onStop。这不是很明显,但是看起来确实很一致。

按下电源按钮使手机进入睡眠状态时,除非明确采取措施停止手机,否则相机将继续运行!如果我让相机为每个预览帧执行每个图像的回调,并且其中有一个Log.d(),则在手机假装进入睡眠状态时,日志语句会不断出现。我认为这是很诡异

另一个困惑是,如果正在创建表面,则会在活动中回调surfaceCreatedsurfaceChanged发生 事件之后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也被销毁并重新创建。


为什么在超级通话之前?
basickarl 2015年

我不知道代码是在超级调用之前还是之后执行都没有关系。super.onResumeonResume例程中的某个地方调用它很重要。我认为。
emrys57

实际上,这取决于surfaceview和surfaceviewholder的初始化。如果您执行一些同步任务,那么您将初始化视图,那么它就不会在onResume之后调用。即使不是在同步任务之后。但当我转到另一个活动并继续时,它会回调onSurfaceCreated或Change函数。顺便说一句,谢谢!@ emrys57。至少我已经解决了我的问题是Surfaceview。:)
穆罕默德·阿迪尔

很高兴为您提供帮助。这是姜饼的一个老答案,如果现在细节有所变化,我一点也不惊讶,我最近也没有看。尽管我的旧代码现在仍在运行!
emrys57 2015年

1
为什么要在surfaceCreated中打开相机并在surfaceChanged中开始预览?
YannickMüller17年

22

另一个运行良好的简单解决方案-更改预览表面的可见性。

private SurfaceView preview;

预览是initonCreate方法的初始化。在预览图面的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
}

你是救星!
gtsouk '17

1
多谢,伙计!如果您想要肾脏,我的肾脏将为您服务!!该解决方案解决了我辛苦的调试工作。
Binil Jacob

2

由于有了上述所有先前的答案,我在从后台或锁定屏幕返回时都能使摄像机预览正常工作。

正如@ e7fendy所提到的,在屏幕锁定时不会调用SurfaceView的回调,因为表面视图对于系统仍然可见。

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

届时,来自@ emrys57的解决方案以及上面的这两个可见性方法调用将使您的相机预览正常工作:)

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


1

SurfaceHolder.Callback与其表面有关。

屏幕上是否有活动?如果是这样,将不会出现SurfaceHolder.Callback,因为Surface仍在屏幕上。

要控制任何SurfaceView,您只能在onPause / onResume中对其进行处理。对于SurfaceHolder.Callback,如果Surface发生更改(创建,更改大小和销毁),则可以使用它,例如,在surfaceCreated时初始化openGL,在SurfaceDestroyed时销毁openGL等。


-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等的活动生命周期,*在这些情况下,我们需要释放内存和句柄供其他应用程序使用。*恢复时,有时需要重新绑定这些项目并将其与其他对象一起激活。通常,这些其他对象从主机环境提供回调方法,这些方法提供onCreated和onDestroy,在这些方法中,我们只能从OnCreated绑定到该对象,然后松开绑定onDestory。*这些类型的回调方法,运行所需的时间是我们的宿主环境*,并且不能保证活动生命周期的行为/执行顺序和这些回调方法*保持一致。*为了开发的目的,交互作用和执行顺序在技术上可以称为undefined *,因为它取决于主机实现实现者,三星,索尼,htc。* *请参阅以下开发人员文档:https://developer.android.com/reference/android/app/Activity.html * Quote:*如果一个活动被另一个活动完全遮盖,则将其停止。它仍然保留所有状态和成员信息,但是,它不再对用户可见,因此它的窗口是隐藏的,当其他地方需要内存时,它通常会被系统杀死。* 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);
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.