You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android WebView加载本地assets文件时WebRTC麦克风权限异常

Android WebView中WebRTC麦克风权限问题解决方案

问题背景

开发集成聊天与WebRTC语音通话的Android WebView应用,前端资源(HTML/JS/CSS)从本地assets文件夹加载。移动浏览器中打开HTML文件时功能完全正常,但打包APK后WebRTC语音通话失效,推测是麦克风/音频权限未正确授予导致。已在AndroidManifest.xml中添加必要权限,但怀疑权限仅作用于原生组件,而非WebView。


核心原因分析

  1. 双层权限机制:Android权限分为原生层(Manifest声明+动态请求)和WebView内部权限(通过PermissionRequest处理),两者必须同时配置才能让WebRTC正常访问麦克风。仅声明Manifest权限或仅处理WebView权限请求都无法生效。
  2. 权限请求时机不匹配:当前代码在onCreate中请求原生权限,但用户操作权限弹窗时WebView已加载页面,WebRTC初始化时权限尚未就绪,导致失败。
  3. WebView本地资源限制:加载file:///android_asset协议页面时,WebView默认安全策略会限制部分API访问,需额外配置解除限制。
  4. 权限结果未处理:onRequestPermissionsResult仅调用父类方法,未根据权限授予结果重新加载页面或处理后续逻辑。

解决方案

1. 完善原生权限请求结果处理

修改onRequestPermissionsResult,在用户授予权限后重新加载WebView,确保WebRTC获取最新权限状态:

@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    if (requestCode == REQUEST_CODE) {
        boolean audioGranted = false;
        for (int i = 0; i < permissions.length; i++) {
            if (Manifest.permission.RECORD_AUDIO.equals(permissions[i]) && grantResults[i] == PackageManager.PERMISSION_GRANTED) {
                audioGranted = true;
                break;
            }
        }
        if (audioGranted) {
            if (webView != null) {
                webView.loadUrl("file:///android_asset/index.html");
            }
        } else {
            Toast.makeText(this, "需要麦克风权限才能进行语音通话", Toast.LENGTH_SHORT).show();
        }
    }
}

2. 优化WebView权限请求逻辑

在WebChromeClient的onPermissionRequest中,先检查原生权限是否已授予,再决定是否授权WebView的资源请求:

webView.setWebChromeClient(new WebChromeClient() {
    @Override
    public void onPermissionRequest(final PermissionRequest request) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED) {
                        List<String> grantedResources = new ArrayList<>();
                        for (String res : request.getResources()) {
                            if (res.equals(PermissionRequest.RESOURCE_AUDIO_CAPTURE)) {
                                grantedResources.add(res);
                            }
                        }
                        if (!grantedResources.isEmpty()) {
                            request.grant(grantedResources.toArray(new String[0]));
                            return;
                        }
                    }
                    request.deny();
                }
            });
        }
    }
});

3. 补充WebSettings必要配置

增加本地资源访问配置,确保WebView加载assets页面时能正常调用WebRTC API:

WebSettings webSettings = webView.getSettings();
webSettings.setJavaScriptEnabled(true);
webSettings.setAllowFileAccess(true);
webSettings.setAllowFileAccessFromFileURLs(true);
webSettings.setAllowUniversalAccessFromFileURLs(true);
webSettings.setDomStorageEnabled(true);
webSettings.setMediaPlaybackRequiresUserGesture(false);

4. 调整权限请求时机

确保WebView在权限请求完成后再加载页面,避免页面初始化时权限未就绪:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    webView = findViewById(R.id.webview);

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this,
                    new String[]{Manifest.permission.RECORD_AUDIO},
                    REQUEST_CODE);
        } else {
            initWebViewAndLoadPage();
        }
    } else {
        initWebViewAndLoadPage();
    }
}

private void initWebViewAndLoadPage() {
    WebSettings webSettings = webView.getSettings();
    webSettings.setJavaScriptEnabled(true);
    webSettings.setAllowFileAccess(true);
    webSettings.setAllowFileAccessFromFileURLs(true);
    webSettings.setAllowUniversalAccessFromFileURLs(true);
    webSettings.setDomStorageEnabled(true);
    webSettings.setMediaPlaybackRequiresUserGesture(false);

    webView.setWebViewClient(new WebViewClient());
    webView.setWebChromeClient(new WebChromeClient() {
        // 上述优化后的onPermissionRequest代码
        @Override
        public void onPermissionRequest(final PermissionRequest request) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED) {
                            List<String> grantedResources = new ArrayList<>();
                            for (String res : request.getResources()) {
                                if (res.equals(PermissionRequest.RESOURCE_AUDIO_CAPTURE)) {
                                    grantedResources.add(res);
                                }
                            }
                            if (!grantedResources.isEmpty()) {
                                request.grant(grantedResources.toArray(new String[0]));
                                return;
                            }
                        }
                        request.deny();
                    }
                });
            }
        }
    });

    webView.loadUrl("file:///android_asset/index.html");
}

WebView与浏览器的差异说明

  • 浏览器是独立应用,已完整处理权限请求流程(动态权限+内部资源授权);WebView是宿主应用的嵌入组件,权限完全由宿主控制,需宿主先获取原生权限,再通过WebChromeClient将权限授予WebView内部页面。
  • 本地file://协议安全策略不同:浏览器对本地文件权限限制更宽松,WebView默认限制较多,需手动开启相关配置。

内容的提问来源于stack exchange,提问作者Tolep Coy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 16:36:00