Existe-t-il un moyen de transmettre un débitmètre de javascript à java sur Android?

Je suis coincé un instant sur cette affaire.

J'ai une vision du Web sur Android 4.4.3 où j'ai une application web qui possède float32array contenant des données binaires. J'aimerais passer ce array sur Java Android via une fonction reliée à JavascriptInterface . Cependant, il semble que, en Java, je ne peux passer que des types primitifs tels que String , int etc …

Existe-t-il un moyen de donner à Java ce arrayBuffer?

Je vous remercie !

D'accord, après avoir discuté avec Google Engineering et après avoir lu le code, je suis parvenu aux conclusions suivantes.

Passer des données binaires efficacement est impossible

Il est impossible de transmettre efficacement des données binaires entre JavaScript et Java via @JavascriptInterface:

Sur le côté Java:

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

Et du côté 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); 

Dans le code ci-dessus (de mon ancienne réponse) et dans Alex's – la conversion effectuée pour le tableau est brutale:

 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; 

Ce qui, à son tour, contraint chaque élément de tableau d'un objet 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); 

Ainsi, pour un 1024 * 1024 * 10 Uint8Array – dix millions d'objets Java sont créés et détruits sur chaque passage, ce qui donne 10 secondes de temps CPU sur mon émulateur.

Création d'un serveur HTTP

Une chose que nous avons essayé était de créer un serveur HTTP et de POST le résultat à l'aide d'un XMLHttpRequest . Cela a fonctionné – mais a fini par coûter environ 200 ms de latence et a également introduit une fuite de mémoire méchante .

MessageChannels est lent

L'API Android 23 a ajouté un support pour MessageChannel s, qui peut être utilisé via createWebMessageChannel() tel qu'illustré dans cette réponse . C'est très lent, toujours sérialisé avec GIN (comme la méthode @JavascriptInterface ) et entraîne une latence supplémentaire. Je n'ai pas réussi à ce que cela fonctionne avec des performances raisonnables.

Il convient de mentionner que Google a déclaré qu'ils croient que c'est la voie à suivre et espère promouvoir les canaux de messages sur @JavascriptInterface à un moment donné.

Passer une chaîne fonctionne

Après avoir lu le code de conversion – on peut voir (et cela a été confirmé par Google) que la seule façon d'éviter de nombreuses conversions est de passer une valeur String . Cela ne fait que traverser:

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

Qui convertit le résultat une fois en UTF8 puis à une chaîne Java. Cela signifie toujours que les données (10 Mo dans ce cas) sont copiées trois fois – mais il est possible de passer 10 Mo de données dans "seulement" 60 ms – ce qui est beaucoup plus raisonnable que les 10 secondes que prend la méthode ci-dessus.

Petka a eu l'idée d'utiliser l'encodage 8859 qui peut convertir un seul octet en une seule lettre. Malheureusement, il n'est pas pris en charge dans l'API TextDecoder de JavaScript. Ainsi, Windows-1252 qui est un autre codage de 1 octet peut être utilisé à la place.

Du côté JavaScript, on peut faire:

 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. 

Ensuite, dans le côté Java:

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

Ce qui se déroule à environ 1/8 du temps de la sérialisation directe. Il n'est toujours pas très rapide (puisque la chaîne est rembourrée à 16 bits au lieu de 8, puis à travers UTF8 puis à UTF16 à nouveau). Cependant, il fonctionne à une vitesse raisonnable par rapport à l'alternative.

Après avoir parlé avec les parties concernées qui conservent ce code, ils m'ont dit qu'il est aussi bon que possible d'obtenir avec l'API actuelle. On m'a dit que je suis la première personne à demander cela (rapidité JavaScript à la sérialisation Java).

C'est assez simple

Section d'entrée

  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 } } 

Section Js

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

TypedArray. De polyfill si besoin: https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/TypedArray/from

Serialisez vos données dans une chaîne, puis insérez-vous dans votre application.

Le clonage de ArrayBuffer le fait fonctionner – quelque chose à propos d'un TypedArray avec un ArrayBuffer ne fonctionne pas bien dans Android.

Si vous copiez votre ArrayBuffer dans un nouveau TypedArray, vous pouvez éviter les coûts élevés de sérialisation.

Sur le lecteur:

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

Et du côté 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); 

Fonctionne parfaitement bien 🙂