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 → Native)</li><li><strong>App 调用网页 JS 方法</strong>(Native → JS)</li></ol>
<p>双向通信的典型场景:</p>
<table><thead><tr><th>场景</th><th>方向</th><th>示例</th></tr></thead><tbody><tr><td>网页点击按钮调用 app 功能</td><td>JS → Native</td><td><code>window.myApp.nativeMethod('a')</code></td></tr><tr><td>App 收集设备信息反馈给网页</td><td>Native → JS</td><td><code>webView.evaluateJavascript("jsMethod('a', 'b')")</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;"><html>
<head><meta charset="utf-8"><title>Login Demo</title></head>
<body>
<h2>Hybrid Login Demo</h2>
<button onclick="login()">Login</button>
<button onclick="logout()">Logout</button>
<script>
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 && window.myApp.onLoginStateChanged) {
window.myApp.onLoginStateChanged(true);
}
}
function logout() {
window.loginState.isLoggedIn = false;
console.log("Logout success!");
if (window.myApp && window.myApp.onLoginStateChanged) {
window.myApp.onLoginStateChanged(false);
}
}
</script>
</body>
</html>
</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 ->
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<WebView?>(null) }
Column(modifier = modifier.fillMaxSize()) {
AndroidView(
modifier = Modifier
.weight(1f)
.fillMaxWidth(),
factory = { context ->
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 ->
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 ->
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>控制网页中“浏览器行为”与 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>浏览器“司机”</td><td>浏览器“仪表盘”</td></tr><tr><td>建议</td><td>必须设置一个(否则无法处理跳转)</td><td>可选(但调试与交互建议加)</td></tr></tbody></table>
<p><strong>总结一句话</strong>:</p>
<blockquote><p>WebViewClient 负责“页面去哪”,WebChromeClient 负责“页面看起来怎样”。</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 app示例代码</li><li>Android用webView包装WebAPP方法</li><li>Android APP之WebView校验SSL证书的方法</li></ul>
</div>
</div>
<!--endmain-->
頁:
[1]