Blog

Blog

PHODAL

React Native 重新封装 Cordova 插件笔记:插件编写与第三方 SDK 编译

最近刚完成项目上的几个 React Native 插件的编写,便想记录这其中遇到的坑——主要是封装第三方 SDK 的问题。记录的过程中,顺便也就写一下相关的插件编写了。因此,本文分成三部分:

  • React Native 插件封装
  • Native 主动向 React Native 发送数据
  • React Native 编译问题

React Native 插件封装

先让我们从 React Native 端的 JS 封装代码看起。假设我们的 SDK 名字是 SDK,那么对应在 React Native 上的 sdk.js 代码如下所示:

import { NativeModules } from 'react-native';

module.exports = NativeModules.SDK;

实际上,在这里,我们应该列出所有的方法。但是,由于时间限制原因,就没有在这一一列出了。

React Native 插件 Android 示例

对应的,我们在 Android 端,只需要在方法开发之前添加 @ReactMethod 注解,再调用 promise 方法即可:

@ReactMethod
public void isLogined(Promise promise) {
    try {
        promise.resolve(SDK.app().is_logined());
    } catch (Exception e) {
        promise.reject(e);
    }
}

React Native Android 插件带参数示例

@ReactMethod
public void login(int userid, String signature, Promise promise) {

}

而在 iOS 上则稍微麻烦一些。

React Native 插件 iOS 示例

RN 对于 iOS 和 Android 的封装方式,到底还是因为两个系统的不同,而有所差异。如下是头文件 sdk.h 的代码:

#import "RCTBridgeModule.h"

@interface SDK : NSObject <RCTBridgeModule>


@end

相应的代码文件 sdk.m 则如下所示:

@implementation SDK

RCT_EXPORT_MODULE();
RCT_EXPORT_METHOD(isLogined:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject) {
    BOOL isLogin = [[SDKHelper sharedHelper] isLogin];
    resolve(@{@"isLogined": @(isLogin)});
}

从代码上来看,iOS 的 promise 使用方式与 Android 还是有些差异的。

React Native iOS 插件带参数示例

RCT_EXPORT_METHOD(login:(int64_t)userID
                  password:(NSString *) password
                  resolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject) {

    [[SDKHelper sharedHelper] login:userID password:password];

    resolve(@"true");
}

Native 主动向 React Native 发送数据

在我们的需求里,还会遇到向 React Native 发送数据的功能。这个时候需要用到 EventEmitter,用于监听来自原生代码的示例。如下是两个不同的平台监听的代码:

webviewOnLoad = () => {
  if(Platform.OS === 'android') {
    DeviceEventEmitter.addListener('SDK_EVENT', this.handleNativeInvoke);
  } else {
    var RCTSDK = new NativeEventEmitter(NativeModules.SDK);
    RCTSDK.addListener('SDK_EVENT', this.handleNativeInvoke)
  }
}

这个时候就需要继承 RCTEventEmitter,其头文件如下所示:

#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>


@interface SDK : RCTEventEmitter <RCTBridgeModule>

@end

代码文件则如下:

#import <React/RCTBridge.h>

@implementation SDK

@synthesize bridge = _bridge;

- (NSArray<NSString *> *) supportedEvents
{
    return @[@"SDK_EVENT"];
}

RCT_EXPORT_MODULE();


- (void)pushEvent:(NSString *)formatString eventName:(NSString *)eventName JSONObject:(id)JSONObject {
    NSData* JSONObjectData = [NSJSONSerialization dataWithJSONObject:[JSONObject copy]
                                                             options:NSJSONWritingPrettyPrinted
                                                               error:nil];
    NSString* JSONObjectString = [[NSString alloc] initWithData:JSONObjectData
                                                       encoding:NSUTF8StringEncoding];
    NSString* javascript = [NSString stringWithFormat:formatString, eventName, JSONObjectString];
    dispatch_async(dispatch_get_main_queue(), ^{
        [self sendEventWithName:@"SDK_EVENT" body: @{@"injectedJS": javascript}];
    });
}

@end

我们需要使用如下的代码,以此来声明事件的类型:

- (NSArray<NSString *> *) supportedEvents
{
    return @[@"SDK_EVENT"];
}

随后使用下面的代码,来向 React Native 发送事件:

[self sendEventWithName:@"SDK_EVENT" body: @{@"injectedJS": javascript}];

React Native 编译问题

在这个过程中,还遇到编译的问题。

以下是编加编译的示例:

//:configuration = Debug
HEADER_SEARCH_PATHS = $(SRCROOT)/** $(SRCROOT)/node_modules/react-native/React $(SRCROOT)/../../../node_modules/react-native/React/** $(SRCROOT)/../react-native/React/**

//:configuration = Release
HEADER_SEARCH_PATHS = $(SRCROOT)/** $(SRCROOT)/node_modules/react-native/React $(SRCROOT)/../../../node_modules/react-native/React/** $(SRCROOT)/../react-native/React/**

//:completeSettings = some
HEADER_SEARCH_PATHS

还有:

//:configuration = Debug
FRAMEWORK_SEARCH_PATHS = $(SRCROOT)/../node_modules/react-native-xxx/ios/RNxxx/**

//:configuration = Release
FRAMEWORK_SEARCH_PATHS = $(SRCROOT)/../node_modules/react-native-xxx/ios/RNZxxx/**

//:completeSettings = some
FRAMEWORK_SEARCH_PATHS

除此,还需要在项目里手动添加库:

Link Binary with Libraries

添加: CrashReporter.framework, CoreTelephony.framework

以及相应的编译选项:

//:configuration = Debug
OTHER_LDFLAGS = -ObjC -lsqlite3 -lz -liconv -lstdc++ -lc++ -lstdc++.6

//:configuration = Release
OTHER_LDFLAGS = -ObjC -lsqlite3 -lz -liconv -lstdc++ -lc++ -lstdc++.6

//:completeSettings = some
OTHER_LDFLAGS

关于我

Github: @phodal     微博:@phodal     知乎:@phodal    

微信公众号(Phodal)

围观我的Github Idea墙, 也许,你会遇到心仪的项目

QQ技术交流群: 321689806
comment

Feeds

RSS / Atom

最近文章

关于作者

Phodal Huang

Engineer, Consultant, Writer, Designer

ThoughtWorks 技术专家

工程师 / 咨询师 / 作家 / 设计学徒

开源深度爱好者

出版有《前端架构:从入门到微前端》、《自己动手设计物联网》、《全栈应用开发:精益实践》

联系我: h@phodal.com

微信公众号: 最新技术分享

标签