资讯详情

安卓原生相机:APK提取与Camera2自研实战

📅 2026/10/31 13:31:19 | 华诺云谱 👁 阅读
安卓原生相机:APK提取与Camera2自研实战
有人问我安卓上那个“android原生相机apk”到底是个什么东西是能从系统里扒出来的安装包还是自己拿源码编译一个“原生模式相机APP”。这两个理解都对也都有人在做只是目的完全不同。前者多出现在定制ROM、行业设备、老旧机型补功能这些场景里想要的是那套跟系统深度绑定的成像管线后者是开发者自己想拿到最干净的画面、最完整的手动参数不想被厂商的算法“美颜”和过度锐化糊一脸。我这几年在这两件事上都踩过坑从拆包看资源到一行行调CaptureRequest掉过的头发不算少所以这篇就把我知道的、验证过的、被坑过的一次性摊开讲。适合已经能写安卓基础界面、想深入相机管线的开发者也适合只想搞明白“为什么我的手机拍出来的照片跟我眼睛看到的不一样”的折腾党。1. 搞清“原生”指什么三条技术路线先分清“原生”这个词在相机语境里非常含糊不同人嘴里的意思能差出十万八千里。有人指的是出厂预装的那个相机应用有人指的是不叠加任何算法处理的原始图像数据还有人指的是走系统标准API而不是厂商私有SDK的开发方式。这三种理解对应三条完全不同的技术路线选错了方向后面所有工作都是白费。所以动手之前先把目标定死在某一类上比急着写代码重要得多。1.1 系统相机APK的提取与替换路线这条路线的核心诉求通常是“我就要那个原厂相机”。常见场景包括某台设备刷了第三方系统之后相机功能残缺想找回原来的拍照体验行业定制设备需要预装一个稳定的相机应用但厂商给的精简版功能太少或者纯粹是好奇想看看原厂相机里到底藏了哪些参数开关。技术上要做的事情大致分四步。第一步是定位安装包通过包名查询拿到APK路径再用备份工具导出第二步是反编译看结构重点看AndroidManifest.xml里的权限声明、uses-feature声明以及res/xml目录下有没有相机相关的配置文件第三步是判断依赖很多原厂相机不是独立应用它依赖厂商私有框架类、私有HAL扩展、甚至依赖特定的系统签名权限这类APK搬到别的设备上要么直接崩溃要么能打开但功能大面积失效第四步才是实际测试。这里有个必须提前说清楚的事实绝大多数原厂相机APK是不可移植的。原因不复杂它们在AndroidManifest.xml里通常声明了一堆signature级别或privileged级别的权限普通应用装上去根本拿不到它们调用的很多接口来自厂商自己的框架层标准Android SDK里压根没有这些类还有一部分功能直接走私有HAL通道换了芯片平台就是一片空白。所以如果你的目标是“搬一个原厂相机到另一台机器”我的建议是先做可行性判断别一头扎进去。判断方法很直接把APK反编译出来搜索import语句里有没有非android.*、非java.*、非androidx.*的包名如果有大量厂商私有包基本可以放弃了。提示提取出来的APK只适合在本机做研究和个人学习用途不要拿去做二次分发也不要用于任何商业场景。签名校验、资源授权这些边界问题提前想清楚比事后补救省事得多。另外还有一个常被忽略的点即使APK能跑起来相机功能的实际表现也取决于HAL层。同一个APK在两台设备上一台拍出来是正常的另一台可能对焦死活不触发因为对焦算法有一部分是在HAL里实现的应用层只能发起请求管不了执行细节。这一点在做兼容性评估时必须纳入考量。1.2 基于Camera2自己写原生模式相机这条路线才是真正意义上的“原生模式相机APP”——走系统标准相机API不依赖任何厂商私有扩展把控制权尽可能拿回到自己手里。它的价值在于可控你想让快门速度停在哪就停在哪想让ISO固定在100就固定在100想输出RAW就输出RAW不会有一层看不见的算法在背后偷偷给你锐化、降噪、拉饱和度。这条路线的前提是设备支持Camera2的LEGACY以上级别。CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL这个字段会告诉你答案取值从LEGACY、LIMITED、FULL到LEVEL_3。LEGACY基本等于只能当个普通相机用手动参数大面积为空LIMITED能拿到部分控制FULL和LEVEL_3才是真正能玩手动模式的级别。很多中低端机型标称支持Camera2实际是LEGACY你兴冲冲写了个手动曝光面板跑起来发现SENSOR_INFO_EXPOSURE_TIME_RANGE返回的是[0,0]白忙一场。我个人的经验是做这类应用首先要写一个能力探测页把所有关键特性一次性列出来支持的硬件级别、可用分辨率列表、是否支持RAW、曝光时间范围、ISO范围、是否支持手动对焦、是否支持逻辑多摄。这个页面看似只是个调试工具实际是整个项目的地基后面所有的功能开关都要依赖它做降级处理。1.3 三条路线的选型对照把前面两条加上“只做预览和简单拍照的轻量封装”这第三条做个横向对照会更清楚。路线技术门槛可控程度可移植性适合谁原厂APK提取替换低操作层面完全依赖原厂实现极差基本限于同型号刷机玩家、设备维护人员Camera2自研高高可深入手动参数好标准API跨设备想深入管线的开发者CameraX轻量封装中低中受封装层限制很好快速出功能的产品团队选型的关键不是哪个“更高级”而是你的目标是什么。要最干净的画质和最完整的手动控制只有Camera2这条路要快速上线一个能用的相机功能CameraX省下来的时间够你多睡好几觉要找回原厂体验那就老老实实做提取和兼容性验证别指望能跨设备通用。2. 相机管线的技术底座从应用到Sensor的完整链路在动手写代码之前脑子里得有一张完整的链路图否则调起参数来完全是盲人摸象。安卓相机的数据流向大致是应用层发起CaptureRequest经过相机服务下到HAL层HAL再跟驱动和Sensor打交道图像数据从Sensor出来之后一部分回流到应用层的Surface一部分产生CaptureResult元数据回传给应用。理解这个“请求-结果”的双向模型是后面所有调优工作的前提。2.1 Camera2的请求-结果模型到底怎么跑Camera2的模型可以用一句话概括你不断下发请求系统不断回传结果。请求是CaptureRequest它描述“这一次我想要的参数组合是什么”结果是CaptureResult它告诉你“实际执行时用的是什么参数”。打开相机的标准流程是固定的几步。先通过CameraManager拿到相机ID列表用getCameraCharacteristics()读取能力然后调用openCamera()在StateCallback的onOpened回调里拿到CameraDevice接着创建会话Android 9及以上推荐用SessionConfiguration之前的版本用createCaptureSession(ListSurface, ...)会话建立后构造预览请求并用setRepeatingRequest()持续下发。// 预览请求的典型构造注意每个 set 都可能返回 false val request device.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW).apply { addTarget(previewSurface) set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE) set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON) set(CaptureRequest.CONTROL_AWB_MODE, CaptureRequest.CONTROL_AWB_MODE_AUTO) } session.setRepeatingRequest(request.build(), captureCallback, backgroundHandler)这段代码里有个很容易被忽略的细节set()方法的返回值是布尔值表示这个key在当前设备上是否被接受。很多人直接忽略返回值结果设了个设备不支持的参数界面显示设置成功了实际拍出来纹丝不动排查半天以为是API有问题。养成检查返回值的习惯不接受就说明这个能力不可用界面上直接置灰比让用户白点强。另一个关键点是CaptureResult的读取。TotalCaptureResult里带着这一次实际生效的曝光时间、ISO、对焦距离、测光结果。手动模式做“所见即所得”的难点就在这——你设了1/8秒快门但实际生效可能是1/8秒加上若干帧的延迟尤其是高像素模式下Sensor读出时间本身就有限制。2.2 CameraX的封装边界在哪里CameraX把上面那套流程包成了三个用例Preview、ImageCapture、ImageAnalysis通过bindToLifecycle()一次性绑定生命周期自动管理确实省事。它的封装边界在Camera2Interop这里——你想设置一些Camera2层面的参数可以通过Camera2Interop.Extender往用例上挂CaptureRequest.Key。但封装就是封装它帮你省掉的是样板代码同时也限制了你对时序的精细控制。举几个实际例子想做一个“按下快门后立即锁曝光再拍”的连拍逻辑CameraX里你得绕一圈用Interop去关AE想严格控制ImageReader的缓冲区数量和格式来做实时处理CameraX的ImageAnalysis给你的是封装后的ImageProxy格式和背压策略的调整空间有限。我的判断标准很朴素如果你的需求能用“预览拍照分析”三个词完整描述用CameraX一旦出现“我要在某个精确时刻改变某个参数”这类需求直接上Camera2。半途从CameraX迁到Camera2的成本比一开始就选Camera2再慢慢补功能要高得多。2.3 硬件能力预检别等跑起来才发现不支持能力预检这块我整理了一份必查清单每次新项目开工先把这些读一遍打印到日志里存档。检查项对应Key说明硬件级别INFO_SUPPORTED_HARDWARE_LEVEL低于FULL基本别想做手动模式曝光时间范围SENSOR_INFO_EXPOSURE_TIME_RANGE单位纳秒常见1/10000s到1/4sISO范围SENSOR_INFO_SENSITIVITY_RANGE有些设备只有固定几档是否支持RAW检查REQUEST_AVAILABLE_CAPABILITIES含RAW支持才谈得上DNG输出支持的分辨率SCALER_STREAM_CONFIGURATION_MAP注意区分预览、拍照、录像三套焦距与传感器尺寸LENS_INFO_AVAILABLE_FOCAL_LENGTHS、SENSOR_INFO_PHYSICAL_SIZE用来算等效焦距防抖支持CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES光学和电子要分开看物理相机列表getPhysicalCameraIds()逻辑多摄设备才有意义这些数据拿到之后记得做降级设计。比如某设备曝光时间范围很窄那手动快门面板就不要给出1/2000秒到1/4秒的完整滑块直接按实际范围生成档位用户拖到边界也不会出现无效值。这类“按能力生成UI”的做法比写死一套参数然后到处加if判断要清爽得多。3. 动手搭一个原生模式相机APP完整实操主线理论铺垫够了接下来是实打实的搭建过程。我会按我实际做项目的顺序走一遍从工程骨架到拍照、录像、RAW输出每一步都给出关键代码和参数计算过程。这里假设你的目标是“一个能手动控制、画质干净、支持专业参数”的相机应用也就是标题里说的原生模式相机APP。3.1 工程骨架与预览链路的落地预览是整条链路里最容易出问题的环节因为它同时涉及Surface管理、宽高比适配和帧率控制。Surface的选择上SurfaceView和TextureView各有取舍。SurfaceView性能更好因为它在独立的合成层上渲染不参与View的绘制流程功耗低、延迟小缺点是它不支持变换动画、层级关系处理麻烦。TextureView可以像普通View一样做变换适合需要动画或者叠加复杂UI的场景但多一层纹理拷贝功耗会高一些。做相机应用我一般选SurfaceView只有在必须做预览变形的场景才换TextureView。创建ImageReader用于预览时格式选ImageFormat.YUV_420_888还是直接把Surface交给SurfaceHolder差别很大。能够直接给Surface的情况下绝对不要走ImageReader做预览。原因很实在YUV数据从HAL拷到应用层再从应用层转成纹理显示多两次拷贝30fps下每帧1080p的YUV420数据约3.1MB一秒钟接近100MB的内存带宽开销中低端机直接卡成幻灯片。// 直接用 SurfaceHolder 的 Surface 做预览目标 val previewSurface surfaceView.holder.surface // 关键设置默认缓冲区尺寸尺寸不匹配是预览拉伸的头号元凶 surfaceView.holder.setFixedSize(previewSize.width, previewSize.height)预览尺寸的选取有一套固定逻辑从StreamConfigurationMap里拿到支持的所有预览尺寸优先选宽高比与屏幕或预览容器最接近的尺寸不低于容器实际像素尺寸的最小值。举个具体例子容器是1080×1920候选里有1280×720、1920×1080、2560×1440那就选1920×1080因为它的宽高比是16:9跟容器一致且尺寸刚好够用不浪费。如果直接拿2560×1440去渲染到1080的容器里虽然清晰度不会变差但每帧多处理70%的像素功耗和发热都会明显上升。还有一个细节预览的宽高比和拍照的宽高比不一定一致。很多设备支持的拍照最大分辨率是4:3而预览是16:9切换时如果只是改Surface尺寸会出现画面跳变。我的做法是拍照完成后按拍照比例裁剪或者在拍照瞬间短暂冻结预览。前者实现简单后者体验更连贯看你愿意投入多少。3.2 拍照参数的计算与手动模式落地手动模式的核心就是曝光三要素的联动曝光时间、ISO、光圈。手机的光圈基本是固定的少数机型有可变光圈所以实际能调的就是快门和ISO。先把曝光量的关系理清楚。曝光值用EV表示公式是EV log2(N² / t) - log2(S / 100)其中N是光圈值t是快门时间秒S是ISO。这个公式在手机上的实用意义不在于精确计算而在于换算补偿关系。举个具体的当前光圈f/1.8快门1/60秒ISO 100算出来EV约等于11.5。如果你想把快门提到1/120秒快一档曝光量少一半要维持同样的亮度ISO就得从100提到200。这就是“快门和ISO互抵”的直觉来源理解了这一点手动模式调起来就不会手忙脚乱。实际写代码时参数设置是这样的val request device.createCaptureRequest(CameraDevice.TEMPLATE_MANUAL).apply { addTarget(captureSurface) // 关闭自动曝光才能手动设定 set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_OFF) // 曝光时间单位是纳秒 set(CaptureRequest.SENSOR_EXPOSURE_TIME, exposureNs) set(CaptureRequest.SENSOR_SENSITIVITY, iso) // 帧间隔要大于等于曝光时间否则请求会被丢弃 set(CaptureRequest.SENSOR_FRAME_DURATION, frameDurationNs) // 自动白平衡也关掉改用固定色温 set(CaptureRequest.CONTROL_AWB_MODE, CaptureRequest.CONTROL_AWB_MODE_OFF) set(CaptureRequest.COLOR_CORRECTION_MODE, CaptureRequest.COLOR_CORRECTION_MODE_TRANSFORM_MATRIX) }这里有两个坑必须提前规避。第一个是SENSOR_FRAME_DURATION和曝光时间的关系帧间隔必须不小于曝光时间否则这个请求会被直接丢弃。假设你要拍1/4秒的长曝光帧间隔就至少得设成250毫秒这时预览帧率会掉到4fps界面上必须给出提示别让用户以为卡死了。第二个坑是CONTROL_AE_MODE_OFF之后有些设备的SENSOR_SENSITIVITY会被限制在一个更窄的范围内因为自动增益关掉了这个要去读SENSOR_INFO_SENSITIVITY_RANGE的实际可用区间不能想当然按最大值设。对焦的手动控制用的是LENS_FOCUS_DISTANCE单位是屈光度也就是焦距的倒数。0表示无穷远数值越大表示焦点越近。多数设备支持从0到10左右的范围对应10厘米到无穷远。这个值的直观映射不好做因为它不是线性的做UI滑块的时候建议按“近-中-远”分段映射别直接用线性滑块用户会疯掉。提示手动对焦滑块拖动过程中不要每帧都下发请求用防抖节流比如60毫秒一次。高频下发对焦请求会让HAL反复重启对焦马达声音难听还费电。3.3 录像链路的码率、帧率与编码器选择录像这条链路比拍照复杂因为它涉及编码器、音频采集、封装容器三个额外环节。先从码率算起这是最容易被忽略但直接影响文件体积和画质的参数。视频码率的经验公式是目标码率 宽 × 高 × 帧率 × bppbppbits per pixel取0.07到0.15之间具体看内容复杂度。按1080p30、bpp取0.1算1920 × 1080 × 30 × 0.1 ≈ 6.2 Mbps。这个值偏低实际做相机应用建议留足余量1080p30用15到20 Mbps4K30用50到60 Mbps。为什么留这么大余量因为bpp公式算的是理论值实际场景里快速运动、复杂纹理、画面噪点都会让编码器吃紧余量给不够就会出现运动场景的马赛克。编码器的选择上MediaRecorder开箱即用适合功能简单的场景MediaCodec灵活但工作量大。我现在的习惯是拍照和简单录像用MediaRecorder需要同时输出录像文件和实时分析帧的时候才上MediaCodec加ImageReader的组合。// MediaRecorder 的关键配置顺序不能乱错一步就抛异常 recorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER) recorder.setVideoSource(MediaRecorder.VideoSource.SURFACE) recorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4) recorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264) recorder.setVideoSize(videoSize.width, videoSize.height) recorder.setVideoFrameRate(30) recorder.setVideoEncodingBitRate(18_000_000) recorder.setOrientationHint(orientationDegrees)setOrientationHint这个调用经常被忘掉导致竖着拍的视频在电脑上播放时是横的。它的取值要从SENSOR_ORIENTATION和当前屏幕方向一起算前置摄像头还要额外考虑镜像。高帧率录制60fps、120fps需要单独处理不是简单改个数字就行。要通过CamcorderProfile查询设备是否支持或者用CONTROL_AE_TARGET_FPS_RANGE指定帧率区间。高帧率模式下曝光时间上限会被压缩因为它必须在更短的帧间隔内完成读出。120fps下每帧间隔只有8.3毫秒快门最长也就到1/120秒这时候拍暗光环境基本废掉界面上要给出明确提示。3.4 RAW/DNG与专业参数面板RAW输出是“原生模式”最有说服力的功能。它的价值在于保留传感器最原始的线性数据白平衡、色调曲线、锐化这些都是“未烘焙”的状态后期空间远大于JPEG。实现上先确认设备支持RAW——REQUEST_AVAILABLE_CAPABILITIES里包含RAW。然后创建输出目标为ImageFormat.RAW_SENSOR的ImageReader拍完拿到Image对象用DngCreator写入DNG文件。val rawReader ImageReader.newInstance( rawSize.width, rawSize.height, ImageFormat.RAW_SENSOR, 2 ) // 拿到 Image 之后 val dngCreator DngCreator(characteristics, captureResult) FileOutputStream(dngFile).use { out - dngCreator.writeImage(out, image) } dngCreator.close() image.close()DNG文件的体积要有心理准备。以12bit、4000×3000的RAW为例理论数据量是4000 × 3000 × 12 / 8 18MB加上元数据和内嵌预览图单个文件20MB上下很正常。连拍十张就是200MB存储管理必须提前设计。专业参数面板的设计上我的原则是只暴露设备真实支持的能力并且给出实时反馈。面板上至少要有这几项快门、ISO、对焦距离、白平衡色温、曝光补偿。每项旁边显示当前实际生效值从CaptureResult里读而不是从你设置的请求里读——这两者经常不一致读结果才真实。参数联动也要做。用户拖快门的时候如果补偿掉太多ISO应该自动跟着变旁边给个“自动”开关让用户选择是保持曝光还是保持ISO。这种细节决定了这个APP是“能用”还是“好用”。4. 画质与体验打磨那些跑通之后才暴露的问题功能跑通只是开始真正拉开差距的是画质调优和异常处理。这一章讲的都是“功能已经能用了但拍出来还是不对劲”的那一类问题也是我自己踩得最多的部分。4.1 3A行为的调优3A指的是自动对焦AF、自动曝光AE、自动白平衡AWB。它们默认的行为各有各的毛病。自动曝光最常见的症状是画面亮度“呼吸”也就是取景时亮度忽明忽暗。原因是默认AE会持续调整遇到画面里有大面积高光或者移动物体时测光结果来回震荡。解决办法是设置测光区域和测光权重用CONTROL_AE_REGIONS指定一个区域坐标要归一化到[-1000, 1000]的坐标系里同时配合CONTROL_AE_PRECAPTURE_TRIGGER在拍照前做一次预曝光锁定。自动对焦的问题是“拉风箱”尤其是在低对比度场景下对焦马达来回找。可以切换到CONTROL_AF_MODE_AUTO单次对焦而不是连续对焦拍照前主动触发一次对焦等CONTROL_AF_STATE变成FOCUSED_LOCKED再拍。这样牺牲一点速度换来的是成片率。自动白平衡的坑在于同场景多张照片色温不一致。这在连拍时特别明显第一张偏暖第二张偏冷。要解决就关掉AWB改用固定的COLOR_CORRECTION_GAINS。计算方法是先用自动模式拍一张从CaptureResult里读出实际生效的增益锁定这组值后续拍摄都用它。这个技巧在需要色彩一致性的场景里非常好用。问题现象原因处理方式取景亮度忽明忽暗AE持续震荡设置测光区域拍照前预锁定对焦反复拉风箱低对比度下连续对焦失效改单次对焦手动触发连拍色温漂移AWB每帧重算锁定COLOR_CORRECTION_GAINS手动模式画面偏色AWB关闭后未设增益显式指定增益矩阵4.2 变焦、多摄与防抖变焦这块Android 11之后有了CONTROL_ZOOM_RATIO直接设倍率比自己算裁剪区域方便得多。但底层实现差异很大有些设备是真正的光学变焦有些是切换物理镜头有些纯粹是数码裁切。用户看到的是“2倍”实际可能是另一颗镜头在工作画质、色彩、对焦表现全变了。判断当前是哪颗镜头在工作可以读CaptureResult里的SENSOR_INFO_ACTIVE_ARRAY_SIZE或者直接看物理相机ID。做多摄切换的时候最大的问题是切换瞬间画面会跳——不同镜头的色彩、亮度、甚至视角都有差异。缓解办法是在切换前先做一次短暂的遮罩过渡或者在应用层做色彩匹配。防抖分成光学防抖OIS和电子防抖EIS。OIS通过LENS_OPTICAL_STABILIZATION_MODE控制EIS通过CONTROL_VIDEO_STABILIZATION_MODE控制。两者可以同时开但要注意EIS开启后会裁切一部分画面来做补偿余量所以实际视野会变小这一点要在UI上说明不然用户会觉得“开了防抖怎么变窄了”。提示录制高帧率视频时不要开EIS因为EIS需要更大的裁切余量高帧率下每帧可用时间短补偿效果反而差还额外增加功耗。4.3 GPU实时滤镜管线想在预览上叠加实时滤镜标准的做法是把预览Surface替换成SurfaceTexture用OpenGL做渲染再把处理后的纹理输出到SurfaceView。这条链路的关键是SurfaceTexture的setDefaultBufferSize必须跟相机输出尺寸一致不一致就是各种拉伸和变形。一个简化但实用的实现思路是相机输出到SurfaceTextureSurfaceTexture在GL线程里更新纹理着色器做处理渲染到屏幕。滤镜参数通过uniform变量传入避免每帧重建着色器。性能上有几个硬指标要盯住GL线程必须是独立的不能占用主线程纹理更新要处理好时间戳避免不同步导致的画面撕裂滤镜复杂度要控制多pass滤镜在1080p下很容易把中端机的GPU跑满帧率掉到20以下就得不偿失了。我一般会准备两套着色器一套轻量版给预览用一套完整版只用于拍照瞬间的单帧处理这样两头都能兼顾。5. 问题排查实录与兼容性速查相机开发最消耗时间的部分不是写功能是排查各种“在A机器上正常、在B机器上崩溃”的问题。这一章把我遇到过的高频问题和定位路径整理出来希望能帮你少熬几个夜。5.1 黑屏、绿屏、拉伸的定位路径黑屏是最常见的症状原因按概率从高到低排权限没给全相机权限、存储权限都要检查、Surface还没准备好就发起会话、相机被其他应用占用、onOpened回调没触发。定位顺序建议是先看日志里有没有CameraAccessException再看Surface.isValid()最后才怀疑HAL。绿屏或者紫边八成是YUV格式处理错了。YUV_420_888的plane排列在不同设备上可能不同rowStride和pixelStride必须从Image.Plane里读不能假设是紧凑排列。如果做NV21转换务必按实际的stride来处理每一行直接假设width rowStride就会错位表现出来就是颜色错乱或者画面斜切。预览拉伸几乎都是宽高比不匹配。检查两件事SurfaceHolder的setFixedSize是否设成了预览尺寸容器的布局是否强制了某个固定的宽高比。这两者不一致时画面就会被硬拉伸。正确做法是按预览的实际宽高比调整容器尺寸或者用裁剪的方式填充。还有一种情况是预览方向不对横屏拍出来是正的竖屏拍出来是横的。这涉及SENSOR_ORIENTATION、屏幕旋转角度、前置镜像三个变量计算公式要用到设备方向和传感器方向别自己瞎试按标准公式算一遍就对。5.2 常见问题速查表现象可能原因快速验证方式打开相机直接崩溃权限缺失或设备被占用查logcat的CameraAccessException预览卡顿掉帧走了ImageReader转YUV改成直接给Surface试试拍照无响应会话被占用或请求队列满检查是否有未关闭的Session照片方向错误JPEG_ORIENTATION未设置设完再拍一张对比手动参数不生效参数超出设备范围set()返回值是否为true高帧率模式不可用设备未支持该档位查CamcorderProfile的可用列表RAW文件打不开DngCreator未正确释放检查writeImage是否抛异常录像文件无声音频源被占用换AudioSource试试切换前后摄黑屏旧会话未关闭先close再open这张表我基本随身带着遇到问题先对一遍能省掉大半的猜测时间。5.3 功耗与发热控制相机是手机上最费电的模块之一Sensor、ISP、编码器、屏幕全都在高负载运转。做原生模式相机应用尤其要注意因为手动模式下的长曝光、高ISO降噪、RAW写入都会显著拉高功耗。几个实用的控制手段预览帧率不要盲目追高30fps对绝大多数场景足够60fps只在需要流畅取景时才开预览分辨率选够用就好不要拿4K去做1080容器的预览手动长曝光结束后立即回到正常预览不要让Sensor一直挂在长曝光状态RAW写入放到后台线程别阻塞相机管线。还有个容易被忽略的点是相机会话的生命周期。很多人只在onPause里关相机但onStop、onDestroy也要处理尤其是横竖屏切换的时候旧会话没关干净新会话就建不起来表现出来就是“转个屏相机就废了”。我做这类项目时的体会是相机应用的稳定性不取决于你写了多少功能而取决于你对异常路径覆盖了多少。正常流程可能只占代码量的三成剩下七成都在处理“设备不支持”“会话失败”“参数越界”这些情况。把这些兜底逻辑写扎实用户才不会在关键时刻掉链子。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑