简述

在移动应用开发中,AndroidJavaScript是两个常用的技术栈。Android作为主流的移动操作系统,而JavaScript则是用于网页前端和跨平台开发的脚本语言。为了完成更好的用户体验和功能扩展,Android与JavaScript之间的通讯变得至关重要。本文将介绍Android与JavaScript之间的回调通讯技巧

通讯基础

  1. 经过 WebView 进行通讯

    Android 的 WebView 组件供给了 evaluateJavascript 办法,该办法能够履行 JavaScript 代码并获取回来成果。我们能够使用这一特性完成 Android 和 JavaScript 之间的通讯。详细完成过程如下:

    1. 在 Android 代码中,经过 evaluateJavascript 办法履行 JavaScript 代码。
  2. 使用 JavaScriptInterface 完成通讯

    我们能够使用 JavascriptInterface 接口完成JavaScript 与 Android 的通讯。详细完成过程如下:

    1. 在 Android 代码中创立一个类,完成 JavascriptInterface 接口。
    2. 在该类中定义需要供 JavaScript 调用的办法,并添加 @JavascriptInterface 注解。
    3. 在 JavaScript 中经过 window.AndroidFunction 对象调用 Android 代码中的办法,完成通讯,其间AndroidFunction是注册JavascriptInterface时指定的, 如下所示:
      webView.addJavascriptInterface(new Object() {
          @JavascriptInterface
          public void jsCallback(String message) { 
              // ... 
          } 
      }, "AndroidFunction");
      

Android调用JavaScript函数

  1. 疏忽回来值

        val webView = findViewById<WebView>(R.id.webView)
        webView.evaluateJavascript("jsFunction('message')", null)
    
  2. 获取回来值

        val webView = findViewById<WebView>(R.id.webView)
        webView.evaluateJavascript("jsFunction('message')") { result ->
            Log.e("TAG", result)
        }
    

JavaScript调用Android函数

// 在JavaScript中调用Android函数,并传递参数
function callAndroidFunctionWithParameter() { 
    var message = "Hello from JavaScript!";
    AndroidFunction.jsCallback(message);
}

在上述示例中,JavaScript函数callAndroidFunctionWithParameter()将参数message传递给Android函数jsCallback()

双向回调通讯

  1. Javascript传递回调给Android

    上述的 AndroidFunction.jsCallback(message)办法目前只能传递字符串,假如不做特殊处理,是无法履行回调函数的, 履行 AndroidFunction.jsCallback(message)时,在Android中获取到的是字符串,这时将回调函数转换成回调令牌,然后经过令牌履行相应的回调函数,过程如下:

    1. 在js中将回调函数转换成仅有令牌,然后使用map以令牌为key存储回调函数,以便让Android端根据令牌来履行回调函数

      const recordCallMap = new Map<string, Function>();
      // Android端调用
      window.jsbridgeApiCallBack = function (callUUID: string, callbackParamsData: any) {
          // 经过ID获取对应的回调函数
          const fun = recordCallMap.get(callUUID);
          // 履行回调 `callbackParamsData`回调函数的参数 可有可无
          fun && fun(callbackParamsData);
          // 履行结束后开释资源
          recordCallMap.delete(callUUID);
      }
      function getUuid() {
          // 生成仅有ID
          return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/\[xy]/g, function (c) {
          var r = (Math.random() \* 16) | 0,
          v = c == 'x' ? r : (r & 0x3) | 0x8;
          return v.toString(16);
          });
      }
      // 一致处理调用Android的办法
      export function androidCall(funname: string, funData: string, fun: Function) {
          if (!AndroidFunction) {
              return;
          }
          const dataObj = {
              funName: funname,
              funData: funData
          }
          if (typeof fun === "function") {
              const funId = getUuid();
              Object.assign(dataObj, { funId });
              recordCallMap.set(funId, fun);
          }
          AndroidFunction.jsCall(JSON.stringify(dataObj))
      }
      
    2. 在Android端注册JavascriptInterface一致让js调用

      class JsCallbackModel {
          lateinit var funData: String
          lateinit var funId: String
          lateinit var funName: String
      }
      abstract class JsFunctionCallBack(var funId: String) {
          abstract fun callback(respData: String?)
          abstract fun callback()
          abstract fun callback(respData: Boolean)
      }
      class JavaScriptCall(private val webView: WebView) {
          private fun jsCall(funName: String, funData: String, jsFunction: JsFunctionCallBack) {
              when(funName) {
                  "screenCapture" -> {
                      screenshot(funData.toInt(), jsFunction)
                  }
              }
          }
          @JavascriptInterface
          fun jsCall(data: String) {
              // 将json字符串解析成kotlin对象
              val gson = GsonBuilder().create()
              val model = gson.fromJson(data, JsCallbackModel::class.java)
              // 假如不存在函数称号 则疏忽
              if (model.funName == "") {
                  return
              }
              val jsFunction: JsFunctionCallBack = object : JsFunctionCallBack(model.funId) {
                  override fun callback(respData: String?) {
                      if (webView == null) {
                          return
                      }
                      if (funId.isEmpty()) {
                          return
                      }
                      content.webView.post {
                        webView.evaluateJavascript("jsBridgeApiCallBack('$funId', "$respData")", null)
                      }
                  }
                  override fun callback() {
                      if (funId.isEmpty()) {
                          return
                      }
                      content.webView.post {
                          webView.evaluateJavascript("jsBridgeApiCallBack('$funId')", null)
                      }
                  }
                  override fun callback(respData: Boolean) {
                      if (funId.isEmpty()) {
                          return
                      }
                      content.webView.post {
                        webView.evaluateJavascript("jsBridgeApiCallBack('$funId', '$respData')", null)
                      }
                  }
              }
              jsCall(model.funName, model.funData, jsFunction);
          }
          private fun screenshot(quality: Int, jsFunction: JsFunctionCallBack) {
              // 履行逻辑...
              // 履行js回调
              jsFunction("base64...")
          }
      }
      
    3. 在js中传递回调函数调用Android截图

      function screenshot(): Promise<string> {
          return new Promise(resolve => {
              androidCall("screenshot", (base64: string) => resolve(base64))
          })
      }
      screenshot().then(base64 => {
      })
      
  2. Android传递回调给Javascript

    原理跟Javascript传递回调给Android是相同的,详细完成如下:

    1. Android端

      class JavaScriptCall(private val webView: WebView) {
          companion object {
              val dataF: MutableMap<String, (callData: String) -> Unit> = mutableMapOf()
              fun jsCall(webView: WebView, funName: String, funData: String, funHandler: (callData: String) -> Unit) {
                  val ctx: MutableMap<String, String> = mutableMapOf()
                  ctx["funName"] = funName
                  ctx["funData"] = funData
                  val uuid = UUID.randomUUID().toString()
                  ctx["funId"] = uuid
                  dataF[uuid] = funHandler
                  webView.post {
                      val gson = GsonBuilder().create()
                      val json = gson.toJson(ctx)
                      webView.evaluateJavascript("jsCall('$json')", null)
                  }
              }
          }
          @JavascriptInterface
          fun androidsBridgeApiCallBack(callUUID: String, callData: String) {
              val funHandler = dataF[callUUID];
              if (funHandler != null) {
                  funHandler(callData)
                  dataF.remove(callUUID)
              }
          }
      }
      
    2. Js端

      function doSome(funId, data, callback) {
          // 履行逻辑...
          // 履行Android的回调函数
          callback(funId, data)
      }
      function androidFunction(funId, respData: any?) {
          AndroidFunction.androidsBridgeApiCallBack(funId, respData)
      }
      function androidCallback(funId, funName, funData, fun) {
          switch (funName) {
              case 'doSome': {
                  doSome(funId, funData, fun)
              }
          }
      }
      window.jsCall = function (json: string) {
          const obj = JSON.parse(json)
          const funName = obj['funName']
          const funData = obj['funData']
          const funId = obj['funId']
          if (!funName) {
               return
          }
          androidCallback(funId, funName, funData, androidFunction)
      }
      
    3. 在Android中传递回调函数调用js的doSome

      JavaScriptCall.jsCall(webView, "doSome", "data") { callParam ->
          Log.e("tAG", "回调参数: $callParam")
      }