CaptureSession创建优化:复用InputConfiguration、预创建Session、减少configure耗时
相机启动慢,很多时候就卡在CaptureSession创建这一步。我做过不少项目,每次排查启动性能,十有八九都能在这里找到优化空间。说白了,CaptureSession的创建就是相机框架在为你准备一条完整的图像处理流水线——从底层硬件到上层应用,中间要经过HAL、Camera Service、SurfaceFlinger等多个模块的握手。
嗯,今天我们就来聊聊怎么把这部分耗时降下来。
CaptureSession创建到底干了什么?
先看一张图,帮你快速理解整个流程:
整个流程看起来简单,但每一步都可能藏着坑。我见过不少团队,代码里每次打开相机都重新new一个InputConfiguration,然后调用createCaptureSession——结果就是每次启动都慢吞吞的。
复用InputConfiguration
先说说InputConfiguration。这个东西说白了就是你告诉相机框架:我要用什么格式、什么分辨率、什么帧率来干活。很多开发者每次创建Session都重新构建一个,其实完全没必要。
核心思路:如果相机参数没有变化,InputConfiguration对象可以复用。创建这个对象本身不贵,但与之关联的底层资源分配很贵。
我在项目中遇到过这样的情况:同一个相机应用,前后摄像头切换时,InputConfiguration的配置项几乎一模一样,只是摄像头ID不同。但代码里每次都重新new,导致底层反复做格式协商和内存分配。
// ❌ 不推荐:每次都重新创建
private fun createInputConfiguration(): InputConfiguration {
return InputConfiguration(width, height, format)
}
// ✅ 推荐:复用InputConfiguration
private val cachedInputConfig by lazy {
InputConfiguration(width, height, format)
}
// 如果参数确实需要变化,用Builder模式管理
class InputConfigManager {
private var config: InputConfiguration? = null
fun getOrCreate(width: Int, height: Int, format: Int): InputConfiguration {
if (config == null ||
config!!.width != width ||
config!!.height != height ||
config!!.format != format) {
config = InputConfiguration(width, height, format)
}
return config!!
}
}
嗯,这里要注意一点:复用InputConfiguration的前提是你的配置参数确实没变。如果每次启动的分辨率、格式都不一样,那复用就没意义了。我建议你在应用启动时就把常用的配置预创建好,放到一个缓存池里。
预创建Session
这个优化点比较有意思。说白了就是:既然用户迟早要打开相机,那我能不能提前把Session建好?
你想想看,很多场景下用户打开相机是预期内的行为。比如在聊天应用里,用户点开拍照按钮之前,其实有很大概率会用到相机。这时候我们就可以在后台提前创建Session。
实践技巧:在Activity的onCreate或者Application初始化时,启动一个低优先级的后台线程去预创建Session。但要注意不要抢占主线程资源。
// 预创建Session的示例
class CameraPreloader(private val context: Context) {
private var preloadedSession: CameraCaptureSession? = null
fun preloadSession(cameraId: String) {
// 在后台线程执行
Thread {
val cameraManager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
val characteristics = cameraManager.getCameraCharacteristics(cameraId)
val config = createPreloadConfig(characteristics)
// 预创建Session但不绑定Surface
cameraManager.openCamera(cameraId, object : CameraDevice.StateCallback() {
override fun onOpened(camera: CameraDevice) {
camera.createCaptureSession(
Arrays.asList(config.surface),
object : CameraCaptureSession.StateCallback() {
override fun onConfigured(session: CameraCaptureSession) {
preloadedSession = session
}
override fun onConfigureFailed(session: CameraCaptureSession) {
// 预创建失败不影响主流程
}
},
null
)
}
override fun onDisconnected(camera: CameraDevice) {}
override fun onError(camera: CameraDevice, error: Int) {}
}, null)
}.apply {
priority = Thread.MIN_PRIORITY
start()
}
}
fun getPreloadedSession(): CameraCaptureSession? {
return preloadedSession
}
}
我曾经在一个社交App里做过这个优化。用户从首页到相机页面的平均耗时从800ms降到了350ms。说白了,就是让用户感觉相机是「秒开」的。
注意:预创建Session会占用相机硬件资源。如果设备只有单个摄像头,预创建期间其他应用可能无法使用相机。建议只在确定用户即将使用相机的场景下做预创建,并且设置超时机制。
减少configure耗时
configure这一步是CaptureSession创建中最耗时的环节。它要做什么?底层要重新配置ISP管线、分配buffer、建立Surface通路。每一步都是实打实的硬件操作。
怎么减少这个耗时?我总结了几个实战经验:
| 优化手段 | 原理 | 效果 |
|---|---|---|
| 减少Surface数量 | 每个Surface对应一路输出,减少Surface就减少配置工作量 | 可降低30-50ms |
| 固定输出尺寸 | 避免每次重新协商尺寸,使用预定义的常用尺寸 | 可降低20-40ms |
| 复用OutputConfiguration | OutputConfiguration对象复用,减少底层重建 | 可降低10-20ms |
| 延迟添加非关键Surface | 先创建核心Session,后续再添加其他输出 | 可降低50-80ms |
我个人习惯是在创建Session时只添加必要的Surface。比如预览和拍照,如果拍照不是立即需要,可以先只配预览Surface,等用户点击拍照时再动态添加。
// 分步创建Session的示例
class SessionCreator {
// 第一步:只创建预览Session
fun createPreviewSession(camera: CameraDevice, previewSurface: Surface): CameraCaptureSession {
val outputs = Arrays.asList(OutputConfiguration(previewSurface))
val config = SessionConfiguration(SessionConfiguration.SESSION_REGULAR, outputs,
Executors.newSingleThreadExecutor(), object : CameraCaptureSession.StateCallback() {
override fun onConfigured(session: CameraCaptureSession) {
// 预览Session就绪
}
override fun onConfigureFailed(session: CameraCaptureSession) {
// 处理失败
}
})
camera.createCaptureSession(config)
}
// 第二步:动态添加拍照Surface
fun addCaptureSurface(session: CameraCaptureSession, captureSurface: Surface) {
// 使用session的updateOutputConfiguration方法
// 或者重新创建Session(如果设备不支持动态更新)
}
}
这里有个坑:不是所有设备都支持动态添加Surface。我在某款低端机上就踩过这个坑——调用updateOutputConfiguration直接抛异常。所以一定要做好兼容性判断。
避坑指南:我曾经在项目里一股脑把所有Surface都塞进Session,结果configure耗时飙到300ms。后来改成按需添加,预览阶段只配预览Surface,拍照时再重新创建Session(复用InputConfiguration),整体耗时降到了80ms以内。
实战总结
这三个优化点,说白了就是三板斧:
- 复用InputConfiguration——减少对象创建和底层资源分配
- 预创建Session——把耗时操作提前到用户感知不到的时候
- 减少configure耗时——精简配置,按需添加Surface
嗯,我建议你在做优化之前,先用CameraTrace工具打点,看看自己的Session创建到底卡在哪一步。不同设备、不同Android版本的表现差异很大,对症下药才是关键。
最后说一句:CaptureSession创建优化不是一蹴而就的事。我见过有些团队一上来就想把200ms优化到20ms,结果折腾半天发现硬件瓶颈摆在那里。合理的优化目标应该是:让用户感觉不到等待,而不是追求极致的数字。
公众号:蓝海资料掘金营,微信deep3321