真要可靠加水印需走原生插件或nvue+live-pusher路线:uni.chooseImage因进程回收、EXIF处理异常、路径差异等问题导致水印错位;nvue中live-pusher实现预览层+cover-view实时水印最稳;审计级需求必须用原生在拍照回调中像素级绘制。

App端(Android/iOS)调用原生拍照并加水印,不能靠 uni.chooseImage 或 <camera> 组件直接实现——它们不支持实时叠加、无法控制渲染层、iOS/Android 行为差异大,且拍照后才拿到图,水印是“事后贴”,位置/缩放/旋转全靠猜,容易错位。真要可靠,得走原生插件或 nvue + live-pusher 路线。
为什么 uni.chooseImage 在 App 端加水印会出问题
这个 API 实际是唤起系统相机 Activity,App 进入后台,部分 Android 厂商(华为、小米、OPPO)会回收进程,导致返回时闪退或白屏;iOS 则常因权限或上下文丢失报 NSInvalidArgumentException。即使成功返回 tempFilePath,图片尺寸、方向(EXIF orientation)、压缩质量都不稳定,canvas 绘制时宽高计算极易偏差,水印偏移、拉伸、被裁切是常态。
-
uni.chooseImage返回路径在 Android 是/storage/emulated/0/Android/data/xxx/cache/xxx.jpg,iOS 是沙盒内临时路径,读取方式不同 - 未处理 EXIF 的图片在 canvas 中可能旋转 90°,但
ctx.drawImage不自动纠正 - H5 和小程序能直接读文件二进制,App 端必须用
uni.getFileSystemManager().readFile+base64,大图易 OOM
nvue 页面用 live-pusher 实现预览层+水印覆盖
这是目前最稳的 App 端方案:用 live-pusher 拉取摄像头原始画面流,再用 cover-view 叠加文字水印(时间、地址等),快门触发时截帧保存。整个过程不离开前台,无进程回收风险,水印位置绝对可控。
- 必须用
.nvue页面,live-pusher在 vue 页面中不生效或黑屏 -
cover-view里的text支持绑定{ { time }}、{ { address }},实时更新无需重绘 canvas - 截帧用
uni.saveImageToPhotosAlbum保存,或用uni.canvasToTempFilePath+live-pusher的snapshot方法(需配置enable-mic="false"避免音频干扰) - 注意
aspect设置:Android 推荐"16:9",iOS 推荐"4:3",否则 cover-view 定位失准
原生插件才是真正的「拍照即水印」
如果业务要求水印必须嵌入到照片像素里(比如审计留痕、防篡改),那 canvas 合成或 cover-view 都不够——前者可被截图绕过,后者只是 UI 层叠。这时必须写 Android/iOS 原生代码,在 onPictureTaken / didFinishMediaRecording 回调里用 Bitmap/CoreGraphics 直接绘制,再回调给 JS。
- Android 插件里用
Canvas+Paint把文字/Logo 绘到Bitmap上,再 compress 成 JPEG 写入文件 - iOS 插件用
UIGraphicsBeginImageContextWithOptions创建上下文,draw(in:)原图,再draw(at:)文字,最后UIImage.jpegData(compressionQuality:) - JS 层调用统一方法名如
uni.startWatermarkCamera({ watermark: { time: true, location: true } }),插件内部判断平台做差异化实现 - 插件需声明权限:
android.permission.CAMERA、android.permission.WRITE_EXTERNAL_STORAGE(Android 10+ 改用MANAGE_EXTERNAL_STORAGE),iOS Info.plist 加NSCameraUsageDescription
水印文字动态获取的坑:定位和时间不准
很多人把 new Date() 和 uni.getLocation 直接塞进水印,结果拍完发现时间慢 2 秒、地址是上一次的——因为 JS 执行和相机捕获不是原子操作。
- 正确做法:在 native 层拍照瞬间调用系统
System.currentTimeMillis()(Android)或CACurrentMediaTime()(iOS),确保毫秒级一致 - 定位必须用 native 的
CLLocationManager(iOS)或FusedLocationProviderClient(Android),JS 的uni.getLocation是异步回调,延迟不可控 - 若必须用 JS 获取,至少用
uni.onLocationChange订阅持续定位,并在拍照前await new Promise(resolve => setTimeout(resolve, 100))等最新值
真正可靠的水印相机,从来不是“JS 画个 canvas 就完事”。它牵扯到原生生命周期、内存管理、多线程调度、平台特性适配——你看到的只是按钮一按出图,背后是 Android SurfaceTexture 与 iOS AVCaptureVideoPreviewLayer 的无声博弈。


















