我在这个案例上卡住了一会儿。
我在 Android 4.4.3 上有一个 webview,其中有一个包含二进制数据的 float32array
的 webapp。我想通过一个使用 JavascriptInterface
绑定的函数将该 array
传递给 Java Android。
然而,在 Java 中,似乎我只能传递像 String
、int
等基本类型。
是否有一种方法可以把这个 arrayBuffer 传递给 Java 呢?
谢谢!
我在这个案例上卡住了一会儿。
我在 Android 4.4.3 上有一个 webview,其中有一个包含二进制数据的 float32array
的 webapp。我想通过一个使用 JavascriptInterface
绑定的函数将该 array
传递给 Java Android。
然而,在 Java 中,似乎我只能传递像 String
、int
等基本类型。
是否有一种方法可以把这个 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);
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);
因此,对于一个1024 * 1024 * 10
的Uint8Array
- 每次通过创建和销毁1000万个Java对象,在我的模拟器上需要10秒的CPU时间。
我们尝试过的一件事是创建一个HTTP服务器,并通过XMLHttpRequest
将结果POST
到它。这个方法可以实现 - 但最终会造成大约200ms的延迟并引入一个nasty memory leak。
Android API 23添加了对MessageChannel
的支持,可以通过createWebMessageChannel()
使用,如this answer所示。这非常慢,仍然与GIN序列化(像@JavascriptInterface
方法)并产生额外的延迟。我无法获得合理的性能。
@JavascriptInterface
。
通过阅读转换代码,人们可以看到(并且谷歌已经证实),避免许多转换的唯一方法是传递一个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支持,因此可以使用另一种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.
@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序列化的人。
很简单
初始化部分
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
将您的数据序列化为一个字符串,然后在您的应用程序中反序列化。
@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);
如果你想进行同步调用,只需使用base64编码和解码:(将base64字符串转换为ArrayBuffer)
@JavascriptInterface
void onBytes(String base64) {
// decode here
}
//javascript
if ((window as any)?.JsBridge?.downloadFile) {
file.arrayBuffer().then(arr => {
(window as any)?.JsBridge?.downloadFile(new Uint8Array(arr), filename)
})
}
//java
@JavascriptInterface
public void downloadFile(byte[] bytes, String filename) {
NativeApi.log("downloadFile", filename + "," + bytes.length);
}
顺便提一下,文件大小限制为10M,在异步任务处理后弹出提示信息!
实际上并不理解 TypedArrays(看起来像是因为它说 TypeArray,但是该文件中的所有内容都是 TypeXZY)
所以我肯定可以想象它复制字符串更快。 然而,没有理由它不能传递一个不需要复制或者至少只需要一次原始复制的类型数组。
这需要对 Chromium 进行补丁。