fayeah / blogs

方法论、问题驱动、总结
6 stars 0 forks source link

【RN】使用NativeModules获取设备信息(Android&iOS) #40

Open fayeah opened 4 years ago

fayeah commented 4 years ago

Why Native Modules

在使用React Native开发手机应用的时候,有时候我们需要使用到platform API,或者写一些多线程的,提高性能的代码,又或者我们需要重新实现一个Native的API,那么React Native提供了这样一个feature来支持我们做这些事情。这次我们以获取设备信息为例来看看如何使用Native Modules实现。

Pre-requisite:

Android

  1. 在项目工程里面我们能看到android的目录,使用Android Studio打开,建议使用Android Studio打开,会自动刷新我们需要的依赖。
  2. 创建一个Java文 DeviceModule.javamobileNew/android/app/src/main/java/com/mobilenew/device/DeviceModule.java,代码如下

    package com.mobilenew.device;
    
    import com.facebook.react.bridge.Callback;
    import com.facebook.react.bridge.ReactApplicationContext;
    import com.facebook.react.bridge.ReactContextBaseJavaModule;
    import com.facebook.react.bridge.ReactMethod;
    
    public class DeviceModule extends ReactContextBaseJavaModule {
        //constructor
        public DeviceModule(ReactApplicationContext reactContext) {
            super(reactContext);
        }
        //Mandatory function getName that specifies the module name
        @Override
        public String getName() {
            return "Device";
        }
        //Custom function that we are going to export to JS
        @ReactMethod
        public void getDeviceName(Callback cb) {
            try{
                cb.invoke(null, android.os.Build.MODEL);
            }catch (Exception e){
                cb.invoke(e.toString(), null);
            }
        }
    }
    • 自定义的native类需要继承ReactContextBaseJavaModule
    • 我们需要重写getName()方法,这个方法的返回值是react导入所需要的module名字
    • 实现一个function,加上@ReactMethod注解,这样该方法就能正常导出
  3. 创建了自定义module之后,我们需要注册该module,创建一个Java类 DevicePackage/mobileNew/android/app/src/main/java/com/mobilenew/device/DevicePackage.java,代码如下:

    package com.mobilenew.device;
    
    import com.facebook.react.ReactPackage;
    import com.facebook.react.bridge.JavaScriptModule;
    import com.facebook.react.bridge.NativeModule;
    import com.facebook.react.bridge.ReactApplicationContext;
    import com.facebook.react.uimanager.ViewManager;
    
    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.List;
    
    public class DevicePackage implements ReactPackage {
    
        @Override
        public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
            return Collections.emptyList();
        }
    
        @Override
        public List<NativeModule> createNativeModules(
                ReactApplicationContext reactContext) {
            List<NativeModule> modules = new ArrayList<>();
            //We import the module file here
            modules.add(new DeviceModule(reactContext));
    
            return modules;
        }
    
        // Backward compatibility
        public List<Class<? extends JavaScriptModule>> createJSModules() {
            return new ArrayList<>();
        }
    }
  4. 注册好module之后,我们需要将 DevicePackage进行初始化,在MainApplication.java文件里面,添加如下代码:
        @Override
        protected List<ReactPackage> getPackages() {
          @SuppressWarnings("UnnecessaryLocalVariable")
          List<ReactPackage> packages = new PackageList(this).getPackages();
          // 这里是 要添加的DevicePackage
           packages.add(new DevicePackage());
          return packages;
        }

    至此,Android的配置已经完成,我们iOS的配置完成之后,再看react native端如何测试验证,RN端的代码是一致的。

iOS

  1. 在项目工程里面我们能看到ios的目录,使用xcode打开后缀为.xcodeproj的文件,看到工程目录

  2. 可以创建一个group,方便更好地组织代码结构,我这里创建了一个device的group

  3. 在该group下面创建device的声明文件Device.h

    image

    声明文件代码:

    #import <React/RCTBridgeModule.h>
    
    @interface Device : NSObject <RCTBridgeModule>
    @end
  4. 类似地,创建实现文件Device.m

    #import "Device.h"
    #import <UIKit/UIKit.h>
    
    @implementation Device
    
    //export the name of the native module as 'Device' since no explicit name is mentioned
    RCT_EXPORT_MODULE();
    
    //exports a method getDeviceName to javascript
    RCT_EXPORT_METHOD(getDeviceName:(RCTResponseSenderBlock)callback){
    @try{
      NSString *deviceName = [[UIDevice currentDevice] name];
      callback(@[[NSNull null], deviceName]);
    }
    @catch(NSException *exception){
      callback(@[exception.reason, [NSNull null]]);
    }
    }
    @end
    • RCT_EXPORT_MODULE()是一个宏方法,用来定义要导出的module的名字
    • RCT_EXPORT_METHOD用来表示要导出的方法的名字,从而是的javascript端可以调用
  5. iOS要注意的一点是,如果使用了自定义module,在开发环境会有一个warning:

    RCTBridge required dispatch_sync to load RCTDevLoadingView. This may lead to deadlocks

    解决的办法是在AppDelegate.m文件里面添加如下代码:

    #if RCT_DEV
    #import <React/RCTDevLoadingView.h>
    #endif
     .......
    
    RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];
    #if RCT_DEV
      [bridge moduleForClass:[RCTDevLoadingView class]];
    #endif

    React Native端调用自定义Module

  6. native 的自定义module实现完成之后,在reactive native的代码里进行测试验证,比如我们点击一个按钮之后,想要弹出设备信息:

    import React from 'react';
    import {
      StyleSheet,
      Text,
      TouchableOpacity,
      View,
      NativeModules,
      Alert,
    } from 'react-native';
    
    const HomeScreen = () => {
      handleHelpPress = () => {
        //  测试自定义module的方法
        const config = NativeModules.Device || {getDeviceName: (f) => f()};
        config.getDeviceName((error, name) => {
          Alert.alert('Device', name);
        });
      };
      return (
        <View style={styles.helpContainer}>
          <TouchableOpacity onPress={this.handleHelpPress} style={styles.helpLink}>
            <Text style={styles.helpLinkText}>get Device from Native Modules</Text>
          </TouchableOpacity>
        </View>
      );
    };
    
    const styles = StyleSheet.create({
      helpContainer: {
        marginTop: 15,
        alignItems: 'center',
      },
      helpLinkText: {
        fontSize: 14,
        color: '#2e78b7',
      },
    });
    
    export default HomeScreen;
  7. 效果如下: image image

Reference: