好的,所以在与 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 序列化)。