Foundation Consolidation (5) Android interacts with Js through WebView

Introduction

WebView is a control based on the webkit engine to display web pages. Android's Webview uses different webkit version kernels in low and high versions, and directly uses Chrome after 4.4.

The role of webview is to:

  • Display and render the web interface
  • Directly use html files (on the network or local assets) as layouts
  • Can be called interactively with JavaScript

The WebView control is powerful. In addition to the properties and settings of the general View, it can also perform powerful processing on url requests, page loading, rendering, and page interaction.

Basic use of WebView

common method

WebView lifecycle/state switching

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

About Forward/Back Page

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

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

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

Common usage: Back key to control the back of the web page

  • Problem: On the premise of not doing any processing, click the system's "back button" when browsing the webpage, and the entire Brower will call finish() to end itself.
  • Solution: Process and consume the Back event in the current Activity.
public boolean onKeyDown(int keyCode, KeyEvent event) {
    
    
	if ((keyCode == KEYCODE_BACK) && mWebView.canGoBack()) {
    
     
		mWebView.goBack();
		return true;
	}
	return super.onKeyDown(keyCode, event);
}

clear cache data

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

common class

WebSettings class

Role: configure and manage WebView
The configuration steps are as follows:

  1. Add network access
<uses-permission android:name="android.permission.INTERNET"/>
  1. Generate a WebView component (there are two ways)
//方式1:直接在在Activity中生成
WebView webView = new WebView(this)

//方法2:在Activity的layout文件里添加webview控件:
WebView webview = (WebView) findViewById(R.id.webView1);
  1. For configuration - make use of the WebSetting subclass
//声明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");//设置编码格式

Common Usage: Setting WebView Cache

  • When loading an HTML page, WebView will generate two folders, database and cache, under the /data/data/package name directory.
  • The requested URL record is saved in WebViewCache.db, and the content of the URL is saved in the WebViewCache folder.
  • Whether to enable caching:
	//优先使用缓存: 
    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);

Combined use (offline loading)

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 缓存目录

Notice:Each Application only calls WebSettings.setAppCachePath(), WebSettings.setAppCacheMaxSize() once

WebViewClient class

WebViewClient is used to handle various notification events and request events.

1. Override url loading : shouldOverrideUrlLoading()
Function: When opening a web page, the system browser is not invoked, but displayed in this WebView; all loading on the web page goes through this method, and we can do many operations with this function.

//步骤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. When the page starts loading : onPageStarted()

Function: When the loading page is called, we can set a loading page to tell the user that the program is waiting for the network response.

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

3. When the page is loaded : onPageFinished()
Function: Called when the page is loaded, we can close the loading bar and switch the program action.

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

4. When loading resources
Function: It will be called when page resources are loaded, and each resource (such as a picture) will be loaded once.

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

5. When an error is received
Function: Called when an error occurs in the service loading the page, such as 404.

When an error such as 404 is encountered when using the webview control in the app, if the error prompt page in the browser is also displayed, it will look ugly, then our app needs to load a local error prompt at this time Page, that is, how webview loads a local page

//步骤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. When an SSL error is received : onReceivedSslError()
Function: Handle https requests
webView does not handle https requests by default, and the page is blank. The following settings need to be made:

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

WebChromeClient class

  • Function: Assist WebView to process JavaScript dialog boxes, website icons, website titles, etc.
    The commonly used methods are as follows:
    1. When the loading progress changes: onProgressChanged()
    function: obtain and display the loading progress of the web page
webview.setWebChromeClient(new WebChromeClient(){
    
    

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

2. When receiving the title of the web page:
Function: Get the title of the web page.
Every web page has a title. For example, the title of the page www.baidu.com is "Baidu, you will know", so how to know What about setting the title of the page currently being loaded by the webview?

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

Precautions

How to avoid WebView memory leaks

  1. Do not define Webview in xml, but create it in Activity when needed, and use getApplicationgContext() for Context
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. When the Activity is destroyed (WebView), let the WebView load null content first, then remove the WebView, then destroy the WebView, and finally leave it empty.
@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();
    }

Use Cases

Goal: To display "www.baidu.com", get its title, prompt loading start & end, and get loading progress
Specific implementation:

1. Add network access permission
manifest.xml:

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

Make native interact with Js through WebView

Android and JS call each other's methods through WebView, which is actually:

  1. Android to call JS code
  2. JS to call Android code

The bridge between the two is WebView

Android calls JS code through WebView

There are two ways for Android to call JS code:

  1. Through WebView's loadUrl()
  2. via WebView's evaluateJavascript()

insert image description here

Method 1: Through WebView's loadUrl()

Example introduction: Click the Android button to call callJS() in WebView JS (the text name is javascript)

The specific implementation steps are as follows:

  1. Put the js code that needs to be called into src/main/ in .html format

Note: The local js code is called here, and the remote js code is called more in actual use, just change the path of loading js to 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. Call JS code through WebView settings in Android

Special attention: the js code call must be called after the onPageFinished() callback, otherwise it will not be called.
onPageFinished() belongs to the method of the WebViewClient class, which is mainly called when the page load ends

 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方法名要对应上
                        // 调用javascript的callJS()方法
                        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;
            }

        });
    }
}

Method 2: through WebView's evaluateJavascript()

This method is more efficient and concise than using loadUrl(), because the execution of this method will not refresh the page, and loadUrl actually calls the HTML file, using the js code operation in HTML.

For specific use, you only need to replace the loadUrl() in the first method with the following method;

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

JS calls Android code through WebView

There are 3 ways to call Android code from JS:

  1. Object mapping through addJavascriptInterface() of webview
  2. Intercept url through WebViewClient's shouldOverrideUrlLoading () method callback
  3. Intercept JS dialog alert(), confirm(), prompt() messages through WebChromeClient's onJsAlert(), onJsConfirm(), onJsPrompt() method callbacks

insert image description here

Object mapping via WebView's addJavascriptInterface()

Step 1: Define an Android class that maps to JS objects: AndroidtoJs

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

Step 2: Put the JS code that needs to be called into the src/main/assets folder in .html format

<!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>

Step 3: In Android, set the mapping between Android class and JS code through WebView 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");

Intercept url through WebViewClient method shouldOverrideUrlLoading () callback

Concrete principle:

  1. Android through the callback method of WebViewClient
  2. shouldOverrideUrlLoading () intercepts the url to parse the protocol of the url
  3. If it is detected that it is a pre-agreed protocol, call the corresponding method


Step 1: Put the following HTML:javascript.html file in the src/main/assets folder for the Url protocol required by the JS convention

<!DOCTYPE html>
<html>

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

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

When the JS is loaded through Android's mWebView.loadUrl("file:///android_asset/javascript.html"), it will call back shouldOverrideUrlLoading(), and then continue to step 2:

Step 2: Override shouldOverrideUrlLoading() via WebViewClient on Android

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);
                                      }
                                  }
        );
   }
        }


The disadvantage of this method is that js calls Android, but it is very complicated to get the return value. If JS wants to get the return value of the Android method, it can only execute the JS method through WebView's loadUrl() to pass the return value back. The relevant code is as follows:

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

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

Intercept JS dialog alert(), confirm(), prompt() messages through WebChromeClient's onJsAlert(), onJsConfirm(), onJsPrompt() method callbacks

In JS, there are three commonly used dialog methods:
insert image description here
The principle of method 3: Android intercepts JS dialog boxes through the onJsAlert(), onJsConfirm(), and onJsPrompt() method callbacks of WebChromeClient.

The following are the specific steps, taking intercepting the prompt() method as an example:

Step 1: Load js code.
Put the javascript.html format in the src/main/assets folder

<!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>

When the above JS code is loaded using mWebView.loadUrl("file:///android_asset/javascript.html"), the callback onJsPrompt() will be triggered.

Step 2: Override onJsPrompt() via WebChromeClient on Android

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 caching mechanism and preloading

Performance issues with WebView

  1. The loading speed of the webview H5 page is relatively slow, because the H5 page requires a lot of resources and the network requests a large amount of data.
  2. The slow rendering speed of H5 pages depends on the js parsing efficiency and the performance of mobile phone hardware devices.
  3. It consumes a lot of traffic, and the H5 page in the webview needs to be reloaded every time it is used, resulting in the need to send a large number of serial network requests.

solution

  • Front-end H5 caching mechanism (webview comes with it)
  • resource preloading
  • resource interception

webview caching mechanism

  1. browser caching mechanism
  2. Application Cache caching mechanism
  3. Dom Storage caching mechanism
  4. Web SQL Database caching mechanism
  5. Indexed Database caching mechanism

browser cache

Cache-ControlA mechanism to control file caching according to the (or Expires) and Last-Modified(or Etag) fields in the HTTP protocol header

  1. Cache-Control: Used to control the effective duration of the local cache of the file:

For example, if the server returns a packet: Cache-Control:max-age=600, it means that the file should be cached locally, and the effective duration is 600 seconds (calculated from the time the request is made). In the next 600 seconds, if there is a request for this resource, the browser will not send an HTTP request, but will directly use the locally cached file.

  1. Expires: The same function as Cache-Control, that is, to control the effective time of the cache

Expires is a field in the HTTP1.0 standard, and Cache-Control is a newly added field in the HTTP1.1 standard
. When these two fields appear at the same time, Cache-Control has a higher priority

  1. Last-Modified: identifies the latest update time of the file on the server

When the next request is made, if the file cache expires, the browser will send this time to the server through the If-Modified-Since field, and the server will compare the timestamp to determine whether the file has been modified. If there is no modification, the server returns 304 to tell the browser to continue using the cache; if there is a modification, it returns 200 and returns the latest file.

  1. Etag: The function is the same as Last-Modified, which identifies the latest update time of the file on the server.

The difference is that the value of Etag is a character string to identify the file.
When querying the server whether the file has been updated, the browser sends the characteristic string to the server through the If-None-Match field, and the server matches the latest characteristic string of the file to determine whether the file has been updated: no update returns packet 304 , there is an update package 200
Etag and Last-Modified, you can use one or both at the same time according to your needs. When the two are used at the same time, as long as one of the conditions in the base is met, the file is considered not to be updated.

Common usage is:

  • Cache-Control is used together with Last-Modified;
  • Expires is used with Etag;

That is, one is used to control the effective time of the cache, and the other is used to query the service for updates after the cache expires.

Cache-Control, Last-Modified, Expires, and Etag are all automatically implemented in webview

Application Cache caching mechanism

  • Application Cache is a caching mechanism specially developed for offline use of web APP, used to store static files such as js, css, font files
  • The file is cached in units, and the file has a certain update mechanism (similar to the browser cache mechanism)
  • The principle of AppCache has two key points: manifest attribute and manifest file.
<!DOCTYPE html>
<html manifest="demo_html.appcache">
// HTML 在头中通过 manifest 属性引用 manifest 文件
// manifest 文件:就是上面以 appcache 结尾的文件,是一个普通文件文件,列出了需要缓存的文件
// 浏览器在首次加载 HTML 文件时,会解析 manifest 属性,并读取 manifest 文件,获取 Section:CACHE MANIFEST 下要缓存的文件列表,再对文件缓存
<body>
...
</body>
</html>

The principle is as follows:

  • AppCache also has an update mechanism after it is first loaded and generated. If the cached file needs to be updated, the manifest file needs to be updated
  • Because the next time the browser loads, in addition to using the cache by default, it will also check whether the manifest file has been modified in the background (byte by byte)
  • If any modification is found, the manifest file will be reacquired, and the file list under Section: CACHE MANIFEST will be checked and updated. Checking and updating the manifest file and the cached file will also follow the browser caching mechanism.
  • If the user manually clears the AppCache cache, the browser will regenerate the cache next time it loads, which can also be regarded as a cache update. AppCache cache files are stored separately from browser cache files, because AppCache has 5MB locally ( sub-HOST) space limit.

The specific implementation is realized through the WebSettings class of webview:

// 通过设置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 caching mechanism

The Dom Storage mechanism is similar to Android's SharedPreference mechanism.

DOM Storage is divided into sessionStorage & localStorage; the usage methods of the two are basically the same, the difference is that the scope of action is different:

  • a. sessionStorage: It is temporary, that is, it stores data related to the page, and it cannot be used after the page is closed
  • b. localStorage: It has persistence, that is, the saved data can also be used after the page is closed.
// 通过设置 `WebView`的`Settings`类实现
WebSettings settings = getSettings();

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

Web SQL Database caching mechanism

SQL-based database storage mechanism.
According to the official statement, the Web SQL Database storage mechanism is no longer recommended (no longer maintained)
and replaced by the IndexedDB cache mechanism, which will be described in detail below

IndexedDB caching mechanism

It belongs to the NoSQL database and is provided by storing the Key-Value pairs of strings, which is similar to the key-value storage method of the Dom Storage storage mechanism.

It is used to store complex and large-volume structured data.

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

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

resource preloading

Load the H5 pages to be used in advance, that is, build the cache in advance.
It is recommended to use this solution for the Android homepage:

Preloading H5 resources:
mainly divided into the following two aspects:

  1. The WebView object used for the first time

    • Initializing the webview for the first time will be much slower than the second time. Even if the webview is released after initialization, some global services and resource objects shared by the webview are still not released, making the second loading faster.
    • Based on this phenomenon, a global webview object can be initialized when the application starts, that is, a webview object is initialized in Android's BaseApplication.
  2. Subsequent use of the WebView object

    • Creating webview objects multiple times will consume a lot of time and resources. You can build your own webview reuse pool, such as using 2 or more webviews for reuse, without having to reuse each time you open H5.

Build your own cache

  1. Put the low-frequency, commonly used & fixed H5 static resource files (such as JS, CSS files, pictures, etc.) locally in advance
  2. Intercept resource network requests of H5 pages and detect them
  3. If it is detected that the local has the same static resource, it will be replaced directly from the local read without sending a network request for the resource to the server to obtain

Rewrite the shouldInterceptRequest method of WebViewClient to intercept when accessing these static resources to the server, and replace them with local resources if the same resources are detected:

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

        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);
            }
    });
}

Guess you like

Origin blog.csdn.net/baiduwaimai/article/details/130992041