作者簡介:ASCE1885, 《Android 高級進階》作者。
在 React Native 開發中,某些情況下存在需要從 Native 端直接調用 Javascript 代碼中某個方法的需求,這時候我們就需要用到 JavaScriptModule
這個接口,如下所示:
/**
* Interface denoting that a class is the interface to a module with the same name in JS. Calling
* functions on this interface will result in corresponding methods in JS being called.
*
* When extending JavaScriptModule and registering it with a CatalystInstance, all public methods
* are assumed to be implemented on a JS module with the same name as this class. Calling methods
* on the object returned from {@link ReactContext#getJSModule} or
* {@link CatalystInstance#getJSModule} will result in the methods with those names exported by
* that module being called in JS.
*
* NB: JavaScriptModule does not allow method name overloading because JS does not allow method name
* overloading.
*/
@DoNotStrip
public interface JavaScriptModule {
}
本文以 Android 平臺為例進行說明,從 JavaScriptModule
的頭注釋中我們可以看出:
- 一個接口如果繼承了
JavaScriptModule
接口,并按照后面我們即將說到的步驟進行配置,那么就可以實現這個 Native 接口到 Javascript 中同名模塊的映射 - 這個 Native 接口中所有的公用方法也將一一映射到同名的 Javascript 模塊中的方法,調用 Native 接口的方法相當于直接調用到同名 Javascript 模塊中的方法
- 由于 Javascript 不支持方法名重載,因此 Native 端繼承
JavaScriptModule
的接口中不能存在重載的方法
那么如何配置才能實現上面說到的功能呢?總的來說,可以分為以下幾個步驟:
實現 JavaScriptModule
新增一個 Native 接口,繼承 JavaScriptModule
接口,并聲明所需的方法,如下所示,我們聲明一個 init
方法:
public interface AppModuleInitializer extends JavaScriptModule {
void init(String name);
}
接著在工程的自定義的 ReactPackage
類的 createJSModules
方法中將這個 Native 接口添加到 React Native 框架的 JavaScriptModule
列表中,如下所示(假設工程中的 ReactPackage
類名為 AppReactPackage
,并繼承自 React Native 框架的 MainReactPackage
類,已省略無關的代碼):
public class AppReactPackage extends MainReactPackage {
@Override
public List<Class<? extends JavaScriptModule>> createJSModules() {
List<Class<? extends JavaScriptModule>> javaScriptModules = new ArrayList<>();
javaScriptModules.addAll(super.createJSModules());
javaScriptModules.add(AppModuleInitializer.class);
return javaScriptModules;
}
}
注冊 AppReactPackage
上面說到的 AppReactPackage
記得注冊到應用中,有兩種方法,一種是在應用的 Application 中通過實現 ReactNativeHost
的 getPackages
方法,如下所示(通過 react-native init 生成的官方 demo):
public class MainApplication extends Application implements ReactApplication {
private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
@Override
public boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}
@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new AppReactPackage()
);
}
};
@Override
public ReactNativeHost getReactNativeHost() {
return mReactNativeHost;
}
@Override
public void onCreate() {
super.onCreate();
SoLoader.init(this, /* native exopackage */ false);
}
}
另一種方法是直接通過 ReactInstanceManager
類來進行注冊,如下代碼片段所示:
ReactInstanceManager.Builder builder = ReactInstanceManager.builder().setApplication(application)
.setJSBundleFile(bundleUrl)
.setJSMainModuleName("index")
.addPackage(new AppReactPackage())
.setUseDeveloperSupport(BuildConfig.DEBUG)
.setInitialLifecycleState(LifecycleState.BEFORE_RESUME);
創建并注冊同名的 Javascript 模塊
在 Javascript 目錄中我們創建名為 AppModuleInitializer.js
的文件,并在其中定義跟 Native 同名的 init
方法,如下所示:
class AppModuleInitializer {
init(json: string) {
// do something
}
}
AppModuleInitializer = new AppModuleInitializer();
// 注冊
BatchedBridge.registerCallableModule(
'AppModuleInitializer',
AppModuleInitializer);
module.exports = AppModuleInitializer;
開始使用
在 React Native 上下文初始化完成后,我們就可以在代碼中調用上面注冊的方法了,調用方式如下所示:
AppModuleInitializer initModule = context.getJSModule(AppModuleInitializer.class);
initModule.init("ASCE1885");
源碼中的例子
在 React Native 源碼中也存在不少對 JavaScriptModule
使用的例子,我們直接在源碼中搜索對 JavaScriptModule
的使用,可以看到如下圖所示,對這個接口的繼承或者實現有三十多處:
例如其中的 HMRClient
和 AppRegistry
的 Native 端定義如下,感興趣的讀者可以自己按著上面介紹的步驟來分析下這些源碼的實現。
/**
* JS module interface for HMRClient
*
* The HMR(Hot Module Replacement)Client allows for the application to receive updates
* from the packager server (over a web socket), allowing for injection of JavaScript to
* the running application (without a refresh).
*/
public interface HMRClient extends JavaScriptModule {
/**
* Enable the HMRClient so that the client will receive updates
* from the packager server.
* @param platform The platform in which HMR updates will be enabled. Should be "android".
* @param bundleEntry The path to the bundle entry file (e.g. index.ios.bundle).
* @param host The host that the HMRClient should communicate with.
* @param port The port that the HMRClient should communicate with on the host.
*/
void enable(String platform, String bundleEntry, String host, int port);
}
/**
* JS module interface - main entry point for launching React application for a given key.
*/
public interface AppRegistry extends JavaScriptModule {
void runApplication(String appKey, WritableMap appParameters);
void unmountApplicationComponentAtRootTag(int rootNodeTag);
void startHeadlessTask(int taskId, String taskKey, WritableMap data);
}