WebView在混合开发中扮演着重要角色,使用的业务场景也十分复杂,因此出现的各种问题及效率优化需求也很多,当然google本身也在不断完善这个控件。本文总结几个常见问题:
在webveiw里加载网页,感觉比PC端慢好多。这是因为webview第一次使用时,需要先初始化浏览器内核,这一步大概耗时几百ms,然后才能继续后面的请求网址、建立连接等一系列操作。也就是说:在初始化webview时,后面的进程完全是阻塞的。那么如何优化呢?美团技术组给出了3种业界常用的方法:
1、全局WebView:在app刚启动时,就初始化一个全局的WebView待用,并隐藏;
当用到WebView时,直接使用这个WebView加载对应网页,并展示。
2、全局WebView+预加载资源:在Android 的BaseApplication里初始化一个WebView对象(用于加载常用的H5页面资源);当需使用这些页面时再从BaseApplication里取过来直接使用
全局WebView对应问题:
1)额外的内存消耗,一个webview大概占几十M的内存吧。
2)页面间跳转需要清空上一个页面的痕迹,更容易内存泄露。
3、客户端代理数据请求:在初始化webview的同时,使用native请求网页数据,然后将返回数据使用webview渲染出来。
从用户体验上下点功夫:
1、继承webview控件,加一个进度条,结合WebChromeClient提供的方法onProgressChanged,提示页面加载进度。
2、对于图片加载,可以设置 webView.getSettings().setLoadsImagesAutomatically(false);
然后在onPageFinished方法中,再设置为ture:webView.getSettings().setLoadsImagesAutomatically(true);
WebView加载过一次页面后,就会在本地留下缓存,利用缓存文件,可以提高以后的页面加载速度。
WebView自带的缓存机制有4种:
Cache-Control
与 Last-Modified
来实现控制缓存有效时间,以及在缓存失效后向服务查询是否有更新。 WebSettings settings = webView.getSettings();
String cacheDirPath = context.getFilesDir().getAbsolutePath()+"cache/";
settings.setAppCachePath(cacheDirPath); // 1. 设置缓存路径
settings.setAppCacheMaxSize(20*1024*1024); // 2. 设置缓存大小
settings.setAppCacheEnabled(true); // 3. 开启Application Cache存储机制
WebSettings settings = webView.getSettings();
settings.setDomStorageEnabled(true);
WebSettings settings = webView.getSettings();
settings.setJavaScriptEnabled(true);// 只需设置支持JS就自动打开IndexedDB存储机制
WebView使用缓存的模式:
// LOAD_CACHE_ONLY: 不使用网络,只读取本地缓存数据
// LOAD_NO_CACHE: 不使用缓存,只从网络获取数据.
// LOAD_DEFAULT: (默认)根据cache-control决定是否从网络上取数据。
// LOAD_CACHE_ELSE_NETWORK,只要本地有,无论是否过期,或者no-cache,都使用缓存中的数据。
WebView.getSettings().setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);
根据业务场景使用如下:
自定义缓存使用:
1、事先将更新频率较低、常用 & 固定的H5静态资源 文件(如JS、CSS文件、图片等) 放到本地;
2、使用shouldInterceptRequest
拦截H5页面的资源网络请求 并进行检测;
3、如果检测到本地具有相同的静态资源 ,就直接从本地读取进行替换,而不发送该资源的网络请求到服务器。
mWebview.setWebViewClient(new WebViewClient() {
// API 21 以下用shouldInterceptRequest(WebView view, String url)
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
// 假设要拦截对logo.gif的网络请求
if (url.contains("logo.gif")) {
InputStream is = null;
try {
is =getApplicationContext().getAssets().open("images/abc.png");
} catch (IOException e) {
e.printStackTrace();
}
WebResourceResponse response = new WebResourceResponse("image/png",
"utf-8", is);
return response;
}
return super.shouldInterceptRequest(view, url);
}
// API 21 以上用shouldInterceptRequest(WebView view, WebResourceRequest request)
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
if (request.getUrl().toString().contains("logo.gif")) {
InputStream is = null;
try {
is = getApplicationContext().getAssets().open("images/abc.png");
} catch (IOException e) {
e.printStackTrace();
}
WebResourceResponse response = new WebResourceResponse("image/png",
"utf-8", is);
return response;
}
return super.shouldInterceptRequest(view, request);
}
};
上述放到本地的静态资源的更新姿势:
对于HTML页面的加载与解析以及如何优化也需要仔细分析,这里不做详细探讨。
开启硬件加速会提升app的响应性能,尤其对WebView、ListView等各类集合View有明显效果。但是开启了硬件加速也会带来一些问题,一般表现为WebView的显示异常,比如:白屏、花屏、字符错乱等。因此当出现这些问题的时候,不妨从硬件加速角度排查一下。
Application级别
Activity级别
window级别(目前为止,Android还不支持在Window级别关闭硬件加速。)
getWindow().setFlags(
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
View级别
view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
WebView继承View,View中有三种layer type:
因此:
硬件加速开 + LAYER_TYPE_HARDWARE
硬件加速关 + LAYER_TYPE_SOFTWARE
在执行某些操作时,会导致webview闪烁,比如侧滑、上滑菜单出现/消失时,这时需要在滑动开始前后,开启/关闭硬件加速。
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
webview.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}
如果ViewPager里放了多个WebView,则除了首屏的Webview外,其余的webview点击网页上的按钮时,界面上没有交互效果,但是事件已执行。我使用onJsAlert方法测试的:
webView.setWebChromeClient(new WebChromeClient(){
@Override
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
Toast.makeText(getActivity(),message,Toast.LENGTH_SHORT).show();
result.cancel();
return true;
}
});
解决办法:继承Webview,重写onTouchEvent方法。如下:
//必须重载此构造函数,否则无法正确加载
public MyWebview(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
onScrollChanged(getScrollX(), getScrollY(), getScrollX(), getScrollY());
}
return super.onTouchEvent(event);
}
完全防止内存泄露,需要在Activity中初始化WebView.
mWebView = new WebView(getApplicationContext());
mWebView.setLayoutParams(params);
mLayout.addView(mWebView);
在 Activity 销毁 WebView 的时候,先让 WebView 加载null内容,然后移除 WebView,再销毁 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();
}
1、关闭本地密码存储功能。如果开启,密码会被明文保到 /data/data/com.package.name/databases/webview.db 中,这样就有被盗取密码的危险
mWebView.setSavePassword(false)
2、setAllowFileAccess 是否允许本地文件(file协议)的访问。
//对于不需要使用 file 协议的应用,禁用file 协议;
setAllowFileAccess(false);
//对于需要使用 file 协议的应用,禁止加载 JavaScript。
setAllowFileAccess(true);
if (url.startsWith("file://") {
setJavaScriptEnabled(false);
} else {
setJavaScriptEnabled(true);
}
3、setAllowFileAccessFromFileURLs 是否允许通过 file url 加载的js代码读取其他的本地文件 ,Android 4.1后默认禁止。
4、setAllowUniversalAccessFromFileURLs 是否允许通过 file url 加载的 js代码访问其他的源(包括http、https等源),Android 4.1后默认禁止.