The article directories

  • Android Default method
    • Android calls JS code
    • JS to call Android code
  • Using jsbridge
  • WebView loads web pages select files to upload

Android Default method

The most comprehensive summary of Android WebView and JS interaction

Android calls JS code

There are two ways to call JS code on Android:

  1. LoadUrl () via WebView
  2. EvaluateJavascript via WebView ()

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

Example: for the convenience of display, this article is to use Andorid call local JS code description; In practice, Android calls more remote JS code, changing the loaded JS code path to a URL

1. Create javascript. HTML and put it in the SRC /main/assets folder (select main and right-click new-folder-assets Folder).

<! DOCTYPEhtml>
<html lang="en">
 <head>
  <meta charset="UTF-8">
  <meta name="Generator" content="EditPlus ®">
  <meta name="Author" content="">
  <meta name="Keywords" content="">
  <meta name="Description" content="">
  <title>Document</title>
 </head>
 <body>
	<script>
	// The method Android needs to call
	function callJS(){
	   alert("Android calls the JS callJS method.");
	}
	</script>
 </body>
</html>
Copy the code

2. Call JS code in Android via WebView Settings

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

        // Set the permission to interact with Js
        webSettings.setJavaScriptEnabled(true);
        // Set to allow JS pop-ups
        webSettings.setJavaScriptCanOpenWindowsAutomatically(true);

        // Load the JS code first
        // The format is file:/// / android_asset/filene.html
        mWebView.loadUrl("file:///android_asset/javascript.html");

        button = (Button) findViewById(R.id.button);
        button.setOnClickListener(v -> {
            // Send messages through Handler
            mWebView.post(new Runnable() {
                @Override
                public void run(a) {
                    // Note that the name of the called JS method must correspond to
                    // Call the javascript callJS() method
                    mWebView.loadUrl("javascript:callJS()"); }}); });// The js dialog box needs to be supported because the popover check result is set
        // The webView is just the carrier, the content rendering needs to use the webviewChromClient class to achieve
        // Handle JavaScript dialogs by setting WebChromeClient objects
        // Set the Alert() function that responds to js
        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; }}); }}Copy the code

Note: the JS code call must be called after the onPageFinished() callback, otherwise the method onPageFinished() belongs to the WebViewClient class will not be called, mainly at the end of page loading

Method 2: Through evaluateJavascript () of the WebView this method does not refresh the page, whereas the first method (loadUrl) does. It is more efficient and simpler to use than the first method. Available only after Android 4.4

button.setOnClickListener(v -> {
            // Send messages through Handler
            mWebView.post(new Runnable() {
                @Override
                public void run(a) {
                    mWebView.evaluateJavascript("javascript:callJS()".new ValueCallback<String>() {
                        @Override
                        public void onReceiveValue(String value) {
                            // This is the result returned by js}}); }}); });Copy the code

JS to call Android code

There are three ways to call Android code with JS:

  1. Through the WebViewaddJavascriptInterface()Object mapping
  2. Through the WebViewClientshouldOverrideUrlLoading()Method callback intercepts url
  3. Through WebChromeClientonJsAlert(),onJsConfirm(),onJsPrompt()Method callback intercepts the JS dialog boxalert(),confirm(),prompt()The message

Demo effect:

Method 1: Use WebView addJavascriptInterface() to map objects. 1. Define an Android class that maps with JS objects: AndroidtoJs

public class AndroidtoJs extends Object {
    // Define the method that JS needs to call
    // Methods called by javascript must be annotated with @javascriptInterface
    @JavascriptInterface
    public void hello(String msg) {
        System.out.println("JS calls Android's Hello method"); }}Copy the code

2. Place the.html JS code in the SRC /main/assets folder

<! DOCTYPEhtml>
<html lang="en">
 <head>
  <meta charset="UTF-8">
  <meta name="Generator" content="EditPlus ®">
  <meta name="Author" content="">
  <meta name="Keywords" content="">
  <meta name="Description" content="">
  <title>Document</title>
  <script>
	function callAndroid(){
		// Because of the object mapping, calling the test object is equal to calling the Android mapping object
        test.hello("Js calls the Hello method in Android");
    }
 </script>
 </head>
 <body>// Click the button to call the callAndroid function<button type="button" id="button1" onclick="callAndroid()"></button>
 </body>
</html>
Copy the code

3. Set the mapping between Android class and JS code through WebView in Android

// Map Java objects to JS objects with addJavascriptInterface()
        // Argument 1: Javascript object name
        // Parameter 2: Java object name
        mWebView.addJavascriptInterface(new AndroidtoJs(), "test");//AndroidtoJS class objects map toJS test objects
Copy the code

This method has serious vulnerability issues, as described in the article: Android WebView Usage Vulnerabilities you didn’t know about

Method 2: Android shouldOverrideUrlLoading() with WebViewClient shouldOverrideUrlLoading() Parse the protocol of the URL. If a predefined protocol is detected, call the corresponding method. 1

<! DOCTYPEhtml>
<html lang="en">
 <head>
  <meta charset="UTF-8">
  <meta name="Generator" content="EditPlus ®">
  <meta name="Author" content="">
  <meta name="Keywords" content="">
  <meta name="Description" content="">
  <title>Document</title>
  <script>
	function callAndroid2(){
       /* The url protocol is: js://webview? arg1=111&arg2=222*/
       document.location = "js://webview? arg1=111&arg2=222";
    }
	</script>
 </head>
 <body>
    <button type="button" id="button" value="Js calling Android method 2" onclick="callAndroid2()"></button>
 </body>
</html>
Copy the code

ShouldOverrideUrlLoading ()

mWebView.setWebViewClient(new WebViewClient() {
                                      @Override
                                      public boolean shouldOverrideUrlLoading(WebView view, String url) {

                                          // Step 2: Check whether it is the required URL according to the protocol parameters
                                          // Use scheme (protocol format) and authority (protocol name) to judge (first two parameters)
                                          // Suppose the incoming URL = "js://webview? Arg1 =111&arg2=222"

                                          Uri uri = Uri.parse(url);
                                          // If the url protocol = the pre-agreed JS protocol
                                          // Parse the parameters down
                                          if (uri.getScheme().equals("js")) {

                                              // If authority = pre-specify webViews in the protocol, that is, represent the protocol that conforms to the convention
                                              // So intercepting the URL, now JS starts calling the method Android needs
                                              if (uri.getAuthority().equals("webview")) {

                                                  // Step 3:
                                                  // The logic needed to execute JS
                                                  System.out.println("Js calls Android methods");
                                                  // Can take parameters on the protocol and pass them to Android
                                                  HashMap<String, String> params = new HashMap<>();
                                                  Set<String> collection = uri.getQueryParameterNames();
                                              }
                                              return true;
                                          }
                                          return super.shouldOverrideUrlLoading(view, url); }});Copy the code

This method does not have the vulnerability of method 1, but JS obtaining the return value of Android method is complicated

If JS wants to get the return value of the Android method, it can only execute the JS method via WebView loadUrl () to pass the return value back.

/ / Android: MainActivity. Java
mWebView.loadUrl("javascript:returnResult("+3+")");

/ / JS: javascript. HTML
function returnResult(result){
    alert("result is" + result);
}
Copy the code

Method 3: Via WebChromeClientonJsAlert(),onJsConfirm(),onJsPrompt()Method callback intercepts the JS dialog boxalert(),confirm(),prompt()The message

Android via WebChromeClientonJsAlert(),onJsConfirm(),onJsPrompt()Method callback intercepts the JS dialog box, respectively

(the above three methods), get their message content, and then parse it



The following example will intercept the JS input box (i.eprompt()Methods). Common intercepts are: intercepts JS input fields (i.eprompt()Method), because onlyprompt()Can return any type of value, the operation of the most comprehensive convenient, more flexible; whilealert()Dialog box has no return value;confirm()The dialog box can only return two values in two states (OK/cancel)

1. Place javascript in assets folder

<! DOCTYPEhtml>
<html lang="en">
 <head>
  <meta charset="UTF-8">
  <meta name="Generator" content="EditPlus ®">
  <meta name="Author" content="">
  <meta name="Keywords" content="">
  <meta name="Description" content="">
  <title>Document</title>
  <script>    
    function callAndroid3(){
     Call prompt ()
     var result=prompt("js://webview? arg1=111&arg2=222");
     alert("demo " + result);
    }

	</script>
 </head>
 <body>
    <button type="button" id="button3" onclick="callAndroid3()">Js calls Android mode 3</button>
 </body>
</html>
Copy the code

The onJsPrompt() callback is triggered when the above JS code is loaded using mwebview.loadurl (“file:///android_asset/javascript.html”) : If it intercepts a warning box (alert()), the onJsAlert() callback is triggered; If it intercepts a confirmation box (that is, confirm()), the onJsConfirm() callback is triggered;

2. Clone onJsPrompt() on Android via WebChromeClient

mWebView.setWebChromeClient(new WebChromeClient() {
      @Override
            public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
                // Check whether it is the required URL according to the protocol parameters (same principle as method 2)
                // Use scheme (protocol format) and authority (protocol name) to judge (first two parameters)
                // Suppose the incoming URL = "js://webview? Arg1 =111&arg2=222"

                Uri uri = Uri.parse(message);
                // If the url protocol = the pre-agreed JS protocol
                // Parse the parameters down
                if ( uri.getScheme().equals("js")) {

                    // If authority = pre-specify webViews in the protocol, that is, represent the protocol that conforms to the convention
                    // So intercepting the URL, now JS starts calling the method Android needs
                    if (uri.getAuthority().equals("webview")) {

                        //
                        // The logic needed to execute JS
                        System.out.println("Js calls Android methods");
                        // Can take parameters on the protocol and pass them to Android
                        HashMap<String, String> params = new HashMap<>();
                        Set<String> collection = uri.getQueryParameterNames();

                        // Parameter result: represents the return value of the message box (input value)
                        result.confirm("Js called the Android method successfully");
                    }
                    return true;
                }
                return super.onJsPrompt(view, url, message, defaultValue, result);
            }

            /*// Alert () and confirm() intercept the same principle, @override public Boolean onJsAlert(WebView view, String URL, String message, JsResult result) { return super.onJsAlert(view, url, message, result); } * /

            // Intercepts the JS validation box
            @Override
            public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
                return super.onJsConfirm(view, url, message, result); }});Copy the code

Using jsbridge

Android and Js interaction JSBridge using Android BridgeWebView simple use, as well as climb the pit

AddJavascriptInterface below Android4.2 has security vulnerabilities. Although addJavascriptInterface is replaced by @javascriptInterface after Android4.2, due to compatibility and security problems, Basically, we don’t use the addJavascriptInterface method or the @javascriptInterface annotation provided by Android to implement it, so we have to find a safe and compatible solution for all versions of Android

1. Add maven {url “https://jitpack.io”} to the gradle file

allprojects {
    repositories {
        google()
        jcenter()
        maven { url "https://jitpack.io"}}}Copy the code

Add dependencies to Module gradle

dependencies {
    ...
    implementation 'com. Making. Lzyzsd: jsbridge: 1.0.4'
}
Copy the code

3. Replace WebView with BridgeWebView in the layout file


      
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="10dp">

    <EditText
        android:id="@+id/et"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <Button
        android:id="@+id/bt"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Android calls JS methods" />

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:padding="10dp"
        android:text="Here's the WebView." />

    <com.github.lzyzsd.jsbridge.BridgeWebView
        android:id="@+id/webview"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>
Copy the code

4. Code in the Activity

public class JsBridgeActivity extends AppCompatActivity {
    private EditText et;
    private Button bt;
    private BridgeWebView webview;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_jsbridge);
        et = (EditText) findViewById(R.id.et);
        bt = (Button) findViewById(R.id.bt);
        webview = (BridgeWebView) findViewById(R.id.webview);
        webview.setDefaultHandler(new DefaultHandler());
        webview.setWebChromeClient(new WebChromeClient());
        webview.loadUrl("file:///android_asset/test.html");
This method is called when a callHandler method is called in JS (handlerName must be the same as in JS).
        webview.registerHandler("submitFromWeb".new BridgeHandler() {
            @Override
            public void handler(String data, CallBackFunction function) {
                Log.e("TAG"."Js returns:" + data);
                // Displays messages passed from JS to Android
                Toast.makeText(JsBridgeActivity.this."Js return." + data, Toast.LENGTH_LONG).show();
                //Android returns a message to JS
                function.onCallBack("I am js calling Android to return data:"+ et.getText().toString()); }}); bt.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
// Call the method in js (must be the same as the handlerName in JS)
                webview.callHandler("functionInJs"."Android calls js66".new CallBackFunction() {
                    @Override
                    public void onCallBack(String data) {
                        Log.e("TAG"."onCallBack:" + data);
                        Toast.makeText(JsBridgeActivity.this, data, Toast.LENGTH_LONG).show(); }}); }}); }}Copy the code

5. Put the HTML in the Assets folder

<! DOCTYPEhtml>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <! -- Compiled and minified CSS -->
    <link rel="stylesheet" href="./materialize.min.css">
    <! -- Compiled and minified JavaScript -->
    <script src="./materialize.min.js"></script>
    <title>Test</title>
</head>
<body>
<div class="input-field col s6">
    <input placeholder="Please enter data" id="text1" type="text" class="validate">
</div>
<button class="waves-effect waves-light btn" onclick="testClick();">Js calls the Android method</button>
</body>
<script>
         // call the Android method: receive the data from Android, and do some processing

         function testClick() {

          SubmitFromWeb is the name of the method. It must be the same as the name of the method when it was registered in Android
          // Parameter 2: The data returned to the Android terminal can be a string, JSON, etc
          // Parameter 3: the corresponding processing logic after js receives the data from Android

            window.WebViewJavascriptBridge.callHandler(
               'submitFromWeb'
               , {'param': "JS received data successfully --"},function(responseData) {
                    alert(responseData)
               }
           );
       }

       //JS registers the event listener
       function connectWebViewJavascriptBridge(callback) {
           if (window.WebViewJavascriptBridge) {
               callback(WebViewJavascriptBridge)
           } else {
               document.addEventListener(
                   'WebViewJavascriptBridgeReady'
                   , function() {
                       callback(WebViewJavascriptBridge)
                   },
                   false); }}// Register the callback function and call the initialization function on the first connection
       connectWebViewJavascriptBridge(function(bridge) {
            / / initialization
           bridge.init(function(message, responseCallback) {
               var data = {
                   'Javascript Responds': 'Wee! '
               };
               alert("jasdashjd");
               responseCallback(data);
           });


           //Android calls js methods: functionInJs method names need to be consistent and return notifications to Android

           bridge.registerHandler("functionInJs".function(data, responseCallback) {
               alert(data);
               var data2 = document.getElementById("text1").value;
               var responseData = "I am the data returned by Android calling the JS method --"+ data2;
               responseCallback(responseData);
           });
       })
</script>
</html>
Copy the code

Operation effect:

WebView loads web pages select files to upload

Android uses WebView to load web pages and select file uploads



Create a new upload. HTML and place it under assets

<! DOCTYPEhtml>
<html>

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,maximun-scale=1,mininum-scale=1,user-scale=1">
    <tile>File selection</tile>
    <style>
        .filechooser {
            width: 95%;
            height: 50px;
            -webkit-background-clip: border-box;
        }

    </style>
    <script>
        var display = function(){
            var path = document.getElementById("file_chooser").textContent;
            document.getElementById("filechooser_display").innerHTML("<b>"+path+"</b>");
        }

    </script>
</head>

<body>
<fieldset>
    <input class="filechooser" id="file_chooser" type="file" placeholder="Select file" onchange="display()"
           oninput="display()">
    <p id="filechooser_display"></p>
</fieldset>
</body>

</html>
Copy the code

Activity

public class UploadActivity extends AppCompatActivity {
    private WebView webView;
    private ValueCallback<Uri> uploadFile;
    private ValueCallback<Uri[]> uploadFiles;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_upload);
        webView = findViewById(R.id.webview);
        webView.setWebChromeClient(new WebChromeClient() {
            / / For Android 3.0 +
            public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) {
                Log.i("test"."openFileChooser 1");
                uploadFile = uploadFile;
                openFileChooseProcess();
            }

            // For Android < 3.0
            public void openFileChooser(ValueCallback<Uri> uploadMsgs) {
                Log.i("test"."openFileChooser 2");
                uploadFile = uploadFile;
                openFileChooseProcess();
            }

            // For Android > 4.1.1
            public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) {
                Log.i("test"."openFileChooser 3");
                uploadFile = uploadFile;
                openFileChooseProcess();
            }

            // For Android >= 5.0
            public boolean onShowFileChooser(WebView webView, ValueCallback
       
         filePathCallback, WebChromeClient.FileChooserParams fileChooserParams)
       []> {
                Log.i("test"."openFileChooser 4:" + filePathCallback.toString());
                uploadFiles = filePathCallback;
                openFileChooseProcess();
                return true; }}); webView.loadUrl("file:///android_asset/upload.html");
    }

    private void openFileChooseProcess(a) {
        Intent i = new Intent(Intent.ACTION_GET_CONTENT);
        i.addCategory(Intent.CATEGORY_OPENABLE);
        i.setType("* / *");
        startActivityForResult(Intent.createChooser(i, "Select file"), 0);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (resultCode == RESULT_OK) {
            switch (requestCode) {
                case 0:
                    if(uploadFile ! =null) {
                        Uri result = data == null|| resultCode ! = RESULT_OK ?null
                                : data.getData();
                        uploadFile.onReceiveValue(result);
                        uploadFile = null;
                    }
                    if(uploadFiles ! =null) {
                        Uri result = data == null|| resultCode ! = RESULT_OK ?null
                                : data.getData();
                        uploadFiles.onReceiveValue(new Uri[]{result});
                        uploadFiles = null;
                    }
                    break;
                default:
                    break; }}else if (resultCode == RESULT_CANCELED) {
            if(uploadFile ! =null) {
                uploadFile.onReceiveValue(null);
                uploadFile = null;
            }
            if(uploadFiles ! =null) {
                uploadFiles.onReceiveValue(null);
                uploadFiles = null; }}}@Override
    protected void onResume(a) {
        super.onResume();
        webView.onResume();
    }

    @Override
    protected void onPause(a) {
        super.onPause();
        webView.onPause();
    }

    /** * Ensure that the logout configuration can be released */
    @Override
    protected void onDestroy(a) {
        if(webView ! =null) {
            webView.destroy();
        }
        super.onDestroy(); }}Copy the code

A few notes on the code: Clicking on the button to upload the file simulates the form submission, which calls openFileChooser(…). Methods. The method then receives and processes the parameter ValueCallback < URI > uploadMsg. The URI is the file path selected locally. Then the Intent’s startActivityForResult(…) Method Go to the page for selecting a file. OnActivityResult (int requestCode, int resultCode, Intent Data)

Webkit no longer supports openFileChooser(…). Instead, use onShowFileChooser(…)

Download resources