SurfaceHolder 回调如何与 Activity 生命周期相关联?
Posted
技术标签:
【中文标题】SurfaceHolder 回调如何与 Activity 生命周期相关联?【英文标题】:How SurfaceHolder callbacks are related to Activity lifecycle? 【发布时间】:2012-07-14 19:13:22 【问题描述】:我一直在尝试实现一个需要在表面上进行相机预览的应用程序。 正如我所看到的,活动和表面生命周期都包含以下状态:
-
当我第一次启动我的活动时:
onResume()->onSurfaceCreated()->onSurfaceChanged()
当我离开我的活动时: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 暂停后表面仍未被破坏的任何想法?另外,在这种情况下如何处理相机生命周期?
【问题讨论】:
您在哪个 android Plaform/API 级别开发? 【参考方案1】:编辑:如果 targetSDK 大于 10,则将应用程序置于睡眠状态调用 onPause
和 onStop
。 Source
我在姜饼手机上的一个微型相机应用中查看了 Activity 和 SurfaceView 的生命周期。你是完全正确的;按下电源按钮使手机进入睡眠状态时,表面不会被破坏。当手机进入睡眠状态时,Activity 执行onPause
。 (并且不做onStop
。)当手机唤醒时它会做onResume
,而且,正如你所指出的,它会在锁定屏幕仍然可见并接受输入时执行此操作,这有点奇怪。当我通过按主页按钮使活动不可见时,活动同时执行onPause
和onStop
。在这种情况下,在onPause
的结尾和onStop
的开头之间,有些东西会导致对surfaceDestroyed
的回调。这不是很明显,但看起来确实很一致。
当按下电源按钮使手机进入睡眠状态时,除非明确采取措施阻止它,否则相机会继续运行!如果我让相机为每个预览帧执行每个图像的回调,其中有一个 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;
然后在Activity中:
@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 也被销毁和重新创建。
【讨论】:
为什么在超级调用之前? 我不知道代码是在超级调用之前还是之后执行的。在onResume
例程中的某处调用super.onResume
非常重要。我想。
其实取决于surfaceview和surfaceviewholder的初始化。如果你做一些同步任务然后你初始化视图然后它永远不会在 onResume 之后调用。即使不是在同步任务之后。但是当我移动到另一个活动并恢复时,它会回调 onSurfaceCreated 或 Change 函数。顺便说一句,谢谢! @emrys57。至少我已经解决了我的问题是表面视图。 :)
很高兴能帮上忙。这是 Gingerbread 的一个旧答案,如果现在细节发生了变化,我一点也不感到惊讶,我最近没有看过。虽然我的旧代码现在还在工作!
为什么在surfaceCreated中打开相机,在surfaceChanged中开启预览?【参考方案2】:
另一个运行良好的简单解决方案 - 更改预览表面的可见性。
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
【讨论】:
你是救命稻草! 谢谢老兄!如果你想要一个肾脏,我的就在那里!!这个解决方案解决了我痛苦的调试时间。【参考方案3】:感谢之前所有的答案,我设法让我的相机预览在从背景或锁屏返回时清晰地工作。
正如@e7fendy 提到的,SurfaceView 的回调在屏幕锁定时不会被调用,因为表面视图对系统仍然可见。
因此,正如@validcat 建议的那样,分别在 onPause() 和 onResume() 中调用 preview.setVisibility(View.VISIBLE);
和 preview.setVisibility(View.GONE);
将强制表面视图重新布局自身并将其称为回调。
到那时,@emrys57 的解决方案加上上面这两个可见性方法调用将使您的相机预览工作简单:)
所以我只能给你们每个人+1,因为你们都应得的;)
【讨论】:
【参考方案4】:SurfaceHolder.Callback 与其 Surface 相关。
活动是否在屏幕上?如果是这样,就不会有SurfaceHolder.Callback,因为Surface还在屏幕上。
要控制任何 SurfaceView,您只能在 onPause/onResume 中处理它。对于SurfaceHolder.Callback,可以在Surface发生变化(created,sizechanged,destroy)时使用,比如surfaceCreated时初始化openGL,surfaceDestroyed时销毁openGL等。
【讨论】:
【参考方案5】:这是所有回调方法的替代解决方案,这些方法可能都受到具有活动周期的相同未定义事件顺序行为的影响。除非您要检查用于确定源触发器和控制实现的每个回调的所有 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 和松散绑定到这个对象 * out 绑定 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);
【讨论】:
以上是关于SurfaceHolder 回调如何与 Activity 生命周期相关联?的主要内容,如果未能解决你的问题,请参考以下文章
SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS 是啥意思?