WebView should know and should know

Keywords: Android Javascript Java ButterKnife

Catalog

1, What is WebView? What can WebView do?

WebView is a control used to display Web pages. It is a micro browser, which contains the basic functions of a browser, such as: scrolling, zooming, moving forward, moving back to the next page, searching, executing Js and other functions. Before Android 4.4, the WebKit kernel was used, and then the Chrome kernel was used.

2, Play WebView

Commonly used API

void loadUrl(String url): load network link url
boolean canGoBack(): judge whether WebView can return to the previous page at present
goBack(): back to previous page
boolean canGoForward(): judge whether WebView can move forward at present
goForward(): back to previous page
onPause(): similar to Activity life cycle, the page enters the invisible state in the background
pauseTimers(): this method is oriented to webview of the whole global application. It will pause layout, parsing and JavaScript Timer of all webviews. When the program enters the background, the call of this method can reduce the CPU power consumption.
onResume(): after calling onPause(), this method can be called to resume the operation of WebView.
resumeTimers(): all operations when resuming pauseTimers. (Note: pauseTimers and resumeTimers must be used together, otherwise there will be problems in using WebView in other scenarios.)
destroy(): destroy WebView
clearHistory(): clears the history of the current WebView access.
clearCache(boolean includeDiskFiles): clear the cached data left by web page access. Note that since the cache is global, any cache used by WebView will be cleared, even if it is used elsewhere. This method takes a parameter, and its function can be seen from the naming. If it is set to false, only the resource cache in the memory will be emptied, not the one in the disk.
reload(): reload the current request
setLayerType(int layerType, Paint paint): set hardware acceleration and software acceleration
removeAllViews(): clears child views.
clearSslPreferences(): clears ssl information.
clearMatches(): clears the highlighted matching characters for web page lookups.
removeJavascriptInterface(String interfaceName): delete the injection object corresponding to interfaceName
addJavascriptInterface(Object object,String interfaceName): inject java objects.
setVerticalScrollBarEnabled(boolean verticalScrollBarEnabled): sets the vertical scroll bar.
setHorizontalScrollBarEnabled(boolean horizontalScrollBarEnabled): sets the horizontal scroll bar.
Loadurl (string url, map < string, string > additionalhttpheaders): load the specified url and carry the http header data.
Evaluate JavaScript (string script, valuecallback ResultCallback): after API 19, you can use this method to go to Js.
stopLoading(): stops the current WebView load.
clearView(): in Android 4.3 and above, this api has been discarded, and in most cases, there will be bug s, often unable to clear the previous rendering data. The official suggestion is to use loadUrl("about:blank") to achieve this function. The natural time of a page that needs to be reloaded in rainy days will be affected.
freeMemory(): free memory, but it seems to be hard to use.
clearFormData(): clears the auto complete populated form data. It should be noted that this method only clears the form data automatically filled by the current form field, and does not clear the data stored by WebView locally.

Basic use

Basically, it is used with WebSettings, WebClient, WebChromeClient, and some interactions with JS

1. Add network permission

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

2. Instantiate WebView

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">
    
    <com.seven.webview.SevenWebView
        android:layout_weight="9"
        android:id="@+id/seven_web"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

    <Button
        android:text="call js Method"
        android:layout_weight="1"
        android:id="@+id/call_js"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
</LinearLayout>

Here, the WebView is encapsulated separately. Cooperate with WebSetting to set some parameters for WebView, such as supporting js, etc

package com.seven.webview;

import android.content.Context;
import android.util.AttributeSet;
import android.webkit.WebSettings;
import android.webkit.WebView;

/**
 * Time:2020/3/13
 * <p>
 * Author:seven
 * <p>
 * Description:
 */
public class SevenWebView extends WebView {
    public SevenWebView(Context context) {
        super(context);
        init(context);
    }



    public SevenWebView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    /**
     *  Initialization settings, used with WebSettings
     * @param context
     */
    private void init(Context context) {

        WebSettings webSettings = getSettings();
        if (webSettings == null) return;
        // Support the use of Js
        webSettings.setJavaScriptEnabled(true);
        // Enable DOM cache. LocalStorage is not supported by default
        webSettings.setDomStorageEnabled(true);
        // Enable database caching
        webSettings.setDatabaseEnabled(true);
        // Set the caching mode of WebView
        webSettings.setCacheMode(WebSettings.LOAD_DEFAULT);
        // Support zoom
        webSettings.setSupportZoom(true);
        // Set UserAgent properties
        webSettings.setUserAgentString("");
        // Allow to load local html file / false
        webSettings.setAllowFileAccess(true);
        // Javascript loaded through file url is allowed to read other local files. Before Android 4.1, it is true by default, and after Android 4.1, it is false by default, that is, it is forbidden
        webSettings.setAllowFileAccessFromFileURLs(false);
        // Javascript loaded through file url can access other sources, including other files, http, https and other sources,
        // Before Android 4.1, it is true by default, and after Android 4.1, it is false by default, that is, it is forbidden
        // If this setting is allowed, setAllowFileAccessFromFileURLs does not work
        webSettings.setAllowUniversalAccessFromFileURLs(false);
        //Setting background
        setBackgroundColor(getResources().getColor(android.R.color.transparent));
    }


}

3. Set up WebClient

Note that to override the shouldOverrideUrlLoading method, you need to call webView to load the web page, otherwise, there will be a system browser to load it. To understand the meaning of this sentence, webView is still used for the first load, but if there is a link on the web page, it will not be blocked.

package com.seven.webview;

import android.graphics.Bitmap;
import android.net.http.SslError;
import android.util.Log;
import android.webkit.SslErrorHandler;
import android.webkit.WebResourceError;
import android.webkit.WebResourceRequest;
import android.webkit.WebResourceResponse;
import android.webkit.WebView;
import android.webkit.WebViewClient;

/**
 * Time:2020/3/13
 * <p>
 * Author:seven
 * <p>
 * Description:
 */
public class SevenWebClient extends WebViewClient {
    private String TAG=getClass().getSimpleName();

    /**
     * Callback when the page Scale value of WebView changes
     */
    @Override
    public void onScaleChanged(WebView view, float oldScale, float newScale) {
        super.onScaleChanged(view, oldScale, newScale);
        Log.d(TAG, "onScaleChanged:oldScale="+oldScale+",newScale="+newScale);
    }

    /**
     *  By default, the system will open the web page through the mobile browser,
     *  In order to display web pages directly through WebView, you must set
     *  Whether to load the page in WebView
     *
     * @param view
     * @param url
     * @return
     */
    @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {
        Log.d(TAG, "shouldOverrideUrlLoading: "+url);
        view.loadUrl(url);
        return true;
    }

    /**
     * WebView Callback at the beginning of page loading, one Frame load corresponding to one callback
     *
     * @param view
     * @param url
     * @param favicon
     */
    @Override
    public void onPageStarted(WebView view, String url, Bitmap favicon) {
        super.onPageStarted(view, url, favicon);
        Log.d(TAG, "onPageStarted: "+url);
    }

    /**
     * WebView Callback when the page is loaded, one Frame load corresponds to one callback
     *
     * @param view
     * @param url
     */
    @Override
    public void onPageFinished(WebView view, String url) {
        super.onPageFinished(view, url);
        Log.d(TAG, "onPageFinished: "+url);
    }

    /**
     * WebView When a page resource is loaded, it will be recalled. Every network load generated by a resource will be loaded unless there is a cache corresponding to the current url locally.
     *
     * @param view WebView
     * @param url  url
     */
    @Override
    public void onLoadResource(WebView view, String url) {
        super.onLoadResource(view, url);
        Log.d(TAG, "onLoadResource: "+url);
    }

    /**
     * WebView We can intercept a certain request to return our own loaded data. This method will play a great role in later caching.
     *
     * @param view    WebView
     * @param request Request request is currently generated
     * @return WebResourceResponse
     */
    @Override
    public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
        return super.shouldInterceptRequest(view, request);
    }

    /**
     * WebView Error accessing url
     *
     * @param view
     * @param request
     * @param error
     */
    @Override
    public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
        super.onReceivedError(view, request, error);
        Log.d(TAG, "onReceivedError: "+error);
    }

    /**
     * WebView ssl Error accessing certificate, handler.cancel() cancels loading, handler. Processed() continues to load even if there is an error
     *
     * @param view
     * @param handler
     * @param error
     */
    @Override
    public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
        super.onReceivedSslError(view, handler, error);
        Log.d(TAG, "onReceivedSslError: "+error);
    }
}

4. Set up WebChromeClient

The onProgressChanged method will call back the progress of web page loading. It can do some progress bars and other things to display friendly effects.

package com.seven.webview;

import android.content.Context;
import android.graphics.Bitmap;
import android.util.Log;
import android.webkit.ConsoleMessage;
import android.webkit.JsPromptResult;
import android.webkit.JsResult;
import android.webkit.WebChromeClient;
import android.webkit.WebView;

/**
 * Time:2020/3/13
 * <p>
 * Author:seven
 * <p>
 * Description:
 */
public class SevenWebChromeClient extends WebChromeClient {

    private String TAG=getClass().getSimpleName();
    private Context mContenxt;

    public SevenWebChromeClient(Context context) {

        this.mContenxt = context;
    }

    /**
     *  Js Log printing for
     * @param consoleMessage
     * @return
     */
    @Override
    public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
        Log.d(TAG, "onConsoleMessage: "+consoleMessage.message());
        return super.onConsoleMessage(consoleMessage);
    }

    /**
     * Current WebView load page progress
     *
     * @param view
     * @param newProgress
     */
    @Override
    public void onProgressChanged(WebView view, int newProgress) {
        super.onProgressChanged(view, newProgress);
        Log.d(TAG, "onProgressChanged: "+newProgress);

    }

    /**
     * Js Call the alert() function to generate the dialog box.
     *
     * @param view
     * @param url
     * @param message
     * @param result
     * @return
     */
    @Override
    public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
        Log.d(TAG, "onJsAlert: "+message);
        return super.onJsAlert(view, url, message, result);
    }

    /**
     * Handling the Confirm dialog in Js
     *
     * @param view
     * @param url
     * @param message
     * @param result
     * @return
     */
    @Override
    public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
        Log.d(TAG, "onJsConfirm: "+message);
        return super.onJsConfirm(view, url, message, result);
    }

    /**
     * Handling Prompt dialog in JS
     *
     * @param view
     * @param url
     * @param message
     * @param defaultValue
     * @param result
     * @return
     */
    @Override
    public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {

        Log.d(TAG, "onJsPrompt: "+message);
        return super.onJsPrompt(view, url, message, defaultValue, result);
    }

    /**
     * Receive icon of web page
     *
     * @param view
     * @param icon
     */
    @Override
    public void onReceivedIcon(WebView view, Bitmap icon) {
        super.onReceivedIcon(view, icon);
    }

    /**
     * Receive Title of web page
     *
     * @param view
     * @param title
     */
    @Override
    public void onReceivedTitle(WebView view, String title) {
        super.onReceivedTitle(view, title);
        Log.d(TAG, "onReceivedTitle: "+title);
    }

}

5. How to interact with JS

When I customize WebView, I have already set javascript support, so here I just instantiate a java object and inject the java object through addjavascript interface.
Strength deteriorates a java object. The method called by js needs to be annotated with @ JavascriptInterface

package com.seven.webview;

import android.content.Context;
import android.webkit.JavascriptInterface;
import android.widget.Toast;

/**
 * Time:2020/3/13
 * <p>
 * Author:seven
 * <p>
 * Description:
 */
public class NativeJsInterface {

    private Context mContext;

    public NativeJsInterface(Context context) {
        mContext = context;
    }

    @JavascriptInterface
    public void helloAndroid() {
        Toast.makeText(mContext, "Hello Android!", Toast.LENGTH_SHORT).show();
    }


    @JavascriptInterface
    public String getAndroid() {
        Toast.makeText(mContext, "getAndroid", Toast.LENGTH_SHORT).show();
        return "Android data";
    }

}

package com.seven.webview;

import android.os.Build;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.webkit.ValueCallback;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import android.webkit.WebViewClient;

import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;

public class MainActivity extends AppCompatActivity {

    private String TAG=getClass().getSimpleName();

    @BindView(R.id.seven_web)
    WebView webView;

    private WebViewClient webViewClient;

    private WebChromeClient webChromeClient;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
        loadUrl();
        supportJs();

    }
    /**
     * Loading webpage
     */
    private void loadUrl() {
        webViewClient = new SevenWebClient();
        webChromeClient = new SevenWebChromeClient(this);
        webView.setWebViewClient(webViewClient);
        webView.setWebChromeClient(webChromeClient);
        webView.loadUrl("file:///android_asset/html/ts.html");
    }

    /**
     * Extend JS object, the page can directly use NativeObject object to call Android provided methods
     */
    private void supportJs() {
        NativeJsInterface nativeJsInterface = new NativeJsInterface(this);
        webView.addJavascriptInterface(nativeJsInterface, "NativeObject");

    }

    /**
     * Method calling JS
     */

    @OnClick(R.id.call_js)
    public void onViewClicked() {
        String jsMethod="javascript:androidCallJs()";
        if(Build.VERSION.SDK_INT< Build.VERSION_CODES.KITKAT){
            webView.loadUrl(jsMethod);
        }else {
              webView.evaluateJavascript(jsMethod, new ValueCallback<String>() {
                  @Override
                  public void onReceiveValue(String value) {
                      Log.d(TAG, "onReceiveValue: "+value);
                  }
              });
        }
    }
}

6. Write a simple web page

</html>


<html>
<head>
    <meta charset="utf-8">
    <title>test page</title>
    <script>
        function callAndroid(){
            NativeObject.helloAndroid();
        }

        function getAndroid(){
            NativeObject.getAndroid();
        }

       function androidCallJs(){
        alert('js Method is executed!!!')
       }
       function jumpApp(){

       }
    </script>

</head>
<body background="images/ts.png">



<center>
    <button type="button" id="button" onclick="callAndroid()">helloAndroid</button>
</center>
<center>
    <button type="button" id="button2" onclick="getAndroid()">getAndroid</button>
</center>

</center> <a href="scheme://host/path?query">Reopen yourself</a></center>

</body>
</html>


Through the above six steps, you can complete the basic loading web page, and support the function of java and js calling each other.

Web pages jump to other applications through Scheme

1. What is scheme?

Scheme in android is a kind of intra page Jump protocol, which is a very good implementation mechanism. By defining its own scheme protocol, it is very convenient to jump to all pages in app. Through scheme protocol, the server can customize to tell app which page to jump to, through notification bar message, through H5 page, etc. schema is also a kind of implicit startup. Under the data attribute, view the implicit startup of Android Intent for other contents (start other app interfaces and transfer data)
Scheme format: the custom URL of the client is used as the basis for calling from one application to another, and follows the RFC 1808 (Relative Uniform Resource Locators) standard. This is the same format as our common web content URL. A normal URL is divided into several parts: scheme, host, relativePath and query.

2. If the web page jumps to app?

1) First of all, you can customize a scheme, which I have written in the page code above.
2) If intercepted in webView client, use shouldOverrideUrlLoading in webView to intercept

    public boolean shouldOverrideUrlLoading(WebView view, String url) {
        Log.d(TAG, "shouldOverrideUrlLoading: "+url);

        if (url.startsWith("scheme")) {
            Log.d(TAG, "shouldOverrideUrlLoading: Handle customization scheme-->" + url);
            try {
                // The following fixed writing
                final Intent intent = new Intent(Intent.ACTION_VIEW,
                        Uri.parse(url));
                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
                        | Intent.FLAG_ACTIVITY_SINGLE_TOP);
                mContext.startActivity(intent);
            } catch (Exception e) {
                // Prevent non installation
                e.printStackTrace();
                Toast.makeText(mContext,"Third party you open App Not installed!",Toast.LENGTH_SHORT).show();
            }
            return true;
        }
        view.loadUrl(url);
        return true;
    }

3) If there is no interception, the WebView client has not been set accurately, and can be opened directly with the page method. The Android manifest file is as follows:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.seven.webview">

    <uses-permission android:name="android.permission.INTERNET"/>
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:usesCleartextTraffic="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>

                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>

            <intent-filter>
                <action android:name="android.intent.action.VIEW"/>
                <category android:name="android.intent.category.DEFAULT"/>
                <category android:name="android.intent.category.BROWSABLE"/>
                <data android:scheme="scheme"/>
            </intent-filter>
        </activity>

    </application>

</manifest>

3. Recommended practices

The general practice is to intercept, and start the app after intercepting. However, you can also provide the native method for the page to jump to the app. There are many ways to implement it.

3, Some small pits

1.onPause() tries to pause any processing that can be paused, such as animation and geographic location. JavaScript will not be paused. To pause JavaScript globally, use pauseTimers. onResume() resumes the operation stopped by onPause(); pauseTimers() pauses the layout, parsing and JavaScript timers of all webviews. This is a global request, not limited to this WebView. resumeTimers() recovers all layout, parsing and JavaScript timers of all webviews, and will resume scheduling of all timers. In addition, pay attention to the use of setTimeout() and setInterval() methods on JS side. When returning the last Activity containing WebView from the Activity without using pauseTimers() and pauseTimers(), the setTimeout() in the page will not be executed, setInterval() It can be resumed. Using pauseTimers() and pauseTimers() in the appropriate life cycle can restore the execution of setTimeout().

2. The extended java method is executed in the sub thread. To update the UI, you need to switch to the main thread, otherwise an error will be reported!

3. Set the setAllowFileAccessFromFileURLs(true) method for cross domain pits

4. The pit where the page and native save the account
When native and page nesting, such as login, how to synchronize the account, the general solution is to use the cookie manage to save the cookie when native login, so as to ensure that repeated login is not necessary during page operation.

5. File selection, rewrite these methods of WebChromeClient

 // For Android < 3.0
    public void openFileChooser(ValueCallback<Uri> valueCallback) {
        if(null!=valueCall){
            valueCall.onValueSelect(valueCallback);
        }
        openImageChooserActivity();
    }

    // For Android  >= 3.0
    public void openFileChooser(ValueCallback valueCallback, String acceptType) {
        if(null!=valueCall){
            valueCall.onValueSelect(valueCallback);
        }
        openImageChooserActivity();
    }

    //For Android  >= 4.1
    public void openFileChooser(ValueCallback<Uri> valueCallback, String acceptType,  String capture) {
//        uploadMessage = valueCallback;
        if(null!=valueCall){
            valueCall.onValueSelect(valueCallback);
        }
        openImageChooserActivity();
    }

    // For Android >= 5.0
    @Override
    public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, WebChromeClient.FileChooserParams fileChooserParams) {
        if(null!=valueCall){
            valueCall.onValuesSelectAboveL(filePathCallback);
        }

        openImageChooserActivity();
        return true;
    }
    private void openImageChooserActivity() {
        Intent i = new Intent(Intent.ACTION_GET_CONTENT);
        i.addCategory(Intent.CATEGORY_OPENABLE);
        i.setType("image/*");
        mContenxt.startActivityForResult(Intent.createChooser(i, "Image Chooser"),     FILE_CHOOSER_RESULT_CODE);
    }

4, Divergent learning

Security vulnerability: don't use JavaScript interface mode below Android 4.2. You need to add annotation @ JavaScript interface to call above 4.2. (this part is related to JsBrige, more details will be introduced later)
When the system accessibility service is enabled, in Android systems under 4.4, the WebView components provided by the system are exported to "accessibility" and "accessibility traversal" interfaces by default. These two interfaces also have the threat of remote arbitrary code execution. Similarly, these two objects need to be deleted through the removeJavascriptInterface method.

       super.removeJavascriptInterface("searchBoxJavaBridge_");
       super.removeJavascriptInterface("accessibility");
       super.removeJavascriptInterface("accessibilityTraversal");

webView cache problem
Reference resources: Analysis and application of WebView caching principle
How to optimize the loading speed
Reference resources: WebView performance, experience analysis and optimization

Five. Conclusion

This article summarizes some common usage of webView, interaction with js and some small pits. I hope it can help you! If I think it's useful for you, I'm also happy. After all, technology is used for sharing.

Published 59 original articles, won praise 8, visited 10000+
Private letter follow

Posted by Robert Elsdon on Fri, 13 Mar 2020 23:27:21 -0700