作者简介:ASCE1885, 《Android 高级进阶》作者。
小密圈: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.
*/
@DoNotStrippublic 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);
}