Android WebView暗夜模式适配

Android WebView暗夜模式适配是一项出海应用的基本开发需求,这篇文章主要介绍如何开发适配。

WebView 的行为可与 prefers-color-schemecolor-scheme 网络标准互操作。根据Android官方的资料介绍,WebView的暗夜适配大致可分为使用 prefers-color-scheme 的 Web 内容和不使用 prefers-color-scheme 的网页内容,下面分别讲解如何适配。

一、使用 prefers-color-scheme 的 Web 内容

暗夜模式的开发初期很容易进入一个误区,平时获取APP是否为暗夜模式,一般会通过下面的函数:

kotlin 复制代码
 /** 检测系统是否为暗夜模式 */
    private fun isSystemInDarkMode(context: Context): Boolean {
        val uiMode = context.resources.configuration.uiMode
        return (uiMode and Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES
    }

但使用 prefers-color-scheme 的 Web 内容与主题有关,与APP是否为暗夜模式无关。

WebView 的暗夜模式是依赖系统或应用主题(Theme)状态变化触发的,而不是直接根据 context.resources.configuration.uiMode 判定的。 因为 WebView 是一个「独立进程 + 独立渲染引擎」组件,它的暗色算法是在 WebView 内核侧控制的,而不是依赖宿主 Context 的配置状态。

OK,修改主题,尝试下面的方式修改为暗色主题:

kotlin 复制代码
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)

结果WebView内的内容依然没有变为暗夜模式,原因是:

AppCompatDelegate.setDefaultNightMode() 只影响 AppCompat 的 UI(即 View 层、主题资源加载), 而 WebView 的暗夜模式属于 Web 内核(Chromium)侧的行为, 它不会自动跟随 AppCompat 的模式切换,除非你在 Activity 重新创建后手动通知 WebView。

这条主题设置只能修改AppCompat资源的切换而不能影响WebView内部。真正影响WebView获取isLightTheme主题的是Activity的Theme,下面是一个可以参考的主题:

kotlin 复制代码
//给Activity设置日间主题
Theme.AppCompat.Light.NoActionBar

//给Activity设置夜间主题
Theme.AppCompat.DayNight.NoActionBar

我们可以通过下面的示例来测试:

html 复制代码
<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <meta name="color-scheme" content="light dark">
    <title>Dark Mode Test</title>
    <style>
        :root {
          --bg: #ffffff;
          --text: #111111;
        }
        :root.dark {
          --bg: #121212;
          --text: #eeeeee;
        }
        body {
          margin: 0;
          padding: 20px;
          font-family: sans-serif;
          background-color: var(--bg);
          color: var(--text);
          transition: all 0.3s;
        }
        .status {
          margin-top: 20px;
          padding: 10px;
          border-radius: 8px;
          background: rgba(128,128,128,0.2);
        }
    </style>
</head>
<body>
<h2>🌗 WebView 暗夜模式测试</h2>
<div class="status" id="status">加载中...</div>

<script>
    function applyTheme(isDark) {
      document.documentElement.classList.toggle('dark', !!isDark);
      document.getElementById('status').innerText =
        '当前主题: ' + (isDark ? '🌙 暗夜模式' : '☀️ 日间模式');
    }

    window.onAndroidThemeChanged = function(isDark) {
      applyTheme(isDark);
      console.log('onAndroidThemeChanged', isDark);
    };

    window.addEventListener('androidThemeChanged', function(e){
      applyTheme(e.detail.isDark);
    });

    document.addEventListener('DOMContentLoaded', function() {
      if (typeof window.__ANDROID_DARK_MODE__ !== 'undefined') {
        applyTheme(window.__ANDROID_DARK_MODE__);
      } else if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
        applyTheme(true);
      } else {
        applyTheme(false);
      }
    });
</script>
</body>
</html>

在assets文件夹创建测试文件test.html,放入上面的内容,用webView加载就能测试效果:

kotlin 复制代码
val testUrl = "file:///android_asset/test.html"
//用WebView加载这个Url
loadUrl(testUrl)

二、不使用 prefers-color-scheme 的网页内容

假如用WebView加载掘金的官网:juejin.cn

会发现暗夜模式并没有起作用,这就需要使用其他实现。

需要导入WebKit:

kotlin 复制代码
implementation "androidx.webkit:webkit:1.14.0"

1、允许算法调暗

对于以 Android 13(API 级别 33)或更高版本为目标平台的应用,请使用 AndroidX setAlgorithmicDarkeningAllowed() 方法并传入 true,以指定 WebView 应允许通过算法加深。此方法向后兼容之前的 Android 版本。

kotlin 复制代码
if (WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING)) {
    WebSettingsCompat.setAlgorithmicDarkeningAllowed(settings, true)
}

2、允许算法加深

对于未使用应用级强制深色模式且以 Android 12(API 级别 32)或更低版本为目标平台的应用,请使用 FORCE_DARK_ON 来允许算法调暗。

如果您的应用提供了在浅色和深色主题之间切换的自己的方法(例如界面中的可切换元素或基于时间的自动选择),请将 FORCE_DARK_ON 与 FORCE_DARK_OFF 搭配使用。

kotlin 复制代码
if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) {
    //向下兼容旧版本
    WebSettingsCompat.setForceDark(
        settings,
        if (isDarkMode) WebSettingsCompat.FORCE_DARK_ON
        else WebSettingsCompat.FORCE_DARK_OFF
    )
} 

3、注入暗夜CSS

对于更老的版本则可以注入暗夜css来实现暗夜模式,后面会给出未经完全测试的代码(没足够场景测试)。

三、代码封装

封装一个WebView的暗夜模式辅助类:

kotlin 复制代码
import android.content.Context
import android.content.res.Configuration
import android.webkit.WebSettings
import android.webkit.WebView
import androidx.appcompat.app.AppCompatDelegate
import androidx.webkit.WebSettingsCompat
import androidx.webkit.WebViewFeature

/**
 * WebView 暗夜模式辅助类
 * - 支持强制日间 / 夜间 / 跟随系统
 * - 自动同步 WebView prefers-color-scheme
 * - Android 13+ 自动生效,无需 JS 注入
 * - 旧版 WebView 自动注入暗夜 CSS
 */
object WebViewDarkModeHelper {

    enum class Mode {
        /** 跟随系统 */
        FOLLOW_SYSTEM,

        /** 强制日间 */
        LIGHT,

        /** 强制夜间 */
        DARK
    }

    /**
     * 应用全局主题(AppCompat + WebView)
     */
    fun apply(context: Context, webView: WebView, mode: Mode = Mode.FOLLOW_SYSTEM) {
        val settings = webView.settings
        settings.mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW
        settings.javaScriptEnabled = true

        // 设置全局 AppCompatDelegate 模式
        when (mode) {
            Mode.LIGHT -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
            Mode.DARK -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
            Mode.FOLLOW_SYSTEM -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
        }

        // 当前是否暗色模式
        val isDarkMode = when (mode) {
            Mode.DARK -> true
            Mode.LIGHT -> false
            Mode.FOLLOW_SYSTEM -> {
                val uiMode = context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
                uiMode == Configuration.UI_MODE_NIGHT_YES
            }
        }

        // 优先使用新特性 AlgorithmicDarkening(Android 13+)
        if (WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING)) {
            WebSettingsCompat.setAlgorithmicDarkeningAllowed(settings, true)
        } else if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) {
            //向下兼容旧版本
            WebSettingsCompat.setForceDark(
                settings,
                if (isDarkMode) WebSettingsCompat.FORCE_DARK_ON
                else WebSettingsCompat.FORCE_DARK_OFF
            )
        } else {
            //最老的 WebView,不支持暗化:注入 CSS
            injectDarkModeCSS(webView, isDarkMode)
        }
    }

    /**
     * 兼容旧 WebView 注入暗夜 CSS
     */
    private fun injectDarkModeCSS(webView: WebView, enable: Boolean) {
        if (!enable) {
            val removeJs = """
                var existingStyle = document.getElementById('dark-mode-style');
                if (existingStyle) existingStyle.remove();
            """.trimIndent()
            webView.evaluateJavascript(removeJs, null)
            return
        }

        val css = """
            html, body {
                background-color: #121212 !important;
                color: #E0E0E0 !important;
            }
            a { color: #BB86FC !important; }
            img, video { filter: brightness(0.8) contrast(1.1); }
        """.trimIndent()

        val js = """
            var style = document.createElement('style');
            style.id = 'dark-mode-style';
            style.type = 'text/css';
            style.appendChild(document.createTextNode(`$css`));
            document.head.appendChild(style);
        """.trimIndent()

        webView.evaluateJavascript(js, null)
    }

    /**
     * 当系统主题变化时调用(例如配置变化).
     */
    fun onSystemThemeChanged(context: Context, webView: WebView) {
        apply(context, webView, Mode.FOLLOW_SYSTEM)
    }
}

使用:

kotlin 复制代码
//跟随系统
WebViewDarkModeHelper.apply(this, webView, WebViewDarkModeHelper.Mode.FOLLOW_SYSTEM)

//强制暗夜
WebViewDarkModeHelper.apply(this, webView, WebViewDarkModeHelper.Mode.DARK)

//强制日间
WebViewDarkModeHelper.apply(this, webView, WebViewDarkModeHelper.Mode.LIGHT)

//监听系统变化
override fun onConfigurationChanged(newConfig: Configuration) {
    super.onConfigurationChanged(newConfig)
    WebViewDarkModeHelper.onSystemThemeChanged(this, webView)
}

Android官方文档-在 WebView 中调暗 Web 内容

部分内容借助AI结论。

相关推荐
frontend_frank3 小时前
脱离 Electron autoUpdater:uni-app跨端更新:Windows+Android统一实现方案
android·前端·javascript·electron·uni-app
薛晓刚4 小时前
MySQL的replace使用分析
android·adb
DengDongQi4 小时前
Jetpack Compose 滚轮选择器
android
stevenzqzq4 小时前
Android Studio Logcat 基础认知
android·ide·android studio·日志
代码不停4 小时前
MySQL事务
android·数据库·mysql
朝花不迟暮4 小时前
使用Android Studio生成apk,卡在Running Gradle task ‘assembleDebug...解决方法
android·ide·android studio
yngsqq5 小时前
使用VS(.NET MAUI)开发第一个安卓APP
android·.net
Android-Flutter5 小时前
android compose LazyVerticalGrid上下滚动的网格布局 使用
android·kotlin
Android-Flutter5 小时前
android compose LazyHorizontalGrid水平滚动的网格布局 使用
android·kotlin
千里马-horse5 小时前
RK3399E Android 11 将自己的库放到系统库方法
android·so·设置系统库