You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Android WebView摄像头视频流无法显示问题求助(适配AndroidX)

解决Android WebView打包网站后摄像头无视频流的问题

我之前也踩过一模一样的坑——网站在手机浏览器里能正常调用摄像头,但打包成WebView的APK就只显示静态截图,完全没有视频流。这主要是因为WebView的默认配置、权限逻辑和浏览器差异很大,尤其是Android 6.0+的动态权限机制,还有WebView对媒体设备访问的特殊要求。下面给你一套亲测可行的修复方案,直接基于你的代码修改:

一、核心问题分析

你的现有代码只配置了基础的WebView渲染设置,缺少了三个关键环节:

  • Android 6.0+的动态摄像头权限申请(仅在Manifest里加静态权限,高版本系统会直接忽略)
  • WebView对网站发起的媒体权限请求的授权处理(浏览器会自动弹权限框,WebView需要手动接管)
  • 媒体播放相关的WebSettings配置(比如允许自动加载摄像头流)

二、具体修复步骤

1. 动态申请摄像头权限

在MainActivity里添加权限检查和申请逻辑,确保App运行时能拿到摄像头权限:

首先导入权限相关的包:

import android.Manifest;
import android.content.pm.PackageManager;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

然后在onCreate方法开头加入权限检查:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    // 第一步:动态申请摄像头权限
    if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
        ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, 100);
    }

    // 原有的WebView初始化代码...
    webview = findViewById(R.id.webView);
    spinner = findViewById(R.id.progressBar1);
    // ...
}

再添加权限申请的回调处理:

@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    if (requestCode == 100) {
        if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            // 权限申请成功,重新加载网页让摄像头流生效
            webview.reload();
        } else {
            Toast.makeText(this, "需要摄像头权限才能使用该功能", Toast.LENGTH_SHORT).show();
        }
    }
}

2. 完善WebView的媒体相关配置

修改WebSettings,添加摄像头流所需的关键配置:

webview.getSettings().setJavaScriptEnabled(true);
webview.getSettings().setDomStorageEnabled(true);
webview.setOverScrollMode(WebView.OVER_SCROLL_NEVER);
// 添加以下媒体相关配置
webview.getSettings().setMediaPlaybackRequiresUserGesture(false); // 允许自动播放媒体流(摄像头流必须开这个)
webview.getSettings().setAllowFileAccess(true);
webview.getSettings().setAllowContentAccess(true);
webview.getSettings().setLoadWithOverviewMode(true);
webview.getSettings().setUseWideViewPort(true);

3. 添加WebChromeClient处理媒体权限请求

WebView需要通过WebChromeClient来接收网站发起的摄像头权限请求,补充到原有的WebViewClient设置里:

先导入相关包:

import android.webkit.WebChromeClient;
import android.webkit.PermissionRequest;
import android.os.Build;

然后设置WebChromeClient:

// 保留原有的CustomWebViewClient处理页面加载
webview.setWebViewClient(new CustomWebViewClient());
// 添加WebChromeClient处理权限请求
webview.setWebChromeClient(new WebChromeClient() {
    @Override
    public void onPermissionRequest(PermissionRequest request) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            // 确认已获取摄像头权限后,授权网站的请求
            if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
                request.grant(request.getResources());
            }
        }
    }
});

4. 优化Manifest配置

去掉冗余权限,确保核心配置正确:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="name">

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.CAMERA" />
    <!-- READ_EXTERNAL_STORAGE在Android 13+已废弃,按需保留 -->
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
    
    <uses-feature android:name="android.hardware.camera" android:required="true" />
    <uses-feature android:name="android.hardware.camera.autofocus" android:required="false" /> <!-- 不需要自动对焦可设为false -->

    <application
        android:usesCleartextTraffic="true"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:exported="true"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

三、完整修改后的MainActivity代码

package name;

import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import android.Manifest;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.View;
import android.webkit.PermissionRequest;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.graphics.Bitmap;
import android.widget.ProgressBar;
import android.widget.Toast;
import android.os.Build;

public class MainActivity extends AppCompatActivity {
    String ShowOrHideWebViewInitialUse = "show";
    private WebView webview ;
    private ProgressBar spinner;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 动态申请摄像头权限
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, 100);
        }

        webview = findViewById(R.id.webView);
        spinner = findViewById(R.id.progressBar1);

        // 配置WebView核心设置
        webview.getSettings().setJavaScriptEnabled(true);
        webview.getSettings().setDomStorageEnabled(true);
        webview.setOverScrollMode(WebView.OVER_SCROLL_NEVER);
        webview.getSettings().setMediaPlaybackRequiresUserGesture(false);
        webview.getSettings().setAllowFileAccess(true);
        webview.getSettings().setAllowContentAccess(true);
        webview.getSettings().setLoadWithOverviewMode(true);
        webview.getSettings().setUseWideViewPort(true);

        // 设置WebViewClient处理页面加载状态
        webview.setWebViewClient(new CustomWebViewClient());
        // 设置WebChromeClient处理媒体权限请求
        webview.setWebChromeClient(new WebChromeClient() {
            @Override
            public void onPermissionRequest(PermissionRequest request) {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                    if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
                        request.grant(request.getResources());
                    }
                }
            }
        });

        webview.loadUrl("url");
    }

    private class CustomWebViewClient extends WebViewClient {
        @Override
        public void onPageStarted(WebView webview, String url, Bitmap favicon) {
            if (ShowOrHideWebViewInitialUse.equals("show")) {
                webview.setVisibility(View.INVISIBLE);
            }
        }

        @Override
        public void onPageFinished(WebView view, String url) {
            ShowOrHideWebViewInitialUse = "hide";
            spinner.setVisibility(View.GONE);
            view.setVisibility(View.VISIBLE);
            super.onPageFinished(view, url);
        }
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (event.getAction() == KeyEvent.ACTION_DOWN) {
            switch (keyCode) {
                case KeyEvent.KEYCODE_BACK:
                    if (webview.canGoBack()) {
                        webview.goBack();
                    } else {
                        finish();
                    }
                    return true;
            }
        }
        return super.onKeyDown(keyCode, event);
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == 100) {
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                webview.reload();
            } else {
                Toast.makeText(this, "需要摄像头权限才能使用该功能", Toast.LENGTH_SHORT).show();
            }
        }
    }
}

四、验证注意事项

  • 一定要用真机测试,模拟器的摄像头模拟经常出问题
  • 如果你的网站是HTTPS协议,确保证书有效;HTTP协议的话,Manifest里的android:usesCleartextTraffic="true"已经配置,无需额外操作
  • Android 13+设备可能会要求通知权限,但摄像头流不需要这个,可以忽略

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

火山引擎 最新活动