脱离 Android Studio:纯命令行开发 Android 相机 App 的实践与踩坑记录

14 阅读5分钟

背景与初衷:从一个“刚需”到硬核踩坑

作为一名计算机专业的学生,某天看着火车穿过站台时突发奇想,手机自带的相机虽然好用,但每次拍特定的场景(比如拍月亮需要低 ISO 和极慢的快门,拍火车需要极快的快门凝固瞬间),都需要去专业模式里重新手动调一堆参数,非常繁琐。

我就想:市面上有没有一款 App,能把“月亮参数”存成一组预设,把“火车参数”存成另一组,下次拍摄直接一键切换?

在查阅了一圈现有的应用后,发现要么功能不够全,要么界面太复杂。于是我萌生了自己写一个的念头。一开始我甚至在想,能不能写个微信小程序或者系统插件来调用原生相机的参数?但经过查阅资料发现,小程序和插件的权限根本摸不到手机底层的 Camera2 硬件接口。

想要实现真正的底层控制,只能开发一个独立的原生 App。

由于电脑和网络的问题,在下载 AS 安装包时网络一直卡顿报错,我最后选择 直接用 VS Code 和命令行,手搓一个。

这才有了今天这个名为 PresetCamera 的项目。目前,项目已经完成了核心功能,并在我的手机(荣耀 Magic7)上成功运行。本文主要记录项目的技术实现方案以及在开发过程中遇到的各种阻碍。


技术栈与功能实现

  • 开发环境:VS Code、JDK 17、Android SDK (Command-line tools)

  • 构建工具:Gradle 8.2(手动下载并配置环境变量,未使用 Wrapper)

  • 核心框架:Kotlin + CameraX (1.3.4)

  • 底层控制:Camera2Interop

  • 数据存储:SharedPreferences + JSON

1. 相机底层参数接管

借助 CameraX 的 Camera2Interop.Extender,我绕过了默认的自动模式,向底层 CaptureRequest 注入了手动参数。

  • 通过设置 CONTROL_AE_MODE_OFF,实现了 ISO(SENSOR_SENSITIVITY)和快门速度(SENSOR_EXPOSURE_TIME)的纯手动调节。

  • 因为关闭自动曝光会影响自动对焦,随后补充了 CONTROL_AF_MODE_OFF 和 LENS_FOCUS_DISTANCE,实现了手动对焦。

2. 软件层色温模拟

在尝试使用 RggbChannelVector 进行底层的 RGB 增益调节时,发现部分手机(测试环境下的荣耀 Magic7)的系统 ISP 忽略了我的注入参数,导致色温调节失效。为了保证功能可用,我采用了一个软件层的妥协方案:

  • 在 PreviewView 上方覆盖一个透明的 View 图层。

  • 通过控制图层的背景色(偏冷加蓝,偏暖加橙)和透明度来模拟色温变化。

  • 在拍照回调(ImageCapture)中,使用 Canvas 将当前图层的颜色叠加到生成的 Bitmap 上,以保证最终照片和预览效果一致。

3. 参数预设与持久化

  • 将四个滑块(ISO、快门、对焦、色温)的进度值打包成 JSON 对象。

  • 使用 SharedPreferences 将 JSON 数组存储在本地。

  • 点击预设按钮时,读取对应的 JSON 值,重置滑块的进度,并触发相机的重新绑定(rebindCamera),实现参数的一键恢复。


核心代码片段

val interop = Camera2Interop.Extender(previewBuilder)
// 强行关闭自动曝光
interop.setCaptureRequestOption(CaptureRequest.CONTROL_AE_MODE, CameraMetadata.CONTROL_AE_MODE_OFF)
interop.setCaptureRequestOption(CaptureRequest.SENSOR_SENSITIVITY, currentIso)
interop.setCaptureRequestOption(CaptureRequest.SENSOR_EXPOSURE_TIME, currentShutterNs)
// 强行关闭自动对焦
interop.setCaptureRequestOption(CaptureRequest.CONTROL_AF_MODE, CameraMetadata.CONTROL_AF_MODE_OFF)
interop.setCaptureRequestOption(CaptureRequest.LENS_FOCUS_DISTANCE, currentFocusDistance)
// UI 叠加颜色
private fun updateColorOverlay(progress: Int) {
    if (progress < 50) {
        val alpha = ((50 - progress) * 128) / 50
        colorOverlay.setBackgroundColor(Color.argb(alpha, 0, 100, 255))
    } else if (progress > 50) {
        val alpha = ((progress - 50) * 128) / 50
        colorOverlay.setBackgroundColor(Color.argb(alpha, 255, 150, 0))
    }
}

// 用 Canvas 渲染颜色
private fun applyColorFilterToBitmap(original: Bitmap, wbProgress: Int): Bitmap {
    val result = original.copy(Bitmap.Config.ARGB_8888, true)
    val canvas = Canvas(result)
    val paint = Paint()
    // ... 根据 wbProgress 计算 alpha 和颜色并画上去
    canvas.drawRect(0f, 0f, result.width.toFloat(), result.height.toFloat(), paint)
    return result
}
//`JSONObject` 组装部分
val preset = JSONObject().apply {
    put("name", name)
    put("isoProgress", sbIso.progress)
    put("shutterProgress", sbShutter.progress)
    put("focusProgress", sbFocus.progress)
    put("wbProgress", sbWb.progress)
}

开发过程中遇到的主要问题

在没有 Android Studio 辅助的情况下,整个开发流程遇到了不少阻碍。以下是一些主要的技术卡点:

1. 环境配置与依赖管理

  • SDK 路径问题:cmdline-tools 解压后多套了一层文件夹,导致命令行找不到 sdkmanager,需要手动调整目录结构至 latest/bin。

  • Wrapper 缺失:由于网络原因无法下载 gradle-wrapper.jar,最终放弃使用 Wrapper,改为直接调用本地解压的 Gradle 8.2。

  • 版本冲突:最初引入 CameraX 1.7.0-alpha03 时,报错提示需要 AGP 8.6.0 及以上版本。为了兼容当前的构建环境,将 CameraX 降级到了 1.3.4。

2. 调试与设备连接

  • ADB 连接不稳定:在 VS Code 终端中,adb devices 经常无法识别到手机(荣耀 Magic7)。尝试过重启 adb 服务、更换数据线和接口,依然未能稳定连接。最终只能采用最原始的办法:每次编译出 app-debug.apk 后,通过微信发送到手机上进行手动覆盖安装。

3. 底层硬件兼容性

  • 失焦与过曝:关闭自动曝光后,画面一度全糊,且因为初始参数范围设置过宽,滑块稍微拖动就会导致画面过曝或死黑。后来收窄了 ISO 和快门的映射范围(如 ISO 限制在 1200 以内,快门限制在 1/1000s 到 1/15s)才有所改善。

  • 跨机型闪退:编译出的 APK 在我的荣耀手机上运行正常,但发给使用 其他手机(OPPO等) 的朋友测试时,出现了“能安装但点击打开后立刻闪退(无报错提示)”的情况。


📝 下一步计划与求助

虽然项目目前在我的手机上实现了核心闭环,但依然存在很多尚未解决的问题。例如:底层白平衡注入失效的根本原因、其他机型上的底层崩溃如何排查、如何在无 AS 环境下抓取其他机型的日志等。

我将在接下来的一篇帖子中,详细列出这些具体的技术问题,希望能得到有相关经验开发大佬的指点。