本文介绍: WebView是一个基于webkit引擎、展现web页面控件。Android的Webview在低版本和高版本采用了不同的webkit版本内核,4.4后直接使用了Chrome。显示和渲染web界面直接使用html文件网络上或者本地asset)作为布局可与JavaScript交互调用WebView控件功能强大,除了具有一般View的属性设置外,还可以url请求页面加载渲染、页面交互进行强大的处理

简介

WebView是一个基于webkit引擎、展现web页面的控件。Android的Webview在低版本和高版本采用了不同的webkit版本内核,4.4后直接使用了Chrome。

webview的作用在于:

WebView控件功能强大,除了具有一般View的属性设置外,还可以url请求、页面加载渲染、页面交互进行强大的处理

WebView基本使用

常用方法

WebView的生命周期 / 状态切换

//激活WebView为活跃状态,能正常执行网页响应
webView.onResume()//当页面被失去焦点被切换到后台不可见状态需要执行onPause
//通过onPause动作通知内核暂停所有的动作,比如DOM的解析plugin执行、JavaScript执行
webView.onPause()//当应用程序(存在webview)被切换到后台时,这个方法不仅仅针对当前webview而是全局的全应用程序webview
//它会暂停所有webview的layoutparsingjavascripttimer。降低CPU功耗。
webView.pauseTimers()
//恢复pauseTimers状态
webView.resumeTimers()//销毁Webview
//在关闭了Activity时,如果Webview音乐视频,还在播放。就必须销毁Webview
//但是注意:webview调用destory时,webview绑定在Activity上
//这是由于自定义webview构建时传入了该Activity的context对象
//因此需要先从父容器移除webview,然后再销毁webview:
rootLayout.removeView(webView); 
webView.destroy();

关于前进 / 后退网页

//是否可以后退
Webview.canGoBack() 
//后退网页
Webview.goBack()

//是否可以前进                     
Webview.canGoForward()
//前进网页
Webview.goForward()

//以当前index为起始点前进或者后退到历史记录指定steps
//如果steps为负数则为后退,正数则为前进
Webview.goBackOrForward(intsteps) 

常见用法:Back控制网页后退

public boolean onKeyDown(int keyCode, KeyEvent event) {
	if ((keyCode == KEYCODE_BACK) && mWebView.canGoBack()) { 
		mWebView.goBack();
		return true;
	}
	return super.onKeyDown(keyCode, event);
}

清除缓存数据

//清除网页访问留下的缓存
//由于内核缓存是全局的因此这个方法不仅仅针对webview而是针对整个应用程序.
Webview.clearCache(true);
//清除当前webview访问的历史记录
//只会webview访问历史记录里的所有记录除了当前访问记录
Webview.clearHistory()//这个api仅仅清除自动完成填充的表单数据,并不会清除WebView存储本地的数据
Webview.clearFormData()

常用类

WebSettings

作用:对WebView进行配置管理
配置步骤如下:

  1. 添加网络访问权限
<uses-permission android:name="android.permission.INTERNET"/>
  1. 生成一个WebView组件(有两种方式
//方式1:直接在在Activity中生成
WebView webView = new WebView(this)

//方法2:在Activity的layout文件添加webview控件:
WebView webview = (WebView) findViewById(R.id.webView1);
  1. 进行配置-利用WebSetting子类
//声明WebSettings子类
WebSettings webSettings = webView.getSettings();

//如果访问的页面中要与Javascript交互,则webview必须设置支持Javascript
webSettings.setJavaScriptEnabled(true);  

//支持插件
webSettings.setPluginsEnabled(true); 

//设置适应屏幕,两者合用
webSettings.setUseWideViewPort(true); //将图片调整到适合webview的大小 
webSettings.setLoadWithOverviewMode(true); // 缩放屏幕大小

//缩放操作
webSettings.setSupportZoom(true); //支持缩放默认true。是下面那个的前提。
webSettings.setBuiltInZoomControls(true); //设置内置的缩放控件。若为false,则该WebView不可缩放
webSettings.setDisplayZoomControls(false); //隐藏原生的缩放控件

//其他细节操作
webSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK); //关闭webview中缓存 
webSettings.setAllowFileAccess(true); //设置可以访问文件 
webSettings.setJavaScriptCanOpenWindowsAutomatically(true); //支持通过JS打开窗口 
webSettings.setLoadsImagesAutomatically(true); //支持自动加载图片
webSettings.setDefaultTextEncodingName("utf-8");//设置编码格式
常见用法:设置WebView缓存
	//优先使用缓存: 
    WebView.getSettings().setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK); 
        //缓存模式如下:
        //LOAD_CACHE_ONLY: 不使用网络只读本地缓存数据
        //LOAD_DEFAULT: (默认)根据cache-control决定是否网络上取数据。
        //LOAD_NO_CACHE: 不使用缓存,只从网络获取数据.
        //LOAD_CACHE_ELSE_NETWORK,只要本地有,无论是否过期,或者no-cache,都使用缓存中的数据。

	//不使用缓存: 
	WebView.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE);

结合使用(离线加载)

if (NetStatusUtil.isConnected(getApplicationContext())) {
    webSettings.setCacheMode(WebSettings.LOAD_DEFAULT);//根据cache-control决定是否网络上取数据。
} else {
    webSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);//没网,则从本地获取,即离线加载
}

webSettings.setDomStorageEnabled(true); // 开启 DOM storage API 功能
webSettings.setDatabaseEnabled(true);   //开启 database storage API 功能
webSettings.setAppCacheEnabled(true);//开启 Application Caches 功能

String cacheDirPath = getFilesDir().getAbsolutePath() + APP_CACAHE_DIRNAME;
webSettings.setAppCachePath(cacheDirPath); //设置  Application Caches 缓存目录

注意: 每个 Application 只调用一次 WebSettings.setAppCachePath(),WebSettings.setAppCacheMaxSize()

WebViewClient类

WebViewClient用于处理各种通知事件和请求事件

1、重载url加载shouldOverrideUrlLoading()
作用:打开网页时不调用系统浏览器, 而是在本WebView中显示;在网页上的所有加载都经过这个方法,这个函数我们可以做很多操作

//步骤1. 定义Webview组件
Webview webview = (WebView) findViewById(R.id.webView1);

//步骤2. 选择加载方式
  //方式1. 加载一个网页:
  webView.loadUrl("http://www.google.com/");

  //方式2:加载apk包中的html页面
  webView.loadUrl("file:///android_asset/test.html");

  //方式3:加载手机本地html页面
   webView.loadUrl("content://com.android.htmlfileprovider/sdcard/test.html");

//步骤3. 复写shouldOverrideUrlLoading()方法,使得打开网页时不调用系统浏览器, 而是在本WebView中显示
    webView.setWebViewClient(new WebViewClient(){
      @Override
      public boolean shouldOverrideUrlLoading(WebView view, String url) {
          view.loadUrl(url);
      return true;
      }
  });

2、当页面开始加载:onPageStarted()

作用:开始载入页面调用的,我们可以设定一个loading的页面,告诉用户程序等待网络响应

 webView.setWebViewClient(new WebViewClient(){
      @Override
      public void  onPageStarted(WebView view, String url, Bitmap favicon) {
         //设定加载开始的操作
      }
  });

3、当页面加载完毕:onPageFinished()
作用:在页面加载结束时调用我们可以关闭loading条,切换程序动作

    webView.setWebViewClient(new WebViewClient(){
      @Override
      public void onPageFinished(WebView view, String url) {
         //设定加载结束的操作
      }
  });

4、当加载资源
作用:在加载页面资源时会调用,每一个资源(比如图片)的加载都会调用一次。

    webView.setWebViewClient(new WebViewClient(){
      @Override
      public boolean onLoadResource(WebView view, String url) {
         //设定加载资源操作
      }
  });

5、当接收错误
作用:当加载页面的服务出现错误时调用,比如404.

App里面使用webview控件的时候遇到了诸如404这类的错误的时候,若也显示浏览器里面的那种错误提示页面就显得很丑陋了,那么这个时候我们app需要加载一个本地错误提示页面,即webview如何加载一个本地的页面

//步骤1:写一个html文件error_handle.html),用于出错时展示用户看的提示页面
//步骤2:将该html文件放置到代码根目录的assets文件夹下

//步骤3:复写WebViewClient的onRecievedError方法
//该方法传回了错误码,根据错误类型可以进行不同的错误分类处理
    webView.setWebViewClient(new WebViewClient(){
      @Override
      public void onReceivedError(WebView view, int errorCode, String description, String failingUrl){
switch(errorCode)
                {
                case HttpStatus.SC_NOT_FOUND:
                    view.loadUrl("file:///android_assets/error_handle.html");
                    break;
                }
            }
        });

6、当接收到SSL错误:onReceivedSslError()
作用:处理https请求
webView默认是不处理https请求的,页面显示空白,需要进行如下设置:

webView.setWebViewClient(new WebViewClient() {    
        @Override    
        public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {    
            handler.proceed();    //表示等待证书响应
        // handler.cancel();      //表示挂起连接,为默认方式
        // handler.handleMessage(null);    //可做其他处理
        }    
    });    

WebChromeClient类

  • 作用:辅助WebView处理JavaScript的对话框网站图标、网站标题等等。
    常用方法如下:
    1、当加载进度变化: onProgressChanged()
    作用:获得网页的加载进度并显示
webview.setWebChromeClient(new WebChromeClient(){

      @Override
      public void onProgressChanged(WebView view, int newProgress) {
          if (newProgress < 100) {
              String progress = newProgress + "%";
              progress.setText(progress);
            } else {
        }
    });

2、当收到web页面标题:
作用:获取Web页中的标题
每个网页的页面都有一个标题,比如www.baidu.com这个页面的标题即“百度一下,你就知道”,那么如何知道当前webview正在加载的页面的title并进行设置呢?

webview.setWebChromeClient(new WebChromeClient(){
    @Override
    public void onReceivedTitle(WebView view, String title) {
       titleview.setText(title)}

注意事项

如何避免WebView内存泄露

  1. 不在xml定义 Webview ,而是在需要的时候在Activity中创建,并且Context使用 getApplicationgContext()
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
        mWebView = new WebView(getApplicationContext());
        mWebView.setLayoutParams(params);
        mLayout.addView(mWebView);

  1. 在 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();
    }

使用案例

目标:实现显示“www.baidu.com”、获取其标题、提示加载开始 &amp; 结束和获取加载进度
具体实现

1、添加网络访问权限
manifest.xml

<uses-permission android:name="android.permission.INTERNET"/>

通过WebView使得native与Js交互

Android与JS通过WebView互相调用方法,实际上是:

  1. Android去调用JS的代码
  2. JS去调用Android的代码

两者沟通的桥梁是WebView

Android通过WebView调用JS代码

Android调用JS代码的方法有2种:

  1. 通过WebView的loadUrl()
  2. 通过WebView的evaluateJavascript()

在这里插入图片描述

方式1:通过WebView的loadUrl()

实例介绍点击Android按钮,即调用WebView JS(文本名为javascript)中callJS()

具体实现步骤如下:

  1. 将需要调用的js代码以.html格式放到src/main/

注:此处调用的是本地的js代码,实际使用时更多调用的是远程js代码,只需要把加载js路径改为url即可

// 文本名:javascript
<!DOCTYPE html>
<html>

   <head>
      <meta charset="utf-8">
      <title>Carson_Ho</title>
      
// JS代码
     <script>
// Android需要调用的方法
   function callJS(){
      alert("Android调用了JS的callJS方法");
   }
</script>

   </head>

</html>

  1. 在Android里通过WebView设置调用JS代码

特别注意:js代码调用一定要在onPageFinished()回调之后才能调用,否则不会调用。
onPageFinished()属于WebViewClient类的方法,主要在页面加载结束时调用

 public class MainActivity extends AppCompatActivity {

    WebView mWebView;
    Button button;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mWebView =(WebView) findViewById(R.id.webview);

        WebSettings webSettings = mWebView.getSettings();

        // 设置与Js交互的权限
        webSettings.setJavaScriptEnabled(true);
        // 设置允许JS弹窗
        webSettings.setJavaScriptCanOpenWindowsAutomatically(true);

        // 先载入JS代码
        // 格式规定为:file:///android_asset/文件名.html
        mWebView.loadUrl("file:///android_asset/javascript.html");

        button = (Button) findViewById(R.id.button);


        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 通过Handler发送消息
                mWebView.post(new Runnable() {
                    @Override
                    public void run() {

                        // 注意调用的JS方法名要对应上
                        // 调用javascriptcallJS()方法
                        mWebView.loadUrl("javascript:callJS()");
                    }
                });
                
            }
        });

        // 由于设置了弹窗检验调用结果,所以需要支持js对话框
        // webview只是载体,内容渲染需要使用webviewChromClient类去实现
        // 通过设置WebChromeClient对象处理JavaScript的对话框
        //设置响应js 的Alert()函数
        mWebView.setWebChromeClient(new WebChromeClient() {
            @Override
            public boolean onJsAlert(WebView view, String url, String message, final JsResult result) {
                AlertDialog.Builder b = new AlertDialog.Builder(MainActivity.this);
                b.setTitle("Alert");
                b.setMessage(message);
                b.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        result.confirm();
                    }
                });
                b.setCancelable(false);
                b.create().show();
                return true;
            }

        });
    }
}

方式2:通过WebView的evaluateJavascript()

这种方式要比使用loadUrl()更高效,更简洁一点儿,因为该方法的执行不会使得页面刷新,而loadUrl实际上我们调用了HTML文件,使用的是HTML中的js代码操作。

具体使用只需要将第一种方法中的loadUrl()换成下面的方法即可;

// 只需要将第一种方法的loadUrl()换成下面该方法即可
    mWebView.evaluateJavascript("javascript:callJS()", new ValueCallback<String>() {
        @Override
        public void onReceiveValue(String value) {
            //此处为 js 返回结果
        }
    });
}

JS通过WebView调用 Android 代码

对于JS调用Android代码的方法有3种:

  1. 通过webview的addJavascriptInterface()进行对象映射
  2. 通过WebViewClient 的shouldOverrideUrlLoading ()方法回调拦截 url
  3. 通过 WebChromeClient 的onJsAlert()、onJsConfirm()、onJsPrompt()方法回调拦截JS对话框alert()、confirm()、prompt() 消息

在这里插入图片描述

通过 WebView的addJavascriptInterface()进行对象映射

步骤1:定义一个与JS对象映射关系的Android类:AndroidtoJs

// 继承自Object类
public class AndroidtoJs extends Object {
    // 定义JS需要调用的方法
    // 被JS调用的方法必须加入@JavascriptInterface注解
    @JavascriptInterface
    public void hello(String msg) {
        System.out.println("JS调用了Android的hello方法");
    }
}

步骤2:将需要调用的JS代码以.html格式放到src/main/assets文件夹里

<!DOCTYPE html>
<html>
   <head>
      <meta charset="utf-8">
      <title>Carson</title>  
      <script>
         
        
         function callAndroid(){
        // 由于对象映射,所以调用test对象等于调用Android映射的对象,这里test可能有些突兀,实际上是下边activity我们声明的AndroidtoJs类型的对象名字
            test.hello("js调用了android中的hello方法");
         }
      </script>
   </head>
   <body>
      //点击按钮则调用callAndroid函数
      <button type="button" id="button1" onclick="callAndroid()"></button>
   </body>
</html>

步骤3:在Android里通过WebView设置Android类与JS代码的映射addJavascriptInterface()

public class MainActivity extends AppCompatActivity {

    WebView mWebView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mWebView = (WebView) findViewById(R.id.webview);
        WebSettings webSettings = mWebView.getSettings();

        // 设置与Js交互的权限
        webSettings.setJavaScriptEnabled(true);

        // 通过addJavascriptInterface()将Java对象映射到JS对象
        //参数1:Javascript对象名
        //参数2:Java对象名
        mWebView.addJavascriptInterface(new AndroidtoJs(), "test");//AndroidtoJS类对象映射到js的test对象

        // 加载JS代码
        // 格式规定为:file:///android_asset/文件名.html
        mWebView.loadUrl("file:///android_asset/javascript.html");

通过 WebViewClient 的方法shouldOverrideUrlLoading ()回调拦截 url

具体原理

  1. Android通过 WebViewClient 的回调方法
  2. shouldOverrideUrlLoading ()拦截 url解析该 url 的协议
  3. 如果检测到是预先约定好的协议,就调用相应方法

步骤1:在JS约定所需要的Url协议
将以下的HTML:javascript.html文件放在src/main/assets文件夹里

<!DOCTYPE html>
<html>

   <head>
      <meta charset="utf-8">
      <title>Carson_Ho</title>
      
     <script>
         function callAndroid(){
            /*约定的url协议为:js://webview?arg1=111&amp;arg2=222*/
            document.location = "js://webview?arg1=111&amp;arg2=222";
         }
      </script>
</head>

<!-- 点击按钮则调用callAndroid()方法  -->
   <body>
     <button type="button" id="button1" onclick="callAndroid()">点击调用Android代码</button>
   </body>
</html>

当该JS通过Android的mWebView.loadUrl(“file:///android_asset/javascript.html”)加载后,就会回调shouldOverrideUrlLoading (),接下来继续看步骤2:

步骤2:在Android通过WebViewClient复写shouldOverrideUrlLoading ()

public class MainActivity extends AppCompatActivity {

    WebView mWebView;
//    Button button;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mWebView = (WebView) findViewById(R.id.webview);

        WebSettings webSettings = mWebView.getSettings();

        // 设置与Js交互的权限
        webSettings.setJavaScriptEnabled(true);
        // 设置允许JS弹窗
        webSettings.setJavaScriptCanOpenWindowsAutomatically(true);

        // 步骤1:加载JS代码
        // 格式规定为:file:///android_asset/文件名.html
        mWebView.loadUrl("file:///android_asset/javascript.html");


// 复写WebViewClient类的shouldOverrideUrlLoading方法
mWebView.setWebViewClient(new WebViewClient() {
                                      @Override
                                      public boolean shouldOverrideUrlLoading(WebView view, String url) {

                                          // 步骤2:根据协议参数判断是否是所需要的url
                                          // 一般根据scheme(协议格式) & authority(协议名)判断(前两个参数
                                          //假定传入进来的 url = "js://webview?arg1=111&arg2=222"(同时也是约定好的需要拦截的)

                                          Uri uri = Uri.parse(url);                                 
                                          // 如果url的协议 = 预先约定的 js 协议
                                          // 就解析往下解析参数
                                          if ( uri.getScheme().equals("js")) {

                                              // 如果 authority  = 预先约定协议里的 webview,即代表都符合约定的协议
                                              // 所以拦截url,下面JS开始调用Android需要的方法
                                              if (uri.getAuthority().equals("webview")) {

                                                 //  步骤3:
                                                  // 执行JS所需要调用的逻辑
                                                  System.out.println("js调用了Android的方法");
                                                  // 可以在协议上带有参数并传递到Android上
                                                  HashMap<String, String> params = new HashMap<>();
                                                  Set<String> collection = uri.getQueryParameterNames();

                                              }

                                              return true;
                                          }
                                          return super.shouldOverrideUrlLoading(view, url);
                                      }
                                  }
        );
   }
        }


这种方式的缺点在于js调用了Android,但是如果想要获取返回值就很复杂。如果JS想要得到Android方法的返回值,只能通过 WebView 的 loadUrl ()去执行 JS 方法把返回值传递回去,相关的代码如下:

// Android:MainActivity.java
mWebView.loadUrl("javascript:returnResult(" + result + ")");

// JS:javascript.html
function returnResult(result){
    alert("result is" + result);
}

通过 WebChromeClient 的onJsAlert()、onJsConfirm()、onJsPrompt()方法回调拦截JS对话框alert()、confirm()、prompt() 消息

在JS中,有三个常用的对话框方法:
在这里插入图片描述
方式3的原理:Android通过 WebChromeClient 的onJsAlert()、onJsConfirm()、onJsPrompt()方法回调分别拦截JS对话框。

以下是具体步骤,以拦截prompt()方法为例:

步骤1:加载js代码。
将javascript.html格式放到src/main/assets文件夹里

<!DOCTYPE html>
<html>
   <head>
      <meta charset="utf-8">
      <title>Carson_Ho</title>
      
     <script>
        
	function clickprompt(){
    // 调用prompt()
    var result=prompt("js://demo?arg1=111&arg2=222");
    alert("demo " + result);
}

      </script>
</head>

<!-- 点击按钮则调用clickprompt()  -->
   <body>
     <button type="button" id="button1" onclick="clickprompt()">点击调用Android代码</button>
   </body>
</html>

当使用mWebView.loadUrl(“file:///android_asset/javascript.html”)加载了上述JS代码后,就会触发回调onJsPrompt()。

步骤2:在Android通过WebChromeClient复写onJsPrompt()

public class MainActivity extends AppCompatActivity {

    WebView mWebView;
//    Button button;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mWebView = (WebView) findViewById(R.id.webview);

        WebSettings webSettings = mWebView.getSettings();

        // 设置与Js交互的权限
        webSettings.setJavaScriptEnabled(true);
        // 设置允许JS弹窗
        webSettings.setJavaScriptCanOpenWindowsAutomatically(true);

// 先加载JS代码
        // 格式规定为:file:///android_asset/文件名.html
        mWebView.loadUrl("file:///android_asset/javascript.html");


        mWebView.setWebChromeClient(new WebChromeClient() {
                                        // 拦截输入框(原理同方式2)
                                        // 参数message:代表promt()的内容(不是url)
                                        // 参数result:代表输入框的返回值
                                        @Override
                                        public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
                                            // 根据协议的参数判断是否是所需要的url(原理同方式2)
                                            // 一般根据scheme(协议格式) & authority(协议名)判断(前两个参数)
                                            //假定传入进来的 url = "js://webview?arg1=111&arg2=222"(同时也是约定好的需要拦截的)

                                            Uri uri = Uri.parse(message);
                                            // 如果url的协议 = 预先约定的 js 协议
                                            // 就解析往下解析参数
                                            if ( uri.getScheme().equals("js")) {

                                                // 如果 authority  = 预先约定协议里的 webview,即代表都符合约定的协议
                                                // 所以拦截url,下面JS开始调用Android需要的方法
                                                if (uri.getAuthority().equals("webview")) {

                                                    //
                                                    // 执行JS所需要调用的逻辑
                                                    System.out.println("js调用了Android的方法");
                                                    // 可以在协议上带有参数并传递到Android上
                                                    HashMap<String, String> params = new HashMap<>();
                                                    Set<String> collection = uri.getQueryParameterNames();

                                                    //参数result:代表消息框的返回值(输入值)
                                                    result.confirm("js调用了Android的方法成功啦");
                                                }
                                                return true;
                                            }
                                            return super.onJsPrompt(view, url, message, defaultValue, result);
                                        }

// 通过alert()和confirm()拦截的原理相同,此处不作过多讲述

                                        // 拦截JS的警告框
                                        @Override
                                        public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
                                            return super.onJsAlert(view, url, message, result);
                                        }

                                        // 拦截JS的确认框
                                        @Override
                                        public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
                                            return super.onJsConfirm(view, url, message, result);
                                        }
                                    }
        );


            }

        }

WebView缓存机制与预加载

WebView存在的性能问题

  1. webview H5页面加载速度比较慢,因为H5页面需要的资源很多,网络请求数据量大。
  2. H5页面渲染速度慢,取决于js解析效率与手机硬件设备的性能。
  3. 耗费流量多,每次使用时都需要重新加载webview里边的H5页面,导致需要发送大量串行的网络请求。

解决方案

webview缓存机制

  1. 浏览器 缓存机制
  2. Application Cache 缓存机制
  3. Dom Storage 缓存机制
  4. Web SQL Database 缓存机制
  5. Indexed Database 缓存机制

浏览器缓存

根据 HTTP 协议头里的 Cache-Control(或 Expires)和 Last-Modified(或 Etag)等字段控制文件缓存的机制

  1. Cache-Control:用于控制文件在本地缓存有效时长:

服务器回包:Cache-Control:max-age=600,则表示文件在本地应该缓存,且有效时长是600秒(从发出请求算起)。在接下来600秒内,如果有请求这个资源浏览器不会发出 HTTP 请求,而是直接使用本地缓存的文件。

  1. Expires:与Cache-Control功能相同,即控制缓存的有效时间

Expires是 HTTP1.0 标准中的字段,Cache-Control 是 HTTP1.1 标准中新加的字段
当这两个字段同时出现时,Cache-Control 优先级较高

  1. Last-Modified:标识文件在服务器上的最新更新时间

下次请求时,如果文件缓存过期,浏览器通过 If-Modified-Since 字段带上这个时间,发送给服务器,由服务器比较时间戳来判断文件是否修改。如果没有修改,服务器返回304告诉浏览器继续使用缓存;如果有修改,则返回200,同时返回最新的文件。

  1. Etag:功能同Last-Modified ,即标识文件在服务器上的最新更新时间。

不同的是,Etag 的取值是一个对文件进行标识特征字串。
在向服务器查询文件是否更新时,浏览器通过If-None-Match 字段特征字串发送给服务器,由服务器和文件最新特征字串进行匹配,来判断文件是否更新没有更新回包304,有更新回包200
Etag 和 Last-Modified 可根据需求使用一个或两个同时使用。两个同时使用时,只要满足基中一个条件,就认为文件没有更新

常见的用法是:

  • Cache-Control与 Last-Modified 一起使用;
  • Expires与 Etag一起使用;

即一个用于控制缓存有效时间,一个用于在缓存失效后,向服务查询是否有更新

Cache-Control、 Last-Modified 、 Expires、 Etag都是webview内置自动实现

Application Cache 缓存机制

  • Application Cache是专门为 web APP 离线使用而开发的缓存机制,用于存储静态文件如js、css、字体文件
  • 以文件为单位进行缓存,且文件有一定更新机制(类似于浏览器缓存机制)
  • AppCache 原理有两个关键点:manifest 属性和 manifest 文件。
<!DOCTYPE html>
<html manifest="demo_html.appcache">
// HTML 在头中通过 manifest 属性引用 manifest 文件
// manifest 文件:就是上面以 appcache 结尾的文件,是一个普通文件文件,列出了需要缓存的文件
// 浏览器在首次加载 HTML 文件时,会解析 manifest 属性,并读取 manifest 文件,获取 Section:CACHE MANIFEST 下要缓存的文件列表,再对文件缓存
<body>
...
</body>
</html>

原理说明如下:

  • AppCache 在首次加载生成后,也有更新机制。被缓存的文件如果要更新,需要更新 manifest 文件
  • 因为浏览器在下次加载时,除了会默认使用缓存外,还会在后台检查 manifest 文件有没有修改(byte by byte)
  • 发现有修改,就会重新获取 manifest 文件,对 Section:CACHE MANIFEST 下文件列表检查更新manifest 文件与缓存文件的检查更新也遵守浏览器缓存机制
  • 用户手动清了 AppCache 缓存,下次加载时,浏览器会重新生成缓存,也可算是一种缓存的更新AppCache 的缓存文件,与浏览器的缓存文件分开存储的,因为 AppCache 在本地有 5MB(分 HOST)的空间限制

具体实现是 通过webview的WebSettings类来实现的:

// 通过设置WebView的settings来实现
WebSettings settings = getSettings();

String cacheDirPath = context.getFilesDir().getAbsolutePath()+"cache/";
settings.setAppCachePath(cacheDirPath);
// 1. 设置缓存路径

 settings.setAppCacheMaxSize(20*1024*1024);
// 2. 设置缓存大小

settings.setAppCacheEnabled(true);
// 3. 开启Application Cache存储机制

// 特别注意
// 每个 Application 只调用一次 WebSettings.setAppCachePath() 和
WebSettings.setAppCacheMaxSize()

Dom Storage 缓存机制

Dom Storage 机制类似于 Android 的 SharedPreference机制。

DOM Storage 分为 sessionStorage & localStorage; 二者使用方法基本相同,区别在于作用范围不同:

  • a. sessionStorage:具备临时性,即存储与页面相关的数据,它在页面关闭后无法使用
  • b. localStorage:具备持久性,即保存的数据在页面关闭后也可以使用。
// 通过设置 `WebView`的`Settings`类实现
WebSettings settings = getSettings();

settings.setDomStorageEnabled(true);
// 开启DOM storage

Web SQL Database 缓存机制

基于 SQL 的数据库存储机制。
根据官方说明,Web SQL Database存储机制不再推荐使用(不再维护)
取而代之的是 IndexedDB缓存机制,下面会详细介绍

IndexedDB 缓存机制

属于 NoSQL 数据库,通过存储字符串的 Key – Value 对来提供,类似于Dom Storage 存储机制 的keyvalue存储方式。

用于存储复杂、数据量大的结构化数据。

// 通过设置WebView的settings实现
        WebSettings settings = getSettings();

        settings.setJavaScriptEnabled(true);
        // 只需设置支持JS就自动打开IndexedDB存储机制
        // Android 在4.4开始加入对 IndexedDB 的支持,只需打开允许 JS 执行的开关就好了。

资源预加载

将需使用的H5页面提早加载,即提前构建缓存。
对于Android首页建议使用这种方案:

预加载H5资源
主要分为以下两个方面:

  1. 首次使用的WebView对象

    • 首次初始化webview会比第2次慢很多,初始化后即便webview被释放,但一些webview共用的全局服务和资源对象仍未释放,使得第2次加载会更快。
    • 基于这种现象,可以在应用启动时就初始化1个全局的webview对象,即在Android的BaseApplication中初始化1个webview对象。
  2. 后续使用的WebView对象

    • 多次创建webview对象会耗费很多时间资源,可以自身构建webview复用池,比如采用2个或者多个webview重复使用,而不需每次打开H5都复用

自身构建缓存

  1. 事先将更新频率较低、常用 & 固定的H5静态资源 文件(如JS、CSS文件、图片等) 放到本地
  2. 拦截H5页面的资源网络请求 并进行检测
  3. 如果检测到本地具有相同的静态资源 就 直接从本地读取进行替换 而 不发送该资源的网络请求 到 服务器获取

重写WebViewClient 的 shouldInterceptRequest 方法,当向服务器访问这些静态资源时进行拦截,检测到是相同的资源则用本地资源代替:

// 假设现在需要拦截一个图片的资源并用本地资源进行替代

        mWebview.setWebViewClient(new WebViewClient() {
            // 重写 WebViewClient  的  shouldInterceptRequest ()
            // API 21 以下用shouldInterceptRequest(WebView view, String url)
            // API 21 以上用shouldInterceptRequest(WebView view, WebResourceRequest request)
            // 下面会详细说明

             // API 21 以下用shouldInterceptRequest(WebView view, String url)
            @Override
            public WebResourceResponse shouldInterceptRequest(WebView view, String url) {

                // 步骤1:判断拦截资源的条件,即判断url里的图片资源的文件名
                if (url.contains("logo.gif")) {
                // 假设网页里该图片资源的地址为:http://abc.com/imgage/logo.gif
                // 图片的资源文件名为:logo.gif

                    InputStream is = null;
                    // 步骤2:创建一个输入

                    try {
                        is =getApplicationContext().getAssets().open("images/abc.png");
                        // 步骤3:获得需要替换的资源(存放在assets文件夹里)
                        // a. 先在app/src/main下创建一个assets文件夹
                        // b. 在assets文件夹里再创建一个images文件夹
                        // c. 在images文件夹放上需要替换的资源(此处替换的是abc.png图片)

                    } catch (IOException e) {
                        e.printStackTrace();
                    }

                    // 步骤4:替换资源
                    WebResourceResponse response = new WebResourceResponse("image/png",
                            "utf-8", is);
                    // 参数1:http请求里该图片的Content-Type,此处图片为image/png
                    // 参数2:编码类型
                    // 参数3:存放着替换资源的输入流(上面创建的那个)
                    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) {

               // 步骤1:判断拦截资源的条件,即判断url里的图片资源的文件名
                if (request.getUrl().toString().contains("logo.gif")) {
                // 假设网页里该图片资源的地址为:http://abc.com/imgage/logo.gif
                // 图片的资源文件名为:logo.gif

                    InputStream is = null;
                    // 步骤2:创建一个输入

                    try {
                        is = getApplicationContext().getAssets().open("images/abc.png");
                         // 步骤3:获得需要替换的资源(存放在assets文件夹里)
                        // a. 先在app/src/main下创建一个assets文件夹
                        // b. 在assets文件夹里再创建一个images文件夹
                        // c. 在images文件夹放上需要替换的资源(此处替换的是abc.png图片

                    } catch (IOException e) {
                        e.printStackTrace();
                    }

                    // 步骤4:替换资源
                    WebResourceResponse response = new WebResourceResponse("image/png",
                            "utf-8", is);
                    // 参数1:http请求里该图片的Content-Type,此处图片为image/png
                    // 参数2:编码类型
                    // 参数3:存放着替换资源的输入流(上面创建的那个)
                    return response;
                }
                return super.shouldInterceptRequest(view, request);
            }
    });
}

原文地址:https://blog.csdn.net/baiduwaimai/article/details/130992041

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。

如若转载,请注明出处:http://www.7code.cn/show_8507.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱:suwngjj01@126.com进行投诉反馈,一经查实,立即删除

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注