Android原生App和WebView的交互方式詳解
一、前言
在移動開發(fā)中,我們有時候會遇到這樣的需求:
- 有一部分功能需要網(wǎng)頁實現(xiàn)(比如登錄頁、主頁,已經(jīng)有網(wǎng)頁端了,不希望在 app 中再寫一遍)
- 另一部分功能需要原生實現(xiàn)(比如硬件訪問、獲取系統(tǒng)權(quán)限、或者一些注重性能的邏輯)
這時候 Hybrid App(原生 + WebView 混合應(yīng)用) 就派上用場了。
本文帶你全面了解 Android 原生 App 和 WebView 的交互方式,并附上實戰(zhàn)示例。
二、交互
WebView 與原生 App 的交互也就兩種:
- 網(wǎng)頁調(diào)用 App 原生方法(JS → Native)
- App 調(diào)用網(wǎng)頁 JS 方法(Native → JS)
雙向通信的典型場景:
| 場景 | 方向 | 示例 |
|---|---|---|
| 網(wǎng)頁點擊按鈕調(diào)用 app 功能 | JS → Native | window.myApp.nativeMethod('a') |
| App 收集設(shè)備信息反饋給網(wǎng)頁 | Native → JS | webView.evaluateJavascript("jsMethod('a', 'b')") |
| 登錄狀態(tài)同步 | 雙向 | 網(wǎng)頁通知 App 用戶登錄了,App 也可以主動查詢網(wǎng)頁是否已登錄 |
2.1 編寫本地 html
寫一個本地的 html 文件 test_login.html,內(nèi)容如下:
<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>
運行效果:

可以看到,頁面內(nèi)容很簡單,一個 title,兩個按鈕。一個用于登入,一個用于登出。
html 中維護(hù)了一個 loginState.isLoggedIn 屬性,表示用戶是否已登錄。
提供了一個 isUserLoggedIn 函數(shù),用于查詢當(dāng)前登錄狀態(tài)。
另外,還有一個 login 和一個 logout 方法,分別用于模擬登入登出,當(dāng)狀態(tài)改變后,通過 window.myApp.onLoginStateChanged 回調(diào)通知 app 登陸狀態(tài)發(fā)生了改變。
2.2 編寫 app
為了便于測試,我們將 test_login.html 文件,放在 assets 文件夾下,app 上的 WebView 直接加載本地 url 即可。
MainActivity 完整代碼:
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)
)
}
}
運行效果:

可以看到,在 MainActivity 中,通過 addJavascriptInterface 函數(shù)添加了 onLoginStateChanged 接口供 Web 端調(diào)用,添加接口時,第二個參數(shù)是 name,Web 端將通過 name.接口名 來調(diào)用對應(yīng)的接口,例如:window.myApp.onLoginStateChanged(true);。
在點擊 Check Login Status 按鈕后,通過 WebView 的 evaluateJavascript 函數(shù)調(diào)用網(wǎng)頁端的 isUserLoggedIn 函數(shù),收到 result 后,更新 loginStatus 變量。
另外,還自定義了 WebViewClient,在 onPageFinished 調(diào)用后,主動調(diào)用一次 isUserLoggedIn 函數(shù),完成 Current Status 的初始化。
三、后話
有一些需要注意的點:
- 調(diào)用 js 方法時,結(jié)果是異步返回的,通過 listener 接收結(jié)果。
@JavascriptInterface的方法在 非 UI 線程 執(zhí)行,如果要更新 UI,需要使用runOnUiThread。- 設(shè)置了
WebView.setWebContentsDebuggingEnabled(true)之后,通過 Chrome DevTools 可直接調(diào)試 WebView。方法是在 app 加載了網(wǎng)頁后,在 Chrome 瀏覽器訪問chrome://inspect/#devices,在這里找到自己的設(shè)備,點擊 inspect。我對這種方式不是很熟悉,就不過多介紹了。

附:一些常見的問題
注:不保真
在 WebView 中,通過 webView.settings 可以獲取到 WebSettings,它可以用來配置一系列網(wǎng)頁渲染與訪問能力。以下是關(guān)鍵屬性解釋:
| 屬性 | 作用 | 是否常用 | 注意事項 |
|---|---|---|---|
javaScriptEnabled = true | 啟用網(wǎng)頁中的 JavaScript 執(zhí)行。沒有這個,網(wǎng)頁的交互和動態(tài)內(nèi)容幾乎全失效。 | ? 必須 | 啟用 JS 后要配合 addJavascriptInterface 謹(jǐn)慎使用,否則存在安全隱患。 |
domStorageEnabled = true | 啟用 HTML5 的 DOM Storage(localStorage / sessionStorage)。網(wǎng)頁才能保存本地狀態(tài)。 | ? 常用 | 現(xiàn)代 Web 必備。 |
databaseEnabled = true | 啟用 Web SQL 數(shù)據(jù)庫(舊標(biāo)準(zhǔn))。 | ?? 較舊 | 新網(wǎng)頁一般用 IndexedDB。 |
allowFileAccess = true | 允許訪問本地文件(file://)。 | ? 常用 | 某些 WebView 資源加載或本地調(diào)試需要。 |
allowContentAccess = true | 允許訪問 content:// URI 內(nèi)容(如系統(tǒng)媒體)。 | ? 常用 | 安全風(fēng)險低。 |
allowFileAccessFromFileURLs = true | 允許網(wǎng)頁 JS 從 file:// 頁面訪問其他本地文件。 | ?? 慎用 | 容易被惡意網(wǎng)頁利用本地文件。 |
allowUniversalAccessFromFileURLs = true | 允許 file:// 頁面訪問任意網(wǎng)絡(luò)資源(http/https)。 | ?? 高風(fēng)險 | 建議僅限調(diào)試環(huán)境啟用。 |
useWideViewPort = true | 啟用自適應(yīng)寬度,讓網(wǎng)頁以「網(wǎng)頁比例」顯示而非手機(jī)分辨率。 | ? 常用 | 與 loadWithOverviewMode 一起使用更佳。 |
loadWithOverviewMode = true | 縮放網(wǎng)頁以適配屏幕寬度。 | ? 常用 | 常配合 responsive 頁面。 |
setSupportZoom(true) | 支持縮放。 | ? 常用 | 可搭配手勢操作。 |
builtInZoomControls = true | 啟用內(nèi)建縮放按鈕。 | ? 可選 | 通常在調(diào)試或舊網(wǎng)頁中啟用。 |
displayZoomControls = false | 隱藏默認(rèn)的縮放控件(僅保留手勢縮放)。 | ? 推薦 | 提升視覺體驗。 |
cacheMode = WebSettings.LOAD_DEFAULT | 啟用緩存策略。 | ? 常用 | 可選 LOAD_NO_CACHE 禁止緩存。 |
WebViewClient 和 WebChromeClient 的區(qū)別:
| 對比項 | WebViewClient | WebChromeClient |
|---|---|---|
| 職責(zé) | 控制頁面導(dǎo)航與加載邏輯 | 控制網(wǎng)頁中“瀏覽器行為”與 UI 事件 |
| 常用回調(diào) | shouldOverrideUrlLoading、onPageStarted、onPageFinished、onReceivedError | onProgressChanged、onReceivedTitle、onConsoleMessage、onJsAlert |
| 場景舉例 | 攔截跳轉(zhuǎn)、處理自定義 URL Scheme、控制加載動畫 | 顯示網(wǎng)頁標(biāo)題、監(jiān)控加載進(jìn)度、攔截 JS 彈窗、打印調(diào)試信息 |
| 比喻 | 瀏覽器“司機(jī)” | 瀏覽器“儀表盤” |
| 建議 | 必須設(shè)置一個(否則無法處理跳轉(zhuǎn)) | 可選(但調(diào)試與交互建議加) |
總結(jié)一句話:
WebViewClient 負(fù)責(zé)“頁面去哪”,WebChromeClient 負(fù)責(zé)“頁面看起來怎樣”。
其他關(guān)鍵配置:
| 配置 | 作用 |
|---|---|
setLayerType(View.LAYER_TYPE_HARDWARE, null) | 啟用硬件加速,提升渲染性能(尤其是視頻或動畫)。 |
setOnLongClickListener { true } + isLongClickable = false | 禁用長按(防止復(fù)制或保存圖片)。 |
WebView.setWebContentsDebuggingEnabled(true) | 允許通過 Chrome 調(diào)試網(wǎng)頁內(nèi)容(chrome://inspect)。 |
CookieManager.getInstance().setAcceptThirdPartyCookies(...) |
以上就是Android原生App和WebView的交互方式詳解的詳細(xì)內(nèi)容,更多關(guān)于Android App和WebView交互的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android獲取系統(tǒng)時間以及網(wǎng)絡(luò)時間
這篇文章主要為大家詳細(xì)介紹了Android獲取系統(tǒng)時間以及網(wǎng)絡(luò)時間的方法,感興趣的小伙伴們可以參考一下2016-07-07
Android實現(xiàn)打開手機(jī)淘寶并自動識別淘寶口令彈出商品信息功能
最近項目經(jīng)理給我們安排一個活兒,基于Android開發(fā)實現(xiàn)打開手機(jī)淘寶,并自動識別淘口令,彈出商品信息,今天小編就抽空給大家分享下這個需求是怎么實現(xiàn)的,需要的朋友參考下吧2017-11-11
Android?Studio實現(xiàn)簡單頁面跳轉(zhuǎn)的詳細(xì)教程
這篇文章主要給大家介紹了關(guān)于Android?Studio實現(xiàn)簡單頁面跳轉(zhuǎn)的詳細(xì)教程,文中通過圖文介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用Android?Studio具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2023-01-01
android實現(xiàn)定位與目的地的導(dǎo)航示例代碼
本篇文章主要介紹了android實現(xiàn)定位與目的地的導(dǎo)航示例代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-02-02

