在 Android 开发中使用相机时对处理程序、线程和执行程序的说明
Clarification on Handlers, Threads and Executors when using a camera in Android Development
我正在尝试在我的应用程序中实现 Camera2 API。以前从未这样做过,我从 the Camera2Basic example code provided by android.
开始
但是,在 createCaptureSession()
方法中有一个已弃用的初始化方法 CameraDevice.createCaptureSession()
,因此我将其更改为以下内容:
private suspend fun createCaptureSession(
device: CameraDevice,
targets: List<Surface>
): CameraCaptureSession = suspendCoroutine { cont ->
// Create a capture session using the predefined targets; this also involves defining the
// session state callback to be notified of when the session is ready
var outConfig = mutableListOf<OutputConfiguration>()
for (target in targets) {
outConfig.add(OutputConfiguration(target))
}
var sessionConfig = SessionConfiguration(SessionConfiguration.SESSION_REGULAR, outConfig, cameraExecutor, object: CameraCaptureSession.StateCallback() {
override fun onConfigured(session: CameraCaptureSession) = cont.resume(session)
override fun onConfigureFailed(session: CameraCaptureSession) {
val exc = RuntimeException("Camera ${device.id} session configuration failed")
cont.resumeWithException(exc)
}
})
device.createCaptureSession(sessionConfig)
}
但是,除了为相机创建的Thread之外,现在我还需要一个Executor,所以现在我有
/** cameraExecutor */
private val cameraExecutor = Executors.newSingleThreadExecutor()
/** [HandlerThread] where all camera operations run */
private val cameraThread = HandlerThread("CameraThread").apply { start() }
/** [Handler] corresponding to [cameraThread] */
private val cameraHandler = Handler(cameraThread.looper)
CameraThread
和 cameraExecutor
很可能是错误的(我猜是??)。但是我怎样才能从执行者那里得到一个处理程序呢?这甚至是正确的方法吗?
文档可能会解决您的问题:
根据this link:
To dispatch events through the main thread of your application, you can use Context.getMainExecutor(). To dispatch events through a shared thread pool, you can use AsyncTask#THREAD_POOL_EXECUTOR.
您可以使用等效方法删除变量 cameraHandler
:
CameraCaptureSession#capture
-> CameraCaptureSession#captureSingleRequest
CameraCaptureSession#setRepeatingRequest
-> CameraCaptureSession#setSingleRepeatingRequest
等等。
但请注意,您提到的方法 (CameraDevice#createCaptureSession(java.util.List<android.view.Surface>, android.hardware.camera2.CameraCaptureSession.StateCallback, android.os.Handler)
) 在 API 24 中引入并在 API 30 中弃用,但 CameraDevice#createCaptureSession(SessionConfiguration)
仅在 [=28= 中引入] 28. 所以很可能你需要处理 API 差异。
处理程序是 Android 的 API 指南推荐用于执行回调等,直到最近。
然而,与更通用的 Executor 相比,它们有各种缺点,因此现在仅当您想要 运行 “主”(也称为“UI") 线程,主要是与视图和其他 UI 元素交互的东西。首先,Handler 将始终将回调执行反弹到与其关联的 Looper 线程,并且由于大多数 Android API 回调源自 Binder IPC 线程,这是一个额外的线程跃点。 Executor 可以 运行 在该 Binder 线程池线程上,在性能非常重要的情况下消除额外的跃点。
对于只接受执行器的 API(许多示例中的 one),您可以非常简单地将处理程序包装在执行器中,因此执行器参数不会限制您的选择。反之则不然。
因此对于 camera2 API,新的重载已添加到所有内容以接受 Executor 之前 Handler 可用(并重新排列参数顺序,以便在 Kotlin 中您可以以惯用的方式提供回调 lambda)。我们已经弃用了一些旧版本的方法,因此现在可以清楚地知道哪些是推荐的方法。
但是旧的 Handler APIs 没有什么特别的问题,如果它们适用于您的用例。在内部,他们只是将 Handler 包装在 Executor 中。
我通常建议 运行ning 相机操作只有一个线程,而不是 UI 线程 - 打开和关闭相机设备很慢,你不希望在启动时阻止你的 UI ~1 秒。我会避免混合使用 Handler 和 Executor,因为这可能会让您自己感到困惑,但是 API 不关心您将哪个用于任何给定的回调。
但是如果你有一个发布到线程 A 的处理程序,以及一个 运行 在线程 B 上的执行器,你必须确保同步访问 A 和 B 需要的任何信息访问,与仅 运行将其全部集中在相机的一个线程上相比,这是一项额外的工作。
我正在尝试在我的应用程序中实现 Camera2 API。以前从未这样做过,我从 the Camera2Basic example code provided by android.
开始但是,在 createCaptureSession()
方法中有一个已弃用的初始化方法 CameraDevice.createCaptureSession()
,因此我将其更改为以下内容:
private suspend fun createCaptureSession(
device: CameraDevice,
targets: List<Surface>
): CameraCaptureSession = suspendCoroutine { cont ->
// Create a capture session using the predefined targets; this also involves defining the
// session state callback to be notified of when the session is ready
var outConfig = mutableListOf<OutputConfiguration>()
for (target in targets) {
outConfig.add(OutputConfiguration(target))
}
var sessionConfig = SessionConfiguration(SessionConfiguration.SESSION_REGULAR, outConfig, cameraExecutor, object: CameraCaptureSession.StateCallback() {
override fun onConfigured(session: CameraCaptureSession) = cont.resume(session)
override fun onConfigureFailed(session: CameraCaptureSession) {
val exc = RuntimeException("Camera ${device.id} session configuration failed")
cont.resumeWithException(exc)
}
})
device.createCaptureSession(sessionConfig)
}
但是,除了为相机创建的Thread之外,现在我还需要一个Executor,所以现在我有
/** cameraExecutor */
private val cameraExecutor = Executors.newSingleThreadExecutor()
/** [HandlerThread] where all camera operations run */
private val cameraThread = HandlerThread("CameraThread").apply { start() }
/** [Handler] corresponding to [cameraThread] */
private val cameraHandler = Handler(cameraThread.looper)
CameraThread
和 cameraExecutor
很可能是错误的(我猜是??)。但是我怎样才能从执行者那里得到一个处理程序呢?这甚至是正确的方法吗?
文档可能会解决您的问题:
根据this link:
To dispatch events through the main thread of your application, you can use Context.getMainExecutor(). To dispatch events through a shared thread pool, you can use AsyncTask#THREAD_POOL_EXECUTOR.
您可以使用等效方法删除变量 cameraHandler
:
CameraCaptureSession#capture
-> CameraCaptureSession#captureSingleRequest
CameraCaptureSession#setRepeatingRequest
-> CameraCaptureSession#setSingleRepeatingRequest
等等。
但请注意,您提到的方法 (CameraDevice#createCaptureSession(java.util.List<android.view.Surface>, android.hardware.camera2.CameraCaptureSession.StateCallback, android.os.Handler)
) 在 API 24 中引入并在 API 30 中弃用,但 CameraDevice#createCaptureSession(SessionConfiguration)
仅在 [=28= 中引入] 28. 所以很可能你需要处理 API 差异。
处理程序是 Android 的 API 指南推荐用于执行回调等,直到最近。
然而,与更通用的 Executor 相比,它们有各种缺点,因此现在仅当您想要 运行 “主”(也称为“UI") 线程,主要是与视图和其他 UI 元素交互的东西。首先,Handler 将始终将回调执行反弹到与其关联的 Looper 线程,并且由于大多数 Android API 回调源自 Binder IPC 线程,这是一个额外的线程跃点。 Executor 可以 运行 在该 Binder 线程池线程上,在性能非常重要的情况下消除额外的跃点。
对于只接受执行器的 API(许多示例中的 one),您可以非常简单地将处理程序包装在执行器中,因此执行器参数不会限制您的选择。反之则不然。
因此对于 camera2 API,新的重载已添加到所有内容以接受 Executor 之前 Handler 可用(并重新排列参数顺序,以便在 Kotlin 中您可以以惯用的方式提供回调 lambda)。我们已经弃用了一些旧版本的方法,因此现在可以清楚地知道哪些是推荐的方法。
但是旧的 Handler APIs 没有什么特别的问题,如果它们适用于您的用例。在内部,他们只是将 Handler 包装在 Executor 中。
我通常建议 运行ning 相机操作只有一个线程,而不是 UI 线程 - 打开和关闭相机设备很慢,你不希望在启动时阻止你的 UI ~1 秒。我会避免混合使用 Handler 和 Executor,因为这可能会让您自己感到困惑,但是 API 不关心您将哪个用于任何给定的回调。
但是如果你有一个发布到线程 A 的处理程序,以及一个 运行 在线程 B 上的执行器,你必须确保同步访问 A 和 B 需要的任何信息访问,与仅 运行将其全部集中在相机的一个线程上相比,这是一项额外的工作。