WebView与js交互总结
简介
使用 WebView 加载网页,有时候需要进行js交互,相互传递数据和响应事件。
android 调用 js 代码:
- WebView#loadUrl("javascript:func('" + arg + "')")
- WebView#evaluateJavascript(String script, @Nullable ValueCallback<String> resultCallback)
js 调用 android 代码:
- 通过 WebView#addJavascriptInterface(Object object, String name) 进行对象映射
- 通过 WebViewClient#shouldOverrideUrlLoading() 来拦截Url调用代码
- 通过 WebChromeClient 的 onJsAlert()、onJsConfirm()、
onJsPrompt() 拦截 js 中的对话框 alert() / confirm() / prompt()
Android(Kotlin)调用 JS
Android 通过 loadUrl 调用 js 代码
基础设置
var settings = webView.settings
// 支持js交互
settings.javaScriptEnabled = true
// 允许js弹窗
settings.javaScriptCanOpenWindowsAutomatically = true
// 使用dom存储,如果加载的是本地 assets 的 HTML,最好设为 false,否则可能会出现第一次加载空白
settings.domStorageEnabled = true
settings.cacheMode = WebSettings.LOAD_NO_CACHE
settings.allowFileAccess = true
settings.useWideViewPort = true
settings.setSupportZoom(false) // 支持缩放
settings.defaultTextEncodingName = "utf-8"
settings.loadWithOverviewMode = true
settings.setNeedInitialFocus(false)
settings.userAgentString = settings.userAgentString + "; Android"
/*解决图片不显示*/
settings.blockNetworkImage = false //解决图片不显示
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
//允许混合(http,https)
settings.mixedContentMode = WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE
}
Android 调用 js 方法
// 不带参数
webView.loadUrl("javascript:postShareParams()")
// 带参数,常用参数 字符串(String),JSONArray 字符串(.toString()),JSONObject字符串(.toString())
var data:JSONArray = ...
// 要把字符串里的单引号转义一下(如果没转义的话)
var json: String = data.toString().replace("'", "\\\'")
// 防止这里成对的单引号,导致报错
webView.loadUrl("javascript:setTreeData('${json}')")
// 也可以是一段 js 代码,下面的代码即是暂停页面中的视频播放
webView.loadUrl("javascript:(function() { var videos = document.getElementsByTagName('video'); for(var i=0;i<videos.length;i++){videos[i].pause();}})()")
// 改变 span 的文字
weView.loadUrl("javascript:$('#broadcast').find('span').text('暂停')")
// 隐藏视图
webView.loadUrl("javascript:$('#info_delete').hide()")
如果调用 js 方法有传参,参数是一个 JSON 字符串( JSONArray 字符串(.toString())或 JSONObject字符串(.toString())),js 端需要使用 eval('(' + result + ')') 或者 "JSON.parse(result)" 将这个JSON 字符串解析为对象。
function setTreeData(result) {
alert(result);
var data = eval('(' + result + ')');
//var data = JSON.parse(result);
//alert(JSON.stringify(data));
// reloadChart(data)
}
使用 eval() 解析 json,容错能力较好,这种情况 [{}, {}, ] 依然能正常解析,而 JSON.parse() 则报错。
eval 的用法可参考:js中eval()的使用说明:https://www.cnblogs.com/firstlady/p/11347382.html
注意,android端不能直接传对象(会被转成地址,或对象重写的toString())给 js,但是可以通过上述方法将 JSON 字符串转成对象;android 端也不能直接接收对象,但是js可以通过JSON.stringify(data) 将对象转成一个 JSON 字符串再传给 android。
Android 通过 evaluateJavascript() 调用JS代码
先来说说使用这个方法的优点:
使用这个方法不会刷新页面,如果使用第一种方法则会刷新页面
*注意:这个方法只能在Android4.4之后使用
使用方式:
1 .将minSdkVersion最低版本改为19 (build.gradle----minSdkVersion)
2. 直接替 loadUrl 方式
androidWeb.evaluateJavascript("javascript:clickJS()",object : ValueCallback<String>{
override fun onReceiveValue(value: String?) {
// 这里返回JS的结果
}
})
两种方式的区别:
- loadUrl()
- 使用起来方便简洁。
- 但是他是在没有返回的情况下使用。
- 效率比较低,获取返回值的时候很麻烦。
- 并且调用的时候会刷新WebView
- evaluateJavascript()
- 效率比loadUrl ()高很多
- 虽然效率高但是只支持Android4.4以上
- 在获取返回值时候很方便
- 调用时候不刷新WebView
根据情况使用两种方式,我们可以根据当前项目开发的需求选择相应的使用方式,我们可以直接判断版本号来区分使用方式:
if (Build.VERSION.SDK_INT< 18) {
android_web.loadUrl("javascript:clickJS()")
} else {
android_web.evaluateJavascript("javascript:clickJS()"), {
//返回JS方法中的返回值,我们没有写返回值所以为null
}
}
js 调用 Android(Kotlin)
使用WebView的addJavascriptInterface()进行对象映射
webView.addJavascriptInterface(AndroidApi(), "AndroidApi")
inner class AndroidApi {
// 有返回值
@JavascriptInterface
fun getAppParams(): String {
var params = JSONObject()
Utils.Clipboard.copy(mParams.token)
return params.toString()
}
// 带参数
@JavascriptInterface
fun jumpDetail(data: String?) {
// { code: code, url: data, abstract: abstract }
var dataStr = StringUtils.getNonNullStr(data, "{}")
var type = object : TypeToken<LinkedHashMap<String, String>>() {}.type
var dataMap = Gson().fromJson<LinkedHashMap<String, String?>>(dataStr, type)
// ...
}
}
注意:android 端也不能直接接收对象,但是js可以通过JSON.stringify(data) 将对象转成一个 JSON 字符串再传给 android。
使用 WebViewClient() 的 shouldOverrideUrlLoading() 方法拦截Url调用 Android 代码、
<script type="text/javascript">
function clickAndroid(){
//定义url协议
document.location = "js://webview?name=zhangsan&age=20&sex=0"
}
</script>
webView.webViewClient = object : WebViewClient() {
override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
super.onPageStarted(view, url, favicon)
}
override fun onPageFinished(view: WebView?, url: String?) {
super.onPageFinished(view, url)
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean {
val url = request?.url.toString()
return shouldOverrideUrlLoading(view, url)
}
override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean {
// 获取Uri 这里的URL是我们在JS方法中写的URL协议"js://webview?name=zhangsan&age=20&sex=0"
val uri = Uri.parse(url)
if (uri.scheme == "js") {
if (uri.authority == "webview") {
val makeText = Toast.makeText(this@MainActivity, url, Toast.LENGTH_LONG)
makeText.setGravity(Gravity.CENTER, 0, 0)
makeText.show()
}
return true
}
view?.loadUrl(url)
return true
}
override fun onReceivedSslError(view: WebView?, handler: SslErrorHandler, error: SslError) {
handler.proceed() //接受所有网站的证书
}
}
使用 WebChromeClient 的 onJsAlert()、onJsConfirm() 、onJsPrompt() 拦截JS中的对话框alert() / confirm() / prompt()
js 代码,注意代码里的 alert(result)
function setTreeData(result) {
alert(result);
var data = eval('(' + result + ')');
//var data = JSON.parse(result);
//alert(JSON.stringify(data));
// reloadChart(data)
}
Kotlin 代码:
webView.webChromeClient = object : WebChromeClient() {
override fun onProgressChanged(view: WebView, newProgress: Int) {
// 进度变化回调,可用于设置进度条进度
}
override fun onReceivedTitle(view: WebView, title: String) {}
override fun onJsAlert(view: WebView?, url: String?, message: String?, result: JsResult?): Boolean {
Utils.Log.i("onJsAlert url=$url; message=$message")
Toast.makeText(this@EventAnalysisActivity, message, Toast.LENGTH_LONG).show()
result!!.confirm()
return super.onJsAlert(view, url, message, result)
}
override fun onJsConfirm(view: WebView?, url: String?, message: String?, result: JsResult?): Boolean {
return super.onJsConfirm(view, url, message, result)
}
override fun onJsPrompt(view: WebView?, url: String?, message: String?, defaultValue: String?, result: JsPromptResult?): Boolean {
return super.onJsPrompt(view, url, message, defaultValue, result)
}
}
这种方式页可以用作 js 的 debug 调试。
三种方式的区别
- addJavascriptInterface() 使用起来方便简洁,但是在 Android 低版本下有问题,用于Android4.4以上。
- shouldOverrideUrlLoading() 使用起来没有漏洞,但是两端定协议,使用起来比较麻烦,主要用于不需要返回值的情况。
- onJsAlert()、onJsConfirm()、onJsPrompt() 拦截 js 中的对话框 alert() / confirm() / prompt() 和第二种方式一样,没有漏洞,而且也复杂,并且需要协议来规定它。