原生模块

在开发 Lynx 应用时,可能会遇到需要与 Lynx 未涵盖的原生平台 API 进行交互的场景。或者,你或许希望复用现有的原生平台代码到 Lynx 应用中。无论出于何种原因,都可以借助原生模块,实现 JavaScript 代码与原生代码的无缝连接,进而在 JavaScript 代码里调用原生平台的功能和 API。下面将详细介绍如何编写一个原生模块。

编写原生模块的基本步骤:

  1. 使用 TypeScript 声明类型化接口规范:通过 TypeScript 为原生模块定义清晰的接口规范,明确原生代码与 Lynx JavaScript 运行时之间传递的方法和数据类型。
  2. 依据接口规范编写 Lynx 应用代码:按照已声明的接口规范,在 Lynx 项目中编写具体的应用代码。
  3. 遵循接口规范编写原生平台代码并连接到 Lynx 运行时:根据接口规范完成原生平台代码的编写,然后将其与 Lynx 运行时环境进行连接。

下面,将通过构建一个原生模块示例,逐步展示这些步骤。

INFO

原生模块目前只能在 Background Thread Scripting 中使用。

本地持久存储模块

本指南旨在展示如何编写一个本地持久存储模块,让 Lynx 应用能够利用 JavaScript 代码在本地持久地存储数据。

要在移动设备上实现本地持久存储功能,需要借助 Android 和 iOS 的原生 API:

声明类型化的接口规范

原生模块的接口规范是连接原生代码和 Lynx JavaScript 运行时的桥梁,它定义了两者之间传递的方法和数据类型。

声明接口规范的步骤如下:

  1. 创建 Lynx 项目:参照创建一个 Lynx 项目指南,创建自己的 Lynx 项目。
  2. 新建类型声明文件:在 Lynx 项目中创建一个名为 src/typing.d.ts 的新文件。
  3. 实现接口规范:在 typing.d.ts 文件中实现原生模块的接口规范。
INFO

可以在类型对照表中查看规范中可用的类型以及对应的原生类型。

以下是本地持久存储模块的接口规范实现:

typing.d.ts
declare let NativeModules: {
  NativeLocalStorageModule: {
    setStorageItem(key: string, value: string): void;
    getStorageItem(key: string): string | null;
    clearStorage(): void;
  };
};

NativeModules 是 Lynx 在 JavaScript 运行时中提供的全局内建对象,它是所有原生模块的访问入口,所有原生模块的声明需定义在其中。

编写 Lynx 应用代码

接下来,在 Lynx 项目的 src/App.tsx 文件中编写应用代码。

以下是本地持久存储模块的 App.tsx 示例,它包含一个用于显示本地存储内容的区域,以及三个用于读写和清除本地存储的按钮。

编写原生平台代码

现在可以开始编写原生平台代码了。

准备 Xcode 项目

首先参照为 iOS 构建 Lynx Explorer 指南在本地创建 Lynx Explorer 项目,并用 Xcode 打开项目。

demo

接着在 Lynx Explorer 项目的 modules 文件夹上右键点击,选择 New Fie... 创建原生模块代码文件。

demo

然后使用 Cocoa Touch Class 模板。

demo

将类命名为 NativeLocalStorageModule,语言你可以选择创建 Objective-C,也可以选择 Swift。接着点击 Next,完成文件的创建。

demo

实现原生模块

INFO

原生模块需要实现额外的静态方法 name,返回原生模块的导出名字;实现额外的静态方法 methodLookup,将需要导出的方法名和对应的选择器进行映射。

Objective-C
Swift
explorer/darwin/ios/lynx_explorer/LynxExplorer/modules/NativeLocalStorageModule.h
#import <Foundation/Foundation.h>
#import <Lynx/LynxModule.h>

NS_ASSUME_NONNULL_BEGIN

@interface NativeLocalStorageModule : NSObject <LynxModule>

@end

NS_ASSUME_NONNULL_END
explorer/darwin/ios/lynx_explorer/LynxExplorer/modules/NativeLocalStorageModule.m
#import "NativeLocalStorageModule.h"

@interface NativeLocalStorageModule()
@property (strong, nonatomic) NSUserDefaults *localStorage;
@end

@implementation NativeLocalStorageModule

static NSString *const NativeLocalStorageKey = @"MyLocalStorage";

- (instancetype)init {
    if (self = [super init]) {
        _localStorage = [[NSUserDefaults alloc] initWithSuiteName:NativeLocalStorageKey];
    }
    return self;
}

+ (NSString *)name {
    return @"NativeLocalStorageModule";
}

+ (NSDictionary<NSString *, NSString *> *)methodLookup {
    return @{
        @"setStorageItem": NSStringFromSelector(@selector(setStorageItem:value:)),
        @"getStorageItem": NSStringFromSelector(@selector(getStorageItem:)),
        @"clearStorage": NSStringFromSelector(@selector(clearStorage))
    };
}

- (void)setStorageItem:(NSString *)key value:(NSString *)value {
    [self.localStorage setObject:value forKey:key];
}

- (NSString *)getStorageItem:(NSString *)key {
    NSString *value = [self.localStorage stringForKey:key];
    return value;
}

- (void)clearStorage {
    NSDictionary *keys = [self.localStorage dictionaryRepresentation];
    for (NSString *key in keys) {
        [self.localStorage removeObjectForKey:key];
    }
}

@end

接下来将原生模块注册到 Lynx 运行时环境中。

在 Lynx Explorer 工程的 explorer/darwin/ios/lynx_explorer/LynxExplorer/LynxInitProcessor.m 文件的 setupLynxEnv 方法中添加如下注册代码,将原生模块注册到 Lynx 运行时环境的全局配置中。

Objective-C
Swift
explorer/darwin/ios/lynx_explorer/LynxExplorer/LynxInitProcessor.m

#import "NativeLocalStorageModule.h"

- (void)setupLynxEnv {
  // ...

  // register global JS module
  [globalConfig registerModule:NativeLocalStorageModule.class];

  // ...
}

运行代码

准备好所有内容后,现在可以构建运行你的代码:

首先参照编译和运行 iOS Lynx Explorer 指南构建 Lynx Explorer,并安装到你的手机上。

接着参考安装依赖&启动开发服务器指南,在 Lynx 项目根目录下安装依赖并且启动开发服务器。

安装依赖:

npm
yarn
pnpm
bun
npm install

启动开发服务器:

npm
yarn
pnpm
bun
npm run dev

你将会看到控制台中看到二维码和产物链接,使用 Lynx Explorer 扫描二维码或者填写产物链接即可打开你的 Lynx 页面。

demo

首先参照为 Android 构建 Lynx Explorer 指南在本地创建 Lynx Explorer 项目。

接着在 Lynx Explorer 项目的 explorer/android/lynx_explorer/src/main/java/com/lynx/explorer/modules/ 路径下新建 NativeLocalStorageModule.javaNativeLocalStorageModule.kt 文件,并继承 LynxModule 实现 NativeLocalStorageModule 原生模块。

INFO

需要在原生模块的方法上添加 @LynxMethod 注解,实现方法的导出。

Java
Kotlin
explorer/android/lynx_explorer/src/main/java/com/lynx/explorer/modules/NativeLocalStorageModule.java
package com.lynx.explorer.modules;

import android.content.Context;
import android.content.SharedPreferences;

import com.lynx.jsbridge.LynxMethod;
import com.lynx.jsbridge.LynxModule;
import com.lynx.tasm.behavior.LynxContext;

public class NativeLocalStorageModule extends LynxModule {
  private static final String PREF_NAME = "MyLocalStorage";
  public NativeLocalStorageModule(Context context) {
    super(context);
  }

  Context getContext() {
    LynxContext lynxContext = (LynxContext) mContext;
    return lynxContext.getContext();
  }

  @LynxMethod
  public void setStorageItem(String key, String value) {
    SharedPreferences sharedPreferences = getContext().getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
    SharedPreferences.Editor editor = sharedPreferences.edit();
    editor.putString(key, value);
    editor.apply();
  }

  @LynxMethod
  public String getStorageItem(String key) {
    SharedPreferences sharedPreferences = getContext().getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
    String value = sharedPreferences.getString(key, null);
    return value;
  }

  @LynxMethod
  public void clearStorage() {
    SharedPreferences sharedPreferences = getContext().getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
    SharedPreferences.Editor editor = sharedPreferences.edit();
    editor.clear();
    editor.apply();
  }
}

接下来将原生模块注册到 Lynx 运行时环境中。

在 Lynx Explorer 项目的 explorer/android/lynx_explorer/src/main/java/com/lynx/explorer/modules/LynxModuleAdapter.java 文件的 Init 方法中添加如下注册代码,将原生模块注册到 Lynx 运行时环境中。在这里需要指定导出的原生模块的名称,需要和上面的接口规范保持一致。

explorer/android/lynx_explorer/src/main/java/com/lynx/explorer/modules/LynxModuleAdapter.java

  public void Init(Context context) {
    // ......

    LynxEnv.inst().registerModule("NativeLocalStorageModule", NativeLocalStorageModule.class);

    // ......
  }

准备好所有内容后,现在可以构建运行你的代码。

首先参照编译和运行 Android Lynx Explorer 指南从源码构建 Lynx Explorer,并安装到你的手机上。

接着参考安装依赖&启动开发服务器指南,在 Lynx 项目根目录下安装依赖并且启动开发服务器。

安装依赖:

npm
yarn
pnpm
bun
npm install

启动开发服务器:

npm
yarn
pnpm
bun
npm run dev

你将会在控制台中看到二维码,使用 Lynx Explorer 扫描二维码来打开页面。

demo

恭喜你,成功在 Lynx Explorer 中创建了原生模块!如果你想在你的应用中创建原生模块,首先需要参考接入现有应用指南集成 Lynx,然后参照上述步骤创建原生模块。

类型对照表

TypeScriptiOS(Objective-C)Android(Java)
nullnilnull
undefinednilnull
booleanBOOL (or NSNumber when used inside objects)boolean (or Boolean when used inside objects)
numberdouble (or NSNumber when used inside objects)double (or Number when used inside objects)
stringNSStringString
BigIntNSStringlong (or Number when used inside objects)
ArrayBufferNSDatabyte[]
objectNSDictionarycom.lynx.react.bridge.ReadableMap
arrayNSArraycom.lynx.react.bridge.ReadableArray
Callback ()=>block void (^)(id)com.lynx.react.bridge.Callback
除非另有说明,本项目采用知识共享署名 4.0 国际许可协议进行许可,代码示例采用 Apache License 2.0 许可协议进行许可。