CameraService 启动:Android 相机系统的“心脏”第一跳
各位好,我是你们的老朋友。今天咱们来聊聊 CameraService 的启动流程。说实话,这部分内容我当年啃源码的时候,也花了不少时间。你想想看,整个 Android 相机系统,从上层 App 到下层硬件,CameraService 就是那个承上启下的“大管家”。它要是没启动好,后面所有拍照、录像的活儿都别想干。
我个人习惯把 CameraService 的启动分成三个阶段:进程诞生、服务注册、初始化就绪。咱们一个一个来看。
1. 进程诞生:谁来拉起 CameraService?
CameraService 并不是 App 自己启动的。它是由 Android 系统的 init 进程,根据 init.rc 脚本拉起来的。说白了,系统一开机,init 进程就会读取脚本,发现“哦,有个叫 cameraserver 的服务需要启动”,于是 fork 出一个新进程。
对应的 rc 文件片段大致长这样:
service cameraserver /system/bin/cameraserver
class main
user cameraserver
group audio camera input drmrpc
ioprio rt 4
task_profiles CameraServiceCapacity
嗯,这里要注意一点:user cameraserver 这个配置。我曾经在项目里遇到过权限问题,就是因为这个用户组配置不对,导致 CameraService 无法访问某些硬件节点。所以,别小看这几行配置,它决定了你的服务能碰哪些资源。
2. 服务注册:把自己“挂”到 ServiceManager 上
进程启动后,CameraService 会调用 ServiceManager::addService(),把自己注册到系统的服务大管家——ServiceManager 上。这样,其他进程(比如 App)才能通过 ServiceManager 找到它。
核心代码在 main_cameraserver.cpp 里:
int main(int argc, char** argv) {
// 实例化 CameraService
sp<CameraService> cameraService = new CameraService();
// 注册到 ServiceManager
if (cameraService->init() != OK) {
ALOGE("Could not initialize CameraService");
return -1;
}
// 把服务添加到 ServiceManager
publishAndJoinThreadPool(cameraService);
return 0;
}
这里有个细节:publishAndJoinThreadPool 这个函数,它不光注册服务,还会把当前线程加入到 Binder 线程池中。说白了,就是让 CameraService 准备好接收来自客户端的 Binder 调用。
adb shell service list | grep camera 看一眼,立竿见影。
3. 初始化就绪:CameraService 内部都干了啥?
服务注册完了,不等于就能干活了。CameraService 的 init() 方法里,还做了一系列重要的初始化工作。我把它总结成一张图,方便你理解:
从图上你能看到,初始化主要做了三件事:
- 加载 HAL 模块:通过
hw_get_module()加载厂商提供的相机 HAL 库。这一步如果失败,整个 CameraService 就废了。我记得有一次在移植新平台时,HAL 库的路径配错了,折腾了我一整天。 - 创建 Flashlight 服务:闪光灯虽然是相机的一部分,但它是独立管理的。CameraService 会单独创建一个 Flashlight 服务,供系统其他模块调用。
- 初始化监听器:比如屏幕旋转监听、传感器监听。这些是为了后续相机预览方向自适应做准备。
权限管理:谁有资格碰相机?
CameraService 启动后,就要面对客户端的连接请求了。但问题来了——谁都能连吗? 当然不是。Android 的权限模型在这里卡得很死。
客户端要使用相机,必须持有 android.permission.CAMERA 权限。这个权限属于 dangerous 级别,需要运行时动态申请。CameraService 在收到连接请求时,会通过 Binder 调用检查调用方的 UID/PID,然后向 PackageManager 查询权限状态。
核心检查逻辑在 CameraService::checkPermission() 里:
status_t CameraService::checkPermission(const String16& permission,
pid_t pid, uid_t uid) {
// 调用系统的 PermissionController 检查
if (checkCallingPermission(permission, pid, uid) == PERMISSION_GRANTED) {
return OK;
}
// 权限不足,返回错误
ALOGE("Permission Denial: need %s", String8(permission).string());
return PERMISSION_DENIED;
}
客户端连接管理:一对一的“独占”模式
权限检查通过后,客户端就可以建立连接了。但这里有个关键点:一个 CameraDevice 同一时刻只能被一个客户端持有。说白了,就是独占模式。
CameraService 内部维护了一个 mClientMap,用来记录当前活跃的客户端连接。当新客户端请求连接时,CameraService 会做以下检查:
- 检查 Camera ID 是否合法(比如后置摄像头是 0,前置是 1)
- 检查该 Camera 是否已经被其他客户端占用
- 如果被占用,返回
CAMERA_IN_USE错误 - 如果空闲,创建新的 Client 对象,加入 mClientMap
代码逻辑大致如下:
sp<ICameraClient> CameraService::connect(const sp<ICameraClient>& client,
int cameraId) {
// 检查权限
if (checkPermission(CAMERA_PERMISSION) != OK) {
return nullptr;
}
// 检查 Camera 是否可用
if (mClientMap.find(cameraId) != mClientMap.end()) {
// 已被占用
return nullptr; // 实际会返回错误码
}
// 创建新的 Client
sp<CameraClient> newClient = new CameraClient(this, client, cameraId);
mClientMap[cameraId] = newClient;
return newClient;
}
你想想看,为什么 Android 要设计成独占模式?说白了,是为了保证硬件资源的稳定性。如果两个 App 同时操作同一个摄像头,那画面不就乱套了吗?
CameraDevice 创建流程:从 Service 到 HAL 的“握手”
客户端连接成功后,下一步就是创建 CameraDevice。这个流程涉及三个角色:App 进程、CameraService、HAL 层。
我画了一张时序图,帮你理清调用关系:
整个流程说白了就是三步握手:
- App 调用 connect():向 CameraService 发起连接请求,携带 Camera ID。
- CameraService 调用 HAL 的 open():通过 HAL 接口打开对应的摄像头硬件。这一步会返回一个
camera_device_t结构体,里面包含了各种操作函数指针。 - 返回 CameraDevice 给 App:CameraService 把 HAL 返回的 device 封装成一个 Binder 对象,通过
ICameraDevice接口返回给 App。App 拿到的是个代理,实际操作还是通过 Binder 转发到 CameraService 再下到 HAL。
关键点: 整个过程中,App 进程和 HAL 层是完全隔离的。App 永远不能直接操作 HAL,所有调用都必须经过 CameraService。这种设计保证了系统的安全性和稳定性。
嗯,到这里,CameraService 的核心启动、权限管理、客户端连接和 CameraDevice 创建流程就讲完了。你可能会问,这些流程里哪个最容易出问题?我个人经验是,权限检查和 HAL 加载这两个环节,是线上问题的高发区。权限检查失败通常是因为 App 没有正确申请运行时权限;HAL 加载失败则多半是厂商适配的问题。
好了,这一章的内容就到这里。下一章我们会深入 CameraDevice 的内部,看看它到底是怎么控制摄像头工作的。