有没有办法在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 中,我只能传递 String
、int
等原始类型...
有没有办法给 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 服务器,并通过 XMLHttpRequest
POST
将结果发送给它。这行得通 - 但最终花费了大约 200 毫秒的延迟,并且还引入了 nasty memory leak。
MessageChannel 很慢
Android API 23 增加了对MessageChannel
s 的支持,可以通过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