Android控件系列——WebView

WebView

简介

实现在app内展示网页并交互,同时也可在其中放入其他view(VebView标签可嵌套其他view标签),WebView组件继承自AbsoluteLayout。从Android4.4开始内核由WebKit变更为Chromium,提升性能和对HTML5,CSS3,JavaScript的支持

WebView的行为定制:WebSettings,JavaScriptInterface,WebViewClient,WebChromeClient

WebView常用方法

状态相关
onPause() 暂停内核所有操作,如DOM的解析、plugin的执行,不包括JavaScript
onResume() 对应onPause()方法
pauseTimers() 暂停所有操作,包括JavaScript,同时降低功耗
resumeTimers() 对应pauseTimers()方法
removeView() 销毁WebView,防止内存泄漏
destroy() 销毁WebView,应被父容器remove后手动调用

前进/后退相关
canGoBack() 是否可回退,通常应用于onKeyDown()方法
goBack() 退到上一项
canGoForward() 是否可向前
goForward() 前进到下一项
goBackOrForward(int steps) 前进(正数)或回退(负数)的步数

清除缓存数据
clearCache(boolean includeDiskFiles) 清除缓存,true:清除包括磁盘缓存
clearHistory() 清除访问记录
clearFormData() 清除自动完成填充的表单数据(不会清理本地数据)

其他
getScrollY() 当前内容滚动距离
getHeight() 获取WebView高度
getContentHeight() 整个HTML页面缩放后的实际高度
判断整个页面是否处于低端:webView.getContentHeight()*webView.getScale() == (webView.getHeight()+ webView.getScrollY())
判断整个页面是否处于顶端:webView.getScrollY() == 0
pageUp(boolean top) 滑动展示中的部分至顶部
pageDown(boolean bottom) 滑动展示中的部分至低端

WebSettings

管理WebView配置,当WebView第一次被创建时,内部包含一个默认配置的集合。生命周期与WebView一致,WebView被销毁后,WebSettings中方法失效。

获取:WebSettings webSettings = webView.getSettings();

WebSettings 常用方法

几乎所有方法都有set,get方法,此处只介绍set

//声明WebSettings子类
WebSettings webSettings = webView.getSettings();
setJavaScriptEnabled(boolean flag) 是否可运行JavaScript
webSettings.setPluginsEnabled() 是否支持插件
//设置自适应屏幕,两者合用
webSettings.setUseWideViewPort(); 将图片调整到适合webview的大小
webSettings.setLoadWithOverviewMode() 缩放至屏幕的大小
//缩放操作
webSettings.setSupportZoom() 是否支持缩放,默认为true,是下述方法的前提。
webSettings.setBuiltInZoomControls() 是否显示缩放按钮(wap网页不支持)。false,则不可缩放
webSettings.setDisplayZoomControls() 隐藏原生的缩放控件
//其他细节操作
setCacheMode(int mode) 设置缓存模式,通常根据网络环境,设置不同缓存模式
缓存模式:
LOAD_DEFAULT 默认的缓存使用模式,优先使用缓存数据
LOAD_CACHE_ELSE_NETWORK 只要有缓存,就使用缓存数据;无缓存,网络加载
LOAD_NO_CACHE 不设置缓存
LOAD_CACHE_ONLY 只用缓存,不使用网络加载
setAllowFileAccess(boolean allow) 是否启动WebView访问文件数据
setJavaScriptCanOpenWindowsAutomatically(boolean flag) 是否由JavaScript自动打开窗口,通常与JavaScript的window.open()配合使用
webSettings.setLoadsImagesAutomatically(true) 支持自动加载图片
webSettings.setDefaultTextEncodingName(“utf-8”) 设置编码格式

setBlockNetworkImage(boolean flag) 加载网络图片,flag变更时,需调用reload()方法才立即生效
setSupportMultipleWindows(boolean support) 是否支持多窗口
setLayoutAlgorithm(WebSettings.LayoutAlgorithm l) 指定页面布局显示形式,会引起页面重绘,默认值:LayoutAlgorithm#NARROW_COLUMNS
setNeedInitialFocus(boolean flag) 通知WebView是佛需要设置一个节点获取焦点,当requestFocus()被调用时,默认为true
setAppCacheEnabled(boolean flag) 是否启用缓存
setAppCachePath(String appCachePath) 设置缓存路径,路径必须可写,且只初次调用有效
setDatabaseEnabled(boolean flag) 是否启用数据库缓存
setDomStorageEnabled(boolean flag) 是否启用DOM缓存
setUserAgentString(String ua) 设置WebView的UserAgent值
setDefaultEncodingName(String encoding) 设置编码格式,通常设为"UTF-8"
setStandardFontFamily(String font) 设置标准的字体族,默认:sans-serif
setCursiveFontFamily(String font) 设置草书字体族,默认:cursive
setFantasyFontFamily(String font) 设置CursiveFont字体族,默认:cursive
setFixedFontFamily(String font) 设置混合字体族,默认:monospace
setSansSerifFontFamily(String font) 设置梵文字体族,默认:sans-serif
setSerifFontFamily(String font) 设置衬线字体族,默认:sans-serif
setDefaultFixedFontSize(int size) 设置默认填充字体大小,默认16,范围:1-72,越界取边界
setDefaultFontSize(int size) 设置默认字体大小,默认16,范围:1-72,越界取边界
setMinimumFontSize(int size) 设置最小字体,默认8,范围:1-72,越界取边界
setMinimumLogicalFontSize(int size) 设置最小逻辑字体,默认8,范围:1-72,越界取边界

使用案例——离线加载

if (NetStatusUtil.isConnected(getApplicationContext())) {
    webSettings.setCacheMode(WebSettings.LOAD_DEFAULT);
} else {
    webSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);
}

webSettings.setDomStorageEnabled(true); 
webSettings.setDatabaseEnabled(true);   
webSettings.setAppCacheEnabled(true);

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

WebViewClient

帮助处理各种通知与请求时间

webView.setWebViewClient(new WebViewClient(){
    @Override
    public void onPageStarted(WebView view, String url, Bitmap favicon){
        super.onPageStarted(view, url, favicon);
    }
});

WebViewClient 常用方法

onLoadResource(WebView view, String url)
每一个资源加载时均回调一次
onPageStarted(WebView view, String url, Bitmap favicon)
在整页开始加载时回调一次,通常在此设置等待动画
onPageFinished(WebView view, String url)
在整页加载完成时回调一次,通常在此关闭动画等操作
onReceivedError(WebView view, WebResourceRequest request, WebResourceError error)
在网络连接问题或布局加载错误时回调
onReceivedHttpError(WebView view, WebResourceRequest request, WebResourceResponse errorResponse)
HTTP错误且code >= 400时回调
onReceivedSslError(WebView view, SslErrorHandler handler, SslError error)
SSL错误时回调,此时可执行handler.cancel()(默认)或handler.proceed(),并记住此决定
WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request)
每次请求数据时回调,可用于拦截特定请求,并替换响应数据返回
shouldOverrideUrlLoading(WebView view, WebResourceRequest request)
重写这个方法,让url在WebView中加载,而不是向ActivityManager寻求一个适合的处理者来加载该url
onScaleChanged(WebView view, float oldScale, float newScale)
页面Scale值发生改变时回调
shouldOverrideKeyEvent(WebView view, KeyEvent event)
默认false,如return true,可以在WebView中处理按键

WebChromeClient

处理JavaScript的网站图标,titile,加载进度等偏外部事件

WebChromeClient 常用方法

onProgressChanged(WebView view, int newProgress) 页面加载进度改变时回调
onReceivedIcon(WebView view, Bitmap icon) 接收Web页面的icon
onReceivedTitle(WebView view, String title) 接收Web页面的title
onShowCustomView(View view, WebChromeClient.CustomViewCallback callback)
页面进入全屏模式时回调,主程序需提供一个包含当前web内容的自定义view
onHideCustomView()
退出全屏模式时回调,主线程需隐藏之前show的view
getDefaultVideoPoster()
返回默认web页面video的预览图。web页面包含视频时,可HTML中设置一个预览图,WebView根据其大小布局,如网络环境差,预览图未获取到,则gitWidth()会报空指针异常
getVideoLoadingProgressView() 重写该方法,可在视频loading时给予一个自定义view,如加载圆环等
onJsAlert(WebView view, String url, String message, JsResult result)
处理JavaScript中Alert对话框
onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result)
处理JavaScript中的Prompt对话框
onJsConfirm(WebView view, String url, String message, JsResult result)
处理JavaScript中的Confirm对话框
onShowFileChooser(WebView webView, ValueCallback filePathCallback,WebChromeClient.FileChooserParams fileChooserParams)
该方法在用户进行了web上某个需要上传文件的操作时回调。我们应该在这里打开一个文件选择器,如果要取消这个请求我们可以调用filePathCallback.onReceiveValue(null)并返回true
onPermissionRequest(PermissionRequest request)
用于web页面需要通过某项权限时回调,主程序调用grant(String[])或deny()方法。该方法未被重写,则默认拒绝
onPermissionRequestCanceled(PermissionRequest request)
用于web页面需要通过某项权限时,被取消后的回调,主程序隐藏任何相关的页面

WebView与Js交互

java调用js

基本格式:webView.loadUrl(“javascript:methodName(parameterValues)”);

<!-- WebView调用JavaScript无参无返回值函数 -->
webView.loadUrl("javascript:readyToGo()"); 	//方法名:readyToGo
<!-- WebView调用JavScript有参无返回值函数 -->
mWebView.loadUrl("javascript: readyToGo('" + code + "', '" + paymentid + "')");//方法名:readyToGo,参数:code,paymentid。多个参数用逗号隔开。javascript的参数用单引号包裹,逗号隔开
<!-- WebView调用JavaScript有参数有返回值的函数 -->
webView.evaluateJavascript("getYourCar()", new ValueCallback<String>() {
    @Override
    public void onReceiveValue(String s) {}
});
//PhotoView开源框架用于图片缩放

js调用java

//创建自定义MyInterface管理所有被js调用的方法,并在方法上添加注解JavascriptInterface
public interface WebViewCallbackInterface{
    @JavascriptInterface
    boolean showToast(String Tag, String content);
}
//创建MyInterface实例
private MyInterface myInterface = new MyInterface(){
    @Override
    @JavascriptInterface
    public boolean showToast(String Tag,String content){
        ...
        return false;
    }
}
//映射JavaScript中的Android对象,此处用的是myInterface
webView.addJavascriptInterface(myInterface, "android");
//JavaScript代码
function toastClick(){
	var boo = window.android.showToast("MainActivity","aaa"); //可直接获取到返回值	
}

WebView加载优化

https://blog.csdn.net/qq122627018/article/details/53351781 图片加载优化

1 资源本地化和缓存

存:将重量级资源放在assert目录下,或第一次加载完成后缓存

取:重写WebViewClient的WebResourceResponse shouldInterceptRequest过滤,减少网络请求频率,转而用本地缓存

更:建立缓存机制,定期更新,保证本地资源最新和可用

2 延迟加载js

页面加载时,页面不能使用JQuery,否则会处理完DOM对象,执行完$(document).ready(function() {})后才会渲染并显示页面

3 禁止滥用JsBridge

https://juejin.im/entry/573534f82e958a0069b27646

Cookie操作

同一url下,只保留最新Cookie

设置Cookie

CookieSyncManager.createInstance(context);	//创建CookieSyncManager 参数是上下文
CookieManager cookieManager = CookieManager.getInstance();	//得到CookieManager
cookieManager.setCookie(url, cookieString);	  //向URL中添加Cookie
CookieSyncManager.getInstance().sync();

获取Cookie

CookieManager instance = CookieManager.getInstance();
String cookie = instance.getCookie(url);

WebView漏洞

1 Android API 16没有正确限制使用WebView.addJavaScripteInterface方法,远程攻击者通过java反射机制,可执行任意java对象方法

2 WebView是独立进程,最好创建一个ViewGroup来放置WebView,再add进Activity。在Activity销毁时,remove掉。

3 JSBridge:<https://github.com/lzyzsd/JsBridge>

4 后台耗电问题:Activity不可见时应停止WebView的操作

5 WebView硬件减速导致页面渲染时白屏,如出现此情况,应关闭硬件加速

6 动态添加WebView布局,对传入WebView中使用的context使用弱引用

https://blog.csdn.net/nextdoor6/article/details/52274909

WebView秒开方案

问题背景

H5页面体能提升,部分功能在H5中实现。

1 页面启动白屏

2 响应流畅度较低:由于webkit的渲染机制,单线程,历史包袱等原因。页面刷新/交互性能体验不如原生

H5页面打开过程

初始化 webview -> 请求页面 -> 下载数据 -> 解析HTML -> 请求 js/css 资源 -> dom 渲染 -> 解析 JS 执行 -> JS 请求数据 -> 解析渲染 -> 下载渲染图片

H5页面与native通信方式

jsapi:客户端提供接口,注入API让 Javascript调用,直接执行相应Native代码,适用于需要通过交互,进行数据请求的场景

URL Scheme: Web 端发送 URL Scheme 请求,之后 Native 拦截到请求并根据 URL Scheme 及所带的参数进行相关操作。适用于进行页面跳转的场景

字符串替换: 客户端读取本地 H5后,通过对 H5 中的约定的标记位进行字符串替换,然后加载展示页面。适用于没有复杂交互,只通过页面渲染数据的场景

前端优化

1 降低请求量:合并资源,减少http请求数,minify / gzip 压缩,webP,lazyLoad

2 加快请求速度:预解析DNS,减少域名数,并行加载,CDN 分发

3 缓存:HTTP 协议缓存请求,离线缓存 manifest,离线数据缓存localStorage

4 **渲染:**JS/CSS优化,加载顺序,服务端渲染,pipeline

其中影响最大的是网络请求,因此优化的重点是缓存,细分为:HTML 的缓存,JS/CSS/image 资源的缓存,以及 json 数据的缓存

JS/CSS/image缓存:

均属于静态文件,http本身提供了缓存协议,浏览器实现了这些协议

1 询问是否有更新:根据 If-Modified-Since / ETag 等协议向后端请求询问是否有更新(查看每个文件hash值,如果有,更新请求url),没有更新返回304,浏览器使用本地缓存

2 直接使用本地缓存:根据协议里的 Cache-Control / Expires 字段去确定多长时间内可以不去发请求询问更新,直接使用本地缓存

json 数据缓存 :

可用localStorage缓存数据,首次显示先用本地数据,再更新,有JS控制

HTML 文件缓存矛盾:如Expires / max-age时间设置过长,长时间使用本地缓存,更新不及时。如设置过短,每次打开页面前需先发网络请求询问是否有更新,再确定是否使用本地资源。因此前端在这里的策略是每次都请求,但在弱网情况下白屏时间依旧过长

客户端优化

HTML缓存:

1 客户端拦截请求,首次请求HTML文件后缓存数据,第二次不发送请求,直接使用缓存数据

2 确定请求时机。请求前先使用本地页面,再发起请求更新页面。也可在APP启动或某个时机后台发起请求预更新

遗留问题

1 没有预加载:首次打开体验差,所有数据均从网络获取

2 缓存不可控:缓存由webview控制。缓存逻辑不可控,可能删除重要缓存,会有IO问题,无法从磁盘预加载数据到内存

3 更新体验差:后台 HTML/JS/CSS 更新时全量下载,数据量大,弱网下载耗时长

4 无法放劫持:若 HTML 页面被运营商或其他第三方劫持,将长时间缓存劫持的页面

遗留问题解决方案

1 配置工具类管理的预加载列表,管理不同时机,不同H5模块所需的页面和资源列表

2 客户单实现缓存机制,不走webview默认缓存逻辑

3 针对每个HTML和资源文件做增量更新

4 客户端使用httpdns + https 防劫持

以上方案实现较为繁琐,原因在于HTML和资源文件分散,管理困难,因此需要用到离线包

离线包

可解决的问题

1 预先下载全部模块离线包,按业务模块划分

2 离线包的核心文件和动态资源缓存分离,也可以整体提前加载进内存,减少IO耗时

3 可方便地根据版本做增量更新

4 离线包以压缩包方式下发,同时会经过加密和校验,减少劫持几率

方案

1 后端使用构建工具将统一业务模块相关的资源打包成一个文件,同时对文件加密、签名

2 客户端根据配置表,自定义时机获取离线包,做解压、解密,校验等工作

3 根据配置表,打开某个业务时转接到离线包对应的入口页面

4 拦截网络请求,对于离线包已有的文件,直接获取返回。否则走http缓存协议逻辑

5 离线包更新时,根据版本号后台下发两个版本间的diff数据,客户端进行合并,增量更新

更多优化

公共资源包

每个包都会使用相同的JS框架和CSS全局样式,抽取成一个全局文件

预加载webview

1 首次预加载:webview首次初始化(即使销毁)后,共用的全局服务或资源对象仍未释放,后面初始化不需要重新创建这些对象,因此加载更快。因此在APP启动时,预先初始化一个webview,然后释放。这样等用户真正使用到H5模块时,加载速度更快

2 webview 池:使用多个webview重复使用。弊端:页面跳转时清除上一个页面,若H5页面出现内存泄漏,影响其他页面。APP运行期间无法释放

预加载数据

webview初始化时并行请求数据。webview初始化需要一定时间,且无任何网络请求

实现:配置表注明某个离线包需要预加载的url,客户端在webview初始化同时发起预加载请求,并使用缓存机制。在webview初始化完成后判断预加载是否完成,如未完成则等待,如已完成,直接获取缓存,进行下一步操作

fallback

用户访问某个离线包某块是,发现服务器已有新版本,处理方案:

1 同步阻塞等待下载最新离线包。体验效果差,很多情况下离线包体积相对较大

2 依旧使用旧包。更新不及时,无法确保用户使用最新版本

3 对离线包做一个线上版本,不同版本离线包在服务器对应不同url地址。如发现有新版本时,直接访问新版本对应的线上地址。适当机会再更新离线包。离线包出错也可使用此方案

使用客户端接口

网路和存储接口如果使用 webkit 的 ajax 和 localStorage 会有不少限制,难以优化,可以在客户端提供这些接口给 JS,客户端可以在网络请求上做像 DNS 预解析/IP直连/长连接/并行请求等更细致的优化,存储也使用客户端接口也能做读写并发/用户隔离等针对性优化

服务端渲染

如今的H5页面,依赖JS的处理逻辑来决定最终渲染。如等待JS请求JSON数据等耗时操作。

优化方法:服务端直接返回渲染好的H5页面,https://mp.weixin.qq.com/s/evzDnTsHrAr2b9jcevwBzA

<meta name="format-detection" content="telephone=no,email=no,adress=no">

点击延迟

click通常会有大约300ms的延迟,同时包括链接的点击,表单的提交,控件的交互等任何用户点击行为

解决方案:使用fastclick/touchend一般可以解决这个问题

国际化

H5页面需要国家化,通常使用国家化插件。但在使用H5页面时,引入额外插件会增加客户端打包大小。轻量级方案:

1 提取语言文案

2 页面和JS中引用提取的文案

3 根据配置切换语言方案

屏蔽对HTML 内容自动识别

在 IOS webView 中默认会自动检测 HTML 中手机号、email、地址格式并标记。

解决方案:通过添加 meta 头来禁止默认行为

IOS的WKWebView 兼容

WKWebView 性能比 UIViewView 更优越。兼容性问题:

1 iOS8 不能在HTML文件中引用本地的css、js或图片文件,IOS8 以上正常。通过系统版本动态加载JS, IOS8以下使用网络资源,IOS8以上使用本地资源

2 iOS8中,使用一些远程的cdn的css或js文件,必须注意在引用标签上加上charset属性,否则 css 和 js 库将会乱码

内存泄漏问题

原因
WebView的开启会关联一个Activity,WebView执行的操作是在新线程。Activity的声明周期和WebView线程声明周期不一致,导致WebView一直持有Activity的引用

解决方法
开启独立进程,使用进程通信,开发中常用
动态添加WebView,并使用Context的弱引用。在Activity的布局中创建一个ViewGroup,通过add()的方式添加WebView,Activity停止时remove()

//WebView的加载
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
mWebView = new WebView(getApplicationContext());
mWebView.setLayoutParams(params);
mLayout.addView(mWebView);
//WebView的销毁
@Override
protected void onDestroy() {
    if (mWebView != null) {
        mWebView.loadDataWithBaseURL(null, "", "text/html", "utf-8", null);
        mWebView.clearHistory();

        ((ViewGroup) mWebView.getParent()).removeView(mWebView);
        mWebView.destroy();
        mWebView = null;
    }
    super.onDestroy();
}

优化总结

优化大体思路:缓存/预加载/并行,客户端缓存一切网络请求,尽量在用户打开之前就加载好所有内容,能并行做的事不串行做

优化方案由实际情况与需求来定:如外部接入的H5页面

其他还需优化的点:webview 本身的启动/渲染机制问题,响应流畅度问题。需要类 RN / Weex 等方案

猜你喜欢

转载自blog.csdn.net/mLuoya/article/details/87926781