WebView 的使用

作者: lijiankun24 | 来源:发表于2017-10-19 22:12 被阅读41次

    现在 Android 应用中都会多多少少嵌入一些 H5 页面,在 Android SDK 中有一个名为 WebView 的组件,它是一个 webkit 内核的高性能浏览器,在 Android 应用中的 H5 页面用它展示是非常合适的。本篇文章就介绍一下 WebView 相关的一些知识,相关代码放在 GitHub 的 WebViewPractice 的工程里。

    本篇文章主要介绍以下几点:

    1. WebView 的简单使用
    2. WebViewClient
    3. WebChromeClient
    4. WebViewClient 和 WebChromeClient 的区别
    5. Js 和 Java 代码的交互
    6. WebView 的缓存
    7. WebView 引起的内存泄露

    1. WebView 的简单使用

    如果只是想简单地使用 WebView,通过下面几步即可展示一般功能需求的 H5 页面。

    • AndroidManifest.xml 申请网络权限

    • 在 XML 布局文件中添加 WebView 控件,并在 Java 代码中得到该 WebView 的对象

        <!-- activity_main.xml -->
        <?xml version="1.0" encoding="utf-8"?>
        <LinearLayout
            xmlns:android="http://schemas.android.com/apk/res/android"
            android:id="@+id/ll_webview_root"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">
      
            ......
      
            <WebView
                android:id="@+id/webview"
                android:layout_width="match_parent"
                android:layout_height="match_parent"/>
      
            ......
      
        </LinearLayout>
      
          // MainActivity.java
          @Override
          protected void onCreate(Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              setContentView(R.layout.activity_main);
              WebView webView = (WebView) findViewById(R.id.webview);
              webView.getSettings().setJavaScriptEnabled(true);
              webView.setWebViewClient(new WebViewClient() {
      
                  @Override
                  public boolean shouldOverrideUrlLoading(WebView view, String url) {
                      view.loadUrl(url);
                      return true;
                  }
      
                  @TargetApi(21)
                  @Override
                  public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
                      view.loadUrl(request.getUrl().toString());
                      return true;
                  }
              });
              webView.loadUrl("https://ke.youdao.com");
          }
      
    • 如果访问的页面中含有 JavaScript 的代码,则需要设置 WebView 支持 Javascript。

          webView.getSettings().setJavaScriptEnabled(true);
      
    • 如果页面中含有连接,点击链接如果想继续在当前浏览器中浏览网页,则需要重写 WebView 的 WebViewClient 对象。

      webView.setWebViewClient(new WebViewClient() {
      
          @Override
          public boolean shouldOverrideUrlLoading(WebView view, String url) {
              view.loadUrl(url);
              return true;
          }
      
          @TargetApi(21)
          @Override
          public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
              view.loadUrl(request.getUrl().toString());
              return true;
          }
      });
      

      如果不重写 WebView 的对象 WebViewClient,点击页面中的连接则会在手机系统自带的浏览器中打开新的链接。

    2. WebViewClient

    在第一节中已经简单使用过 WebViewClient 这个类,还有其他一些常用的方法可以重写并使用,其含义如下所示:

    webView.setWebViewClient(new WebViewClient() {
    
        // 给应用接管处理某些 url 请求的机会,返回 true 则拦截该请求,返回 false 不拦截,已经被废弃
        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {
            view.loadUrl(url);
            return true;
        }
    
        // 给应用接管处理某些 url 请求的机会,返回 true 则拦截该请求,返回 false 不拦截,是上面方法的替代方法
        // WebResourceRequest 中含有请求的 url,请求方法和请求头等信息
        @TargetApi(21)
        @Override
        public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
            view.loadUrl(request.getUrl().toString());
            return true;
        }
    
        // 页面开始加载时回调
        @Override
        public void onPageStarted(WebView view, String url, Bitmap favicon) {
            super.onPageStarted(view, url, favicon);
        }
    
        // 页面结束加载时回调
        @Override
        public void onPageFinished(WebView view, String url) {
            super.onPageFinished(view, url);
        }
    
        // 在加载 url 对应的资源时会回调此方法
        @Override
        public void onLoadResource(WebView view, String url) {
            super.onLoadResource(view, url);
        }
    
        // 在加载 url 对应资源时会回调此方法,不同的是,可以通过返回值控制加载的数据。此方法已被废弃
        // 若返回 null,WebView 会正常加载该资源
        // 若返回 WebResourceResponse 类型的对象,则 WebView 会使用该对象
        // 需要注意的是,此方法不在 UI 线程中被调用
        @Override
        public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
            return super.shouldInterceptRequest(view, url);
        }
    
        // 是上面方法的替代方法,使用方法和上面方法一致
        @Override
        public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
            return super.shouldInterceptRequest(view, request);
        }
    
        // 加载资源出错时会被回调的方法
        @Override
        public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
            super.onReceivedError(view, request, error);
        }
    
        // 加载资源时 HTTP 请求出错会回调此方法
        @Override
        public void onReceivedHttpError(WebView view, WebResourceRequest request, WebResourceResponse errorResponse) {
            super.onReceivedHttpError(view, request, errorResponse);
        }
    
        // 请求 HTTPS 资源出错时会回调此方法
        @Override
        public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
            super.onReceivedSslError(view, handler, error);
        }
    
        // 拦截浏览器中的按键事件
        // 若返回 true,则拦截按键事件
        // 若返回 false,则由 WebView 处理该事件
        @Override
        public boolean shouldOverrideKeyEvent(WebView view, KeyEvent event) {
            return super.shouldOverrideKeyEvent(view, event);
        }
    
        // 当页面的缩放比例发生变化时会回调此方法
        @Override
        public void onScaleChanged(WebView view, float oldScale, float newScale) {
            super.onScaleChanged(view, oldScale, newScale);
        }
    });
    

    3. WebChromeClient

    同 WebViewClient 一样,通过为 WebView 设置 WebChromeClient 对象,并重写其中的一些方法可以对 WebView 的一些行为进行控制。

    webView.setWebChromeClient(new WebChromeClient() {
    
         // 网页的加载进度
         @Override
         public void onProgressChanged(WebView view, int newProgress) {
             L.i("onProgressChanged " + newProgress);
             super.onProgressChanged(view, newProgress);
         }
    
         // 接收到网页的 title
         @Override
         public void onReceivedTitle(WebView view, String title) {
             L.i("onReceivedTitle " + title);
             super.onReceivedTitle(view, title);
         }
    
         // 接收到网页的 icon
         @Override
         public void onReceivedIcon(WebView view, Bitmap icon) {
             super.onReceivedIcon(view, icon);
         }
    
         @Override
         public void onReceivedTouchIconUrl(WebView view, String url, boolean precomposed) {
             super.onReceivedTouchIconUrl(view, url, precomposed);
         }
    
         // 当 H5 页面中点击播放的 flash video 的全屏按钮时,会调用这个方法
         @Override
         public void onShowCustomView(View view, CustomViewCallback callback) {
             super.onShowCustomView(view, callback);
         }
    
         // 与 onShowCustomView() 对应的取消全屏时会调用的方法
         @Override
         public void onHideCustomView() {
             super.onHideCustomView();
         }
    
         @Override
         public void onShowCustomView(View view, int requestedOrientation, CustomViewCallback callback) {
             super.onShowCustomView(view, requestedOrientation, callback);
         }
    
         // http://www.cnblogs.com/ufreedom/p/4229590.html
         // 当创建新的 Window 时会调用此方法
         @Override
         public boolean onCreateWindow(WebView view, boolean isDialog, boolean isUserGesture, Message resultMsg) {
             return super.onCreateWindow(view, isDialog, isUserGesture, resultMsg);
         }
    
         // 和 onCreateWindow() 方法对应的,关闭 Window
         @Override
         public void onCloseWindow(WebView window) {
             super.onCloseWindow(window);
         }
    
         // 当 WebView 获取焦点时会调用此方法
         @Override
         public void onRequestFocus(WebView view) {
             super.onRequestFocus(view);
         }
    
         // 在 Js 代码中弹出 Alert 窗口时会调用此方法
         @Override
         public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
             return super.onJsAlert(view, url, message, result);
         }
    
         // 在 Js 代码中弹出 Confirm 窗口时会调用此方法
         @Override
         public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
             return super.onJsConfirm(view, url, message, result);
         }
    
         // 在 Js 代码中弹出 Prompt 窗口时会调用此方法
         @Override
         public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
             return super.onJsPrompt(view, url, message, defaultValue, result);
         }
    
         // 在 Js 加载之前会调用此方法
         @Override
         public boolean onJsBeforeUnload(WebView view, String url, String message, JsResult result) {
             return super.onJsBeforeUnload(view, url, message, result);
         }
    
         // 打印 Js 中的日志 console 信息,被废弃
         @Override
         public void onConsoleMessage(String message, int lineNumber, String sourceID) {
             super.onConsoleMessage(message, lineNumber, sourceID);
         }
    
         // 打印 Js 中的日志 console 信息,被废弃
         @Override
         public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
             return super.onConsoleMessage(consoleMessage);
         }
     });
    

    4. WebViewClient 和 WebChromeClient 的区别

    WebViewClient 主要帮助 WebView 处理一些网络请求方面的行为和操作,比如:各种资源的请求、请求资源出现错误、HTTPS请求出现错误的通知等。

    WebChromeClient 主要帮助 WebView 处理一些 Javascript 相关的一些细节,比如:各种对话框的弹出、Js 中日志信息的打印、Window 的创建和关闭、以及 title 和 icon 的接收等。

    5. Js 和 Java 代码的交互

    Java 代码和 Js 代码额相互调用已经老生常谈了,现在也有一些很优秀的开源框架,使用起来非常的方便,但是其原理都是一样的,下面就介绍一下 Js 代码和 Java 代码是如何进行交互的。

    5.1 Js 代码调用 Java 代码

    private class InnerClass {
    
        private Context mContext = null;
    
        public InnerClass(Context context) {
            mContext = context;
        }
    
        @JavascriptInterface
        public void toastMessage(String msg) {
            Toast.makeText(mContext, msg, Toast.LENGTH_SHORT).show();
        }
    }
    
    public class JSBridgeActivity extends AppCompatActivity {
    
        private WebView webView = null;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_jsbridge);
            initWebView();
        }
    
        private void initWebView() {
            webView = (WebView) findViewById(R.id.webView);
            webView.getSettings().setJavaScriptEnabled(true);
            webView.addJavascriptInterface(new InnerClass(JSBridgeActivity.this), "innerClass");
            webView.loadUrl("file:///android_asset/demo.html");
            webView.setWebChromeClient(new WebChromeClient());
       }
    }
    
    
    • 有名为 InnerClass 的类,其中的方法 toastMessage(String msg)@JavascriptInterface 注解所修饰
    • 设置 WebView 对象支持 JavaScript,并且调用 addJavascriptInterface(Object object, String name) 方法,将 InnerClass 的对象传入 WebView 对象中
    • 在 WebView 加载页面的 Js 代码中,即可通过 name 调用 InnerClass 对象的 toastMessage(String msg) 方法,如下所示:
    function testPrompt(){
          window.innerClass.toastMessage("jsbridge");
    }
    

    5.2 Java 代码调用 Js 代码

    假如有如下的 Js 方法,如下所示:

    function testConfirm(){
        var temp = "temp";
        confirm(temp);
    }
    

    在 Java 中可以通过如下方法调用该 Js 方法:

    webView.loadUrl("javascript:testConfirm()");
    

    注意

    • 在 Java 代码中调用 Js 代码弹出 alertconfirmprompt 对话框的时候,需要给 WebView 设置 WebChromeClient 对象才可以正常的弹出对话框,否则不会有效果。
    • Java 调用 Js 的代码需要在主线程中调用才会生效
    • 在 Activity 的 onCreate() 方法中直接调用 WebView.loadUrl() 方法是不会生效的,需要在WebView 加载完成之后再调用 Js 的代码,才会生效。

    6. WebView 的缓存

    6.1 CacheMode 缓存模式

    通过 WebView 的 WebSettings 对象可以设置 WebView 的 CacheMode 缓存策略,共有如下几个值可供设置:

    • LOAD_DEFAULT:默认设置,如果有本地缓存,且缓存有效未过期,则直接使用本地缓存,否则加载网络数据
    • LOAD_NORMAL:已被废弃,同 LOAD_DEFAULT 效果一样
    • LOAD_CACHE_ELSE_NETWORK:如果存在缓存时,不论缓存是否过期,都使用缓存;若缓存不存在,则从网络获取数据
    • LOAD_NO_CACHE:不使用缓存,只从网络获取数据
    • LOAD_CACHE_ONLY:不从网络获取数据,只使用缓存数据

    6.2 WebView 缓存路径

    我测试用的手机是小米 note,Android 系统是 4.4,WebView 缓存的文件存在于 /data/data/${package_name}/app_webview/cache/ 路径下

    WebViewCache.png

    6.3 H5 缓存机制

    H5 的缓存机制共有 5 种缓存机制,分别是:

    • 浏览器缓存机制:主要通过 HTTP 的协议头中的 Cache-Control(或 Expires)和 Last-Modified(或 Etag)字段控制控制文件的缓存。
    • Application Cache(AppCache)缓存机制:AppCache 缓存是对浏览器缓存的补充,不可替代。
    • Dom Storage 缓存机制:Dom Storage 提供 5MB 大小的缓存空间,以键值对的形式存取文件。
    • Web SQL Database 缓存机制:H5 提供了基于 SQL 的数据库存储机制,用于存储一些结构化的数据。
    • Indexed Database 缓存机制:不同于 SQL 数据库,属于 NoSQL 数据库。Indexed Database 存储的数据是以键值对的形式存储数据的。

    共有这 5 种缓存机制,值得详细说明,在此就不再做过多的说明,争取后续单独写一篇博客介绍缓存机制。

    7. WebView 引起的内存泄露

    Activity 中使用 WebView 有可能会出现内存泄露的情况,Android 5.1 WebView内存泄漏分析【Android】 WebView内存泄漏优化之路 这两篇文章已经介绍的很详细。

    Android 5.1 中,如果 WebViewdestroy() 方法在 onDetachedFromWindow() 方法之前被执行,则会出现 WebView 持有 Activity 的引用,从而导致内存泄露。

    为了避免 WebView 引起内存泄露,有两个比较有效的办法:

    • 使用代码 New 一个 WebView 对象,而不是在 xml 文件中静态写入一个 WebView
    • ActivityonDestory() 方法中,主动的将 WebView 从父容器中移除并销毁该 WebView 对象,如下所示:
    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (webView != null) {
            ViewParent viewParent = webView.getParent();
            if (viewParent != null) {
                ((ViewGroup) viewParent).removeView(webView);
            }
            webView.getSettings().setJavaScriptEnabled(false);
            webView.clearHistory();
            webView.clearView();
            webView.removeAllViews();
            webView.destroy();
        }
    }
    

    本文中涉及到的自定义类的源码都在 Github 上的 WebViewPractice 工程中。


    参考资料:

    Android 5.1 WebView内存泄漏分析 -- 永远即等待

    Android:手把手教你构建 WebView 的缓存机制 & 资源预加载方案 -- Carson_Ho

    【Android】 WebView内存泄漏优化之路 -- kaka's blog

    Android WebView 性能轻量优化

    WebView缓存原理分析和应用 -- UncleChen的博客

    相关文章

      网友评论

        本文标题:WebView 的使用

        本文链接:https://www.haomeiwen.com/subject/yhnpuxtx.html