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的实现原理

相关推荐
bloxed23 分钟前
前端文件下载多方式集合
前端·filedownload
余生H28 分钟前
前端Python应用指南(三)Django vs Flask:哪种框架适合构建你的下一个Web应用?
前端·python·django
LUwantAC37 分钟前
CSS(四)display和float
前端·css
cwtlw41 分钟前
CSS学习记录20
前端·css·笔记·学习
界面开发小八哥1 小时前
「Java EE开发指南」如何用MyEclipse构建一个Web项目?(一)
java·前端·ide·java-ee·myeclipse
米奇妙妙wuu1 小时前
react使用sse流实现chat大模型问答,补充css样式
前端·css·react.js
傻小胖1 小时前
React 生命周期完整指南
前端·react.js
爱数学的程序猿1 小时前
Python入门:6.深入解析Python中的序列
android·服务器·python
brhhh_sehe2 小时前
重生之我在异世界学编程之C语言:深入文件操作篇(下)
android·c语言·网络
zhangphil2 小时前
Android基于Path的addRoundRect,Canvas剪切clipPath简洁的圆形图实现,Kotlin(2)
android·kotlin