欢迎访问个人博客:https://glumes.com
在进行安卓相机开发时,遇到一个 app passed NULL surface
的问题。
主要就是当拍摄完一张照片后,跳转到另一个 Activity 去显示拍的图像,然后再返回到拍摄界面时就出现了崩溃闪退。
后来经过排查,才发现是我在 Activity 的生命周期中没有处理好 Camera 对应的状态。
Activity 的生命周期从 onCreate 到 onStart 再到 onResume。
这时如果跳转到其他的 Activity 后再回来,则是先经过 onPause,再到 onResume 。
而对相机的设置也主要是在这个两个状态里面进行的。
假如使用 TextureView
来预览相机内容,那么就不要在 onCreate 里面给 TextureView 设置了回调接口 TextureView.SurfaceTextureListener
,而是要在 onResume 里面进行。该接口的主要作用就是当 TextureView 准备好了之后,就去打开相机。
onResume 中的代码如下:
if (textureView.isAvailable()) {
openCamera();
} else {
textureView.setSurfaceTextureListener(surfaceTextureListener);
}
在谷歌的 Sample 样例给出了注释说明:
// When the screen is turned off and turned back on, the SurfaceTexture is already
// available, and "onSurfaceTextureAvailable" will not be called. In that case, we can open
// a camera and start preview from here (otherwise, we wait until the surface is ready in
// the SurfaceTextureListener).
因为当屏幕关闭或者返回时,TextureView 的 SurfaceTexture 已经是可用的了,onSurfaceTextureAvailable
的回调接口就不会被调用,这样就不能去打开相机进行预览了,所以我们要进一个判断才行。
而在 onPause 状态中,要做一些释放的工作,关闭 Camera 之类的。
@Override
protected void onPause() {
super.onPause();
closeCamera();
stopBackgroundThread();
}
private void closeCamera() {
if (cameraCaptureSessions != null) {
cameraCaptureSessions.close();
}
if (null != mCameraDevice) {
mCameraDevice.close();
mCameraDevice = null;
}
if (null != mImageReader) {
mImageReader.close();
mImageReader = null;
}
}
protected void stopBackgroundThread() {
mBackgroundThread.quitSafely();
try {
mBackgroundThread.join();
mBackgroundThread = null;
mBackgroundHandler = null;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
要注意是,cameraCaptureSessions
的关闭要在 mBackgroundThread
之前,否则回报的,虽然不会导致 Crash。
然而,当我照上面那样处理生命周期状态时,依旧会报 app passed NULL surface 的问题。
最后发现是由于 ImageReader 的处理不当导致的。
在 onCreate 状态时去初始化了 ImageReader ,然后再到 onResume 里面去设置回调打开 Camera 之类的。
打开 Cemera 进行预览,就要创建 CaptureSession,要传递 Surface 作为参数,这个时候就要传递 ImageReader.getSurface() 的作为参数之一。
而报错的内容也就是 ImageReader.getSurface 的为 null 了。
当跳转之后再回到当前 Activity,ImageReader 的 Surface 就已经为 NULL 了。
ImageReader.getSurface() 的方法有一段注解如下:
* Please note that holding on to the Surface object returned by this method is not enough
* to keep its parent ImageReader from being reclaimed. In that sense, a Surface acts like a
* {@link java.lang.ref.WeakReference weak reference} to the ImageReader that provides it.
*
* @return A {@link Surface} to use for a drawing target for various APIs.
也就是说 Surface 相当于是 ImageReader 的一个弱引用。
所以,要避免 app passed NULL surface 的问题出现,就应该在 onResume 里面去初始化 ImageReader ,最好是在 打开相机 openCamera 之前去初始化 ImageReader。
一起交流学习,答疑解惑,有问题,我们星球见~~~
最后,如果觉得文章不错,欢迎关注微信公众号:纸上浅谈