WebView 面试准备

本文转载自

作者: 骑着蜗牛闯红灯原文链接

如有侵权,请联系我及时删除,本文只做自我学习之用。

先简单介绍一下,Android在4.4之后采用了Chrome内核,所以我们在开发web页面的时候,es6的语法,css3的样式等大可放心使用。

我将分下面几个模块去介绍Android上面WebView。

WebView自身的一些方法

```

//方式1. 加载一个网页:

webView.loadUrl("http://www.google.com/");

//方式2:加载apk包中的html页面

webView.loadUrl("file:///android_asset/test.html");

//方式3:加载手机本地的html页面

webView.loadUrl("content://com.android.htmlfileprovider/sdcard/test.html");

```

正常情况下,在WebView界面,用户点击返回键是直接退出该页面的,着当然不是我们想要的,我们想要的是网页自己的前进和后退,所以下面介绍网页前进和后退的一些API

'''

//判断是否可以后退

Webview.canGoBack()

//后退网页

Webview.goBack()

//判断是否可以前进

Webview.canGoForward()

//前进网页

Webview.goForward()

// 参数传负的话表示后退,传正值的话表示的是前进

Webview.goBackOrForward(int steps)

对返回键的监听,来实现网页的后退

public boolean onKeyDown(int keyCode, KeyEvent event) {

if ((keyCode == KEYCODE_BACK) && mWebView.canGoBack()) {

mWebView.goBack();

return true;

}

return super.onKeyDown(keyCode, event);

}

'''

如何防止WebView内存泄漏

防止内存泄漏的一个原则就是:生命周期长的不要跟生命周期短的玩。

为了防止WebView不造成内存泄漏,

不要在xml里面定义WebView,而是在Activity选中使用代码去构建,并且Context使用ApplicationContext

在Activity销毁的时候,先让WebView加载空内容,然后重rootView中移除WebView,再销毁WebView,最后置空

'''

override fun onDestroy() {

if (webView != null) {

webView!!.loadDataWithBaseURL(null, "", "text/html", "utf-8", null)

webView!!.clearHistory()

(webView!!.parent as ViewGroup).removeView(webView)

webView!!.destroy()

webView = null

}

super.onDestroy()

}

'''

WebSetting和WebViewClient,WebChromeClien

WebSetting

作用:对WebView进行配置和管理

'''

WebSettings webSettings = webView.getSettings();

// 设置可以与js交互,为了防止资源浪费,我们可以在Activity

// 的onResume中设置为true,在onStop中设置为false

webSettings.setJavaScriptEnabled(true);

//设置自适应屏幕,两者合用

//将图片调整到适合webview的大小

webSettings.setUseWideViewPort(true);

// 缩放至屏幕的大小

webSettings.setLoadWithOverviewMode(true);

//设置编码格式

webSettings.setDefaultTextEncodingName("utf-8");

// 设置允许JS弹窗

webSettings.setJavaScriptCanOpenWindowsAutomatically(true);

//设置缓存的模式

webSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);

'''

关于缓存的设置

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

缓存模式如下:

//LOAD_CACHE_ONLY: 不使用网络,只读取本地缓存数据

//LOAD_DEFAULT: (默认)根据cache-control决定是否从网络上取据。

//LOAD_NO_CACHE: 不使用缓存,只从网络获取数据.

//LOAD_CACHE_ELSE_NETWORK,只要本地有,无论是否过期,或no-cache,都使用缓存中的数据。

离线加载

'''

if (NetStatusUtil.isConnected(getApplicationContext())) {

webSettings.setCacheMode(WebSettings.LOAD_DEFAULT);//根据cache-control决定是否从网络上取数据。

} else {

webSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);//没网,则从本地获取,即离线加载

}

webSettings.setDomStorageEnabled(true); // 开启 DOM storage API 功能

webSettings.setDatabaseEnabled(true); //开启 database storage API 功能

webSettings.setAppCacheEnabled(true);//开启 Application Caches 功能

String cacheDirPath = getFilesDir().getAbsolutePath() + APP_CACAHE_DIRNAME;

webSettings.setAppCachePath(cacheDirPath); //设置 Application Caches 缓存目录

'''

WebViewClient 作用

处理各种通知,请求事件,主要有,网页开始加载,记载结束,加载错误(如404),处理https请求,具体使用请看下面代码,注释清晰。

'''

webView!!.webViewClient = object : WebViewClient() {

// 启用WebView,而不是系统自带的浏览器

override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean {

view.loadUrl(url)

return true

}

// 页面开始加载,我们可以在这里设置loading

override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {

super.onPageStarted(view, url, favicon)

tv_start.text = "开始加载了..."

}

// 页面加载结束,关闭loading

override fun onPageFinished(view: WebView?, url: String?) {

super.onPageFinished(view, url)

tv_end.text = "加载结束了..."

}

// 只要加载html,js,css的资源,每次都会回调到这里

override fun onLoadResource(view: WebView?, url: String?) {

loge("onLoadResource invoked")

}

// 在这里我们可以加载我们自己的404页面

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

loge("加载错误:${error.toString()}")

}

// webview默认设计是不开启https的,下面的设置是允许使用https

override fun onReceivedSslError(view: WebView?, handler: SslErrorHandler?, error: SslError?) {

handler?.proceed()

}

// js调用Android的方法,在这里可以,该方法不存在通过注解的方式的内存泄漏,但是想拿到Android的返回值的话很难,

// 可以通过Android调用js的代码的形式来传递返回值,例如下面的方式

// Android:MainActivity.java

// mWebView.loadUrl("javascript:returnResult(" + result + ")");

// JS:javascript.html

// function returnResult(result){

// alert("result is" + result);

// }

override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean {

val uri = Uri.parse(request?.url.toString())

// 一般根据scheme(协议格式) & authority(协议名)判断(前两个参数)

//假定传入进来的 url = "js://webview?arg1=111&arg2=222"(同时也是约定好的需要拦截的)

if (uri.scheme == "js") {

if (uri.authority == "webview") {

toast_custom("js调用了Android的方法")

val queryParameterNames = uri.queryParameterNames

queryParameterNames.forEach {

loge(it + ":" + uri.getQueryParameter(it))

}

}

return true

}

return super.shouldOverrideUrlLoading(view, request)

}

// 拦截资源 通常用于h5的首页页面,将常用的一些资源,放到本地

override fun shouldInterceptRequest(view: WebView?, request: WebResourceRequest?): WebResourceResponse? {

if(request?.url.toString().contains("logo.gif")){

var inputStream: InputStream? = null

inputStream = applicationContext.assets.open("images/test.png")

return WebResourceResponse("image/png","utf-8", inputStream)

}

return super.shouldInterceptRequest(view, request)

}

}

'''

注意:

5.1 以上默认禁止了https和http的混用下面的设置是开启:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {

    webView.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);

}

WebChromeClient 作用

辅助webview的一下回调方法,可以得到网页加载的进度,网页的标题,网页的icon,js的一些弹框,直接看代码,注释清晰。

'''

webView!!.webChromeClient = object : WebChromeClient() {

// 网页加载的进度

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

tv_progress.text = "$newProgress%"

}

// 获得网页的标题

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

tv_title.text = title

}

//js Alert

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

AlertDialog.Builder(this@WebActivity)

.setTitle("JsAlert")

.setMessage(message)

.setPositiveButton("OK") { _, _ -> result?.confirm() }

.setCancelable(false)

.show()

return true

}

// js Confirm

override fun onJsConfirm(view: WebView?, url: String?, message: String?, result: JsResult?): Boolean {

return super.onJsConfirm(view, url, message, result)

}

//js Prompt

override fun onJsPrompt(

view: WebView?,

url: String?,

message: String?,

defaultValue: String?,

result: JsPromptResult?

): Boolean {

return super.onJsPrompt(view, url, message, defaultValue, result)

}

}

'''

Android和js的交互

Android调用js

1. 通过webview的loadUrl

注意:该方式必须在webview加载完毕之后才能调用,也就是webviewClient的onPageFinished()方法回调之后,而且该方法的执行 会刷新界面,效率较低

js代码:

'''

functioncallJs(){

alert("Android 调用了 js代码)

}

kotlin代码:

webView?.loadUrl("javascript:callJs()")

'''

2. 通过webview的evaluateJavaScript

比起第一种方法,效率更高,但是要在4.4之后才能使用

js代码:

'''

function callJs(){

// alert("Android 调用了 js代码)

    return {name:'wfq',age:25}

}

'''

kotlin代码:

'''

webView?.evaluateJavascript("javascript:callJs()") {

// 这里直接拿到的是js代码的返回值

    toast(it) // {name:'wfq',age:25}

}

'''

js调用Android

1. 通过webview的addJavaScriptInterface进行对象映射

我们可以单独定义一个类,所有需要交互的方法可以全部写在这个类里面,当然也可以直接写在Activity里面,下面以直接定义在Activity里面为例,优点:使用方便,缺点:存在漏洞(4.2之前),请看下面的“WebView的一些漏洞以及如何防止”

kotlin中定义被js调用的方法

'''

@JavascriptInterface

fun hello(name: String) {

toast("你好,我是来自js的消息:$msg")

}

'''

js代码

'''

function callAndroid(){

android.hello("我是js的,我来调用你了")

}

kotlin中们在webview里面设置Android与js的代码的映射

webView?.addJavascriptInterface(this, "android")

'''

2. 通过webviewClient的shouldOverrideUrlLoading的回调来拦截url

具体使用:解析该url的协议,如果监测到是预先约定好的协议,那么就调用相应的方法。比较安全,但是使用麻烦,js获取Android的返回值的话很麻烦,只能通过上面介绍的通过loadurl()去执行js代码把返回值通过参数传递回去

首先在js中约定号协议

'''

function callAndroid(){

// 约定的url协议为:js://webview?name=wfq&age=24

document.location = "js://webview?name=wfq&age=24"

}

'''

在kotlin里面,当loadurl的时候就会回调到shouldOverrideUrlLoading()里面

'''

override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean {

val uri = Uri.parse(request?.url.toString())

// 一般根据scheme(协议格式) & authority(协议名)判断(前两个参数)

//假定传入进来的 js://webview?name=wfq&age=24

if (uri.scheme == "js") {

if (uri.authority == "webview") {

toast_custom("js调用了Android的方法")

val queryParameterNames = uri.queryParameterNames

queryParameterNames.forEach {

loge(it + ":" + uri.getQueryParameter(it))

}

}

return true

}

return super.shouldOverrideUrlLoading(view, request)

}

'''

3.通过webChromeClient的onJsAlert,onJsConfirm,onJsPrompt回调来拦截对话框

通过拦截js对话框,得到他们的消息,然后解析即可,为了安全,建议内容采用上面介绍的url协议, 常用的拦截的话就是拦截prompt,因为它可以返回任意值,alert没有返回值,confirm只能返回两种类型,确定和取消

js代码

'''

function clickprompt(){

var result=prompt("wfq://demo?arg1=111&arg2=222");

alert("demo " + result);

}

'''

kotlin代码

'''

override fun onJsPrompt(

view: WebView?,

url: String?,

message: String?,

defaultValue: String?,

result: JsPromptResult?

): Boolean {

val uri = Uri.parse(message)

if (uri.scheme == "wfq") {

if (uri.authority == "demo") {

toast_custom("js调用了Android的方法")

val queryParameterNames = uri.queryParameterNames

queryParameterNames.forEach {

loge(it + ":" + uri.getQueryParameter(it))

}

// 将需要返回的值通过该方式返回

result?.confirm("js调用了Android的方法成功啦啦啦啦啦")

}

return true

}

return super.onJsPrompt(view, url, message, defaultValue, result)

}

'''

由于拦截了弹框,所以js代码的alert需要处理 这里的message便是上面代码的返回值通过alert显示出来的信息

'''

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

AlertDialog.Builder(this@WebActivity)

.setTitle("JsAlert")

.setMessage(message)

.setPositiveButton("OK") { _, _ -> result?.confirm() }

.setCancelable(false)

.show()

return true

}

'''

上面三种方式的区别:

addJavascriptInterface() 方便简洁,4.0以下存在漏洞,4.0以上通过@JavascriptInterface注解修复漏洞。

WebViewClient.shouldOverrideUrlLoading()回调,不存在漏洞,使用复杂,需要定义协议的约束,但是返回值的话有些麻烦,在不需要返回值的情况下可以使用这个方式。

通过WebChromeClient的onJsAlerta,onJsConfirm,onJsPrompt,不存在漏洞问题,使用复杂,需要进行协议的约束,可以返回值,能满足大多数情况下的互调通信。

WebView的一些漏洞以及如何防止

密码明文存储漏洞

webview默认开启了密码保存功能,在用户输入密码后会弹出提示框询问用户是否保存密码,保存后密码会被明文保存在 /data/data/com.package.name/databases/webview.db 下面,手机root后可以查看,那么如何解决?

WebSettings.setSavePassword(false) // 关闭密码保存提醒功能

WebView 任意代码执行漏洞

addJavascriptInterface漏洞,首先先明白一点,js调用Android代码的时候,我们经常使用的是addJavascriptInterface, JS调用Android的其中一个方式是通过addJavascriptInterface接口进行对象映射,那么Android4.2之前,既然拿到了这个对象,那么这个对象中的所有方法都是可以调用的,4.2之后,需要被js调用的函数加上@JavascriptInterface注解后来避免该漏洞

所以怎么解决

对于Android 4.2以前,需要采用拦截prompt() 方式进行漏洞修复

对于Android 4.2以后,则只需要对被调用的函数以 @JavascriptInterface进行注解

域控制不严格漏洞

原因分析 当我们在Applilcation里面,android:exported="true"的时候,A 应用可以通过 B 应用导出的 Activity 让 B 应用加载一个恶意的 file 协议的 url,从而可以获取 B 应用的内部私有文件,从而带来数据泄露威胁,

下面来看下WebView中getSettings类的方法对 WebView 安全性的影响 setAllowFileAccess()

'''

// 设置是否允许 WebView 使用 File 协议

// 默认设置为true,即允许在 File 域下执行任意 JavaScript 代码

webView.getSettings().setAllowFileAccess(true);

'''

如果设置为false的话,便不会存在威胁,但是,webview也无法使用本地的html文件

setAllowFileAccessFromFileURLs()

// 设置是否允许通过 file url 加载的 Js代码读取其他的本地文件

// 在Android 4.1前默认允许

// 在Android 4.1后默认禁止

webView.getSettings().setAllowFileAccessFromFileURLs(true);

我们应该明确的设置为false,禁止读取其他文件

setAllowUniversalAccessFromFileURLs()

// 设置是否允许通过 file url 加载的 Javascript 可以访问其他的源(包括http、https等源)

// 在Android 4.1前默认允许(setAllowFileAccessFromFileURLs()不起作用)

// 在Android 4.1后默认禁止

webView.getSettings().setAllowUniversalAccessFromFileURLs(true);

WebView预加载以及资源预加载

为什么需要预加载

h5页面加载慢,慢的原因:页面渲染慢,资源加载慢

如何优化?

h5的缓存,资源预加载,资源拦截

h5的缓存 Android WebView自带的缓存 

1. 浏览器缓存

根据 HTTP 协议头里的 Cache-Control(或 Expires)和 Last-Modified(或Etag)

等字段来控制文件缓存的机制浏览器自己实现,我需我们处理

2. App Cache

方便构建Web App的缓存,存储静态文件(如JS、CSS、字体文件)

'''

WebSettings settings = getSettings();

String cacheDirPath = context.getFilesDir().getAbsolutePath()+"cache/";

settings.setAppCachePath(cacheDirPath);

settings.setAppCacheMaxSize(20*1024*1024);

settings.setAppCacheEnabled(true);

'''

3. Dom Storage

'''

WebSettings settings = getSettings();

settings.setDomStorageEnabled(true);

'''

4. Indexed Database

'''

// 只需设置支持JS就自动打开IndexedDB存储机制

// Android 在4.4开始加入对 IndexedDB 的支持,只需打开允许 JS 执行的开关就好了。

WebSettings settings = getSettings();

settings.setJavaScriptEnabled(true);

'''

资源预加载 预加载webview对象,首次初始化WebView会比第二次慢很多的原因:初始化后,即使webview已经释放,但是WebView的一些共享的对象依然是存在的,我们可以在Application里面提前初始化一个Webview的对象,然后可以直接loadurl加载资源

资源拦截 可以将跟新频率低的一些资源静态文件放在本地,拦截h5的资源网络请求并进行检测,如果检测到,就直接拿本地的资源进行替换即可

'''

// 拦截资源 通常用于h5的首页页面,将常用的一些资源,放到本地

override fun shouldInterceptRequest(view: WebView?, request: WebResourceRequest?): WebResourceResponse? {

if(request?.url.toString().contains("logo.jpg")){

var inputStream: InputStream? = null

inputStream = applicationContext.assets.open("images/test.jpg")

return WebResourceResponse("image/png","utf-8", inputStream)

}

return super.shouldInterceptRequest(view, request)

}

'''

常见的使用注意事项

1. Android9.0 已经禁止了webview使用http,怎么解决?

在manifest Application标签下面使用:

android:usesCleartextTraffic="true"

2. 开启混淆之后,Android无法与h5交互?

'''

#保留annotation, 例如 @JavascriptInterface 等 annotation

-keepattributes *Annotation*

#保留跟 javascript相关的属性

-keepattributes JavascriptInterface

#保留JavascriptInterface中的方法

-keepclassmembers class * {

@android.webkit.JavascriptInterface ;

}

#这个类是用来与js交互,所以这个类中的 字段 ,方法, 不能被混淆、全路径名称.类名

-keepclassmembers public class com.youpackgename.xxx.H5CallBackAndroid{

;

;

public *;

private *;

}

'''

3. 如何调试?

3.1 在WebViewActivity里面,开启调试

'''

// 开启调试

WebView.setWebContentsDebuggingEnabled(true)

'''

3.2 chrome浏览器地址栏输入 chrome://inspect

3.3 手机打开USB调试,打开webview页面,点击chrome页面的最下面的inspect,这样,便可以进入了web开发,看控制台,网络请求等

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,864评论 6 494
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,175评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,401评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,170评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,276评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,364评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,401评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,179评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,604评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,902评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,070评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,751评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,380评论 3 319
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,077评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,312评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,924评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,957评论 2 351