javascript技巧

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript技巧 > Android与JS交互

Android与JS进行交互的实现方法

作者:XRay

该文章主要介绍了Android与JavaScript之间的交互方式,包括两种调用方法和三种调用方式,详细描述了每种方法的使用步骤、优缺点和适用场景,需要的朋友可以参考下

1. 交互方式总结

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

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

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

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

二者沟通的桥梁是 WebView。

2. Android 调用 JS 代码

2.1 通过 WebView 的 loadUrl()

需要加载的 html 文件——javascript.html,把该文件放到 assets 目录下。

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title>JS Interaction</title>
        This is a Web View
    <script>
        function callJS(){
            console.log("Android 调用了 JS 的 callJS() 方法");
        }
    </script>
</head>

</html>

MainActivity.java:

package com.example.webviewtest;

import android.os.Bundle;
import android.view.View;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.widget.Button;
import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {

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

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

        WebSettings webSettings = mWebView.getSettings();

        // 允许在 WebView 中执行 JS
        webSettings.setJavaScriptEnabled(true);
        // 载入 url
        mWebView.loadUrl("file:///android_asset/javascript.html");

        Button button = findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 调用 JS 的 callJS() 方法
                mWebView.loadUrl("javascript:callJS()");
            }
        });
    }
}

activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <WebView
        android:id="@+id/webview"
        android:layout_width="match_parent"
        android:layout_height="300dp"/>

    <Button
        android:id="@+id/button"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Call JS"
        android:textAllCaps="false"
        android:layout_below="@+id/webview"
        android:layout_marginTop="10dp" />
</RelativeLayout>

这里直接通过 WebView 的 loadUrl() 方法传入字符串 javascript:callJS() 即可调用 JS 中的 callJS() 方法,但是 loadUrl() 会使页面刷新,并且该方法拿不到 JS 的返回值。

2.2 通过 WebView 的 evaluateJavascript()

该方法与第一种方法的区别是:

  1. 该方法比第一种方法执行效率更高,因为该方法的执行不会使页面刷新;
  2. 该方法在 Android 4.4 及以后才可以使用;
  3. 该方法可以拿到 JS 的返回值;

把第一种方法中的 JS 改成下面这样:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title>JS Interaction</title>
        This is a Web View
    <script>
        function callJS(){
            return "Android 调用了 JS 的 callJS() 方法";
        }
    </script>
</head>

</html>

MainActivity.java 修改如下:

package com.example.webviewtest;

import android.app.AlertDialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.View;
import android.webkit.ValueCallback;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.widget.Button;

import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {

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

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

        WebSettings webSettings = mWebView.getSettings();

        webSettings.setJavaScriptEnabled(true);

        mWebView.loadUrl("file:///android_asset/javascript.html");

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

        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                mWebView.evaluateJavascript("javascript:callJS()", new ValueCallback<String>() {
                    @Override
                    public void onReceiveValue(String value) {
                        // values 是 JS 的返回

                        // evaluateJavascript() 方法返回的值会被封装成一个 JSON 格式的字符串,
                        // 如果 JS 函数返回 "Hello from JavaScript!",
                        // Android 端会接收到 "\"Hello from JavaScript!"\" 这样的结果,
                        // 所以需要转换
                        String result = value != null ? value.replace("\"", "") : "";
                        AlertDialog.Builder b = new AlertDialog.Builder(MainActivity.this);
                        b.setTitle("Alert");
                        b.setMessage(result);
                        b.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                dialog.dismiss();
                            }
                        });
                        b.setCancelable(false);
                        b.create().show();
                    }
                });
            }
        });

    }
}

evaluateJavascript() 方法有两个参数,第一个参数是要执行的 JS 方法,第二个参数是 JS 执行后触发的回调,在这里可以拿到 JS 方法的返回值。

建议在 Android 4.4 以下使用第一种方法,Android 4.4 及以上使用第二种方法。

final int version = Build.VERSION.SDK_INT;
if (version < Build.VERSION_CODES.KITKAT) {
        mWebView.loadUrl("javascript:callJS()");
    } else {
        mWebView.evaluateJavascript("javascript:callJS()", new ValueCallback<String>() {
            @Override
            public void onReceiveValue(String value) {

            }
        });
}

注意,我这里都是在点击事件中调用 JS 的代码,最好等 WebView 页面加载完,在在 WebViewClient 的 onPageFinished() 方法中调用 JS 的代码。

2.3 两种方法的对比

调用方式优点缺点使用建议
loadUrl()简单执行效率低,获取返回值麻烦Android 4.4 以下用这个方法
evaluateJavascript()执行效率高向下兼容性差(Android 4.4 以上可用)Android 4.4 以上用这个方法

3. JS 调用 Android 代码

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

步骤 1 :定义一个要注入到 JS 中的类:

package com.example.webviewtest;

import android.webkit.JavascriptInterface;

public class JsObject {

    // 对于 Build.VERSION_CODES.JELLY_BEAN_MR1 及以上的版本,
    // 只有添加了 @JavascriptInterface 注解的 public 方法才能从 JavaScript 中访问
    @JavascriptInterface
    public void hello(String msg) {
        System.out.println(" JS 调用了 Android 的 hello 方法");
    }
}

步骤 2 :在 assets 目录下添加需要加载的 JS 代码:javascript.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>JS Interaction</title>
    <script>
        function callAndroid(){
            // 由于对象映射,所以调用 test 对象等于调用 Android 中的对象
            test.hello("js调用了android中的hello方法");
        }
    </script>
</head>
<body>
<button type="button" id="button1" onclick="callAndroid()">点击调用 Android 代码</button>
</body>
</html>

步骤 3 :在 Android 中通过 WebView 的 addJavascriptInterface() 方法将 Java 对象注入 WebView 中的 JavaScript。

package com.example.webviewtest;

import android.os.Bundle;
import android.webkit.WebSettings;
import android.webkit.WebView;

import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {

    WebView mWebView;

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

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

        webSettings.setJavaScriptEnabled(true);

        // 通过 addJavascriptInterface() 方法将 Java 对象注入 WebView 中的 JavaScript
        // 参数1:注入的 Java 对象
        // 参数2:暴露在 JS 中对应的实例的名字
        mWebView.addJavascriptInterface(new JsObject(), "test");

        mWebView.loadUrl("file:///android_asset/javascript.html");
    }
}

activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <WebView
        android:id="@+id/webview"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

</RelativeLayout>

这样点击 html 中的 button 会调用 callAndroid() 方法,然后会调用 test 实例的 hello() 方法,相应会调用 JsObject 实例的 hello() 方法。

这个方法的优点是简单,缺点是存在安全漏洞。Google 在 Android 4.2 及以上的版本中规定对被调用的函数要以 @JavascriptInterface 进行注解从而避免漏洞攻击,在 Android 4.2 以下 @JavascriptInterface 是无效的,存在安全漏洞。在 Android 4.2 以下需要采用拦截 prompt() 的方式进行漏洞修复。

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

javascript.html:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title>JS Interaction</title>

    <script>
        function callAndroid(){
            document.location = "js://webview?arg1=111&arg2=222";
        }
    </script>
</head>

<body>
<button type="button" id="button1" onclick="callAndroid()">点击调用 Android 代码</button>
</body>
</html>

MainActivity.java:

package com.example.webviewtest;

import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.webkit.WebResourceRequest;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;

import androidx.appcompat.app.AppCompatActivity;

import java.util.Set;

public class MainActivity extends AppCompatActivity {

    private static final String TAG = MainActivity.class.getSimpleName();

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

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

        WebSettings webSettings = mWebView.getSettings();

        webSettings.setJavaScriptEnabled(true);

        mWebView.loadUrl("file:///android_asset/javascript.html");

        mWebView.setWebViewClient(new WebViewClient() {

            //该重载方法不建议使用了,Android 7.0 以上已经摒弃了
            @Override
            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                return super.shouldOverrideUrlLoading(view, url);
            }

            @Override
            public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
                // 传入进来的 url = "js://webview?arg1=111&arg2=222"
                Uri uri;
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                    uri = request.getUrl();
                } else {
                    uri = Uri.parse(request.toString());
                }
                // 先判断 scheme
                if ("js".equals(uri.getScheme())) {
                    // 再判断 authority
                    if ("webview".equals(uri.getAuthority())) {

                        // 执行对应的代码
                        Log.d(TAG, "JS 调用了 Android 的方法");
                        Set<String> sets = uri.getQueryParameterNames();
                        for (String key : sets) {
                            Log.d(TAG, "param:" + uri.getQueryParameter(key));
                        }
                        return true;
                    }
                }
                return super.shouldOverrideUrlLoading(view, request);
            }
        });
    }
}

原理就是通过解析 url 的协议调用相应的代码。

该方式的优点是不存在方式一的漏洞,缺点是如果 JS 想获取 Android 方法的返回值会比较麻烦。

如果 JS 想要拿到 Android 方法的返回值,只能通过 WebView 的 loadUrl() 去执行 JS 代码中的方法把返回值传递给 JS,代码如下:

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

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

3.3 通过 WebChromeClient 的 onJsAlert()、onJsConfirm()、onJsPrompt() 方法回调拦截

在 JS 中,有三个常用的弹出对话框的方法:

这 3 个方法分别会触发 WebChromeClient 的 onJsAlert()、onJsConfirm()、onJsPrompt() 方法。

最常用的是使用 JS 的 prompt() 方法,因为只有 prompt() 可以返回任意类型的值,看下面的示例:

javascript.html:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>JS Interactiion</title>

    <script>
        function callPrompt(){
            var result = prompt("js://webview?arg1=8&arg2=9");
            console.log("result is:" + result);
        }
    </script>
</head>
<body>
<button type="button" id="button1" onclick="callPrompt()">点击调用 Android 代码</button>
</body>
</html>

MainActivity.java:

package com.example.webviewtest;

import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.webkit.JsPromptResult;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;

import androidx.appcompat.app.AppCompatActivity;

import java.util.Set;

public class MainActivity extends AppCompatActivity {

    private static final String TAG = MainActivity.class.getSimpleName();

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

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

        WebSettings webSettings = mWebView.getSettings();

        webSettings.setJavaScriptEnabled(true);

        mWebView.loadUrl("file:///android_asset/javascript.html");

        mWebView.setWebChromeClient(new WebChromeClient() {
            @Override
            public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
                // 传入进来的 url = "js://webview?arg1=8&arg2=9"

                Uri uri = Uri.parse(message);
                if (uri.getScheme().equals("js")) {

                    if (uri.getAuthority().equals("webview")) {

                        // 执行对应的代码
                        Log.d(TAG, "JS 调用了 Android 的方法");
                        Set<String> sets = uri.getQueryParameterNames();
                        for (String key : sets) {
                            Log.d(TAG, "param:" + uri.getQueryParameter(key));
                        }

                        // JsPromptResult 用于把结果返回给 JS
                        result.confirm("JS 调用 Android 的方法成功啦!");
                    }
                    return true;
                }
                return super.onJsPrompt(view, url, message, defaultValue, result);
            }
        });
    }
}

3.4 三种方法的对比

调用方式优点缺点使用场景
通过 WebView 的 addJavascriptInterface() 进行对象映射简单Android 4.2 以下存在漏洞Android 4.2 以上用这个方法
通过 WebViewClient 的 shouldOverrideUrlLoading() 方法拦截 url没有漏洞问题相对复杂,需要解析 url不需要 Android 端的方法的返回值的场景
通过 WebChromeClient 的 onJsAlert()、onJsConfirm()、onJsPrompt() 方法回调拦截没有漏洞问题相对复杂,需要解析 url能满足大多数场景

以上就是Android与JS进行交互的实现方法的详细内容,更多关于Android与JS交互的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文