Blog

Generischer JS Android API Wrapper für React Native

Albert Brand

Aktualisiert Oktober 22, 2025
6 Minuten

code { display: inline !important; } Während eines React Native-Projekts für einen unserer Kunden fügten wir einige benutzerdefinierte Android- und iOS-Bibliotheken zu unserem Code hinzu und wollten ein paar offengelegte Methoden aufrufen. In einem solchen Fall verlangt React Native, dass Sie eine Wrapper-Klasse schreiben, um diese öffentlichen APIs aufzurufen. Das war ein kleines Boilerplate-Ärgernis und diese Wrapper wären unnötig, wenn wir eine generische API zur Überbrückung von Methodenaufrufen erstellen würden. Außerdem können Sie mit einem solchen API-Wrapper jede (obskure) verfügbare Android-API aufrufen, die noch nicht gewrappt ist. Schauen wir mal, wie weit wir kommen!

Ziel

Für den folgenden Proof-of-Concept ist das Ziel einfach: Wir wollen einige JS-Anweisungen schreiben, die den API-Aufrufen auf der nativen Plattform sehr ähnlich sind. Die JS-API sollte auf der JS-Seite dynamisch auf der Grundlage der nativen API-Eigenschaften generiert werden, und der Aufruf von Methoden und die Rückgabe von Antworten an den JS-Thread sollten funktionieren. Um das Ganze ein wenig einzugrenzen und ein schönes Beispiel zu erhalten, wollen wir dies für die Android Toast API. Wir sind uns darüber im Klaren, dass es bereits eine Wrapped API von React Native gibt. Dieser Code dient nur als Beweis für meine Implementierung. Also, meine vorgestellte API, um eine 'Awesome'-Toast-Nachricht anzuzeigen, wenn sie ausgeführt wird, ist: [javascript title="Vorgestellter Code"] const Toast = createBridge('android.widget.Toast'); const context = getReference('context'); Toast.makeText(context, 'Awesome', Toast.LENGTH_SHORT).show(); [/javascript] Die Funktion createBridge sollte ein Toast Objekt auf der JS-Seite erstellen, das auf der öffentlichen API basiert, die von der Klasse android.widget.Toast bereitgestellt wird. Dieses Objekt sollte mindestens die Funktion makeText enthalten, die einen Aufruf auf der nativen Seite durchführt und die Parameter von JS in native Typen umwandelt. Das zurückgegebene Objekt sollte wiederum mit einer show Funktion in ein JS-Objekt umgewandelt werden. Um einen Verweis auf das aktuelle Android context zu erhalten, sollte eine Hilfsfunktion verfügbar sein.

Implementierung

Der erste Schritt besteht darin, dynamisch ein JS-Objekt zu erstellen, das einer API auf der nativen Seite ähnelt. Java Reflection wird verwendet, um die statischen öffentlichen Mitglieder der API zu untersuchen und eine Darstellung zu erstellen, die über die React Native-Brücke übergeben werden kann. Die native Seite sieht wie folgt aus: [java] // ... public class AndroidJSAPIModule extends ReactContextBaseJavaModule { @Override public String getName() { return "AndroidJSAPI"; } @ReactMethod public void reflect(String className, Promise promise) { try { Class<?> clazz = ClassUtils.getClass(className); ReadableMap classReflection = jsAPI.reflect(clazz); promise.resolve(classReflection); } catch (Exception e) { promise.reject(e); } } // ... } [/java] Die Annotation code> @ReactMethod </code macht die Methode reflect über die React Native Bridge zugänglich. Normalerweise würden Sie dies verwenden, um Ihren benutzerdefinierten umhüllten nativen Code freizugeben. Hier taucht das erste Problem auf: Die API ist asynchron - was aus Sicht der Leistung eine gute Sache ist, aber für synchrone, zwingende Anweisungen, die aufgerufen werden, nicht sehr klar. Es könnte einige Tricks geben, um diese Tatsache vor JS-Code zu verbergen, aber das würde zu einer weiteren undichten Abstraktion führen, also haben wir uns mit einer asynchronen Behandlung in JS zufrieden gegeben. Die JS-Seite für dieses Modul sieht folgendermaßen aus: [javascript] import { NativeModules } from 'react-native'; const JSAPI = NativeModules.AndroidJSAPI; async function createBridge(className) { const reflection = await JSAPI.reflect(className); return createWrapper(reflection); } function createWrapper(reflection) { const ret = {}; for (let method of reflection.methods) { ret[method.name] = wrapMethod(reflection.objectId, reflection.className, method, ret[method.name]); } for (let field of reflection.fields) { ret[field.name] = field.value; } return ret; } // ... [/javascript] Wir rufen die Methode reflect auf, um ein JSON-Objekt mit statischen Mitgliedsinformationen (Felder, Methoden und Argumenttypinformationen, um auf einen bestimmten Methodenaufruf zu verweisen) zu erhalten und ein JS-Objekt mit Funktionen und Eigenschaften zu erstellen, das der API ähnelt. async/await wird verwendet, um sauberen Code für die Handhabung der asynchronen Natur der React Native-Methodenbrücke zu schreiben. Wir möchten die Überladung von Java-Methoden unterstützen, also übergeben wir die vorherige JS-Funktion mit demselben Namen an den JS-Funktionsgenerator. Wir können die vorherige Funktion aufrufen, die möglicherweise besser zu den in JS bereitgestellten Argumenten passt. Jetzt müssen wir den direkten Methodenaufruf über die React Native-Bridge bereitstellen: [java] @ReactMethod public void methodCall(Integer objectId, String className, String staticMethod, ReadableArray arguments, Promise promise) { try { Object receiver = objectId > 0 ? registry.get(objectId) : null; Class<?> clazz = ClassUtils.getClass(className); Method method = clazz.getMethod(staticMethod, jsAPI.createParameterTypes(arguments)); Object result = method.invoke(receiver, jsAPI.createParameters(arguments)); int resultObjectId = registry.add(result); WritableMap classReflection = jsAPI.reflect(result); classReflection.putInt("objectId", resultObjectId); promise.resolve(classReflection); } catch (Exception e) { promise.reject(e); } } [/java] Hier passiert eine ganze Menge. Falls angegeben, wird der Parameter objectId über eine einfache Objektregistrierung in einen tatsächlichen Objektverweis umgewandelt. Dann wird die Methode über die Instanz Class auf der Grundlage der von JS zurückgesendeten Parametertypen gesucht und mit den angegebenen Parameterwerten aufgerufen. Das Ergebnis wird als Objekt angenommen und der Registry hinzugefügt. Die Reflektion dieses Objekts wird zusammen mit dem objectId Verweis auf das Objekt zurückgegeben, so dass die JS-Seite schließlich Methoden auf dieser Instanz aufrufen kann. Diese methodCall Methode wird auf der JS-Seite wie folgt verwendet: [javascript] function wrapMethod(objectId = 0, className, methodReflection, prevWrappedFn) { return async function () { // ... const reflection = await JSAPI.methodCall(objectId, className, methodReflection.name, typedArguments); return createWrapper(reflection); [/javascript] } } Die Methode reflection wird verwendet, um auf eine Methode mit einem bestimmten Namen und typisierten Argumenten zu verweisen, die dynamisch mit JS-Typen abgeglichen werden. Bei der aktuellen Implementierung der JS-zu-Java-Typübereinstimmung (die hier aus Gründen der Kürze nicht gezeigt wird) gehen einige Informationen verloren, aber für das Beispiel Toast funktioniert es.

Ergebnis

Wenn wir also all diese Teile kombinieren, können wir den folgenden Code in einer asynchronen Funktion ausführen: [javascript] (async function () { const Toast = await AndroidBridge.createBridge('android.widget.Toast')); (await Toast.makeText(AndroidBridge.context, 'Awesome', Toast.LENGTH_SHORT)).show(); })(); [/javascript] Dies zeigt in der Tat eine schöne 'Awesome'-Meldung für eine kurze Zeit an. Wir haben eine spezielle AndroidBridge.context Referenz hinzugefügt, die auf die aktuelle Kontextinstanz innerhalb der Aktivität verweist. Außerdem müssen wir jeden createBridge und dynamisch erzeugten Aufruf mit await umhüllen, da die asynchrone API dies erfordert.

Fazit

Der Aufbau einer dynamisch generierten JS-API unter Verwendung der Bridge von React Native ist möglich (zumindest für Android). Die Verwendung einer solchen Brücke widerspricht jedoch wahrscheinlich der Designphilosophie von React Native, da sie niemals so performant sein wird wie der Aufruf vorkompilierter nativer Anweisungen aus einer >@ReactMethod </code annotierten Methode. Der positive Teil ist jedoch, dass diese Implementierung zeigt, dass Sie auf jede (benutzerdefinierte) Android-API auf der Plattform einfach von JS aus zugreifen können, was ein oft gehörter fehlender Teil von React Native ist. Es liegt an den Entwicklern zu entscheiden, ob es sich lohnt, die Leistung für eine schnellere Entwicklung zu opfern. Natürlich könnte diese Lösung an vielen Stellen verbessert werden:

  • Beenden Sie die Umwandlung von Typen, da sie unvollständig sind.
  • Implementieren Sie Konstruktoraufrufe von JS für nicht-statische APIs
  • Intelligentere Objektregistrierung, die Referenzen auf erstellte Objekte tatsächlich freigibt
  • Verzichten Sie auf async/await, da es für Anweisungen, die normalerweise synchron sind, unnatürlich ist
  • IDE-Hinweise für dynamisches JS hinzufügen
  • Einige Leistungsoptimierungen hinzufügen

Sie können sich den aktuellen Proof of Concept in diesem Repo ansehen.

Verfasst von

Albert Brand

Contact

Let’s discuss how we can support your journey.