更新记录

1.0(2026-09-20)

处理 Android WebView 的 onPermissionRequest,确保 web-view 内嵌网页(如 RTC 音视频页面)能正常获取摄像头、麦克风权限。

解决的问题

Android 系统 WebView 在网页调用 navigator.mediaDevices.getUserMedia() 时,会直接拒绝 网页的权限请求,表现为:App 级权限已授权,但 H5 页面仍无法打开摄像头/麦克风。

适用环境

项目类型 uni-app x(App-Android)
HBuilderX 4.25+(需 getAndroidView 能力)
Vapor 蒸汽模式 5.21+(Android(Vapor) UTS 插件支持原生调用)
注意 Android 蒸汽模式下页面运行在 JS 环境,无法直接获取原生 View,原生调用必须在 UTS 插件内完成

平台兼容性

uni-app x(4.26)

Chrome Safari Android iOS 鸿蒙 微信小程序
× × 5.0 × × ×

outlove-webview

uni-app x 原生 UTS 插件:处理 Android WebView 的 onPermissionRequest,确保 web-view 内嵌网页(如 RTC 音视频页面)能正常获取摄像头、麦克风权限。

解决的问题

Android 系统 WebView 在网页调用 navigator.mediaDevices.getUserMedia() 时,会回调 WebChromeClient.onPermissionRequest。宿主 App 若不覆写该方法,系统会直接拒绝 网页的权限请求,表现为:App 级权限已授权,但 H5 页面仍无法打开摄像头/麦克风。

本插件通过 uni-app x 提供的 UniElement.getAndroidView<WebView>()(HBuilderX 4.25+)拿到 web-view 对应的原生 WebView,注入自定义 WebChromeClient

适用环境

项目类型 uni-app x(App-Android)
HBuilderX 4.25+(需 getAndroidView 能力)
Vapor 蒸汽模式 5.21+(Android(Vapor) UTS 插件支持原生调用)
注意 Android 蒸汽模式下页面运行在 JS 环境,无法直接获取原生 View,原生调用必须在 UTS 插件内完成

安装

outlove-webview 文件夹放入项目的 uni_modules 目录即可,HBuilderX 会自动识别并参与编译。

使用

建议分两步:

① 申请系统权限(可提前) —— 放在任意页面/组件即可,如登录成功回调、 web-view 所在组件的 onMountedApp.vueonLaunch 等: 权限请求不依赖任何 UI 元素,越早授权,RTC 页面加载时越稳妥。

// #ifdef APP-ANDROID
import {
    checkMediaPermission,
    requestMediaPermission,
    checkLocationPermission,
    requestLocationPermission
} from '@/uni_modules/outlove-webview'

// 摄像头/麦克风
if (!checkMediaPermission()) {
    requestMediaPermission(
        () => console.log('摄像头/麦克风权限已授予'),
        (errMsg) => console.warn('权限申请失败:', errMsg)
    )
}
// 定位(web-view 内网页地理位置请求需要)
if (!checkLocationPermission()) {
    requestLocationPermission(
        () => console.log('定位权限已授予'),
        (errMsg) => console.warn('定位权限申请失败:', errMsg)
    )
}
// #endif

② 注入 WebChromeClient(必须在 web-view 组件内) —— 在承载 <web-view> 的 页面/组件中,拿到 web-view 的 UniElement 后注入:

// #ifdef APP-ANDROID
import { enableWebViewMediaPermission } from '@/uni_modules/outlove-webview'

// 页面 onReady / 组件 onMounted 后调用
onMounted(() => {
    const element = uni.getElementById('web-view')
    enableWebViewMediaPermission(element)
})
// #endif

模板中给 web-view 添加 id:

<web-view id="web-view" :src="src" />

提示:元素渲染完成前 getAndroidView 可能返回 null,建议在 onReady/onMounted 之后调用;如返回 false,可做有限次延时重试(如每 250ms 一次,最多 20 次)。

建议:发起系统授权前先弹业务确认框,说明所需权限,用户确认后再调用 requestMediaPermission / requestLocationPermission;用户取消则调用 uni.exit() 退出 App(uni-app x 中 uni.exit() 替代了 plus.runtime.quit,App-Android 支持)。

导出的 API

函数 说明
checkMediaPermission(): boolean 检查 CAMERA、RECORD_AUDIO 是否均已授权
requestMediaPermission(success, fail) 申请 CAMERA、RECORD_AUDIO 系统权限;fail 的 errMsg 区分“被拒绝”和“被永久拒绝”
checkLocationPermission(): boolean 检查 ACCESS_FINE_LOCATION、ACCESS_COARSE_LOCATION 是否均已授权
requestLocationPermission(success, fail) 申请定位权限;fail 的 errMsg 区分“被拒绝”和“被永久拒绝”
enableWebViewMediaPermission(element: UniElement \| null): boolean 为指定 web-view 注入 onPermissionRequest 授权处理,返回是否成功
enableWebViewRequestInterceptor(element: UniElement \| null, rules: RequestRule[]): boolean 注入静态资源请求拦截:URL 命中 urlPrefix 的请求改从本地 assets 返回文件,未命中走网络

本地静态资源替换(可选)

把网页的部分静态资源(JS/CSS/图片/字体等)替换为随 App 打包的本地文件, 用于加载加速、离线可用。通过 enableWebViewRequestInterceptor 注入:

// #ifdef APP-ANDROID
import { enableWebViewRequestInterceptor } from '@/uni_modules/outlove-webview'

const requestRules = [
    // URL 以 urlPrefix 开头时,urlPrefix 之后的路径映射到 assets/localDir 下读取
    { urlPrefix: 'https://www.example.com/rtc/', localDir: 'web/rtc' },
    // 首页(主框架文档)也替换成本地 html:加 mainFrame: true
    // 包含了一条默认的规则,无需配置即可生效。默认规则是
    // { urlPrefix: 'https://app.local/', localDir: 'local', mainFrame: true },
]
onMounted(() => {
    const element = uni.getElementById('web-view')
    enableWebViewRequestInterceptor(element, requestRules)
})
// #endif

本地文件放置位置(随 App 打包):

项目目录/
└── static/
    └── local/                ← localDir 对应目录
        └── js/app.js    ← 命中 https://app.local/js/app.js

说明:

  • 默认主页面导航不拦截,仅拦截子资源请求(JS/CSS/图片/字体/fetch 等);WebSocket 无法拦截
  • 规则设置 mainFrame: true 后也拦截主框架文档(首页);拦截不改变 WebView 的 URL,查询参数保留
  • 目录请求自动补 index.html(如 https://app.local/local/index.html),适配整站离线包入口
  • 离线包方案:URL 使用无 DNS 的假域名(如 https://app.local/),规则 urlPrefix 指向该假域名,localDir 指向离线包目录;未命中或本地缺失的请求仍会走网络,假域名会解析失败,因此需保证离线包文件齐全且使用自定义基座/正式包(标准基座不含插件 assets)
  • 命中规则但本地文件不存在时自动回退到网络加载,不会导致页面资源缺失
  • MIME 按扩展名自动识别;无法识别的扩展名不拦截(走网络)
  • URL 中的查询参数/锚点映射时自动去除
  • 本地文件更新需重新打包安装 App

前置配置## 前置配置

manifest.jsonapp-android.distribute.permissions 需声明(本项目已包含):

"<uses-permission android:name=\"android.permission.CAMERA\"/>",
"<uses-permission android:name=\"android.permission.RECORD_AUDIO\"/>",
"<uses-permission android:name=\"android.permission.MODIFY_AUDIO_SETTINGS\"/>"
"<uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\"/>"

其中 MODIFY_AUDIO_SETTINGS 为普通权限(免运行时弹窗),部分机型上 WebView getUserMedia 录音报 NotReadableError 时补充该权限可解决。

常见问题

  • 页面已调用但返回 false:web-view 原生 View 尚未构建,请延后调用或重试; 确认 uni.getElementById 传入的 id 与模板中 web-view 的 id 一致。
  • 授权弹窗被永久拒绝requestMediaPermission 的 fail 回调会返回对应提示, 此时需引导用户到系统设置中手动开启摄像头/麦克风权限。
  • iOS 端onPermissionRequest 是 Android 专有机制;iOS WKWebView 使用系统 权限弹窗,需在 manifest 中配置 NSCameraUsageDescriptionNSMicrophoneUsageDescription 隐私描述,本插件不处理 iOS。

隐私、权限声明

1. 本插件需要申请的系统权限列表:

<!-- 使用网络权限 --> <uses-permission android:name="android.permission.INTERNET"/> <!-- 拍照权限 --> <uses-permission android:name="android.permission.CAMERA"/> <uses-permission android:name="android.permission.RECORD_AUDIO" /> <!-- WebView getUserMedia 兼容所需音频设置权限(免运行时弹窗) --> <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

插件不采集任何数据

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

暂无用户评论。