在移动开发中或多或少都会使用的WebView,这篇文章总结下自己开发过程中所遇到的坑。
一、WebView的那些坑
(1)如何获取到网页的标题与描述信息
mWebView.setWebChromeClient(new WebChromeClient() {
@Override
public void onReceivedTitle(WebView view, String title) {
super.onReceivedTitle(view, title);
mShareTitle = title; //获取到Html页面中的Title
}
});
@Override
public void onProgressChanged(WebView view, int newProgress)
{
//这个方法可以监测WebView进度条变化,可以通过这个方法加上炫酷进度条
super.onProgressChanged(view, newProgress);
}
mWebView.setWebViewClient(new WebViewClient(){
@Override
public void onPageFinished(WebView view, String url) {
// 在结束加载网页时会回调
// 获取页面内容
view.loadUrl("javascript:window.java_obj.showSource("+ "document.getElementsByTagName('html')[0].innerHTML);");
// 获取解析<meta name="share-description" content="获取到的值">
view.loadUrl("javascript:window.java_obj.showDescription("+ "document.querySelector('meta[name=\"share-description\"]').getAttribute('content')"+ ");");
super.onPageFinished(view, url);
}
});
注意: 在一些机型上面,Webview.goBack()后,这个方法不一定会调用,所以标题还是之前页面的标题。那么你就需要用一个ArrayList来保持加载过的url,一个HashMap保存url及对应的title.然后就是用WebView.canGoBack()来做判断处理了。
(2)内存泄露问题:
当activity执行生命周期的时候,这里需要注意的是在onDestroy的时候,需要销毁WebView,不然也会出现内存泄漏的。
@Override
protected void onPause() {
super.onPause();
if (webView != null) {
webView.onPause(); //通知内核尝试停止所有处理,如动画和地理位置,但是不能停止Js,如果想全局停止Js,可以调用pauseTimers()全局停止Js,调用onResume()恢复。
}
}
@Override
protected void onResume() {
super.onResume();
if (webView != null) {
webView.onResume();
}
}
@Override
protected void onDestroy() {
if (webView != null) {
webView.clearCache(true); //清空缓存
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
if (webViewLayout != null) {
webViewLayout.removeView(webView);
}
webView.removeAllViews();
webView.destroy();
}else {
webView.removeAllViews();
webView.destroy();
if (webViewLayout != null) {
webViewLayout.removeView(webView);
}
}
webView = null;
}
}
可以看到上面的onDestroy方法中对系统的版本进行了判断,那是因为我在不同的版本中进行了测试,如果低于5.0版本的WebView中,如果先在parent中remove了WebView,那WebView将无法进行destroy了,这样就会造成内存的泄漏,下来你们可以自己去尝试一下这个说法是不是正确的。
(3)Html界面与原生界面交互:
交互方法主要有两种,一种是通过JS方法,另外一种是通过截取Html的跳转链接,开发过程中可以与前端开发者定义好跳转方法文档,预先埋点,便于在各个活动中使用。
Js调用Android接口:
WebSettings settings = getSettings();
//默认是false 设置true允许和js交互
settings.setJavaScriptEnabled(true);
public class WebAppInterface {
Context mContext;
/** Instantiate the interface and set the context */
WebAppInterface(Context c) {
mContext = c;
}
/** Show a toast from the web page */
@JavascriptInterface
public void showToast(String toast) {
Toast.makeText(mContext, toast, Toast.LENGTH_SHORT).show();
}
}
SDK>=17(4.2)以上,必须添加@JavascriptInterface声明,然后通过addJavascriptInterface() 方式供Js调用如:
webView.addJavascriptInterface(new WebAppInterface(this), "android");
超级大坑:
记得有一次活动,发现调用Html的JS方法时, 7.0以上的手机正常调用,而7.0一下的手机调用无效,耗费了一整晚,最后发现是因为前端使用了ES6语法,低于7.0的WebView运行不了JS,所以。
最好不要用ES6语法
最好不要用ES6语法
最好不要用ES6语法
Android调用Js的接口:
可以通过webview.loadUrl(“javascript:JsMethod()”)方式加载Js接口,如果有参数,直接加到JsMethod()里面即可,这里不过多说明。
截取Html的跳转链接:
这里需要通过WebCromeClient常用方法:shouldOverrideUrlLoading(WebView view, String url)
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (Pattern.compile("webapp/login").matcher(url).find())
{
//根据定义好的协议去匹配,做相对应的动作
ActivityUtils.push(getActivity(), LoginAct.class);
return true; //返回true则拦截掉跳转
}
}
注:如果使用的是Post请求方式,则此方法不会被回调
通过localStorage传输数据给Html:
在打开活动H5中,通过localStorage传输给H5所需要的数据,比如登录状态,user_id等信息。这里需要用到WebCromeClient的onPageFinished(WebView view, String url)方法:
final String mobileType = "window.localStorage.setItem('mobileType','" + "2" + "')";
final String versionNumber = "window.localStorage.setItem('versionNumber','" + AppTools.getVersion() + "')";
"//Android版本在19以及以上
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
if (mobileType != null)
viewHolder.web_view.evaluateJavascript(mobileType, null);
if (versionNumber != null)
viewHolder.web_view.evaluateJavascript(versionNumber, null);
}
//Android版本在19以下
else{
viewHolder.web_view.loadUrl("Javascript:" + mobileType);
viewHolder.web_view.loadUrl("Javascript:" + versionNumber);
}
(4)5.0 以后的WebView加载的链接为Https开头,但是链接里面的内容,比如图片为Http链接,这时候,图片就会加载不出来,怎么解决?
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
// 两者都可以
webSetting.setMixedContentMode(webSetting.getMixedContentMode());
}
(5)WebView白屏:
WebView设置setLayerType(View.LAYER_TYPE_SOFTWARE,null); 示例代码如此下:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}
LAYER_TYPE_NONE:表明视图没有多余渲染层。
LAYER_TYPE_SOFTWARE:表明视图有一个软件渲染层。无论是否开启硬件加速,都会有一张
Bitmap(software layer),并在上面对 WebView 进行渲染。好处:在进行动画,使用software可以只画一次ViewTree,很省。不适合使用场景:View树经常更新时不要用。尤其是在硬件加速打开时,每次更新消耗的时间更多。因为渲染完这张Bitmap后还需要再把这张Bitmap渲染到hardware layer上面去。
LAYER_TYPE_HARDWARE:
表明视图有一个硬件渲染层。硬件加速关闭时,作用同software。硬件加速打开时会在FBO(Framebuffer Object)上做渲染,在进行动画时,View树也只需要画一次。
(6)加载速度慢
默认情况html代码下载到WebView后,webkit开始解析网页各个节点,发现有外部样式文件或者外部脚本文件时,会异步发起网络请求下载文件,但如果在这之前也有解析到image节点,那势必也会发起网络请求下载相应的图片。在网络情况较差的情况下,过多的网络请求就会造成带宽紧张,影响到css或js文件加载完成的时间,造成页面空白loading过久。解决的方法就是告诉WebView先不要自动加载图片,等页面finish后再发起图片加载。
public void int () {
if(Build.VERSION.SDK_INT >= 19) {
webView.getSettings().setLoadsImagesAutomatically(true);
} else {
webView.getSettings().setLoadsImagesAutomatically(false);
}
}
同时在WebView的WebViewClient实例中的onPageFinished()方法添加如下代码:
@Override
public void onPageFinished(WebView view, String url) {
if(!webView.getSettings().getLoadsImagesAutomatically()) {
webView.getSettings().setLoadsImagesAutomatically(true);
}
}