java - 有没有办法在 Android 上将 arraybuffer 从 javascript 传递到 java?

标签 java javascript android binding

我在这个案子上卡住了片刻。

我在 Android 4.4.3 上有一个 webview,其中我有一个包含二进制数据的 float32array 的 webapp。我想通过与 JavascriptInterface 绑定(bind)的函数将该 array 传递给 Java Android。 但是,似乎在 Java 中,我只能传递原始类型,如 Stringint 等...

有没有办法把这个 arrayBuffer 给 Java?

谢谢!

最佳答案

好的,在与 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);

在上面的代码中(来 self 的旧答案)和 Alex 的 - the conversion performed for the array是残酷的:

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;

依次coerces every array element to a Java object :

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);

因此,对于 1024 * 1024 * 10 Uint8Array - 每次传递都会创建和销毁一千万个 Java 对象,导致我的模拟器占用 10 秒的 CPU 时间。

创建 HTTP 服务器

我们尝试的一件事是创建一个 HTTP 服务器并通过 XMLHttpRequestPOST将结果发送给它。这有效 - 但最终花费了大约 200 毫秒的延迟并且还引入了 nasty memory leak .

MessageChannels 很慢

Android API 23 添加了对 MessageChannel 的支持s,可以通过 createWebMessageChannel() 使用如图this answer .这非常慢,仍然使用 GIN 进行序列化(如 @JavascriptInterface 方法)并导致额外的延迟。我无法让它以合理的性能运行。

值得一提的是,Google 表示他们相信这是前进的方向,并希望在某个时候通过 @JavascriptInterface 推广消息 channel 。

传递字符串有效

阅读转换代码后 - 可以看到(Google 证实了这一点)避免多次转换的唯一方法是传递一个 String 值。这只会通过:

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

将结果一次转换为 UTF8,然后再次转换为 Java 字符串。这仍然意味着数据(在本例中为 10MB)被复制了三次——但可以在“仅”60 毫秒内传递 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 序列化)。

关于java - 有没有办法在 Android 上将 arraybuffer 从 javascript 传递到 java?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27034897/

相关文章:

java - 无法使用 Eclipse 导入 Azure 发布设置文件

java - 集成测试 Spring SseEmitters

javascript - 在虚拟表中单击功能不起作用

javascript - 在遍历数组中有序的一系列字母时,如何避免将三次重复计数为对?

android - Kotlin的 'const val num = 1'是什么类型?他们如何定义它?

android - 使用 YouTube 数据 API 的 YouTube ImageView

android - 延迟加载android ExpandableListView

java - Android-Studio-2.3 错误 :java. util.concurrent.ExecutionException : java. lang.RuntimeException: AAPT 进程未准备好接收命令

java - 使用 Java 为多个图像制作动画

javascript - Typeahead.js 使用数组对象,在每次 onkeyup 时重新加载源