WebView是一个基于WebKit引擎、展现Web页面的控件,Android的WebView在低版本和高版本采用了不同的WebKit版本内核
WebView的最简单的使用方式即是直接显示网页内容,首先别忘了添加网络权限
<uses-permission android:name="android.permission.INTERNET" />
<WebView
android:id="@+id/webview"
android:layout_width="match_parent"
android:layout_height="match_parent" />
webview.loadUrl("http://www.baidu.com")
webview.webViewClient = object : WebViewClient() {
override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean {
//在本WebView中直接显示网页
view.loadUrl(url)
return true
}
}
状态
webview.onResume() //激活WebView为活跃状态,能正常执行网页的响应
webview.onPause() //当页面被失去焦点被切换到后台不可见状态,执行
webview.pauseTimers() //当应用程序(存在webview)被切换到后台时,这个方法针对的是全局的webview,它会暂停所有webview的布局显示、解析、延时,从而降低CPU功耗
webview.resumeTimers() //恢复pauseTimers状态
//销毁webview,先从父容器移除,再销毁
father.removeView(webview)
webview.destroy()
前进或后退网页
//是否可以后退
webview.canGoBack()
//后退网页
webview.goBack()
//是否可以前进
webview.canGoForward()
//前进网页
webview.goForward()
//以当前的index为起始点前进或者后退到历史记录中指定的steps
//如果steps为负数则为后退,正数则为前进
webview.goBackOrForward(steps)
常见用法:在Activity中处理该Back键事件
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
if ((keyCode == KEYCODE_BACK) && webview.canGoBack()) {
webview.goBack()
return true
}
return super.onKeyDown(keyCode, event)
}
清除缓存
//由于内核缓存是全局的因此这个方法不仅仅针对webview而是针对整个应用程序.
webview.clearCache(true)
//清除当前webview访问的历史记录
webview.clearHistory()
//这个api仅仅清除自动完成填充的表单数据,并不会清除WebView存储到本地的数据
webview.clearFormData()
WebSettings类
作用:对WebView进行配置和管理
下面是一些常规设置
val webSettings: WebSettings = webview.settings
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) { //解决网页中图片显示不出问题
webSettings.mixedContentMode = WebSettings.LOAD_DEFAULT
}
webSettings.javaScriptEnabled = true //支持JS
webSettings.domStorageEnabled = true //支持DOM Storage
webSettings.defaultTextEncodingName = "utf-8" //设置编码格式
webSettings.pluginState = WebSettings.PluginState.ON// 支持插件
webSettings.loadsImagesAutomatically = true //支持自动加载图片
webSettings.setSupportZoom(true) //支持缩放,默认为true。是下面那个的前提。
webSettings.builtInZoomControls = true //设置内置的缩放控件。若为false,则该WebView不可缩放
webSettings.displayZoomControls = false //隐藏原生的缩放控件
webSettings.databaseEnabled = true // 数据库存储API是否可用
webSettings.layoutAlgorithm = WebSettings.LayoutAlgorithm.SINGLE_COLUMN //WebView底层布局算法
webSettings.cacheMode = WebSettings.LOAD_CACHE_ELSE_NETWORK //设置缓存,只要本地有,无论是否过期,或者no-cache,都使用缓存中的数据。本地没有缓存时才从网络上获取
webSettings.allowFileAccess = true //设置可以访问文件
webSettings.javaScriptCanOpenWindowsAutomatically = true //支持通过JS打开新窗口
//设置自适应屏幕,两者合用
webSettings.useWideViewPort = true //将图片调整到适合WebView的大小
webSettings.loadWithOverviewMode = true // 缩放至屏幕的大小
WebViewClient类
作用:处理各种通知、请求事件
(1)shouldOverrideUrlLoading()
作用:打开网页时,不调用系统浏览器进行打开,而是在本WebView中直接显示
(2)onPageStarted()
作用:开始载入页面时调用此方法,在这里我们可以设定一个loading的页面,告诉用户程序正在等待网络响应
(3)onPageFinished()
作用:在页面加载结束时调用,我们可以关闭loading 条,切换程序动作
(4)onLoadResource()
作用:在加载页面资源时会调用,每一个资源(比如图片)的加载都会调用一次
(5)onReceivedSslError()
作用:处理https请求,WebView默认是不处理https请求的,页面显示空白
webview.webViewClient = object : WebViewClient() {
override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean {
//使用WebView加载显示url
view.loadUrl(url)
return true
}
override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
//设定加载开始的操作
}
override fun onPageFinished(view: WebView?, url: String?) {
//设定加载结束的操作
}
override fun onLoadResource(view: WebView?, url: String?) {
//设定加载资源的操作
}
override fun onReceivedError(
view: WebView?,
errorCode: Int,
description: String?,
failingUrl: String?
) {
when (errorCode) {
HttpStatus.NOT_FOUND -> view!!.loadUrl("file:///android_assets/error_handle.html")
}
}
override fun onReceivedSslError(
view: WebView?,
handler: SslErrorHandler?,
error: SslError?
) {
// 接受所有网站的证书,忽略SSL错误,执行访问网页
handler?.proceed()
// handler?.cancel() //表示挂起连接,为默认方式
// handler?.handleMessage(msg!!) //可做其他处理
}
}
WebChromeClient类
作用:辅助 WebView 处理 Javascript 的对话框,网站图标,网站标题等等
(1)onProgressChanged()
作用:获得网页的加载进度并显示
(2)onReceivedTitle()
作用:获取Web页中的标题
webview.webChromeClient = object : WebChromeClient() {
override fun onProgressChanged(view: WebView?, newProgress: Int) {
if (newProgress < 100)
progressbar.text = "${newProgress}%"
else
progressbar.text = "100%"
}
override fun onReceivedTitle(view: WebView?, title: String?) {
title_text.text = title
}
}
Android通过WebView调用 JS 代码
注意:JS代码调用一定要在 onPageFinished() 回调之后才能调用,否则不会调用
(1)通过WebView的loadUrl()
// 先载入JS代码
webview.loadUrl(url)
// 调用javascript的callJS()方法,注意调用的JS方法名要对应上
webview.loadUrl("javascript:callJS()")
(2)通过WebView的evaluateJavascript()
该方法比第一种方法效率更高、使用更简洁。因为该方法的执行不会使页面刷新,而第一种方法(loadUrl)会,该方法在Android 4.4 后才可使用,不过现在的安卓版本都在4.4以上了,所以这点不用在意了。如果需要返回值的话,请务必使用这个方法
webview.evaluateJavascript("javascript:callJS()", object : ValueCallback<String> {
override fun onReceiveValue(p0: String?) {
//此处为 js 返回的结果
}
})
JS通过WebView调用 Android 代码
(1)通过WebView的addJavascriptInterface()进行对象映射
//定义一个内部类
inner class AndroidJSInterFace{
// 定义JS需要调用的方法,被JS调用的方法必须加入@JavascriptInterface注解
@JavascriptInterface
fun hello(msg: String?) {
runOnUiThread{
title_text.text=msg
}
}
}
//AndroidJSInterFace类对象映射到js的test对象
webview.addJavascriptInterface(AndroidJSInterFace(), "test")
(2)在Android通过WebViewClient复写shouldOverrideUrlLoading()
webview.webViewClient = object : WebViewClient() {
override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean {
// 根据协议的参数,判断是否是所需要的url
// 一般根据scheme(协议格式) & authority(协议名)判断(前两个参数)
//假如约定好的传入进来的url = "js://webview?arg1=111&arg2=222"
val uri = Uri.parse(url)
if (uri.scheme == "js" && uri.authority == "webview") {
// 执行JS所需要调用的逻辑
Log.d("TestLog", "js调用了Android的方法")
// 也可以在协议上带有参数并传递到Android上
val collection = uri.queryParameterNames
}
return true
}
}
如果JS想要得到Android方法的返回值,只能通过 WebView 的 loadUrl ()去执行 JS 方法把返回值传递回去,相关的代码如下:
webview.loadUrl("javascript:returnResult($result)")
(3)通过 WebChromeClient 的onJsAlert()、onJsConfirm() 、onJsPrompt() 方法回调拦截JS对话框alert()、confirm()、prompt()消息
常用的拦截是:拦截 JS的prompt()方法
因为只有prompt()可以返回任意类型的值,操作最全面方便、更加灵活,而alert()没有返回值,confirm()只能返回两种状态(确定 / 取消)两个值
webview.webChromeClient = object : WebChromeClient() {
override fun onJsPrompt(
view: WebView?,
url: String?,
message: String?,
defaultValue: String?,
result: JsPromptResult?
): Boolean { //输入框
// 一般根据scheme(协议格式) & authority(协议名)判断(前两个参数)
//假定传入进来的需要拦截的 url = "js://webview?arg1=111&arg2=222"
val uri = Uri.parse(message)
if (uri.scheme == "js" && uri.authority == "webview") {
// 执行JS所需要调用的逻辑
Log.d("WebviewTest", "js调用了Android的方法")
// 可以在协议上带有参数并传递到Android上
val collection = uri.queryParameterNames
//参数result:代表消息框的返回值(输入值)
result!!.confirm("js调用了Android的方法成功啦")
return true
}
return super.onJsPrompt(view, url, message, defaultValue, result)
}
override fun onJsAlert(
view: WebView?,
url: String?,
message: String?,
result: JsResult?
): Boolean { //警告框
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)
}
}
(1)初始化需要的变量
private var uploadMessageAboveL: ValueCallback<Array<Uri>>? = null
private var cameraFielPath: String? = null
private var uploadMessage: ValueCallback<Uri>? = null
webview.webChromeClient = object :WebChromeClient(){
override fun onShowFileChooser(
webView: WebView?,
filePathCallback: ValueCallback<Array<Uri>>?,
fileChooserParams: FileChooserParams?
): Boolean {
uploadMessageAboveL = filePathCallback
openImageChooserActivity()
return true
}
// For Android < 3.0
fun openFileChooser(valueCallback: ValueCallback<Uri>) {
uploadMessage = valueCallback
openImageChooserActivity()
}
}
// 2.回调方法触发本地选择文件
private fun openImageChooserActivity() {
//拍照
val imageStorageDir = File(
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),
"heart_image"
)
if (!imageStorageDir.exists()) {
imageStorageDir.mkdirs()
}
cameraFielPath = imageStorageDir.toString() + File.separator + "IMG_" + System.currentTimeMillis()
.toString() + ".jpg"
val file = File(cameraFielPath)
//需要显示应用的意图列表,这个list的顺序和选择菜单上的图标顺序是相关的,请注意。
val cameraIntents: MutableList<Intent> = ArrayList()
val captureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
val packageManager: PackageManager = packageManager
//获取手机里所有注册相机接收意图的应用程序,放到意图列表里(无他相机,美颜相机等第三方相机)
val listCam: List<ResolveInfo> = packageManager.queryIntentActivities(captureIntent, 0)
for (res in listCam) {
val packageName: String = res.activityInfo.packageName
val i = Intent(captureIntent)
i.component = ComponentName(res.activityInfo.packageName, res.activityInfo.name)
i.setPackage(packageName)
i.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(file))
cameraIntents.add(i)
}
//相册
val i = Intent(Intent.ACTION_GET_CONTENT)
i.action = Intent.ACTION_PICK
i.type = "image/*"
val chooserIntent = Intent.createChooser(i, "选择")
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, cameraIntents.toTypedArray())
startActivityForResult(
chooserIntent,
101
)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == 101) {
val result = if (data == null || resultCode != Activity.RESULT_OK) null else data.data
if (uploadMessageAboveL != null) {
onActivityResultAboveL(requestCode, resultCode, data)
} else if (uploadMessage != null) {
uploadMessage?.onReceiveValue(result)
uploadMessage = null
}
if (resultCode != RESULT_OK) {
//这里uploadMessage跟uploadMessageAboveL在不同系统版本下分别持有了
//WebView对象,在用户取消文件选择器的情况下,需给onReceiveValue传null返回值
//否则WebView在未收到返回值的情况下,无法进行任何操作,文件选择器会失效
if (uploadMessage != null) {
uploadMessage?.onReceiveValue(null)
uploadMessage = null
} else if (uploadMessageAboveL != null) {
uploadMessageAboveL?.onReceiveValue(null)
uploadMessageAboveL = null
}
}
}
}
// 4. 选择内容回调到Html页面
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
fun onActivityResultAboveL(requestCode: Int, resultCode: Int, intent: Intent?) {
if (requestCode != 101 || uploadMessageAboveL == null)
return
var results = arrayOf<Uri>()
var result: Uri? = null
if (resultCode == Activity.RESULT_OK) {
if (intent != null) {
var dataString = intent.dataString
var clipData = intent.clipData
if (clipData != null) {
for (i in 0 until clipData.itemCount) {
var itemAt = clipData.getItemAt(i)
results[i] = itemAt.uri
}
}
if (dataString != null) {
results = arrayOf<Uri>(Uri.parse(dataString))
}
uploadMessageAboveL?.onReceiveValue(results)
uploadMessageAboveL = null
} else { //
if (result == null && File(cameraFielPath).exists()) {
result = Uri.fromFile(File(cameraFielPath))
}
uploadMessageAboveL?.onReceiveValue(arrayOf(result!!))
uploadMessageAboveL = null
}
}
}
webview.setDownloadListener { url, userAgent, contentDisposition, mimetype, contentLength ->
val uri = Uri.parse(url)
val intent = Intent(Intent.ACTION_VIEW, uri)
startActivity(intent)
}