Android的WebView学习总结

1,469 阅读5分钟

环境:基于Android API-30

WebView适用的内核是webkit,在4.4版本之后,直接使用Chrome作为内置网页浏览器。 使用的时候需要在清单文件加网络权限(如果有读写则添加读写权限):

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

从API 28开始,默认情况下禁用明文支持,http的url均无法再webview中加载,解决办法:在清单文件中application节点添加:

android:usesCleartextTraffic="true"

注意:

// 5.1以上默认禁止了https和http混用,可以如下开启:
webView.settings.mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW

一般使用:

webView.settings.javaScriptEnabled = true
webView.webViewClient = object : WebViewClient() {
    override fun shouldOverrideUrlLoading(
        view: WebView?,
        request: WebResourceRequest?
    ): Boolean {
        if (request != null) {
            view?.loadUrl(request.url.toString())
        }
        return true
    }
}
webView.loadUrl("http://www.baidu.com")

如果访问的网页中有Javascript,则必须设置支持Javascript

webView.getSettings().setJavaScriptEnabled(true);

如果希望点击webView中的链接在当前browser响应,而不是打开Android系统browser中响应该连接,则需要覆盖WebView的WebViewClient:

webView.webViewClient = object : WebViewClient() {
    override fun shouldOverrideUrlLoading(
        view: WebView?,
        request: WebResourceRequest?
    ): Boolean {
        if (request != null) {
            view?.loadUrl(request.url.toString())
        }
        return true
    }
}

加载h5方式:

// 加载网络h5
webView.loadUrl("http://www.baidu.com")
// 加载assets文件下的h5
webView.loadUrl("file:///android_asset/test.html")
// 加载sdcard的h5,需要读写权限
webView.loadUrl("file:///xxx/test.html")
// 加载html代码段
val str = "<html><body>Hello <b>world</b></body></html>"
webView.loadData(str,"text/html",null)

前进/后退 返回栈

//是否可以后退
webView.canGoBack()
//后退网页
webView.goBack()

//是否可以前进                     
webView.canGoForward()
//前进网页
webView.goForward()

//以当前的index为起始点前进或者后退到历史记录中指定的steps
//如果steps为负数则为后退,正数则为前进
val step = 1
webView.goBackOrForward(step)

清除缓存数据

//清除网页访问留下的缓存
//由于内核缓存是全局的因此这个方法不仅仅针对webview而是针对整个应用程序.
webView.clearCache(true)

//清除当前webview访问的历史记录
//只会清除访问历史记录里的所有记录(除了当前访问记录)
webView.clearHistory()

//这个api仅仅清除自动完成填充的表单数据,并不会清除WebView存储到本地的数据
webView.clearFormData()

WebSettings类

val webSettings = webView.settings
//如果访问的页面中要与Javascript交互,则webview必须设置支持Javascript
// 若加载的 html 里有JS 在执行动画等操作,会造成资源浪费(CPU、电量)
// 在 onStop 和 onResume 里分别把 setJavaScriptEnabled() 给设置成 false 和 true 即可
webSettings.javaScriptEnabled = true
// 支持插件:已废弃,SystemApi,无法直接调用
// webSettings.setPluginsEnabled(true)
//设置自适应屏幕,两者合用
webSettings.useWideViewPort = true //将图片调整到适合webview的大小
webSettings.loadWithOverviewMode = true // 缩放至屏幕的大小
//缩放操作
webSettings.setSupportZoom(true) //支持缩放,默认为true。是下面那个的前提。
webSettings.builtInZoomControls = true //设置内置的缩放控件。若为false,则该WebView不可缩放
webSettings.displayZoomControls = false //隐藏原生的缩放控件
//其他细节操作
webSettings.cacheMode = WebSettings.LOAD_CACHE_ELSE_NETWORK //关闭webview中缓存
webSettings.allowFileAccess = true //设置可以访问文件
webSettings.javaScriptCanOpenWindowsAutomatically = true //支持通过JS打开新窗口
webSettings.loadsImagesAutomatically = true //支持自动加载图片
webSettings.defaultTextEncodingName = "utf-8" //设置编码格式

设置WebView缓存

  • 当加载html页面时,WebView会在/data/data/包名目录下生成database与cache两个文件夹
  • 请求的URL记录保存在WebViewCache.db,而URL的内容是保存在WebViewCache文件下

是否使用缓存:

//优先使用缓存: 
webSettings.cacheMode = WebSettings.LOAD_CACHE_ELSE_NETWORK
//缓存模式如下:
//LOAD_CACHE_ONLY: 不使用网络,只读取本地缓存数据
//LOAD_DEFAULT: (默认)根据cache-control决定是否从网络上取数据。
//LOAD_NO_CACHE: 不使用缓存,只从网络获取数据.
//LOAD_CACHE_ELSE_NETWORK,只要本地有,无论是否过期,或者no-cache,都使用缓存中的数据。
//不使用缓存: 
webSettings.cacheMode = WebSettings.LOAD_NO_CACHE

离线加载:

if (isConnected(applicationContext)) {
    webSettings.cacheMode = WebSettings.LOAD_DEFAULT //根据cache-control决定是否从网络上取数据。
} else {
    webSettings.cacheMode = WebSettings.LOAD_CACHE_ELSE_NETWORK //没网,则从本地获取,即离线加载
}
webSettings.domStorageEnabled = true // 开启 DOM storage API 功能
webSettings.databaseEnabled = true //开启 database storage API 功能
webSettings.setAppCacheEnabled(true) //开启 Application Caches 功能;已经废弃
val cacheDirPath = filesDir.absolutePath + APP_CACAHE_DIRNAME
webSettings.setAppCachePath(cacheDirPath) //设置  Application Caches 缓存目录;已经废弃

注意:每个Application只调用一次webSettings.setAppCachePath,WebSettings.setAppCacheMaxSize()

WebViewClient类

shouldOverrideUrlLoading是在本地显示还是在系统浏览器显示;可以拦截url进行处理。

webView.webViewClient = object : WebViewClient() {
    override fun shouldOverrideUrlLoading(
        view: WebView?,
        request: WebResourceRequest?
    ): Boolean {
        if (request != null) {
            view?.loadUrl(request.url.toString())
        }
        return true
    }
}

开始载入页面的回调,可以设定loading显示,等待网络响应。

override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
    // 加载开始的操作
}

加载结束回调。可以关闭loading。

override fun onPageFinished(view: WebView?, url: String?) {
    // 加载结束的操作
}

加载资源回调,每一个资源(如:图片)的加载都会调用一次

override fun onLoadResource(view: WebView?, url: String?) {
    // 加载资源
}

加载页面时出现错误(如:404),我们可以加载一个本地的错误页面替代系统丑陋的页面。(再例如在无网络的时候error会报:net::ERR_NAME_NOT_RESOLVED),可以根据错误来显示具体信息。但是注意:有可能多次错误回调。

override fun onReceivedError(
view: WebView?,
request: WebResourceRequest?,
error: WebResourceError?
) {
super.onReceivedError(view, request, error)
}

处理https请求,webView默认是不处理https请求的,页面显示空白等问题。

override fun onReceivedSslError(
view: WebView?,
handler: SslErrorHandler?,
error: SslError?
) {
if (handler != null) {
    handler.proceed()// 等待证书响应
    handler.cancel()// 挂起连接,默认
    handler.handleMessage(handler.obtainMessage())// 做其他处理
}
}

WebChromeClient类

获得网页加载进度并显示:

override fun onProgressChanged(view: WebView?, newProgress: Int) {
}

获取Web页中的标题

override fun onReceivedTitle(view: WebView?, title: String?) {
}

javaScript警告,拦截js alert转换原生dialog

override fun onJsAlert(
    view: WebView?,
    url: String?,
    message: String?,
    result: JsResult?
): Boolean {
    return true
}

javaScript确认框

override fun onJsConfirm(
    view: WebView?,
    url: String?,
    message: String?,
    result: JsResult?
): Boolean {
    // 返回ture表示点击确认;返回false表示点击取消
    return super.onJsConfirm(view, url, message, result)
}

javaScript输入框,点击确认返回输入框中的值,点击取消返回null

override fun onJsPrompt(
    view: WebView?,
    url: String?,
    message: String?,
    defaultValue: String?,
    result: JsPromptResult?
): Boolean {
    return super.onJsPrompt(view, url, message, defaultValue, result)
}

避免WebView内存泄漏

不要在xml定义WebView,而是在需要的时候在Activity中创建,并且Context使用getApplicationContext()

val param = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT)
var myWebView = WebView(applicationContext)
myWebView.layoutParams = param
rootView.addView(webView)

在activity销毁WebView的时候,先WebView加载null内容,然后移除WebView再销毁WebView,然后置null

override fun onDestroy() {
    if (webView != null) {
        webView.loadDataWithBaseURL(null, "", "text/html", "utf-8", null)
        webView.clearHistory()
        (webView.parent as ViewGroup).removeView(webView)
        webView.destroy()
    }
    super.onDestroy()
}

常见问题:

问题: github.com/gzsll/WebVi…

存在内存泄漏点:

  1. WVJWebViewClientProxy泄漏
  2. WVJBWebView下的messageHandlers泄漏 解决:
// 在WVJBWebView类中添加解注册
public void unregisterAllHandler() {
    messageHandlers.clear();
}
// 在销毁webView的时候(例如activity的onDestroy需要调用)
webView!!.unregisterAllHandler()
webView!!.clientProxy = null

存在数据解析异常:特别是h5和native交互的时候特殊字符,例如:h5传消息到native,"""" json中双引号嵌套,虽然不crash,但是消息无法解析传递。 建议:不要在项目里再用这个库了