欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android webview与js交互

程序员文章站 2022-04-30 21:10:13
...

Android 默认方法

最全面总结 Android WebView与 JS 的交互方式

Android调用JS代码

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

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

示例演示:点击Android按钮,即调用WebView JS(文本名为javascript)中callJS()
示例说明:为了方便展示,本文是采用Andorid调用本地JS代码说明;实际情况时,Android更多的是调用远程JS代码,即将加载的JS代码路径改成url即可
Android webview与js交互

方式一:通过WebView的loadUrl()
1、新建 javascript.html 放着 src/main/assets 文件夹下(选中 main,右键 new - folder - Assets Folder)

<!DOCTYPE html>
<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>
	// Android需要调用的方法
	function callJS(){
	   alert("Android调用了JS的callJS方法");
	}
	</script>
 </body>
</html>

2、在Android里通过WebView设置调用JS代码

public class MainActivity extends AppCompatActivity {
    WebView mWebView;
    Button button;

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

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

        WebSettings webSettings = mWebView.getSettings();

        // 设置与Js交互的权限
        webSettings.setJavaScriptEnabled(true);
        // 设置允许JS弹窗
        webSettings.setJavaScriptCanOpenWindowsAutomatically(true);

        // 先载入JS代码
        // 格式规定为:file:///android_asset/文件名.html
        mWebView.loadUrl("file:///android_asset/javascript.html");

        button = (Button) findViewById(R.id.button);
        button.setOnClickListener(v -> {
            // 通过Handler发送消息
            mWebView.post(new Runnable() {
                @Override
                public void run() {
                    // 注意调用的JS方法名要对应上
                    // 调用javascript的callJS()方法
                    mWebView.loadUrl("javascript:callJS()");
                }
            });

        });

        // 由于设置了弹窗检验调用结果,所以需要支持js对话框
        // webview只是载体,内容的渲染需要使用webviewChromClient类去实现
        // 通过设置WebChromeClient对象处理JavaScript的对话框
        //设置响应js 的Alert()函数
        mWebView.setWebChromeClient(new WebChromeClient() {
            @Override
            public boolean onJsAlert(WebView view, String url, String message, final JsResult result) {
                AlertDialog.Builder b = new AlertDialog.Builder(MainActivity.this);
                b.setTitle("Alert");
                b.setMessage(message);
                b.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        result.confirm();
                    }
                });
                b.setCancelable(false);
                b.create().show();
                return true;
            }
        });
    }
}

注意:JS代码调用一定要在 onPageFinished() 回调之后才能调用,否则不会调用
onPageFinished()属于WebViewClient类的方法,主要在页面加载结束时调用

方式二:通过WebView的evaluateJavascript()
该方法的执行不会使页面刷新,而第一种方法(loadUrl )的执行则会。比第一种方法效率更高、使用更简洁。Android 4.4 后才可使用

button.setOnClickListener(v -> {
            // 通过Handler发送消息
            mWebView.post(new Runnable() {
                @Override
                public void run() {
                    mWebView.evaluateJavascript("javascript:callJS()", new ValueCallback<String>() {
                        @Override
                        public void onReceiveValue(String value) {
                            //此处为 js 返回的结果
                        }
                    });
                }
            });
        });

JS去调用Android的代码

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

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

演示效果:
Android webview与js交互

方式1:通过 WebView的 addJavascriptInterface() 进行对象映射
1、定义一个与JS对象映射关系的Android类:AndroidtoJs

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

2、将JS代码以 .html 格式放到 src/main/assets 文件夹里

<!DOCTYPE html>
<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(){
		// 由于对象映射,所以调用test对象等于调用Android映射的对象
        test.hello("js调用了android中的hello方法");
    }
 </script>
 </head>
 <body>
	//点击按钮则调用callAndroid函数
    <button type="button" id="button1" onclick="callAndroid()"></button>
 </body>
</html>

3、在Android里通过WebView设置Android类与JS代码的映射

// 通过addJavascriptInterface()将Java对象映射到JS对象
        //参数1:Javascript对象名
        //参数2:Java对象名
        mWebView.addJavascriptInterface(new AndroidtoJs(), "test");//AndroidtoJS类对象映射到js的test对象

此方法存在严重的漏洞问题,具体请看文章:你不知道的 Android WebView 使用漏洞

方式2:通过 WebViewClient 的方法shouldOverrideUrlLoading ()回调拦截 url
Android通过 WebViewClient 的回调方法shouldOverrideUrlLoading()拦截 url,解析该 url 的协议,如果检测到是预先约定好的协议,就调用相应方法
1、将JS代码以 .html 格式放到 src/main/assets 文件夹里

<!DOCTYPE html>
<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(){
       /*约定的url协议为:js://webview?arg1=111&arg2=222*/
       document.location = "js://webview?arg1=111&arg2=222";
    }
	</script>
 </head>
 <body>
    <button type="button" id="button" value="js调用安卓方式2" onclick="callAndroid2()"></button>
 </body>
</html>

2、在Android通过 WebViewClient复写shouldOverrideUrlLoading()

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

                                          // 步骤2:根据协议的参数,判断是否是所需要的url
                                          // 一般根据scheme(协议格式) & authority(协议名)判断(前两个参数)
                                          //假定传入进来的 url = "js://webview?arg1=111&arg2=222"(同时也是约定好的需要拦截的)

                                          Uri uri = Uri.parse(url);
                                          // 如果url的协议 = 预先约定的 js 协议
                                          // 就解析往下解析参数
                                          if (uri.getScheme().equals("js")) {

                                              // 如果 authority  = 预先约定协议里的 webview,即代表都符合约定的协议
                                              // 所以拦截url,下面JS开始调用Android需要的方法
                                              if (uri.getAuthority().equals("webview")) {

                                                  //  步骤3:
                                                  // 执行JS所需要调用的逻辑
                                                  System.out.println("js调用了Android的方法");
                                                  // 可以在协议上带有参数并传递到Android上
                                                  HashMap<String, String> params = new HashMap<>();
                                                  Set<String> collection = uri.getQueryParameterNames();
                                              }
                                              return true;
                                          }
                                          return super.shouldOverrideUrlLoading(view, url);
                                      }
                                  }
        );

该方式不存在方式1的漏洞,但JS获取Android方法的返回值复杂

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

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

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

方式3:通过 WebChromeClient 的onJsAlert()onJsConfirm()onJsPrompt() 方法回调拦截JS对话框alert()confirm()prompt() 消息

Android通过 WebChromeClient 的onJsAlert()onJsConfirm()onJsPrompt()方法回调分别拦截JS对话框
(即上述三个方法),得到他们的消息内容,然后解析即可
Android webview与js交互
下面的例子将用拦截 JS的输入框(即prompt()方法)。常用的拦截是:拦截 JS的输入框(即prompt()方法),因为只有prompt()可以返回任意类型的值,操作最全面方便、更加灵活;而alert()对话框没有返回值;confirm()对话框只能返回两种状态(确定 / 取消)两个值

1、javascript 放在 assets 文件夹下

<!DOCTYPE html>
<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(){
     // 调用prompt()
     var result=prompt("js://webview?arg1=111&arg2=222");
     alert("demo " + result);
    }

	</script>
 </head>
 <body>
    <button type="button" id="button3" onclick="callAndroid3()">js调用安卓方式3</button>
 </body>
</html>

当使用mWebView.loadUrl("file:///android_asset/javascript.html")加载了上述JS代码后,就会触发回调onJsPrompt(),具体如下:
如果是拦截警告框(即alert()),则触发回调onJsAlert();
如果是拦截确认框(即confirm()),则触发回调onJsConfirm();

2、在Android通过WebChromeClient复写onJsPrompt()

mWebView.setWebChromeClient(new WebChromeClient() {
      @Override
            public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
                // 根据协议的参数,判断是否是所需要的url(原理同方式2)
                // 一般根据scheme(协议格式) & authority(协议名)判断(前两个参数)
                //假定传入进来的 url = "js://webview?arg1=111&arg2=222"(同时也是约定好的需要拦截的)

                Uri uri = Uri.parse(message);
                // 如果url的协议 = 预先约定的 js 协议
                // 就解析往下解析参数
                if ( uri.getScheme().equals("js")) {

                    // 如果 authority  = 预先约定协议里的 webview,即代表都符合约定的协议
                    // 所以拦截url,下面JS开始调用Android需要的方法
                    if (uri.getAuthority().equals("webview")) {

                        //
                        // 执行JS所需要调用的逻辑
                        System.out.println("js调用了Android的方法");
                        // 可以在协议上带有参数并传递到Android上
                        HashMap<String, String> params = new HashMap<>();
                        Set<String> collection = uri.getQueryParameterNames();

                        //参数result:代表消息框的返回值(输入值)
                        result.confirm("js调用了Android的方法成功啦");
                    }
                    return true;
                }
                return super.onJsPrompt(view, url, message, defaultValue, result);
            }

            /*// 通过alert()和confirm()拦截的原理相同,此处不作过多讲述
            // 拦截JS的警告框
            @Override
            public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
                return super.onJsAlert(view, url, message, result);
            }*/

            // 拦截JS的确认框
            @Override
            public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
                return super.onJsConfirm(view, url, message, result);
            }

        });

Android webview与js交互

使用 jsbridge

Android与Js交互之JSBridge的使用
Android 中BridgeWebView 简单使用,以及爬坑

Android4.2以下的addJavascriptInterface存在安全漏洞,虽然在Android4.2之后用@JavascriptInterface代替了addJavascriptInterface但是由于兼容性和安全性问题,基本上我们不会再利用Android系统为我们提供的addJavascriptInterface方法或者@JavascriptInterface注解来实现,所以我们只能另辟蹊径,去寻找既安全,又能实现兼容Android各个版本的方案

1、项目gradle文件中添加maven { url "https://jitpack.io" },位置如下

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

2、在module的gradle中添加依赖

dependencies {
    ...
    implementation 'com.github.lzyzsd:jsbridge:1.0.4'
}

3、布局文件中用BridgeWebView代替WebView

<?xml version="1.0" encoding="utf-8"?>
<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调用js方法" />

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:padding="10dp"
        android:text="以下是webview" />

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

</LinearLayout>

4、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");
//      注册监听方法当js中调用callHandler方法时会调用此方法(handlerName必须和js中相同)
        webview.registerHandler("submitFromWeb", new BridgeHandler() {
            @Override
            public void handler(String data, CallBackFunction function) {
                Log.e("TAG", "js返回:" + data);
                //显示js传递给Android的消息
                Toast.makeText(JsBridgeActivity.this, "js返回:" + data, Toast.LENGTH_LONG).show();
                //Android返回给JS的消息
                function.onCallBack("我是js调用Android返回数据:" + et.getText().toString());
            }
        });
        bt.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
//              调用js中的方法(必须和js中的handlerName想同)
                webview.callHandler("functionInJs", "Android调用js66", new CallBackFunction() {
                    @Override
                    public void onCallBack(String data) {
                        Log.e("TAG", "onCallBack:" + data);
                        Toast.makeText(JsBridgeActivity.this, data, Toast.LENGTH_LONG).show();
                    }
                });
            }
        });
    }
}

5、html 放在 assets 文件夹中

<!DOCTYPE html>
<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="请输入数据" id="text1" type="text" class="validate">
</div>
<button class="waves-effect waves-light btn" onclick="testClick();">js调用Android方法</button>
</body>
<script>
         //js调用Android方法:接收Android传递过来的数据,并做处理

         function testClick() {

          //参数一:调用java中的方法   submitFromWeb是方法名,必须和Android中注册时候的方法名称保持一致
          //参数二:返回给Android端的数据,可以为字符串,json等信息
          //参数三:js接收到Android传递过来的数据之后的相应处理逻辑

            window.WebViewJavascriptBridge.callHandler(
               'submitFromWeb'
               , {'param': "JS成功接收到数据---"}
               , function(responseData) {
                    alert(responseData)
               }
           );
       }

       //JS注册事件监听
       function connectWebViewJavascriptBridge(callback) {
           if (window.WebViewJavascriptBridge) {
               callback(WebViewJavascriptBridge)
           } else {
               document.addEventListener(
                   'WebViewJavascriptBridgeReady'
                   , function() {
                       callback(WebViewJavascriptBridge)
                   },
                   false
               );
           }
       }

        //注册回调函数,第一次连接时调用 初始化函数
       connectWebViewJavascriptBridge(function(bridge) {
            //初始化
           bridge.init(function(message, responseCallback) {
               var data = {
                   'Javascript Responds': 'Wee!'
               };
               alert("jasdashjd");
               responseCallback(data);
           });


           //Android调用js方法:functionInJs方法名称需要保持一致 ,并返回给Android通知

           bridge.registerHandler("functionInJs", function(data, responseCallback) {
               alert(data);
               var data2 = document.getElementById("text1").value;
               var responseData = "我是Android调用js方法返回的数据---"+ data2;
               responseCallback(responseData);
           });
       })
</script>
</html>

运行效果:
Android webview与js交互
资源下载