当前位置: 首页 > news >正文

Android Camera2 API实战:从零搭建一个能拍照的Demo App(附完整代码)

Android Camera2 API实战从零搭建拍照Demo的完整指南TextureView预览窗口中的画面突然凝固手指按下快门键的瞬间手机微微震动——这是每个Android开发者都希望在自己的应用中实现的相机功能。不同于早期简单的Camera APICamera2提供了更精细的控制能力但同时也带来了更高的复杂度。本文将带你从零开始用最精简的代码实现一个具备基础预览和拍照功能的Demo避开那些官方文档中没有明确指明的坑。1. 环境准备与基础配置在AndroidManifest.xml中我们需要声明相机权限和存储权限用于保存拍摄的照片uses-permission android:nameandroid.permission.CAMERA / uses-permission android:nameandroid.permission.WRITE_EXTERNAL_STORAGE / uses-feature android:nameandroid.hardware.camera /关键配置说明确保targetSdkVersion≥21Camera2 API的最低要求对于Android 6.0设备需要动态申请权限推荐使用AndroidX库以获得更好的兼容性在build.gradle中添加必要的依赖dependencies { implementation androidx.appcompat:appcompat:1.3.0 implementation androidx.core:core-ktx:1.6.0 }2. 相机核心组件初始化Camera2 API的核心类包括CameraManager系统的相机服务入口CameraDevice代表物理相机设备CameraCaptureSession管理相机数据流CaptureRequest定义单次图像捕获的参数创建基础Activity布局RelativeLayout xmlns:androidhttp://schemas.android.com/apk/res/android android:layout_widthmatch_parent android:layout_heightmatch_parent TextureView android:idid/textureView android:layout_widthmatch_parent android:layout_heightmatch_parent / Button android:idid/captureButton android:layout_widthwrap_content android:layout_heightwrap_content android:layout_alignParentBottomtrue android:layout_centerHorizontaltrue android:text拍照 / /RelativeLayout3. 相机打开与预览实现在Activity中初始化相机管理器和视图private lateinit var cameraManager: CameraManager private lateinit var textureView: TextureView private var cameraDevice: CameraDevice? null private var cameraCaptureSession: CameraCaptureSession? null private lateinit var captureRequestBuilder: CaptureRequest.Builder private lateinit var backgroundHandler: Handler private lateinit var backgroundThread: HandlerThread override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_camera) textureView findViewById(R.id.textureView) cameraManager getSystemService(Context.CAMERA_SERVICE) as CameraManager // 启动后台线程处理相机回调 backgroundThread HandlerThread(CameraBackground).apply { start() } backgroundHandler Handler(backgroundThread.looper) }实现TextureView.SurfaceTextureListener来处理预览表面private val surfaceTextureListener object : TextureView.SurfaceTextureListener { override fun onSurfaceTextureAvailable(surface: SurfaceTexture, width: Int, height: Int) { openCamera() } override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture, width: Int, height: Int) { // 处理尺寸变化 } override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean { return true } override fun onSurfaceTextureUpdated(surface: SurfaceTexture) { // 预览帧更新 } }相机打开逻辑private fun openCamera() { try { if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) PackageManager.PERMISSION_GRANTED) { val cameraId cameraManager.cameraIdList[0] // 默认使用后置摄像头 val characteristics cameraManager.getCameraCharacteristics(cameraId) val streamConfigurationMap characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)!! // 选择适合TextureView的预览尺寸 val previewSize streamConfigurationMap.getOutputSizes( SurfaceTexture::class.java).maxBy { it.height * it.width }!! textureView.setAspectRatio(previewSize.width, previewSize.height) cameraManager.openCamera(cameraId, stateCallback, backgroundHandler) } } catch (e: CameraAccessException) { Log.e(TAG, 无法访问相机, e) } } private val stateCallback object : CameraDevice.StateCallback() { override fun onOpened(camera: CameraDevice) { cameraDevice camera createPreviewSession() } override fun onDisconnected(camera: CameraDevice) { camera.close() cameraDevice null } override fun onError(camera: CameraDevice, error: Int) { camera.close() cameraDevice null } }4. 创建预览会话与拍照功能创建预览会话的完整实现private fun createPreviewSession() { try { val texture textureView.surfaceTexture texture.setDefaultBufferSize(previewSize.width, previewSize.height) val surface Surface(texture) captureRequestBuilder cameraDevice!!.createCaptureRequest( CameraDevice.TEMPLATE_PREVIEW) captureRequestBuilder.addTarget(surface) cameraDevice?.createCaptureSession( listOf(surface), object : CameraCaptureSession.StateCallback() { override fun onConfigured(session: CameraCaptureSession) { cameraCaptureSession session updatePreview() } override fun onConfigureFailed(session: CameraCaptureSession) { Toast.makeText(thisCameraActivity, 配置失败, Toast.LENGTH_SHORT).show() } }, backgroundHandler) } catch (e: CameraAccessException) { Log.e(TAG, 相机访问异常, e) } } private fun updatePreview() { try { cameraCaptureSession?.setRepeatingRequest( captureRequestBuilder.build(), null, backgroundHandler) } catch (e: CameraAccessException) { Log.e(TAG, 更新预览失败, e) } }实现拍照功能private lateinit var imageReader: ImageReader private val captureButton: Button by lazy { findViewById(R.id.captureButton) } override fun onCreate(savedInstanceState: Bundle?) { // ...其他初始化代码... // 初始化ImageReader用于保存照片 imageReader ImageReader.newInstance( previewSize.width, previewSize.height, ImageFormat.JPEG, 2).apply { setOnImageAvailableListener({ reader - saveImage(reader.acquireNextImage()) }, backgroundHandler) } captureButton.setOnClickListener { takePicture() } } private fun takePicture() { try { val captureBuilder cameraDevice?.createCaptureRequest( CameraDevice.TEMPLATE_STILL_CAPTURE)?.apply { addTarget(imageReader.surface) set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE) set(CaptureRequest.JPEG_ORIENTATION, getJpegOrientation(cameraManager, cameraId)) } cameraCaptureSession?.capture( captureBuilder?.build()!!, object : CameraCaptureSession.CaptureCallback() { override fun onCaptureCompleted( session: CameraCaptureSession, request: CaptureRequest, result: TotalCaptureResult) { super.onCaptureCompleted(session, request, result) updatePreview() // 拍照后恢复预览 } }, backgroundHandler) } catch (e: CameraAccessException) { Log.e(TAG, 拍照失败, e) } } private fun getJpegOrientation( manager: CameraManager, cameraId: String): Int { val characteristics manager.getCameraCharacteristics(cameraId) val sensorOrientation characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION)!! return when (windowManager.defaultDisplay.rotation) { Surface.ROTATION_0 - 90 Surface.ROTATION_90 - 0 Surface.ROTATION_180 - 270 Surface.ROTATION_270 - 180 else - 0 } sensorOrientation }5. 图像保存与资源释放实现图像保存功能private fun saveImage(image: Image) { val buffer image.planes[0].buffer val bytes ByteArray(buffer.remaining()) buffer.get(bytes) val outputFile File(getExternalFilesDir(Environment.DIRECTORY_PICTURES), IMG_${System.currentTimeMillis()}.jpg) try { FileOutputStream(outputFile).use { output - output.write(bytes) runOnUiThread { Toast.makeText(this, 照片已保存: ${outputFile.absolutePath}, Toast.LENGTH_SHORT).show() } } } catch (e: IOException) { Log.e(TAG, 保存照片失败, e) } finally { image.close() } }正确处理Activity生命周期和资源释放override fun onResume() { super.onResume() if (textureView.isAvailable) { openCamera() } else { textureView.surfaceTextureListener surfaceTextureListener } } override fun onPause() { closeCamera() super.onPause() } private fun closeCamera() { cameraCaptureSession?.close() cameraCaptureSession null cameraDevice?.close() cameraDevice null imageReader.close() } override fun onDestroy() { backgroundThread.quitSafely() super.onDestroy() }6. 常见问题排查与优化问题1相机预览方向不正确解决方案根据设备旋转调整TextureView的变换矩阵private fun configureTransform(viewWidth: Int, viewHeight: Int) { val rotation windowManager.defaultDisplay.rotation val matrix Matrix() val viewRect RectF(0f, 0f, viewWidth.toFloat(), viewHeight.toFloat()) val bufferRect RectF(0f, 0f, previewSize.height.toFloat(), previewSize.width.toFloat()) val centerX viewRect.centerX() val centerY viewRect.centerY() if (Surface.ROTATION_90 rotation || Surface.ROTATION_270 rotation) { bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY()) matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL) val scale max( viewHeight.toFloat() / previewSize.height, viewWidth.toFloat() / previewSize.width) matrix.postScale(scale, scale, centerX, centerY) matrix.postRotate(90 * (rotation - 2), centerX, centerY) } textureView.setTransform(matrix) }问题2拍照后预览停止解决方案在拍照完成的回调中重新开始预览override fun onCaptureCompleted( session: CameraCaptureSession, request: CaptureRequest, result: TotalCaptureResult) { super.onCaptureCompleted(session, request, result) updatePreview() }性能优化建议使用ImageReader时设置合适的缓冲区数量通常2-3个避免在主线程执行任何相机操作重用CaptureRequest.Builder以减少开销根据设备能力选择合适的预览尺寸7. 扩展功能实现实现连续自动对焦private fun setupContinuousFocus() { try { captureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE) updatePreview() } catch (e: CameraAccessException) { Log.e(TAG, 设置自动对焦失败, e) } }添加闪光灯支持private fun toggleFlash() { try { val currentMode captureRequestBuilder.get(CaptureRequest.CONTROL_AE_MODE) val newMode when (currentMode) { CaptureRequest.CONTROL_AE_MODE_ON - CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH - CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH else - CaptureRequest.CONTROL_AE_MODE_ON } captureRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, newMode) updatePreview() } catch (e: Exception) { Log.e(TAG, 切换闪光灯失败, e) } }实现手动曝光控制private fun setManualExposure(exposureTime: Long, iso: Int) { try { captureRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_OFF) captureRequestBuilder.set(CaptureRequest.SENSOR_EXPOSURE_TIME, exposureTime) captureRequestBuilder.set(CaptureRequest.SENSOR_SENSITIVITY, iso) updatePreview() } catch (e: Exception) { Log.e(TAG, 设置手动曝光失败, e) } }在实际项目中Camera2 API的这些扩展功能可以显著提升用户体验。记得在实现这些功能前检查设备支持情况val characteristics cameraManager.getCameraCharacteristics(cameraId) val availableAfModes characteristics.get( CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES) ?: emptyArray() val isContinuousFocusSupported availableAfModes.contains( CameraCharacteristics.CONTROL_AF_MODE_CONTINUOUS_PICTURE)
http://www.rkmt.cn/news/1394484.html

相关文章:

  • 家居收纳品牌推荐哪家:正想家居实力出众 - 19120507004
  • 深圳超鸿再生资源:深圳靠谱的工厂酒楼设备回收公司 - LYL仔仔
  • 实测8款论文降AI率免费工具,亲测好用降率指南
  • 10款免费降AI率工具实测,论文降AIGC高效神器推荐
  • 2026泰州黄金回收筛选结果:经6轮对比,仅4家符合要求 - 天天生活分享日志
  • 为什么你的ChatGPT插件始终无法调用API?揭秘插件安装中被低估的OAuth2.1 Scope权限链(附curl级调试模板)
  • 戴森球计划蓝图宝库:从手忙脚乱到星际工厂主的完美蜕变之路
  • Windows 7 SP2终极指南:如何让经典系统完美适配现代硬件
  • 拯救你的双眼:Windows用眼保护神器EyesGuard完全指南
  • 2026如何挑选一家靠谱的无尘室工程公司?资质和案例不能忽略 - 品牌2025
  • PyMe:零代码门槛的Python可视化开发平台,3步创建专业级应用
  • 北京昊泽鸿源文化传播:平谷展台舞台搭建公司 - LYL仔仔
  • 如何构建跨平台私有音乐播放服务:any-listen完整指南
  • 厦门钻石别乱卖!2026本地回收行情规则+靠谱平台盘点 - 合扬奢侈品交易中心
  • 2026年金华海关备案代办与电商侵权维权完全指南 - 年度推荐企业名录
  • 别再只抄代码了!微信支付Native/JSAPI开发中,这3个配置坑我踩了整整两天
  • 2026年6月最新:劳力士全国维修中心地址汇总及常见故障解析 - 博客万
  • Grok 4 实战七技:HTML动画、网络图、社媒摘要等工程化落地指南
  • 基于多分支CNN与可解释AI的眼科贫血筛查模型构建与优化
  • STM32F429的USART2用PA2/PA3不工作?别急,试试这个隐藏的引脚复用方案
  • 从账单明细看Taotoken按Token计费模式的实际成本优势
  • 利用Taotoken多模型选型能力优化内容生成与摘要应用
  • 拒绝答非所问!2026拿Offer必备,5款高口碑“AI面试”工具深度盘点
  • 保姆级调试实录:搞定HC32F4A0的USART1 DMA接收,从波特率计算到超时中断全流程避坑
  • Python开发者三步实现OpenAI兼容接口调用Taotoken全模型
  • 终极指南:轻松三步让老款Mac焕发新生,免费升级最新macOS系统
  • 2026气管插管模型厂家推荐与行业解析 - 品牌排行榜
  • 图片 / 视频 SEO:独立站免费增量流量
  • SAP增强点(Enhancement Spot)深度解析:如何用它管理你的多个NEW BADI?
  • 乌尔都语短文本重用检测:字符n-gram方法在低资源语言中的实践