简体   繁体   English

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

[英]Is there a way to pass an arraybuffer from javascript to java on Android?

I'm stuck for a moment on this case.我在这个案子上卡了一会儿。

I have a webview on Android 4.4.3 where I have a webapp who has float32array containing binary data.我在Android 4.4.3上有一个 webview,我有一个 webapp,它的float32array包含二进制数据。 I would like to pass that array to the Java Android via a function binded with JavascriptInterface .我想通过与JavascriptInterface绑定的函数将该array传递给 Java Android。 However, it seems like in Java, I can only pass primitive types like String , int etc...但是,似乎在 Java 中,我只能传递基本类型,如Stringint等......

Is there a way to give to Java this arrayBuffer ?有没有办法给 Java 这个 arrayBuffer ?

Thank you !谢谢 !

Ok, so following a chat with Google engineering and after reading the code I've reached the following conclusions. 好的,所以在与Google工程师聊天后,在阅读完代码后,我得出了以下结论。

Passing binary data efficiently is impossible 有效地传递二进制数据是不可能的

It is impossible to pass binary data efficiently between JavaScript and Java through a @JavascriptInterface: 通过@JavascriptInterface在JavaScript和Java之间有效传递二进制数据是不可能的:

On the Java side: 在Java方面:

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

And on the JavaScript side: 在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);

In the code above (from my old answer) and in Alex's - the conversion performed for the array is brutal: 在上面的代码中(来自我的旧答案)和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;

Which in turn coerces every array element to a Java object : 它反过来将每个数组元素强制转换为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);

So, for a 1024 * 1024 * 10 Uint8Array - ten million Java objects are created and destroyed on each pass resulting in 10 seconds of CPU time on my emulator. 因此,对于1024 * 1024 * 10 Uint8Array - 每次传递都会创建和销毁一千万个Java对象,从而导致我的仿真器上有10秒的CPU时间。

Creating an HTTP server 创建HTTP服务器

One thing we tried was creating an HTTP server and POST ing the result to it via an XMLHttpRequest . 我们尝试的一件事是创建一个HTTP服务器和POST通过荷兰国际集团,结果它XMLHttpRequest This worked - but ended up costing about 200ms of latency and also introduced a nasty memory leak . 这有效 - 但最终花费了大约200毫秒的延迟,并引入了令人讨厌的内存泄漏

MessageChannels are slow MessageChannels很慢

Android API 23 added support for MessageChannel s, which can be used via createWebMessageChannel() as shown in this answer . Android API 23增加了对MessageChannel的支持,可以通过createWebMessageChannel() ,如本答案所示。 This is very slow, still serializes with GIN (like the @JavascriptInterface method) and incurs additional latency. 这非常慢,仍然使用GIN进行序列化(如@JavascriptInterface方法)并产生额外的延迟。 I was not able to get this to work with reasonable performance. 我无法以合理的性能使其工作。

It is worth mentioning that Google said they believe this is the way forward and hopes to promote message channels over @JavascriptInterface at some point. 值得一提的是,Google表示他们相信这是前进的方向,并希望在某些时候推广@JavascriptInterface上的消息渠道。

Passing a string works 传递字符串有效

After reading the conversion code - one can see (and this was confirmed by Google) that the only way to avoid many conversions is to pass a String value. 阅读转换代码后 - 人们可以看到(这已得到Google确认),避免多次转换的唯一方法是传递String值。 This only goes through: 这只能通过:

case JavaType::TypeString: {
  std::string string_result;
  value->GetAsString(&string_result);
  result.l = ConvertUTF8ToJavaString(env, string_result).Release();
  break;
}

Which converts the result once to UTF8 and then again to a Java string. 将结果一次转换为UTF8,然后再转换为Java字符串。 This still means the data (10MB in this case) is copied three times - but it is possible to pass 10MB of data in "only" 60ms - which is a lot more reasonable than the 10 seconds the above array method takes. 这仍然意味着数据(在这种情况下为10MB)被复制三次 - 但是可以在“仅”60ms内传递10MB数据 - 这比上述数组方法花费的10秒更合理。

Petka came up with the idea of using 8859 encoding which can convert a single byte to a single letter. Petka提出了使用8859编码的想法,它可以将单个字节转换为单个字母。 Unfortunately it is not supported in JavaScript's TextDecoder API - so Windows-1252 which is another 1 byte encoding can be used instead. 不幸的是,JavaScript的TextDecoder API不支持它 - 因此可以使用另一个1字节编码的Windows-1252

On the JavaScript side one can do: 在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.

Then, in the Java side: 然后,在Java方面:

@JavascriptInterface
public void onBytes(String dec) throws UnsupportedEncodingException
    byte[] bytes = dec.getBytes("windows-1252");
    // work with bytes here
}

Which runs in about 1/8th the time of direct serialization. 其中大约在直接序列化的1/8时运行。 It's still not very fast (since the string is padded to 16 bits instead of 8, then through UTF8 and then to UTF16 again). 它仍然不是很快(因为字符串被填充到16位而不是8位,然后再通过UTF8再到UTF16)。 However, it runs in reasonable speed compared to the alternative. 但是,与替代方案相比,它以合理的速度运行。

After speaking with the relevant parties who are maintaining this code - they told me that it's as good as it can get with the current API. 在与维护此代码的相关方面交谈后 - 他们告诉我,它与当前的API一样好。 I was told I'm the first person to ask for this (fast JavaScript to Java serialization). 有人告诉我,我是第一个要求这个问题的人(快速JavaScript到Java序列化)。

It is pretty simple 这很简单

Init section 初始部分

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

JavaScriptInterface JavaScriptInterface

public class JavaScriptInterface {
        private Activity activity;

        public JavaScriptInterface(Activity activiy) {
            this.activity = activiy;
        }
        @JavascriptInterface
        public void putData(byte[] bytes){
            //do whatever
        }
    }

Js section Js部分

<script>
  function putAnyBinaryArray(arr) {
        var uint8 = Uint8Array.from(arr);
        window.JSInterface.putData(uint8);
  };
</script>

TypedArray.from polyfill if need : https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/TypedArray/from 如果需要,可以从polyfill中输入TypedArray: https//developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/TypedArray/from

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

Cloning the ArrayBuffer makes it work - something about a TypedArray backed with an ArrayBuffer doesn't marshall well into Android. 克隆ArrayBuffer使它工作 - 一个关于由ArrayBuffer支持的TypedArray的东西不能很好地编入Android。

If you copy your ArrayBuffer into a new TypedArray you can avoid the expensive serialization overhead. 如果将ArrayBuffer复制到新的TypedArray中,则可以避免昂贵的序列化开销。

On the reader: 在读者:

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

And on the JS side: 在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);

Works perfectly fine :) 工作得很好:)

If you want Sync call, just use base64 encode & decode: ( Convert base64 string to ArrayBuffer ) 如果你想要同步调用,只需使用base64编码和解码:( 将base64字符串转换为ArrayBuffer

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

If you want Async call: 如果你想要异步调用:

You can create http server in Android appliction, and then use "xhr" or "fetch" in javascript side to send binary or string async 您可以在Android应用程序中创建http服务器,然后在javascript端使用“xhr”或“fetch”发送二进制或字符串异步


And don't use "iso-8859-1" or "windows-1252" mentioned above, it's dangerous !!! 并且不要使用上面提到的“iso-8859-1”或“windows-1252”,这很危险!
"iso-8859-1" has undefined code which can't be decode between javascript and java. “iso-8859-1”有未定义的代码,无法在javascript和java之间进行解码。 ( https://en.wikipedia.org/wiki/ISO/IEC_8859-1 ) https://en.wikipedia.org/wiki/ISO/IEC_8859-1

the code linked in the second answer 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第二个答案中链接的代码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

doesn't actually understand TypedArrays (it looks like it does because it says TypeArray but, everything in that file is TypeXZY)实际上并不理解 TypedArrays(看起来是这样,因为它说的是 TypeArray 但是,该文件中的所有内容都是 TypeXZY)

So I can definitely imagine that it's faster to copy a string.所以我绝对可以想象复制一个字符串会更快。 However, there is no reason that it shouldn't be able to pass a typed array without copying, or at least with just a single raw copy.但是,没有理由不通过复制或至少只有一个原始副本就不能传递类型化数组。

It would require a patch to chromium though.不过,它需要对铬进行修补。

in my case, app tranfers blob data each other without a http server, so there's no choice but to send arrayBuffer to javainterface, as follows:在我的例子中,应用程序在没有 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);
}

by the way, the file limit to 10M, toast something after asyncTask processed!顺便说一下,文件限制为10M,在asyncTask处理后吐司!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM