פיתוח מודול Native עבור React Native (אנדרואיד)
דמיינו שאתם צריכים לחבר מכשיר Bluetooth ספציפי לאפליקציית React Native שלכם, אבל הספריות הסטנדרטיות לא מספקות את השליטה הדרושה. JavaScript לא יכול לקרוא ישירות ל-API של אנדרואיד—אתם צריכים Native Module. שגיאה במימוש שלו יכולה לקרוס את האפליקציה או לגרום לדליפות זיכרון. במשך יותר מ-5 שנים, השלמנו יותר מ-30 פרויקטים עם Native Modules ואנחנו מכירים את המלכודות האופייניות. בואו נפרק את זה באמצעות Bluetooth LE כדוגמה.
Native Module הוא גשר בין JS לקוד אנדרואיד Native. React Native מציעה שתי גישות: ה-Bridge הקלאסי וה-TurboModule החדש המבוסס על JSI. בחירת הארכיטקטורה משפיעה ישירות על הביצועים: TurboModule מספק זמן השהיה של 1–2 אלפיות שנייה לעומת 10–20 אלפיות שנייה עבור Bridge—רווח של עד פי 10 עבור קריאות בתדירות גבוהה. ההבדל הזה מורגש עם זרמי נתונים רציפים (אודיו, חיישנים).
מימוש מודול Bridge
עבור פרויקטים המשתמשים בארכיטקטורה הישנה (ללא תמיכת JSI), השתמשו בגישה הקלאסית.
// BluetoothModule.kt
class BluetoothModule(private val reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
override fun getName(): String = "BluetoothModule"
@ReactMethod
fun isBluetoothEnabled(promise: Promise) {
val bluetoothManager = reactContext.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
promise.resolve(bluetoothManager.adapter?.isEnabled ?: false)
}
@ReactMethod
fun startScan(promise: Promise) {
val scanner = (reactContext.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager)
.adapter?.bluetoothLeScanner
if (scanner == null) {
promise.reject("BT_ERROR", "Bluetooth LE not supported")
return
}
promise.resolve(null)
}
private fun sendEvent(eventName: String, params: WritableMap?) {
reactContext
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
.emit(eventName, params)
}
}רשמו את המודול באמצעות // BluetoothModule.kt class BluetoothModule(private val reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) { override fun getName(): String = "BluetoothModule" @ReactMethod fun isBluetoothEnabled(promise: Promise) { val bluetoothManager = reactContext.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager promise.resolve(bluetoothManager.adapter?.isEnabled ?: false) } @ReactMethod fun startScan(promise: Promise) { val scanner = (reactContext.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager) .adapter?.bluetoothLeScanner if (scanner == null) { promise.reject("BT_ERROR", "Bluetooth LE not supported") return } promise.resolve(null) } private fun sendEvent(eventName: String, params: WritableMap?) { reactContext .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) .emit(eventName, params) } } והוסיפו אותו ל-ReactPackage במתודת MainApplication.kt.
איך לבחור בין Bridge ל-TurboModule?
| קריטריון | Bridge (ארכיטקטורה ישנה) | TurboModule (ארכיטקטורה חדשה) |
|---|---|---|
| ביצועים | קריאה דרך סריאליזציית JSON, זמן השהיה ~10-20 אלפיות שנייה | JSI ללא סריאליזציה, זמן השהיה ~1-2 אלפיות שנייה |
| תאימות | פרויקטים ללא תמיכת JSI | פרויקטים עם ארכיטקטורה חדשה (JSI) |
| תמיכה | מופסק אך עדיין בשימוש נרחב | העתיד של React Native, פיתוח פעיל |
| מורכבות | פשוט יותר, אין צורך ב-Codegen | דורש מפרט TypeScript ויצירת קוד |
עבור קריאות בתדירות גבוהה (אודיו, חיישנים), TurboModule מספק רווח ביצועים של עד פי 10. עלות הפיתוח של מודול כזה משתלמת בזכות הפחתת זמן התיקונים ב-30%.
למה TurboModule מהיר יותר מ-Bridge?
TurboModule משתמש ב-JSI (JavaScript Interface) כדי לקרוא לפונקציות Native ישירות, תוך עקיפת סריאליזציית JSON. Bridge אורז כל קריאה ל-JSON, מה שמוסיף עומס. עבור קריאות בודדות ההבדל זניח, אבל ב-1000 קריאות בשנייה, TurboModule חוסך עד 20 אלפיות שנייה—קריטי עבור אנימציות או עיבוד חיישנים.
איך להעביר אירועים מקוד Native ל-JS?
התבנית זהה עבור שתי הארכיטקטורות—השתמשו ב-getPackages().
fun emitScanResult(deviceAddress: String, rssi: Int) {
val params = Arguments.createMap().apply {
putString("address", deviceAddress)
putInt("rssi", rssi)
}
reactApplicationContext
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
.emit("onBluetoothDeviceFound", params)
}בצד ה-JS, הירשמו דרך RCTDeviceEventEmitter:
import { NativeEventEmitter, NativeModules } from 'react-native';
const { BluetoothModule } = NativeModules;
const emitter = new NativeEventEmitter(BluetoothModule);
useEffect(() => {
const subscription = emitter.addListener('onBluetoothDeviceFound', (event) => {
console.log('Found device:', event.address, 'RSSI:', event.rssi);
});
return () => subscription.remove();
}, []);תמיד קראו ל-fun emitScanResult(deviceAddress: String, rssi: Int) { val params = Arguments.createMap().apply { putString("address", deviceAddress) putInt("rssi", rssi) } reactApplicationContext .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) .emit("onBluetoothDeviceFound", params) } בניקוי—דליפת הרשמה תגרום ל-handler לפעול לאחר שהרכיב מתבטל.
בקשות הרשאות: אחריות שכבת JS
המודול ה-Native לא צריך לבקש הרשאות זמן ריצה בעצמו—זו אחריות שכבת ה-JS באמצעות NativeEventEmitter או import { NativeEventEmitter, NativeModules } from 'react-native'; const { BluetoothModule } = NativeModules; const emitter = new NativeEventEmitter(BluetoothModule); useEffect(() => { const subscription = emitter.addListener('onBluetoothDeviceFound', (event) => { console.log('Found device:', event.address, 'RSSI:', event.rssi); }); return () => subscription.remove(); }, []); . המודול רק בודק זמינות ומחזיר שגיאה. לדוגמה, אם הרשאת subscription.remove() לא ניתנה, המודול מחזיר שגיאת react-native-permissions.
שימוש ב-Codegen עבור TurboModule
בעת שימוש ב-TurboModule, צריך ליצור מפרט TypeScript ולהריץ Codegen. זה יוצר אוטומטית ממשקי Kotlin וקוד JNI. דוגמה:
export interface Spec extends TurboModule {
readonly getConstants: () => { [key: string]: any };
isBluetoothEnabled(): Promise<boolean>;
startScan(): Promise<void>;
}Codegen מפחית את זמן המימוש ב-30% ומבטל שגיאות קישור ידניות.
בדיקת המודול
אנחנו בודקים את המודול ה-Native ברמת Kotlin (JUnit + Mockk) וברמת האינטגרציה דרך Detox. לדוגמה, בדיקה ש-PermissionsAndroid מחזיר false כאשר Bluetooth אינו זמין:
@Test
fun `isBluetoothEnabled returns false when adapter is null`() {
val context = mockk<ReactApplicationContext>()
every { context.getSystemService(Context.BLUETOOTH_SERVICE) } returns mockk<BluetoothManager> {
every { adapter } returns null
}
val module = BluetoothModule(context)
val promise = mockk<Promise>(relaxed = true)
module.isBluetoothEnabled(promise)
verify { promise.resolve(false) }
}חשוב גם לבדוק טיפול בשגיאות ומחזור חיים. בזכות בדיקות יחידה, לקוחות חוסכים עד 30% מתקציב הדיבוג.
טעויות פיתוח אופייניות
-
דליפת Context: אל תשמרו הפניה ל-
BLUETOOTH_SCANבשדה סטטי—השתמשו ב-PERMISSION_DENIED. -
התעלמות מ-ProGuard: הוסיפו כללי
export interface Spec extends TurboModule { readonly getConstants: () => { [key: string]: any }; isBluetoothEnabled(): Promise<boolean>; startScan(): Promise<void>; }עבור המודול שלכם, אחרת בניית הייצור תקרוס עם@Test fun `isBluetoothEnabled returns false when adapter is null`() { val context = mockk<ReactApplicationContext>() every { context.getSystemService(Context.BLUETOOTH_SERVICE) } returns mockk<BluetoothManager> { every { adapter } returns null } val module = BluetoothModule(context) val promise = mockk<Promise>(relaxed = true) module.isBluetoothEnabled(promise) verify { promise.resolve(false) } }. - ערבוב ארכיטקטורות: אל תשתמשו במתודות Bridge בפרויקט TurboModule—החתימה תישבר.
מה כלול בפיתוח Native Module
| רכיב | תיאור |
|---|---|
| מפרט API | ממשק TypeScript עבור Codegen (אם TurboModule) או הצהרות מתודות עבור Bridge |
| מימוש Native | מחלקת מודול ב-Kotlin המטפלת בכל המקרים |
| בדיקות יחידה | בדיקת המודול ב-Kotlin עם תלויות מדומות |
| אינטגרציה לפרויקט | הגדרת Package, הרשאות ו-Gradle |
| תיעוד | API, דוגמאות, טיפול בשגיאות |
| תמיכה לאחר מסירה | 30 יום לתיקוני באגים |
תהליך עבודה
- ניתוח דרישות — לימוד Android API, הסכמה על חתימות מתודות.
- עיצוב מפרט — יצירת ממשק TypeScript או פרוקסי JS.
- מימוש קוד Native — כתיבת מודול ב-Kotlin, טיפול במקרי קצה.
- בדיקות — בדיקות יחידה + אינטגרציה בפרויקט לדוגמה.
- אינטגרציה ותיעוד — חיבור לאפליקציה שלכם, עדכון מסמכים.
- מסירה — קוד מקור, פרויקט הדגמה, הוראות בנייה.
לוחות זמנים: מודול בסיסי עם 3–5 מתודות — 2–4 ימים; מודול עם אירועים ובדיקות — משבוע. העלות מחושבת באופן אישי. בקשו ייעוץ כדי לקבל הערכה מדויקת לפרויקט שלכם וניתוח דרישות. צרו קשר תוך יום עסקים אחד.







