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. I would like to pass that array
to the Java Android via a function binded with JavascriptInterface
.
However, it seems like in Java, I can only pass primitive types like String
, int
etc...
Is there a way to give to Java this arrayBuffer ?
Thank you !
Ok, so following a chat with Google engineering and after reading the code I've reached the following conclusions.
It is impossible to pass binary data efficiently between JavaScript and Java through a @JavascriptInterface:
On the Java side:
@JavascriptInterface
void onBytes(byte[] bytes) {
// bytes available here
}
And on the JavaScript side:
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:
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:
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.
One thing we tried was creating an HTTP server and POST
ing the result to it via an XMLHttpRequest
. This worked - but ended up costing about 200ms of latency and also introduced a nasty memory leak.
Android API 23 added support for MessageChannel
s, which can be used via createWebMessageChannel()
as shown in this answer. This is very slow, still serializes with GIN (like the @JavascriptInterface
method) and incurs additional latency. 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.
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. 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. 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.
Petka came up with the idea of using 8859 encoding which can convert a single byte to a single letter. Unfortunately it is not supported in JavaScript's TextDecoder API - so Windows-1252 which is another 1 byte encoding can be used instead.
On the JavaScript side one can do:
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:
@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. 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). 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. I was told I'm the first person to ask for this (fast JavaScript to Java serialization).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With