有没有办法将数组缓冲区从javascript传递到Android上的java
Is there a way to pass an arraybuffer from javascript to java on Android?
我在这个案子上卡了一会儿。
我在Android 4.4.3
上有一个网络视图,其中我有一个包含二进制数据的float32array
的网络应用程序。我想通过与JavascriptInterface
绑定的函数将该array
传递给Java Android。但是,似乎在 Java 中,我只能传递原始类型,如 String
、int
等......
有没有办法给Java这个arrayBuffer?
谢谢!
,所以在与谷歌工程部门聊天并阅读代码后,我得出了以下结论。
有效地传递二进制数据是不可能的
在JavaScript 和 Java 之间有效地传递二进制数据是不可能的@JavascriptInterface:
在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 的代码中 - 对数组执行的转换是残酷的:
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 对象,从而导致模拟器上的 CPU 时间达到 10 秒。
创建 HTTP 服务器
我们尝试的一件事是创建一个HTTP服务器并通过XMLHttpRequest
将结果POST
给它。这有效 - 但最终花费了大约 200 毫秒的延迟,并且还引入了令人讨厌的内存泄漏。
消息通道很慢
Android API 23 增加了对 MessageChannel
s 的支持,可以通过createWebMessageChannel()
使用,如本答案所示。这非常慢,仍然使用 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)被复制三次 - 但可以在"仅"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序列化)。
这很简单
初始化部分
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
序列化为字符串,然后在应用中取消序列化。
克隆 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);
工作得很好:)
如果你想要同步调用,只需使用 base64 编码和解码:(将 base64 字符串转换为 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)
第二个答案中链接的代码 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)
所以我可以肯定地想象复制字符串更快。但是,没有理由说它不应该能够在不复制的情况下传递类型化数组,或者至少只通过单个原始副本。
不过,这需要对铬进行修补。
我而言,应用程序在没有 http 服务器的情况下相互传输 blob 数据,因此别无选择,只能将 arrayBuffer 发送到 javainterface,如下所示:
//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,在 asyncTask 处理后烤一些东西!
- 我需要学习Java才能使用phoneGap创建android应用程序吗
- 从javascript调用android原生java代码
- 使用java脚本将Android WebView滚动到下一页
- 从Android WebView调用java方法
- 如何将值从Java传递到Javascript,从而建立Android WebView
- 适用于 Android 应用程序的 Java 代码中的实例化异常
- 如何从 java 代码 android native 调用 JavaScript 函数
- 如何使用java脚本在android中打开外部页面
- Android Java 处理程序在循环中的每个记录处发布延迟
- Android java runnable runTimeException
- 无法在我的 Android 应用程序中从 Javascript 调用我的 Java 类
- 从Flash AS3 Android应用程序调用Java脚本函数以检索设备ID和序列号
- 将 java 变量从 Android 活动传递到 phonegap 应用程序中的 javascript
- Android java json parse empty
- 如何将变量从html传递到android java类
- 我怎么能得到JavaScript Cookie在android Java代码
- Javascript函数不接收Android-Java值
- 如何让Android Java代码调用Node.js代码
- [android]Java脚本到Java本地绑定& &;亦然
- Android / Java多线程——直到主线程完成它的工作,线程才会继续