Android与Web通信架构设计:通信方式盘点

Android与Web的通信方式

Native调用JS

Native端调用Web端,这个比较简单,JavaScript作为解释性语言,最大的一个特性就是可以随时随地地通过解释器执行一段JS代码,所以可以将拼接的JavaScript代码字符串,传入JS解析器执行就可以,JS解析器在这里就是webView。

loadUrl

Android 4.4之前只能用loadUrl来实现,并且无法执行回调:

java 复制代码
String jsCode = String.format("window.showWebDialog('%s')", text);
webView.loadUrl("javascript: " + jsCode);

evaluateJavascript

Android 4.4之后提供了evaluateJavascript来执行JS代码,并且可以获取返回值执行回调:

java 复制代码
String jsCode = String.format("window.showWebDialog('%s')", text);
webView.evaluateJavascript(jsCode, new ValueCallback<String>() {
  @Override
  public void onReceiveValue(String value) {

  }
});

JS调用Native

拦截JS对话框

这种方式用的比较少,本质上也是通过解析URL来实现通信。JS有三种类型的对话框分别是alert()、confirm()、prompt(),可以重写WebViewClient对应的onJsAlert()、onJsConfirm()、onJsPrompt()三个方法,以onJsPrompt为例:

Web段代码

javascript 复制代码
<!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>

Android段代码:

java 复制代码
 public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
                // 根据协议的参数,判断是否是所需要的url
                //假定传入进来的 message = "jsbridge://webview?arg1=111&arg2=222"(同时也是约定好的需要拦截的)

                Uri uri = Uri.parse(message);
                if ( uri.getScheme().equals("jsbridge")) {
     
                    // 所以拦截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);
            }

拦截请求的URL

类似拦截对话框,与Web段约定一个用来通信交互的URL,如:jsbridge://showToast?text=hello,Natvie重写WebViewClient的shouldOverrideUrlLoading方法进行拦截,原理和对话框拦截相似,这里就不贴代码了

addJavascriptInterface

这个方法会通过webView提供的接口,App将Native的相关接口注入到JS的Context(window)的对象中,一般来说这个对象内的方法名与Native相关方法名是相同的,Web端就可以直接在全局window下使用这个暴露的全局JS对象,进而调用原生端的方法。

Android(4.2+)提供了addJavascriptInterface注入:

java 复制代码
webView.addJavascriptInterface(new NativeBridge(this), "NativeBridge");

class NativeBridge {
  private Context ctx;
  NativeBridge(Context ctx) {
    this.ctx = ctx;
  }

  // 增加JS调用接口
  @JavascriptInterface
  public void showNativeDialog(String text) {
    new AlertDialog.Builder(ctx).setMessage(text).create().show();
  }
}

Web端调用:

javascript 复制代码
window.NativeBridge.showNativeDialog('hello');

如何实现回调

前面已经讲完了Native、Web间双向通信的常用方法,但站在一端而言还是一个单向通信的过程 ,比如站在Web的角度:Web调用Native的方法,Native直接相关操作但无法将结果返回给Web,但实际使用中会经常需要将操作的结果返回,也就是JS回调。如何实现?

基于之前的单向通信,我们在一端调用的时候在参数中加一个**callbackId**标记对应的回调,对端接收到调用请求后,进行实际操作,如果带有callbackId,对端再进行一次调用,将结果、callbackId回传回来,这端根据callbackId匹配相应的回调,将结果传入执行就可以了。

有点抽象是不是?下面结合我们在上一节讲的通信方式来举例:

基于代码注入addJavascriptInterface的方式

javascript 复制代码
// Web端代码:
<body>
  <div>
    <button id="showBtn">获取Native输入,以Web弹窗展现</button>
  </div>
</body>
<script>
  let id = 1;
  // 根据id保存callback
  const callbackMap = {};
  // 使用JSSDK封装调用与Native通信的事件,避免过多的污染全局环境
  window.JSSDK = {
    // 获取Native端输入框value,带有回调
    getNativeEditTextValue(callback) {
      const callbackId = id++;
      callbackMap[callbackId] = callback;
      // 调用JSB方法,并将callbackId传入
      window.NativeBridge.getNativeEditTextValue(callbackId);
    },
    // 接收Native端传来的callbackId
    receiveMessage(callbackId, value) {
      if (callbackMap[callbackId]) {
        // 根据ID匹配callback,并执行
        callbackMap[callbackId](value);
      }
    }
  };

    const showBtn = document.querySelector('#showBtn');
  // 绑定按钮事件
  showBtn.addEventListener('click', e => {
    // 通过JSSDK调用,将回调函数传入
    window.JSSDK.getNativeEditTextValue(value => window.alert('Natvie输入值:' + value));
  });
</script>

代码很简单,简而言之:JS以键值对的形式保存回调,调用native方法时,将key传过去

java 复制代码
// Android端代码
webView.addJavascriptInterface(new NativeBridge(this), "NativeBridge");

class NativeBridge {
  private Context ctx;
  NativeBridge(Context ctx) {
    this.ctx = ctx;
  }

  // 获取Native端输入值
  @JavascriptInterface
  public void getNativeEditTextValue(int callbackId) {
    MainActivity mainActivity = (MainActivity)ctx;
    // 获取Native端输入框的value
    String value = mainActivity.editText.getText().toString();
    // 需要注入在Web执行的JS代码
    String jsCode = String.format("window.JSSDK.receiveMessage(%s, '%s')", callbackId, value);
    // 在UI线程中执行
    mainActivity.runOnUiThread(new Runnable() {
      @Override
      public void run() {
        mainActivity.webView.evaluateJavascript(jsCode, null);
      }
    });
  }
}

Native接收到callbackId后,在执行evaluateJavascript方法反向调用JS方法,将返回值传过去。

基于拦截请求的方式

同理,重写shouldOverrideUrlLoading拦截请求,解析URL参数,去执行Native方法,一样可以达到我们的目的。

为什么要提这种方法呢?在接下来的文章将会讲解基于shouldOverrideUrlLoading 的通信框架JsBridge的实现原理

相关推荐
也无晴也无风雨1 小时前
深入剖析输入URL按下回车,浏览器做了什么
前端·后端·计算机网络
SRC_BLUE_171 小时前
SQLI LABS | Less-39 GET-Stacked Query Injection-Intiger Based
android·网络安全·adb·less
Martin -Tang1 小时前
Vue 3 中,ref 和 reactive的区别
前端·javascript·vue.js
FakeOccupational3 小时前
nodejs 020: React语法规则 props和state
前端·javascript·react.js
放逐者-保持本心,方可放逐3 小时前
react 组件应用
开发语言·前端·javascript·react.js·前端框架
曹天骄4 小时前
next中服务端组件共享接口数据
前端·javascript·react.js
无尽的大道4 小时前
Android打包流程图
android
阮少年、4 小时前
java后台生成模拟聊天截图并返回给前端
java·开发语言·前端
镭封6 小时前
android studio 配置过程
android·ide·android studio
夜雨星辰4876 小时前
Android Studio 学习——整体框架和概念
android·学习·android studio