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

Posted

技术标签:

【中文标题】有没有办法在Android上将arraybuffer从javascript传递到java?【英文标题】:Is there a way to pass an arraybuffer from javascript to java on Android? 【发布时间】:2015-01-18 00:53:03 【问题描述】:

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

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

有没有办法给 Java 这个 arrayBuffer ?

谢谢!

【问题讨论】:

您能否在服务器上创建数组并让您的 Android 应用程序中的 Java 代码通过 HTTP 请求它? 【参考方案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 的 - 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 对象,从而导致我的模拟器上的 CPU 时间为 10 秒。

创建 HTTP 服务器

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

MessageChannel 很慢

Android API 23 增加了对MessageChannels 的支持,可以通过createWebMessageChannel() 使用,如this answer 所示。这非常慢,仍然使用 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)被复制了 3 次——但可以在“仅”60 毫秒内传递 10MB 的数据——这比上述数组方法所花费的 10 秒要合理得多。

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

在 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 序列化)。

【讨论】:

我要感谢 CommonWare、Petka 和 Chromium 的 Tarbo 帮助我并指导我解决这个问题 :) 我无法让它与这些编码一样快,最快的方法是使用 utf-8。 3MB 也需要 150-300 毫秒。你知道为什么会这样吗? 您在什么设备上进行了测试? UTF8 是 300 毫秒,这要慢得多 - 您是在真正的旧设备上测试还是使用文本编码 polyfill 进行测试 我确实检查了每个可能的字节值(并将其运送到一年前每天移动数百 TB 的生产环境)。我不认为 JavaScript 编码 APIs 实际上做 x-user-defined,检查起来可能很有趣。 本这个修复太棒了。你是个种马【参考方案2】:

其实很简单

初始化部分

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

JavaScript接口

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

【讨论】:

其实,有一种更好的方法——使用 MessagePort——避免在这个答案中复制——developer.android.com/reference/android/webkit/…——html.spec.whatwg.org/multipage/… 意思是,它可能会快两倍。当赏金到期时,这仍然是最好的答案 - 所以欢呼。如果您想更新您的答案,我将不胜感激不必再发布一个。 所以,事实证明这真的工作,如果您尝试传递超过 10mb,它会使 Chrome 进程崩溃,并且传递 10mb 需要 10s(和 1s通过 1mb) @BenjaminGruenbaum 尝试将您的阵列切片 1mb。将数据传递给android或js的操作是一个繁重的过程。您实际上没有传递数据,而是创建了数据的副本。所以它需要两倍的内存。 我修好了,我会在本周晚些时候发布一个有效的答案【参考方案3】:

将您的数据序列化为字符串,然后在您的应用中反序列化。

【讨论】:

似乎 Cordova 也是如此。【参考方案4】:

克隆 ArrayBuffer 使其工作 - 以 ArrayBuffer 为后盾的 TypedArray 无法很好地整合到 Android 中。

如果将 ArrayBuffer 复制到新的 TypedArray 中,则可以避免昂贵的序列化开销。

在阅读器上:

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

在 JS 方面:

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

工作得很好:)

【讨论】:

【参考方案5】:

如果你想要 Sync 调用,只需使用 base64 编码和解码:(Convert base64 string to ArrayBuffer)

@JavascriptInterface
void onBytes(String base64) 
    // decode here

如果你想异步调用:

您可以在Android应用程序中创建http服务器,然后在javascript端使用“xhr”或“fetch”发送二进制或字符串异步


并且不要使用上面提到的“iso-8859-1”或“windows-1252”,这很危险!!! “iso-8859-1”具有无法在 javascript 和 java 之间解码的未定义代码。 (https://en.wikipedia.org/wiki/ISO/IEC_8859-1)

【讨论】:

使用 iso-8859-1/windows-1252 是个坏主意,但 x-user-defined 工作。你有比较 this 和 base64 的性能吗?【参考方案6】:

第二个答案https://source.chromium.org/chromium/chromium/src/+/master:content/browser/android/java/gin_java_script_to_java_types_coercion.cc;l=628?q=gin_java_scr&ss=chromium中链接的代码

实际上并不理解 TypedArrays(看起来是这样,因为它说 TypeArray 但是,该文件中的所有内容都是 TypeXZY)

所以我绝对可以想象复制一个字符串会更快。 但是,没有理由不进行复制就不能传递类型化数组,或者至少只有一个原始副本。

不过,它需要一个铬补丁。

【讨论】:

以上是关于有没有办法在Android上将arraybuffer从javascript传递到java?的主要内容,如果未能解决你的问题,请参考以下文章

有没有办法在打字稿的类型级别上将两个数字相加?

XMLHttpRequest responseText到arrayBuffer,unicode U + FFFD

android python 完全集成

在JavaScript中如何将ArrayBuffer转换为Float64Array?

在Android上将int数组转换为Bitmap

Windows上的Kivy独立android apk