【参考:】

  1. Android webview与js 交换JSON对象数据
  2. 使用Kotlin:让Android与JS交互的详解
  3. js中eval()的使用说明

简介

使用 WebView 加载网页,有时候需要进行js交互,相互传递数据和响应事件。
android 调用 js 代码:

  1. WebView#loadUrl("javascript:func('" + arg + "')")
  2. WebView#evaluateJavascript(String script, @Nullable ValueCallback<String> resultCallback)

js 调用 android 代码:

  1. 通过 WebView#addJavascriptInterface(Object object, String name) 进行对象映射
  2. 通过 WebViewClient#shouldOverrideUrlLoading() 来拦截Url调用代码
  3. 通过 WebChromeClientonJsAlert()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()的使用说明

注意,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
    }
}

参考:使用Kotlin:让Android与JS交互的详解

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() //接受所有网站的证书
    }
}

使用 WebChromeClientonJsAlert()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 调试。

三种方式的区别

  1. addJavascriptInterface() 使用起来方便简洁,但是在 Android 低版本下有问题,用于Android4.4以上。
  2. shouldOverrideUrlLoading() 使用起来没有漏洞,但是两端定协议,使用起来比较麻烦,主要用于不需要返回值的情况。
  3. onJsAlert()onJsConfirm()onJsPrompt() 拦截 js 中的对话框 alert() / confirm() / prompt() 和第二种方式一样,没有漏洞,而且也复杂,并且需要协议来规定它。