秦清泉 發表於 2025-11-12 08:53:56

Android原生App和WebView的交互方式详解

<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li>一、前言</li><li>二、交互</li><ul class="second_class_ul"><li>2.1 编写本地 html</li><li>2.2 编写 app</li></ul><li>三、后话</li><ul class="second_class_ul"></ul><li>附:一些常见的问题</li><ul class="second_class_ul"></ul></ul></div><p class="maodian"></p><h2>一、前言</h2>
<p>在移动开发中,我们有时候会遇到这样的需求:</p>
<ul><li>有一部分功能需要网页实现(比如登录页、主页,已经有网页端了,不希望在 app 中再写一遍)</li><li>另一部分功能需要原生实现(比如硬件访问、获取系统权限、或者一些注重性能的逻辑)</li></ul>
<p>这时候 <strong>Hybrid App(原生 + WebView 混合应用)</strong> 就派上用场了。</p>
<p>本文带你全面了解 <strong>Android 原生 App 和 WebView 的交互方式</strong>,并附上实战示例。</p>
<p class="maodian"></p><h2>二、交互</h2>
<p>WebView 与原生 App 的交互也就两种:</p>
<ol><li><strong>网页调用 App 原生方法</strong>(JS &rarr; Native)</li><li><strong>App 调用网页 JS 方法</strong>(Native &rarr; JS)</li></ol>
<p>双向通信的典型场景:</p>
<table><thead><tr><th>场景</th><th>方向</th><th>示例</th></tr></thead><tbody><tr><td>网页点击按钮调用 app 功能</td><td>JS &rarr; Native</td><td><code>window.myApp.nativeMethod(&#39;a&#39;)</code></td></tr><tr><td>App 收集设备信息反馈给网页</td><td>Native &rarr; JS</td><td><code>webView.evaluateJavascript(&quot;jsMethod(&#39;a&#39;, &#39;b&#39;)&quot;)</code></td></tr><tr><td>登录状态同步</td><td>双向</td><td>网页通知 App 用户登录了,App 也可以主动查询网页是否已登录</td></tr></tbody></table>
<p class="maodian"></p><h3>2.1 编写本地 html</h3>
<p>写一个本地的 html 文件 test_login.html,内容如下:</p>
<div class="jb51code"><pre class="brush:xhtml;">&lt;html&gt;
&lt;head&gt;&lt;meta charset="utf-8"&gt;&lt;title&gt;Login Demo&lt;/title&gt;&lt;/head&gt;
&lt;body&gt;
&lt;h2&gt;Hybrid Login Demo&lt;/h2&gt;
&lt;button onclick="login()"&gt;Login&lt;/button&gt;
&lt;button onclick="logout()"&gt;Logout&lt;/button&gt;

&lt;script&gt;
    window.loginState = { isLoggedIn: false };

    window.isUserLoggedIn = function() {
      console.log("isUserLoggedIn = " + window.loginState.isLoggedIn);
      return window.loginState.isLoggedIn;
    }

    function login() {
      window.loginState.isLoggedIn = true;
      console.log("Login success!");
      if (window.myApp &amp;&amp; window.myApp.onLoginStateChanged) {
      window.myApp.onLoginStateChanged(true);
      }
    }

    function logout() {
      window.loginState.isLoggedIn = false;
      console.log("Logout success!");
      if (window.myApp &amp;&amp; window.myApp.onLoginStateChanged) {
      window.myApp.onLoginStateChanged(false);
      }
    }
&lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;
</pre></div>
<p>运行效果:</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202511/2025111208403790.jpg" /></p>
<p>可以看到,页面内容很简单,一个 title,两个按钮。一个用于登入,一个用于登出。</p>
<p>html 中维护了一个 loginState.isLoggedIn 属性,表示用户是否已登录。</p>
<p>提供了一个 isUserLoggedIn 函数,用于查询当前登录状态。</p>
<p>另外,还有一个 login 和一个 logout 方法,分别用于模拟登入登出,当状态改变后,通过 <code>window.myApp.onLoginStateChanged</code> 回调通知 app 登陆状态发生了改变。</p>
<p class="maodian"></p><h3>2.2 编写 app</h3>
<p>为了便于测试,我们将 test_login.html 文件,放在 assets 文件夹下,app 上的 WebView 直接加载本地 url 即可。</p>
<p>MainActivity 完整代码:</p>
<div class="jb51code"><pre class="brush:java;">package com.example.interaction

import android.os.Bundle
import android.webkit.CookieManager
import android.webkit.WebChromeClient
import android.webkit.WebSettings
import android.webkit.WebView
import android.webkit.WebViewClient
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Button
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import com.example.interaction.ui.theme.WebViewJsInteractionDemoTheme

class MainActivity : ComponentActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
      super.onCreate(savedInstanceState)
      enableEdgeToEdge()
      setContent {
            WebViewJsInteractionDemoTheme {
                Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -&gt;
                  LoginWebView(modifier = Modifier.padding(innerPadding))
                }
            }
      }
    }
}

@Composable
fun LoginWebView(modifier: Modifier = Modifier) {
    var loginStatus by remember { mutableStateOf("Unknown") }
    val context = LocalContext.current
    val webViewRef = remember { mutableStateOf&lt;WebView?&gt;(null) }

    Column(modifier = modifier.fillMaxSize()) {
      AndroidView(
            modifier = Modifier
                .weight(1f)
                .fillMaxWidth(),
            factory = { context -&gt;
                WebView(context).apply {
                  settings.apply {
                        javaScriptEnabled = true
                        domStorageEnabled = true
                        allowFileAccess = true
                        allowContentAccess = true
                        cacheMode = WebSettings.LOAD_DEFAULT
                  }
                  webChromeClient = WebChromeClient()

                  webViewClient = object : WebViewClient() {
                        override fun onPageFinished(view: WebView?, url: String?) {
                            super.onPageFinished(view, url)
                            // Query login status when page is loaded
                            evaluateJavascript("isUserLoggedIn()") { result -&gt;
                              val isLoggedIn = result?.contains("true") == true
                              loginStatus = if (isLoggedIn) "Logged In" else "Logged Out"
                            }
                        }
                  }

                  // Register the JavaScript interface
                  addJavascriptInterface(object {
                        @android.webkit.JavascriptInterface
                        fun onLoginStateChanged(isLoggedIn: Boolean) {
                            (context as ComponentActivity).runOnUiThread {
                              loginStatus = if (isLoggedIn) "Logged In" else "Logged Out"
                              Toast.makeText(context, "Login status changed: $loginStatus", Toast.LENGTH_SHORT).show()
                            }
                        }
                  }, "myApp")

                  WebView.setWebContentsDebuggingEnabled(true)
                  CookieManager.getInstance().setAcceptCookie(true)

                  loadUrl("file:///android_asset/test_login.html")
                  webViewRef.value = this
                }
            }
      )

      Spacer(modifier = Modifier.height(16.dp))

      // Check login status button
      Button(
            onClick = {
                webViewRef.value?.evaluateJavascript("isUserLoggedIn()") { result -&gt;
                  val isLoggedIn = result?.contains("true") == true
                  loginStatus = if (isLoggedIn) "Logged In" else "Logged Out"
                  Toast.makeText(context, "Login status: $loginStatus", Toast.LENGTH_SHORT).show()
                }
            },
            modifier = Modifier
                .fillMaxWidth()
                .padding(horizontal = 16.dp)
      ) {
            Text("Check Login Status")
      }

      Spacer(modifier = Modifier.height(8.dp))

      Text(
            text = "Current Status: $loginStatus",
            modifier = Modifier.padding(horizontal = 16.dp)
      )
    }
}
</pre></div>
<p>运行效果:</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202511/2025111208403763.jpg" /></p>
<p>可以看到,在 MainActivity 中,通过 addJavascriptInterface 函数添加了 onLoginStateChanged 接口供 Web 端调用,添加接口时,第二个参数是 name,Web 端将通过 <code>name.接口名</code> 来调用对应的接口,例如:<code>window.myApp.onLoginStateChanged(true);</code>。</p>
<p>在点击 Check Login Status 按钮后,通过 WebView 的 evaluateJavascript 函数调用网页端的 isUserLoggedIn 函数,收到 result 后,更新 loginStatus 变量。</p>
<p>另外,还自定义了 WebViewClient,在 onPageFinished 调用后,主动调用一次 isUserLoggedIn 函数,完成 Current Status 的初始化。</p>
<p class="maodian"></p><h2>三、后话</h2>
<p>有一些需要注意的点:</p>
<ul><li>调用 js 方法时,结果是异步返回的,通过 listener 接收结果。</li><li><code>@JavascriptInterface</code> 的方法在 <strong>非 UI 线程</strong> 执行,如果要更新 UI,需要使用 <code>runOnUiThread</code>。</li><li>设置了 <code>WebView.setWebContentsDebuggingEnabled(true)</code> 之后,通过 Chrome DevTools 可直接调试 WebView。方法是在 app 加载了网页后,在 Chrome 浏览器访问 <code>chrome://inspect/#devices</code>,在这里找到自己的设备,点击 inspect。我对这种方式不是很熟悉,就不过多介绍了。</li></ul>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202511/2025111208403798.jpg" /></p>
<p class="maodian"></p><h2>附:一些常见的问题</h2>
<blockquote><p>注:不保真</p></blockquote>
<p>在 WebView 中,通过 webView.settings 可以获取到 WebSettings,它可以用来配置一系列网页渲染与访问能力。以下是关键属性解释:</p>
<table><thead><tr><th>属性</th><th>作用</th><th>是否常用</th><th>注意事项</th></tr></thead><tbody><tr><td><code>javaScriptEnabled = true</code></td><td>启用网页中的 JavaScript 执行。没有这个,网页的交互和动态内容几乎全失效。</td><td>✅ 必须</td><td>启用 JS 后要配合 <code>addJavascriptInterface</code> 谨慎使用,否则存在安全隐患。</td></tr><tr><td><code>domStorageEnabled = true</code></td><td>启用 HTML5 的 DOM Storage(<code>localStorage</code> / <code>sessionStorage</code>)。网页才能保存本地状态。</td><td>✅ 常用</td><td>现代 Web 必备。</td></tr><tr><td><code>databaseEnabled = true</code></td><td>启用 Web SQL 数据库(旧标准)。</td><td>⚠️ 较旧</td><td>新网页一般用 IndexedDB。</td></tr><tr><td><code>allowFileAccess = true</code></td><td>允许访问本地文件(<code>file://</code>)。</td><td>✅ 常用</td><td>某些 WebView 资源加载或本地调试需要。</td></tr><tr><td><code>allowContentAccess = true</code></td><td>允许访问 <code>content://</code> URI 内容(如系统媒体)。</td><td>✅ 常用</td><td>安全风险低。</td></tr><tr><td><code>allowFileAccessFromFileURLs = true</code></td><td>允许网页 JS 从 file:// 页面访问其他本地文件。</td><td>⚠️ 慎用</td><td>容易被恶意网页利用本地文件。</td></tr><tr><td><code>allowUniversalAccessFromFileURLs = true</code></td><td>允许 file:// 页面访问任意网络资源(http/https)。</td><td>⚠️ 高风险</td><td>建议仅限调试环境启用。</td></tr><tr><td><code>useWideViewPort = true</code></td><td>启用自适应宽度,让网页以「网页比例」显示而非手机分辨率。</td><td>✅ 常用</td><td>与 <code>loadWithOverviewMode</code> 一起使用更佳。</td></tr><tr><td><code>loadWithOverviewMode = true</code></td><td>缩放网页以适配屏幕宽度。</td><td>✅ 常用</td><td>常配合 responsive 页面。</td></tr><tr><td><code>setSupportZoom(true)</code></td><td>支持缩放。</td><td>✅ 常用</td><td>可搭配手势操作。</td></tr><tr><td><code>builtInZoomControls = true</code></td><td>启用内建缩放按钮。</td><td>✅ 可选</td><td>通常在调试或旧网页中启用。</td></tr><tr><td><code>displayZoomControls = false</code></td><td>隐藏默认的缩放控件(仅保留手势缩放)。</td><td>✅ 推荐</td><td>提升视觉体验。</td></tr><tr><td><code>cacheMode = WebSettings.LOAD_DEFAULT</code></td><td>启用缓存策略。</td><td>✅ 常用</td><td>可选 <code>LOAD_NO_CACHE</code> 禁止缓存。</td></tr></tbody></table>
<p>WebViewClient 和 WebChromeClient 的区别:</p>
<table><thead><tr><th>对比项</th><th>WebViewClient</th><th>WebChromeClient</th></tr></thead><tbody><tr><td>职责</td><td>控制页面导航与加载逻辑</td><td>控制网页中&ldquo;浏览器行为&rdquo;与 UI 事件</td></tr><tr><td>常用回调</td><td><code>shouldOverrideUrlLoading</code>、<code>onPageStarted</code>、<code>onPageFinished</code>、<code>onReceivedError</code></td><td><code>onProgressChanged</code>、<code>onReceivedTitle</code>、<code>onConsoleMessage</code>、<code>onJsAlert</code></td></tr><tr><td>场景举例</td><td>拦截跳转、处理自定义 URL Scheme、控制加载动画</td><td>显示网页标题、监控加载进度、拦截 JS 弹窗、打印调试信息</td></tr><tr><td>比喻</td><td>浏览器&ldquo;司机&rdquo;</td><td>浏览器&ldquo;仪表盘&rdquo;</td></tr><tr><td>建议</td><td>必须设置一个(否则无法处理跳转)</td><td>可选(但调试与交互建议加)</td></tr></tbody></table>
<p><strong>总结一句话</strong>:</p>
<blockquote><p>WebViewClient 负责&ldquo;页面去哪&rdquo;,WebChromeClient 负责&ldquo;页面看起来怎样&rdquo;。</p></blockquote>
<p>其他关键配置:</p>
<table><thead><tr><th>配置</th><th>作用</th></tr></thead><tbody><tr><td><code>setLayerType(View.LAYER_TYPE_HARDWARE, null)</code></td><td>启用硬件加速,提升渲染性能(尤其是视频或动画)。</td></tr><tr><td><code>setOnLongClickListener { true }</code> + <code>isLongClickable = false</code></td><td>禁用长按(防止复制或保存图片)。</td></tr><tr><td><code>WebView.setWebContentsDebuggingEnabled(true)</code></td><td>允许通过 Chrome 调试网页内容(chrome://inspect)。</td></tr><tr><td><code>CookieManager.getInstance().setAcceptThirdPartyCookies(...)</code></td><td></td></tr></tbody></table>
<p>以上就是Android原生App和WebView的交互方式详解的详细内容,更多关于Android App和WebView交互的资料请关注琼殿技术社区其它相关文章!</p>
                           
                            <div class="art_xg">
                              <b>您可能感兴趣的文章:</b><ul><li>Android开发使用WebView打造web&nbsp;app示例代码</li><li>Android用webView包装WebAPP方法</li><li>Android APP之WebView校验SSL证书的方法</li></ul>
                            </div>

                        </div>
                        <!--endmain-->
頁: [1]
查看完整版本: Android原生App和WebView的交互方式详解