Android Development Learning (10) WebView Access to Web Pages

Posted by Kisses on Wed, 05 Jun 2019 18:58:59 +0200

In many applications, H5 is also a good choice if you want to build your own applications quickly. On the App side, keep the entrance of H5, which is also the entrance to display web pages. This article will make an entrance to display web pages with you.
First, look at what we've achieved:

WebView

Functions implemented with H5 can be dynamically updated without upgrading App, and run simultaneously on App on Android or iOS, saving costs and improving development efficiency.
Principle: It's actually a call between Java code and JavaScript.

common method

loadUrl
Load interfaces, followed by LoadData and LoadDataWithBase methods

    //Load test under assets directory.htmlfile
    webView.loadUrl("file:///android_asset/test.html");
    //Load network resources (note adding network privileges)
    webView.loadUrl("http://blog.csdn.net");

setWebViewClient (If the user has set a WebViewClient, it will not jump to the system browser after clicking a new link, but will be displayed in this WebView instead.Note: You don't need to override the shouldOverrideUrlLoading method, you can also make all links open in WebView.)
WebViewClient is mainly used to assist WebView in handling various notifications, requests, and other events, which are set by the setWebViewClient method.Here are some of its common uses:
Implement the blocking of hyperlinks in web pages (for example, if it is the home page of other web pages, then directly block the go to Baidu home page):
When you click on a link in a page, the shouldOverrideUrlLoading method is called back before the WebView loads the URL. Typically, this method is called once by clicking on a link.

         webView.setWebViewClient(new WebViewClient(){
        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {
            if("http://www.jikedaohang.com/".equals(url))                   {
                view.loadUrl("https://www.baidu.com/");
            }

                        return true;
                    }
                });

Myth about shouldOverrideUrlLoading return value: Many online explanations are that return true opens a link in this WebView and return false opens a link on behalf of calling a system browser.In fact, as long as the WebViewClient is set, the system browser will not be invoked.
So what exactly does the return value of shouldOverrideUrlLoading represent?return true, the WebView will not reload this url when a new url is opened, all processing needs to be done in the WebView, including loading; return false, the system assumes that the upper layer has not processed, and will continue to load the url next; default return false.The specific differences are shown below:
After loading the Baidu homepage and setting up the WebViewClient, override the shouldOverrideUrlLoading(WebView view, String url) method, return to false click and normal jump) return to true click unresponsive, if you want to be able to jump, you need to do it yourself
It is also important to note that if we intercept a url, there is little difference between return false and return true, so it is generally recommended to return false.
Replace a resource when loading a web page (for example, when loading a web page, you need to load a logo picture, and we want to replace this logo picture with one from our assets directory)
We know that when we load a web page we also load js,css, pictures and other resources, so we call the shouldInterceptRequest method several times and we can replace pictures in shouldInterceptRequest.
Note: shouldInterceptRequest has two overloads:
public WebResourceResponse shouldInterceptRequest (WebView view, String url) [obsolete]
public WebResourceResponse shouldInterceptRequest (WebView view, WebResourceRequest request)
The main difference between these two methods is that the WebResourceRequest will be able to get more information by providing methods such as getUrl(),getMethod,getRequestHeaders, and so on.This is mainly to show the effect, using the first callback method.The implementation is as follows:

        mWebView.setWebViewClient(new WebViewClient(){
                    @Override
                    public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
                        WebResourceResponse response = null;
                        if (url.contains("logo")) {
                            try {
                                InputStream logo = getAssets().open("logo.png");
                                response = new WebResourceResponse("image/png", "UTF-8", logo);
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }
                        return response;
                    }
                });

Set handling when starting to load web pages, loading completes, loading errors

        webView.setWebViewClient(new WebViewClient() {    

            @Override  
            public void onPageStarted(WebView view, String url, Bitmap favicon) {  
                super.onPageStarted(view, url, favicon);  
                // Processing when starting to load a web page such as: Show"Load Tips" Load Dialog  
                ...
            }  

            @Override  
            public void onPageFinished(WebView view, String url) {  
                super.onPageFinished(view, url);  
                // Handle when the page is loaded, for example, to make the loading dialog disappear  
                ...
            }  

            @Override  
            public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {  
                super.onReceivedError(view, errorCode, description, failingUrl);  
                // Handle when a Web page fails to load, such as prompting for a failure or displaying a new interface
                ...
            }    
        });  

Processing https requests, setting WebView to process ssl certificates for WebView does not process https requests by default, requiring the onReceivedSslError function of the parent class to be overridden in the WebViewClient subclass

        webView.setWebViewClient(new WebViewClient() {    

            @Override  
            public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {  
                handler.proceed();  // Accept certificates that trust all websites  
                // handler.cancel(); //default action not handled  
                // handler.handleMessage(null); //Can do other processing  
            }   
        });   

setWebChromeClient
WebChromeClient is mainly used to assist WebView with JavaScript dialogs, site icons, site titles, and page loading progress.Set through the setWebChromeClient() method of WebView.
Displays the page loading progress Overrides the parent's onProgressChanged function in the WebChromeClient subclass. Programes represents the current page loading progress, an integer between 1 and 100

        webView.setWebChromeClient(new WebChromeClient() {    

            public void onProgressChanged(WebView view, int progress) {    
                setTitle("Please wait while the page loads..." + progress + "%");    
                setProgress(progress * 100);    

                if (progress == 100) {    
                    //... 
                }    
            }    
        });  

Speed up the loading of HTML pages (by default, when HTML code is downloaded to WebView, webkit starts parsing each node of the page, and asynchronously initiates a network request to download the file when an external style file or external script file is found, but if an image node is also parsed before that, it will also initiate a network request to download the corresponding picture.In poor network conditions, too many network requests can cause bandwidth constraints, affect the time when css or js files are loaded, and cause pages to be blank for too long.The solution is to tell WebView not to load pictures automatically and wait until the page finish es before launching a picture load.)
First add the following code when the WebView is initialized

        if(Build.VERSION.SDK_INT >= 19) {  
        /*Compatible with system API versions over 19.Because if more than 4.4 systems resume picture loading on onPageFinished, only one image tag will be loaded if multiple pictures refer to the same src, so for such systems we will load them directly first.*/        webView.getSettings().setLoadsImagesAutomatically(true);  
            } else {  
                webView.getSettings().setLoadsImagesAutomatically(false);  
            }  

Override the onPageFinished() method in the WebViewClient subclass of WebView to add the following code:

         @Override  
        public void onPageFinished(WebView view, String url) {  
            if(!webView.getSettings().getLoadsImagesAutomatically()) {  
                webView.getSettings().setLoadsImagesAutomatically(true);  
            }  
        }  

setDownloadListener
Usually the webview rendered interface contains a link to a downloadable file, after clicking the link, you should start the download and save the file locally.

Create DownloadListener

        class MyDownloadListenter implements DownloadListener{
              @Override
              public void onDownloadStart(String url, String userAgent,String contentDisposition, String mimetype, long contentLength) {
                  //There are two main ways to download tasks...
                  //(1) Custom download tasks
                  //(2) Modules that call the download of the system
                  Uri uri = Uri.parse(url);
                  Intent intent = new Intent(Intent.ACTION_VIEW, uri);
                  startActivity(intent);
              }
        }

Add monitoring to webview

        webview.setDownloadListener(new MyDownloadListenter());

goBack()
Return to the previous browse page by overriding the onKeyDown method to click the return key to return to the previous browse page instead of exiting the program

    public boolean onKeyDown(int keyCode, KeyEvent event) {  
    //Where webView.canGoBack() returns true if the webView contains a retractable browsing record

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

WebSettings Configuration

Get the WebSettings object

WebSettings webSettings = webView.getSettings();

Common setup methods
Supports js

        settings.setJavaScriptEnabled(true);

There are several main ways to set up a cache:
LOAD_CACHE_ONLY: Does not use a network, only reads locally cached data.
LOAD_DEFAULT: Determines whether to retrieve data from the network based on cache-control.
LOAD_CACHE_NORMAL: API level 17 is obsolete and works in the same LOAD_DEFAULT mode as API level 11.
LOAD_NO_CACHE: Do not use caching, only get data from the network.
LOAD_CACHE_ELSE_NETWORK: Use the data in the cache whenever it is available locally, whether it is expired or no-cache.

        settings.setCacheMode(WebSettings.LOAD_NO_CACHE);

Turn on the DOM storage API functionality (HTML5 provides a standard interface for storing key-value pairs locally, which can be manipulated via javascript after the page has been loaded.)

        settings.setDomStorageEnabled(true);

Setting the database cache path

        settings.setDatabasePath(cacheDirPath);

Set Application Caches Cache Directory

        settings.setAppCachePath(cacheDirPath);

Set default encoding

        settings.setDefaultTextEncodingName("utf-8");

Resize pictures to fit webview

        settings.setUseWideViewPort(false);

Supports zooming

        settings.setSupportZoom(true);

Supports content re-layout

        settings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.SINGLE_COLUMN);

Multiple windows

        settings.supportMultipleWindows();

Set Accessible Files

        settings.setAllowFileAccess(true);

Set node for webview when webview calls requestFocus

        settings.setNeedInitialFocus(true);

Set support for zooming

        settings.setBuiltInZoomControls(true);

Support opening new windows via JS

        settings.setJavaScriptCanOpenWindowsAutomatically(true);

Zoom to screen size

        settings.setLoadWithOverviewMode(true);

Supports automatic loading of pictures

        settings.setLoadsImagesAutomatically(true);

Callback for WebViewClient

WebViewClient is mainly used to assist WebView in handling various notifications, requests, and other events, which are set by the setWebViewClient method.

Update History

    doUpdateVisitedHistory(WebView view, String url, boolean isReload)

Application Rerequests Web Page Data

    onFormResubmission(WebView view, Message dontResend, Message resend)

Called when page resources are loaded, and each resource (such as a picture) is loaded once.

    onLoadResource(WebView view, String url)

Start the load page call, where we can usually set up a loading page to tell the user that the program is waiting for a network response.

    onPageStarted(WebView view, String url, Bitmap favicon)

Called at the end of page loading.Similarly, we know a page is loaded, so we can close the loading bar and switch program actions.

    onPageFinished(WebView view, String url)

Report Error Information

    onReceivedError(WebView view, int errorCode, String description, String failingUrl)

Get Return Information Authorization Request

    onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host,String realm)

Rewriting this method allows the webview to process https requests.

    onReceivedSslError(WebView view, SslErrorHandler handler, SslError error)

Called when the WebView changes

    onScaleChanged(WebView view, float oldScale, float newScale)

Called when the Key event is not loaded

    onUnhandledKeyEvent(WebView view, KeyEvent event)

Rewrite this method to handle key events in the browser.

    shouldOverrideKeyEvent(WebView view, KeyEvent event)

Called when a web page jumps, this function can do many things, such as we read some special URL s, so we can not open the address, cancel this operation, and do other pre-defined operations, which is necessary for a program.

    shouldOverrideUrlLoading(WebView view, String url)

Called multiple times when loading a resource for a Web page (obsolete)

    shouldInterceptRequest(WebView view, String url)

Called multiple times when loading a resource for a Web page

    shouldInterceptRequest(WebView view, WebResourceRequest request)

Be careful:
ShouOverrideUrlLoading is called when a page is jumped, and is typically called only once per jump.
ShouShouInterceptRequest is called whenever a web page is loaded. This method is called back when a resource is loaded, and it is called multiple times.

Callback for WebChoromeClient

WebChromeClient is mainly used to assist WebView with Javascript dialogs, site icons, site titles, and page loading progress.Set through the setWebChromeClient() method of WebView.

Monitor page loading progress

    onProgressChanged(WebView view, int newProgress)

Listen for web page title: For example, the title of Baidu page is "Baidu, you know"

    onReceivedTitle(WebView view, String title)

Listening Page Icon

    onReceivedIcon(WebView view, Bitmap icon)

Java and JavaScript interoperability

For demonstration purposes, use addJavascriptInterface to interact with local js (with vulnerabilities).It can also be implemented in other ways, such as intercepting ur for parameter resolution l.
Java Tuning JS

First is a piece of code for JS:

function javaCallJs(arg){
         document.getElementById("content").innerHTML =
             ("Welcome:"+arg );
    }

Then call the method in JS in java

webView.loadUrl("javascript:javaCallJs("+"'"+name+"'"+")");

The above code calls a method called javaCallJs(arg) in the JS and passes in a name parameter.
JS tuning java
Configure Javascript interface

webView.addJavascriptInterface(new JSInterface (),"Android");

Implementing Javascript interface classes

class JSInterface {
    @JavascriptInterface
     public void showToast(String arg){
                   Toast.makeText(MainActivity.this,arg,Toast.LENGTH_SHORT).show();
     }
}

Calling java code in JS

<input type="button" value="click Android Called" onclick="window.Android.showToast('JS Parameters passed in')"/>

The Android in window.Android.showToast('Parameters passed in JS') is specified in addJavascriptInterface (), and JS passes parameters to Java of type String.showToast(String arg) pops up this parameter as Toast.
The code is very simple and annotated, just look at it.
First, the local JavaAndJavaScriptCall.html file, placed in the asstes directory

<html>
<head>
    <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
    <script type="text/javascript">

    function javaCallJs(arg){
         document.getElementById("content").innerHTML =
             ("Welcome:"+arg );
    }

    </script>
</head>
<body>
    <div id="content"> Please enter your user name above</div>
    <input type="button" value="click Android Called" onclick="window.Android.showToast('JS Parameters passed in')"/>
</body>
</html>

javaCallJs is the method that java calls JS, showToast method is the method that JS calls java

Next comes the layout file, activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/ll_root"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:padding="20dp"
        android:background="#000088">
        <EditText
            android:id="@+id/et_user"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:hint="input WebView User name to display in"
            android:background="#008800"
            android:textSize="16sp"
            android:layout_weight="1"/>
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="40dp"
            android:layout_marginRight="20dp"
            android:textSize="16sp"
            android:text="Determine"
            android:onClick="click"/>
    </LinearLayout>

</LinearLayout>

Simply, an input box and a OK button call the method in the JS.

MainActivity

package com.wangjian.webviewdemo;

import android.annotation.SuppressLint;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.JavascriptInterface;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

    private WebView webView;
    private LinearLayout ll_root;
    private EditText et_user;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ll_root = (LinearLayout) findViewById(R.id.ll_root);
        et_user = (EditText) findViewById(R.id.et_user);
        initWebView();
    }

    //Initialize WebView

    private void initWebView() {
        //Dynamically create a WebView object and add it to LinearLayout
        webView = new WebView(getApplication());
        LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
        webView.setLayoutParams(params);
        ll_root.addView(webView);
        //Don't jump to another browser
        webView.setWebViewClient(new WebViewClient() {
            @Override
            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                view.loadUrl(url);
                return true;
            }
        });
        WebSettings settings = webView.getSettings();
        //Support JS
        settings.setJavaScriptEnabled(true);
        //Load local html file
        webView.loadUrl("file:///android_asset/JavaAndJavaScriptCall.html");
        webView.addJavascriptInterface(new JSInterface(),"Android");
    }

    //Button Click Event
    public void click(View view){
        //java calls JS methods
        webView.loadUrl("javascript:javaCallJs(" + "'" + et_user.getText().toString()+"'"+")");
    }

    //Remove webView when page is destroyed
    @Override
    protected void onDestroy() {
        super.onDestroy();
        ll_root.removeView(webView);
        webView.stopLoading();
        webView.removeAllViews();
        webView.destroy();
        webView = null;
    }

    private class JSInterface {
        //Method JS needs to call
        @JavascriptInterface
        public void showToast(String arg){
            Toast.makeText(MainActivity.this,arg,Toast.LENGTH_SHORT).show();
        }
    }
}

Points to pay attention to

Reference link: Some pits in Android webview
The webView.addJavascriptInterface() method has some bugs before API 17 (interesting to refer to this article, a brief analysis of WebView remote code execution bugs), so after API 17, you need to add the @JavascriptInterface comment to the JavaScript interface class method.
If you look closely, you will see that the WebView objects we have above are not written directly in the layout file, but dynamically added to it using addview(webview) through a LinearLayout container.It is also important to note that creating a WebView requires using the applicationContext instead of the active context, destroying the active object when destroying it, destroying the WebView in time when leaving at the end, removing the WebView from the LinearLayout before calling webview.remove AllViews (); webview.destory ();
If you want webView to generate OOM without affecting the main process, you can start another process by adding the Android:process attribute to the activity tag of androidmanifest.xml.

After the activity is killed, the webView is still in state so that users can return to their previous state the next time they open it.WebView supports saveState(bundle) and restoreState(bundle) methods.

Save State

@Override  
protected void onSaveInstanceState(Bundle outState) {  
    super.onSaveInstanceState(outState);  
    wv.saveState(outState);  
    Log.e(TAG, "save state...");  
}  

Recovery state (in activity onCreate (bundle saved InstanceState)

if(null!=savedInstanceState){  
    wv.restoreState(savedInstanceState);  
    Log.i(TAG, "restore state");  
}else{  
    wv.loadUrl("http://3g.cn");  
}  

Other common questions:

  • WebViewClient.onPageFinished().
    You will never be able to determine if the content of a Web page is really loaded when the WebView calls this method.This method may be called multiple times when the currently loaded page is causing a jump, and there is a more specific explanation on StackOverflow (How to listen for a Webview finishing loading a URL in Android?), but the solutions listed here are not perfect.So when your WebView needs to load a wide variety of pages and do something when the page is loaded, it's possible that WebChromeClient.onProgressChanged() is more reliable than WebViewClient.onPageFinished().

  • WebView background power consumption issue.
    When your program calls WebView to load a web page, WebView will start some threads by itself (?)If you did not properly destroy the WebView, these remaining threads (?)It runs in the background all the time, causing your application to consume a lot of power.The way I handle this is lazy, simple and rude (not recommended), that is, calling System.exit(0) directly in Activity.onDestroy(), so that the application is completely removed from the virtual machine, so there is no problem.

  • Toggle WebView Flash Screen issue.
    If you need to switch back and forth between different WebView s (including different web pages) in the same ViewGroup, you will find that a flash screen is inevitable.This should be an Android hardware acceleration Bug. If you turn off hardware acceleration, it's much better, but you don't get a good browsing experience. You'll feel like you're clicking one card at a time when the page slides.

  • On some mobile phones, when Webview has videos, the video resource is not destroyed after activity is destroyed, and even you can hear the playback in the background.Even destroying webviews like you just did doesn't help, the solution is to change the url to an empty address before onDestory.

  • WebView hardware acceleration causes page rendering to flicker
    About Android hardware acceleration started with Android 3.0 (API level 11), turning on hardware acceleration makes WebView render pages faster and drag smoother.However, one side effect is that it is easy to see page loading whiteblocks and interface flickering.The solution to this problem is to set WebView to temporarily turn off the hardware acceleration code as follows:

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { 
    webview.setLayerType(View.LAYER_TYPE_SOFTWARE, null); 
    }
  • ERR_CACHE_MISS
    The webview prompted NET when loading the web page:: ERR_CACHE_MISS error, the simple reason is that there is no permission to add network access, just add it in AndroidManifest.xml, as follows:
    </application>  
        <uses-permission android:name="android.permission.INTERNET" />  
  • ERR_UNKNOWN_URL_SCHEME

If you use a more secure Web page in WebView, you need to add the following code, otherwise the operating system will intercept the URL and you will not be able to open the page.

    mWebView.setWebViewClient(new WebViewClient(){  
            @Override  
            public boolean shouldOverrideUrlLoading(WebView view, String url) {  
                if( url.startsWith("http:") || url.startsWith("https:") ) {  
                    return false;  
                }  
       try{
                Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));  
                startActivity( intent );  
       }catch(Exception e){}
                return true;  
            }  
        });  

It is important to note that you need to add try catch above, because the url you pass in may not be legal.It is possible to open the url of the local app, which will crash if you do not install it.

Topics: Android Javascript network Java