Bootstrap

WebView,我所遇到的那些坑


在移动开发中或多或少都会使用的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);
    }
}
;