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

我一直在尝试实现一个应用程序,需要在一个表面上的相机预览。 在我看来,活动和表面生命周期都由以下状态组成:

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

在这个方案中,我可以在 onPause/onResumeonSurfaceCreated()/onSurfaceDestroyed()中进行相应的调用,如打开/释放摄像头和开始/停止预览。

没问题,除非我把屏幕锁上。当我启动应用程序,然后锁定屏幕和解锁后,我看到:

屏幕锁定后没有其他东西,解锁后 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");
}
}

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

46478 次浏览

回调与它的表面相关。

屏幕上有活动吗?如果是这样,就不会有 Surface Holder 了。回调,因为 Surface 还在屏幕上。

要控制任何 SurfaceView,只能在 onPace/onResume 中处理它。对于 SurfaceHolder。回调,如果 Surface 被更改(创建、大小更改和销毁) ,你可以使用它,比如在 Surface 创建时初始化 openGL,在 Surface 销毁时销毁 openGL,等等。

编辑: 如果 targetSDK 大于10,则使应用程序处于睡眠状态调用 onPause 还有 onStop. 来源

我在我的姜饼手机上的一个微型摄像头应用程序中查看了 Activity 和 SurfaceView 的生命周期。你完全正确; 当按下电源按钮使手机进入睡眠状态时,表面并没有被破坏。当手机进入睡眠状态时,活动进行 onPause。(而且不做 onStop。)当手机醒来时,它执行 onResume,并且,正如您指出的,它在锁定屏幕仍然可见并接受输入时执行此操作,这有点奇怪。当我通过按 Home 按钮使活动不可见时,活动同时执行 onPauseonStop。在这种情况下,在 onPause的末尾和 onStop的开始之间,有些东西会导致对 surfaceDestroyed的回调。虽然不是很明显,但看起来很一致。

当按下电源按钮使手机处于睡眠状态时,除非有明确的措施来阻止它,否则摄像头将继续运行!如果我让摄像头对每个预览帧进行每个图像的回调,并在其中添加 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();
}

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

另一个工作良好的简单解决方案-改变预览表面的可见性。

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
}

感谢所有以前的答案,我设法使我的相机预览工作,而从背景或锁定屏幕回来。

正如@e7fendy 提到的,当屏幕锁定时,SurfaceView 的回调不会被调用,因为系统仍然可以看到表面视图。

因此,正如@validcat 所建议的,分别在 onPuse ()和 onResume ()中调用 preview.setVisibility(View.VISIBLE);preview.setVisibility(View.GONE);将强制表面视图重新布局本身,并将调用它的回调。

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

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

下面是所有回调方法的替代解决方案,它们可能都受到活动周期中相同的未定义事件顺序行为的影响。除非你要检查每次回调的所有安卓代码来确定原始触发器和控制实现的人并且希望代码库在未来不会改变,否则我们可以说,回调和活动生命周期事件之间的事件顺序是可以保证的。

目前,出于开发的目的,这些有序的交互通常被称为未定义行为。

所以最好的办法就是正确处理这个未定义行为 它从一开始就不会成为一个问题,通过确保订单是定义好的行为。

例如,我的索尼 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; Import android.util.SparseArray;

* 创作于2016/06/24。 * * Android 主机环境,规定 OnCreate、 onStart、 onResume、 onPace、 onStop、 onDestory 的活动生命周期, * 我们需要释放内存和句柄供其他应用程序使用。 * 当恢复时,我们有时需要重新绑定和激活这些项目与其他对象。 * 通常这些其他对象提供来自主机环境的回调方法,这些方法提供 * an onCreated and onDestroy,其中我们只能绑定到这个对象从 OnCreated and and lose * out bind on Destory. * 这些类型的回调方法,运行时间是由我们的主机环境控制器 * 它们并不能保证活动生命周期和这些回调方法的执行行为/顺序 始终如一 * 。 * 为了开发的目的,交互作用和执行顺序在技术上可以称为未定义的 * 因为它取决于主机实现者,三星,索尼,htc。 * * 见以下开发人员文件: https://developer.android.com/reference/android/app/Activity.html * 语录: * 如果某个活动被另一个活动完全遮挡,则该活动将被停止,但仍保持所有状态 * 和成员信息,但是,它不再对用户可见,所以它的窗口是 * 隐藏起来,当系统需要其他地方的内存时,它往往会被系统杀死。 * EndQuato: * * 如果活动没有被隐藏,那么任何预期会被主机调用的回调 * system,将不会被调用,例如 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);
}
}