有没有办法在Android上将数组缓冲区从javascript传递到java?

2022-09-03 04:35:39

我在这个案子上卡了一会儿。

我有一个Web视图,关于我在哪里有一个包含二进制数据的web应用程序。我想通过与 绑定的函数将其传递给Java Android。但是,似乎在Java中,我只能传递诸如,等等的基元类型。Android 4.4.3float32arrayarrayJavascriptInterfaceStringint

有没有办法给Java这个数组Buffer?

谢谢!


答案 1

好吧,所以在与Google工程部门聊天之后,在阅读代码之后,我得出了以下结论。

有效地传递二进制数据是不可能的

通过@JavascriptInterface在 JavaScript 和 Java 之间有效地传递二进制数据是不可能的:

在Java方面:

@JavascriptInterface
void onBytes(byte[] bytes) {
   // bytes available here
}

在JavaScript方面:

var byteArray = new Uint8Array(buffer);
var arr = new Uint8Array(byteArray.length);
for(var i = 0; i < byteArray.length; i++) {
  arr[i] = byteArray[i];
}
javaObject.onBytes(arr);

在上面的代码(来自我的旧答案)和Alex的代码中 - 对数组执行的转换是残酷的:

case JavaType::TypeArray:
  if (value->IsType(base::Value::Type::DICTIONARY)) {
    result.l = CoerceJavaScriptDictionaryToArray(
        env, value, target_type, object_refs, error);
  } else if (value->IsType(base::Value::Type::LIST)) {
    result.l = CoerceJavaScriptListToArray(
        env, value, target_type, object_refs, error);
  } else {
    result.l = NULL;
  }
  break;

这反过来又将每个数组元素强制为Java对象

for (jsize i = 0; i < length; ++i) {
    const base::Value* value_element = null_value.get();
    list_value->Get(i, &value_element);
    jvalue element = CoerceJavaScriptValueToJavaValue(
        env, value_element, target_inner_type, false, object_refs, error);
    SetArrayElement(env, result, target_inner_type, i, element);

因此,对于一个 - 每次传递都会创建并销毁一千万个Java对象,从而在我的模拟器上产生10秒的CPU时间。1024 * 1024 * 10Uint8Array

创建 HTTP 服务器

我们尝试的一件事是创建一个HTTP服务器,并通过.这有效 - 但最终导致大约200ms的延迟,并且还引入了令人讨厌的内存泄漏POSTXMLHttpRequest

消息通道速度慢

Android API 23 增加了对 MessageChannels 的支持,可以通过 createWebMessageChannel() 使用,如本答案所示。这非常慢,仍然使用GIN(如方法)进行序列化并产生额外的延迟。我无法让它以合理的性能工作。@JavascriptInterface

值得一提的是,谷歌表示,他们相信这是前进的方向,并希望在某个时候推广信息渠道。@JavascriptInterface

传递字符串有效

阅读转换代码后 - 可以看到(Google已确认这一点),避免多次转换的唯一方法是传递值。这只能通过:String

case JavaType::TypeString: {
  std::string string_result;
  value->GetAsString(&string_result);
  result.l = ConvertUTF8ToJavaString(env, string_result).Release();
  break;
}

这会将结果转换为 UTF8 一次,然后再次转换为 Java 字符串。这仍然意味着数据(在本例中为10MB)被复制三次 - 但是可以在“仅”60ms内传递10MB的数据 - 这比上述数组方法所花费的10秒要合理得多。

Petka提出了使用8859编码的想法,该编码可以将单个字节转换为单个字母。不幸的是,JavaScript的TextDecoder API不支持它 - 因此可以使用Windows-1252,这是另一个1字节编码。

在JavaScript方面,人们可以做到:

var a = new Uint8Array(1024 * 1024 * 10); // your buffer
var b = a.buffer
// actually windows-1252 - but called iso-8859 in TextDecoder
var e = new TextDecoder("iso-8859-1"); 
var dec = e.decode(b);
proxy.onBytes(dec); // this is in the Java side.

然后,在Java方面:

@JavascriptInterface
public void onBytes(String dec) throws UnsupportedEncodingException
    byte[] bytes = dec.getBytes("windows-1252");
    // work with bytes here
}

它的运行时间大约是直接序列化的1/8。它仍然不是很快(因为字符串被填充到16位而不是8位,然后通过UTF8,然后再次填充到UTF16)。但是,与替代方案相比,它以合理的速度运行。

在与维护此代码的相关方交谈后, 他们告诉我,它尽可能好地使用当前的API。我被告知我是第一个要求这个的人(快速JavaScript到Java序列化)。


答案 2

这很简单

初始化部分

 JavaScriptInterface jsInterface = new JavaScriptInterface(this);
 webView.getSettings().setJavaScriptEnabled(true);
 webView.addJavascriptInterface(jsInterface, "JSInterface");

JavaScriptInterface

public class JavaScriptInterface {
        private Activity activity;

        public JavaScriptInterface(Activity activiy) {
            this.activity = activiy;
        }
        @JavascriptInterface
        public void putData(byte[] bytes){
            //do whatever
        }
    }

Js 部分

<script>
  function putAnyBinaryArray(arr) {
        var uint8 = Uint8Array.from(arr);
        window.JSInterface.putData(uint8);
  };
</script>

TypedArray.from polyfill 如果需要 : https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/TypedArray/from