市場には、Cordova、Xamarin、React Nativeなど、あまり知られていないクロスプラットフォームソリューションがいくつかあります。 多くのモバイル開発者は、クロスプラットフォームソリューションでは、ネイティブアプリケーションでできることを実行できないと考えています。
記事では、この神話を解き明かし、React Nativeのメカニズムについて説明します。これにより、ネイティブアプリケーションで可能なすべてのことを実行できます。 このメカニズムはネイティブモジュールです。 Under the cut-AndroidおよびiOS用のネイティブモジュールの作成方法の詳細な説明。
携帯電話向けのクロスプラットフォーム開発のネイティブモジュールは、いくつかのことを支援します。
- プラットフォーム機能へのアクセスを提供し、AndroidのコンテンツプロバイダーまたはiOSのアドレス帳から読み取ります
- サードパーティライブラリをラップしてjsを呼び出す
- React Nativeパーツをアプリケーションに追加するときに既存のコードをラップする
- パフォーマンスに重要な部分(暗号化など)を実装する
React Nativeのサンプルアプリケーション図
オペレーティングシステムはネイティブアプリケーションを実行します。 React Nativeランタイムと、アプリケーション開発者(またはReact Nativeのライブラリの作成者)によって作成されたネイティブモジュールのコードは、低レベルで動作します。 その上で、React Native Bridgeは機能します-ネイティブコードとjsの間の中間リンク。 Js自体はJavaScript VM内で実行されるJS VM内で実行されます。 iOSでは、システムによって提供され、Androidでは、アプリケーションはそれをライブラリの形式でドラッグします。
ネイティブモジュールの作成
Androidの下で
計画はこれです:
- ReactNativeHostにパッケージを登録する
- パッケージを作成
- モジュールを作成する
- モジュールをパッケージに登録する
- モジュールでメソッドを作成
桁1-Androidコンポーネント
主な背景がAndroidの場合、このリトリートをスキップできます。 基本的なiOSまたはReact JSの経験を持つ開発者の場合、Androidアプリケーションに次のコンポーネントを含めることができることを確認する必要があります。
- アクティビティ
- BroadcastReceiver
- サービス
- ContentProvider
- 申込み
このコンテキスト(khe-khe)では、もちろん、アプリケーションにのみ興味があります。 このコンポーネントはアプリケーション自体のオブジェクトであることを思い出させてください。 React Nativeアプリケーションの場合は、アプリケーションクラスを実装し、このクラスを使用してReactApplicationインターフェイスを実装できます(およびReact Nativeアプリケーションの場合)。
package com.facebook.react; public interface ReactApplication { ReactNativeHost getReactNativeHost(); }
これは、ReactNativeが使用したいネイティブパッケージについて学習するために必要です。 これを行うには、アプリケーションはパッケージリストをリストするReactNativeHostのインスタンスを返す必要があります。
class MainApplication : Application(), ReactApplication { private val mReactNativeHost = object : ReactNativeHost(this) { override fun getPackages(): List<ReactPackage> { return Arrays.asList( MainReactPackage(), NativeLoggerPackage() ) } override fun getReactNativeHost(): ReactNativeHost { return mReactNativeHost } }
NativeLoggerPackageは、お客様と一緒に作成するパッケージです。 渡された値のみを記録し、実際の機能ではなくネイティブモジュールの作成プロセスに焦点を合わせます。
アプリケーションがReactApplicationを実装する必要があるのはなぜですか? React Nativeの中にはこんな面白いコードがあるからです:
public class ReactActivityDelegate { protected ReactNativeHost getReactNativeHost() { return ((ReactApplication) getPlainActivity().getApplication()) .getReactNativeHost(); } }
次に、NativeLoggerPackageを実装します。
class NativeLoggerPackage : ReactPackage { override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> { return Arrays.asList<NativeModule>(NativeLoggerModule()) } override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> { return emptyList<ViewManager<*, *>>() } }
createViewManagersメソッドは省略していますが、この記事では重要ではありません。 また、作成されたモジュールのリストを返すcreateNativeModulesメソッドが重要です。 モジュールは、jsから呼び出すことができるメソッドを含むクラスです。 NativeLoggerModuleを作成しましょう:
class NativeLoggerModule : BaseJavaModule() { override fun getName(): String { return "NativeLogger" } }
Androidコンテキストにアクセスする必要がない場合は、少なくともBaseJavaModuleからモジュールを継承する必要があります。 必要がある場合は、別の基本クラスを使用する必要があります。
class NativeLoggerModule(context : ReactApplicationContext) : ReactContextBaseJavaModule(context) { override fun getName(): String { return "NativeLogger" } }
いずれの場合でも、getName()メソッドを定義する必要があります。このメソッドは、jsでモジュールを使用できる名前を返します。これについては後で説明します。
最後に、jsのメソッドを作成しましょう。 これは、ReactMethodアノテーションを使用して行われます。
class NativeLoggerModule : BaseJavaModule() { override fun getName(): String { return "NativeLogger" } @ReactMethod fun logTheObject() { Log.d(name, “Method called”) } }
ここで、jsからの呼び出しにlogTheObjectメソッドが使用可能になります。 しかし、何も返さないパラメータなしでメソッドを呼び出したいだけではありません。 引数を扱いましょう(左側にjavaタイプ、右側にjs):
ブール->ブール
整数->数値
ダブル->数字
フロート->数値
文字列->文字列
コールバック->関数
ReadableMap->オブジェクト
ReadableArray->配列
jsオブジェクトをネイティブメソッドに渡したいとします。 ReadableMapはjavaに含まれます。
@ReactMethod fun logTheObject(map: ReadableMap) { val value = map.getString("key1") Log.d(name, "key1 = " + value) }
配列の場合、ReadableArrayが渡され、反復処理が問題になりません。
@ReactMethod fun logTheArray(array: ReadableArray) { val size = array.size() for (index in 0 until size) { val value = array.getInt(index) Log.d(name, "array[$index] = $value") } }
ただし、最初の引数でオブジェクトを渡し、2番目の引数で配列を渡したい場合は、ここでも驚くことはありません。
@ReactMethod fun logTheMapAndArray(map: ReadableMap, array: ReadableArray): Boolean { logTheObject(map) logTheArray(array) return true }
これをjavascriptから呼び出すにはどうすればよいですか? これ以上簡単なことはありません。 最初に行うことは、react-nativeルートライブラリからNativeModulesをインポートすることです。
import { NativeModules } from 'react-native';
そして、モジュールをインポートします(NativeLoggerと呼びましたか?):
import { NativeModules } from 'react-native'; const nativeModule = NativeModules.NativeLogger;
これでメソッドを呼び出すことができます:
import { NativeModules } from 'react-native'; const nativeModule = NativeModules.NativeLogger; export const log = () => { nativeModule.logTheMapAndArray( { key1: 'value1' }, ['1', '2', '3'] ); };
うまくいく! しかし、待ってください、私はすべてが正常であるかどうか、記録したいものを記録できたかどうかを知りたいです。 戻り値はどうですか?
ただし、ネイティブモジュールからの関数の戻り値はありません。 関数を渡して外に出なければなりません:
@ReactMethod fun logWithCallback(map: ReadableMap, array: ReadableArray, callback: Callback) { logTheObject(map) logTheArray(array) callback.invoke("Logged") }
唯一のinvokeメソッド(Object ... args)を持つCallbackインターフェイスは、ネイティブコードに入ります。 js側から見ると、これは単なる関数です。
import { NativeModules } from 'react-native'; const nativeModule = NativeModules.NativeLogger; export const log = () => { const result = nativeModule.logWithCallback( { key1: 'value1' }, [1, 2, 3], (message) => { console.log(`[NativeLogger] message = ${message}`) } ); };
残念ながら、コンパイル時にjsのネイティブコードと関数からコールバックパラメーターを検証するツールはありません。注意してください。
幸いなことに、Promisesメカニズムを使用できます。これは、ネイティブコードではPromiseインターフェイスでサポートされています。
@ReactMethod fun logAsync(value: String, promise: Promise) { Log.d(name, "Logging value: " + value) promise.resolve("Promise done") }
その後、async / awaitを使用してこのコードを呼び出すことができます。
import { NativeModules } from 'react-native'; const nativeModule = NativeModules.NativeLogger; export const log = async () => { const result = await nativeModule.logAsync('Logged value'); console.log(`[NativeModule] results = ${result}`); };
これで、Androidのjsでネイティブメソッドを設定する作業が完了しました。 iOSを見てください。
iOSでネイティブモジュールを作成する
最初に、NativeLogger.hモジュールを作成します。
#import <Foundation/Foundation.h> #import <React/RCTBridgeModule.h> @interface NativeLogger : NSObject<RCTBridgeModule> @end
NativeLogger.mの実装:
#import <Foundation/Foundation.h> #import "NativeLogger.h" @implementation NativeLogger { } RCT_EXPORT_MODULE();
RCT_EXPORT_MODULEは、モジュールが宣言されているファイルの名前でReactNativeにモジュールを登録するマクロです。 js内のこの名前があまり適していない場合は、変更できます。
@implementation NativeLogger { } RCT_EXPORT_MODULE(NativeLogger);
それでは、Androidに対して行ったメソッドを実装しましょう。 これにはパラメーターが必要です。
string -> (NSString*) number -> (NSInteger*, float, double, CGFloat*, NSNumber*) boolean -> (BOOL, NSNumber*) array -> (NSArray*) object -> (NSDictionary*) function -> (RCTResponseSenderBlock)
メソッドを宣言するには、RCT_EXPORT_METHODマクロを使用できます。
RCT_EXPORT_METHOD(logTheObject:(NSDictionary*) map) { NSString *value = map[@"key1"]; NSLog(@"[NativeModule] %@", value); }
RCT_EXPORT_METHOD(logTheArray:(NSArray*) array) { for (id record in array) { NSLog(@"[NativeModule] %@", record); } }
RCT_EXPORT_METHOD(log:(NSDictionary*) map withArray:(NSArray*)array andCallback:(RCTResponseSenderBlock)block) { NSLog(@"Got the log"); NSArray* events = @[@"Logged"]; block(@[[NSNull null], events]); }
ここで最も興味深いのは、約束のサポートです。 これを行うには、別のマクロRCT_REMAP_METHODを使用する必要があります。このマクロは、jsのメソッド名を最初の引数として使用し、objective-cのメソッドシグネチャを2番目以降の引数として使用します。
インターフェイスの代わりに、2つの引数が渡されます。約束を解決するためのRCTPromiseResolveBlockと拒否のためのRCTPromiseRejectBlockです。
RCT_REMAP_METHOD(logAsync, logAsyncWith:(NSString*)value withResolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { NSLog(@"[NativeModule] %@", value); NSArray* events = @[@"Logged"]; resolve(events); }
以上です。 イベントをネイティブモジュールからjsに送信するメカニズムについては、別の記事で説明します。
ニュアンス
ネイティブモジュールの主な概念は、クロスプラットフォームコード用のオペレーティングシステムの抽象化であることを忘れないでください。 つまり、モジュールのインターフェースはAndroidとiOSの間で一貫している必要があります。 残念ながら、これを自動制御する方法はわかりません。
- jsとネイティブコードは別々に高速に動作することに注意してください。 それらの間のブリッジは比較的遅いです。 ネイティブモジュールを呼び出すjsループを記述しないでください-ループをネイティブに転送してください。