2. CameraX入门:从零搭一个能跑起来的预览
说真的,很多同学刚接触 CameraX 时,最容易卡在第一步——明明照着文档写了代码,预览就是黑屏。我当初带团队做第一版拍照功能时,也折腾了大半天,最后发现是权限声明写错了一个字母。好,咱们今天就把这“第一步”彻底踩实。
2.1 项目依赖配置:别多写,也别少写
你要想用 CameraX,首先得在 build.gradle(模块级别)里加依赖。我个人习惯统一用 BOM(Bill of Materials)管理版本,省得以后库之间版本冲突。
核心原则:只加你需要的 artifact,不要无脑复制一大串。
dependencies {
// CameraX 核心库 + 生命周期绑定
def camerax_version = "1.3.4"
implementation "androidx.camera:camera-core:${camerax_version}"
implementation "androidx.camera:camera-camera2:${camerax_version}"
implementation "androidx.camera:camera-lifecycle:${camerax_version}"
// PreviewView 控件
implementation "androidx.camera:camera-view:${camerax_version}"
// 如果你还要录制视频,再加这个
// implementation "androidx.camera:camera-video:${camerax_version}"
}
为什么会推荐用 BOM?我之前在一个项目里,camera-core 是 1.2.0,camera-view 用了 1.3.0,结果编译时直接报 NoSuchMethodError。你想想看,这两个版本不匹配,底层 API 签名都变了。用 BOM 就一句话解决:
implementation "androidx.camera:camera-bom:${camerax_bom_version}"
// 然后所有 CameraX 库不用写版本号,统一由 BOM 管理
👨💻 我的小技巧:每次升级 CameraX 版本后,一定跑一遍所有相机相关的测试用例。CameraX 团队偶尔会改内部实现,哪怕 API 没变,行为也可能不一样。
2.2 AndroidManifest 权限声明:两颗雷,别踩
Android 的相机权限分为普通权限和运行时权限。CameraX 用到了 CAMERA,这个属于危险权限,必须在运行时申请。但 清单文件里也得先声明。
| 权限 | 必须? | 说明 |
|---|---|---|
| android.permission.CAMERA | 是 | 使用相机预览/拍照/录像都必须 |
| android.permission.RECORD_AUDIO | 按需 | 录制视频时需要 |
| android.permission.WRITE_EXTERNAL_STORAGE | 按需 | 如果保存图片到公共目录(Android 10+ 推荐用 MediaStore) |
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" android:required="false" />
注意第二行 uses-feature 设置 required="false"——这样没有摄像头的平板也能安装你的应用,只是无法使用相机功能罢了。我曾经有个团队把这行写成 required="true",结果 Nexus 7 用户全都不给安装,排查了整整一下午。
⚠️ 避坑:有些国产手机在 Android 11 以上,即便你声明了权限,如果用户拒绝“总是允许”,onReuestPermissionsResult 里的回调可能比你预期的晚很多。最好在 UI 层加一个权限检查状态机。
2.3 CameraX 与 Lifecycle 绑定:为什么你必须用它
CameraX 最大的卖点就是“生命周期感知”。你不用自己在 onResume 打开相机、onPause 关闭相机——这些脏活全由 ProcessCameraProvider 帮您搞定。说白了,你只需要告诉它:哪个 LifecycleOwner,我就跟着它活。
具体做法:
class CameraActivity : AppCompatActivity() {
private lateinit var cameraProviderFuture: ListenableFuture<ProcessCameraProvider>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_camera)
cameraProviderFuture = ProcessCameraProvider.getInstance(this)
}
// 稍后绑定预览时会用到 lifecycleOwner = this
}
当你调用 cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, useCaseGroup) 时,CameraX 内部会注册生命周期观察者。一旦 Activity 进入 onDestroy,它自动释放相机资源。嗯,这里要注意:千万别手动解绑,否则反而可能导致资源泄漏。
个人经验:如果你在 Fragment 里用 CameraX,一定要确保 Fragment 的 viewLifecycleOwner 是活的。我习惯把 lifeCycleOwner 传到 ViewModel 里面去,但后来发现 ViewModel 不能持有 LifecycleOwner 引用(容易内存泄漏),所以现在都用 LifecycleBoundCamera 那套模式,代码写在这儿已经上生产了,没出过问题。
2.4 最简单的预览实现:6 行代码 + 1 个 XML
好,前面全是准备工作。现在才是正题——把相机画面显示到屏幕上。CameraX 提供了一个 PreviewView 控件,你只需要把它放到布局里,然后用代码把 camera 和这个 View 绑起来。
第一步:布局
<!-- activity_camera.xml -->
<androidx.camera.view.PreviewView
android:id="@+id/viewFinder"
android:layout_width="match_parent"
android:layout_height="match_parent" />
第二步:代码
class CameraActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_camera)
val viewFinder = findViewById<PreviewView>(R.id.viewFinder)
// 1. 获取 ProcessCameraProvider
val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
cameraProviderFuture.addListener(Runnable {
val cameraProvider = cameraProviderFuture.get()
// 2. 配置 Preview 用例
val preview = Preview.Builder().build()
preview.setSurfaceProvider(viewFinder.surfaceProvider)
// 3. 选择后置摄像头
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
// 4. 绑定生命周期
cameraProvider.unbindAll()
val camera = cameraProvider.bindToLifecycle(
this, cameraSelector, preview
)
}, ContextCompat.getMainExecutor(this))
}
}
就这么简单。如果你跑起来发现预览是黑的,别急,先检查下面几样:
- 你有没有在
onCreate之后 及时请求 CAMERA 权限?没有权限的话 SurfaceProvider 不会提供画面。 - 你的
PreviewView是否被别的 View 挡住了?Z-order 问题。 - 最低 API 版本是否 ≥ 21?CameraX 只支持 API 21+。
👀 调试技巧:在 preview.setSurfaceProvider 之前打印一句 log,确认它被调用了。很多时候问题出在 ProcessCameraProvider.getInstance 还没完成就执行绑定——你看到的黑屏其实是 CameraX 还没准备好。
2.5 本章知识体系速览
我把整个入门流程画成了一张图,方便你建立全局印象。红色节点是必须做的,绿色是可选但推荐的。
到现在为止,你应该已经能用 CameraX 在屏幕上看到相机画面了。这个流程虽然看起来步骤多,但核心只有三件事:配依赖、要权限、绑 Lifecycle。只要你把这三件做对了,预览基本不会出问题。
⚠️ 再次强调:千万别在 onPause 里调用 cameraProvider.unbindAll()!CameraX 生命周期自己会处理。我曾经见过有人为了省电加上手动释放,结果导致 SurfaceProvider 被多次注销,最后画面闪烁——排查了两天,删掉那几行代码就好了。
好,到今天为止,你的相机预览应该可以稳定运行了。如果遇到任何奇怪的黑屏、闪退,优先检查权限弹窗有没有被用户拒绝、或者 PreviewView 的尺寸是否为零。这些都是我踩过无数遍的坑,希望你能一次跳过。