简体   繁体   English

如何将非静态的按需NativeModules注入React Native Android?

[英]How to inject non static, on demand NativeModules into React Native Android?

In a hybrid app where multiple Activity / Fragment instances nesting their own ReactRootView s share a single ReactInstanceManager instance, there seems to be no official way to inject a native module per Activity / Fragment instance. 在一个混合应用程序中,多个嵌套他们自己的ReactRootViewActivity / Fragment实例共享一个ReactInstanceManager实例,似乎没有正式的方法为每个Activity / Fragment实例注入一个本机模块。 By nature, native modules are singleton like javascript modules residing in a .js file. 从本质上讲,本机模块与.js文件中的javascript模块一样,是单例的。 This is not the intended behavior if JS code running inside different instances of the same Fragment wants to access Java/Kotlin side to interact with the local properties of the Fragment . 如果在同一Fragment不同实例中运行的JS代码想要访问Java / Kotlin端以与Fragment的本地属性进行交互,则这不是预期的行为。

I tried using registerAdditionalPackages() method of ReactInstanceManager but it fails with an assertion error like below if used simultaneously by multiple Fragment s/ Activity s. 我尝试使用ReactInstanceManager registerAdditionalPackages()方法,但如果由多个Fragment / Activity同时使用,则会失败,并出现如下所示的断言错误。

"Extending native modules with non-matching application contexts."

It wasn't a thread safety issue but a design consequence of registerAdditionalPackages() implementation. 这不是线程安全问题,而是registerAdditionalPackages()实现的设计结果。 Is there another way, and if so, how do you access the injected module on the JS side? 还有另一种方法,如果是这样,如何在JS端访问注入的模块?

This problem kept me awake for days. 这个问题使我醒了好几天。 Finally, I have a solution. 最后,我有一个解决方案。 I hope it helps someone in the future. 我希望它对将来的人有所帮助。 The solution is in Kotlin but it is quite straightforward to translate it into Java. 该解决方案位于Kotlin中,但是将其转换为Java非常简单。 someVariable!! is a "not null" assertion, uppercase SomeObject() calls are instance creation, SomeType:SomeOtherType is inheritence or implementation, val someVar:SomeType is a variable declaration. 是“非空”声明,大写SomeObject()调用是实例创建, SomeType:SomeOtherType是继承或实现, val someVar:SomeType是变量声明。 The rest is the same. 其余部分相同。

Steps: 脚步:

1) Run the below code at the time you want to inject a module to your RN runtime. 1)在要将模块注入RN运行时时,运行以下代码。 Inside of Activity.onCreate() or Fragment.onCreateView() are some good candidates. Activity.onCreate()Fragment.onCreateView()是一些不错的候选对象。 mReactInstanceManager is your singleton, global react runtime. mReactInstanceManager是您的单例全局反应运行时。 packageToInject definition will be given later. packageToInject定义将在以后给出。

synchronized(mReactInstanceManager!!.currentReactContext!!) {
    val nativeModuleRegistryBuilder = NativeModuleRegistryBuilder(
        mReactInstanceManager!!.currentReactContext as ReactApplicationContext?, 
        mReactInstanceManager!!, 
        false
    )

    nativeModuleRegistryBuilder.processPackage(packageToInject)
    mReactInstanceManager!!.currentReactContext!!.catalystInstance!!.extendNativeModules(nativeModuleRegistryBuilder.build())
}

2) Instance held inside packageToInject must be prepared like below to have a unique module with a unique name per Fragment / Activity instance. 2)packageToInjectpackageToInject实例必须按照以下方式进行准备,以具有每个Fragment / Activity实例具有唯一名称的唯一模块。 Make these inner classes of your Fragment or Activity . 使这些内部类成为FragmentActivity

class ReactManagerPackage : ReactPackage {
    override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
        return emptyList()
    }

    override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
        val modules = ArrayList<NativeModule>()
        modules.add(ReactBridge(reactContext))
        return modules
    }
}

class ReactBridge(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
    override fun getName(): String {
        return myFragmentOrActivity.hashCode().toString()
    }

    @ReactMethod
    fun showToast(text: String) {
        Toast.makeText(text, Toast.LENGTH_SHORT).show()
    }  
}

3) Pass the myFragmentOrActivity.hashCode().toString() as a prop when you initiate mReactRootView.startReactApplication() either in your Activity.onCreate() or Fragment.onCreateView() . 3)Activity.onCreate()Fragment.onCreateView()启动mReactRootView.startReactApplication()时,将myFragmentOrActivity.hashCode().toString()作为道具mReactRootView.startReactApplication() Put it in the bundle that you give to mReactRootView.startReactApplication() as 3rd argument. 将其作为第三个参数放在提供给mReactRootView.startReactApplication()的捆绑软件中。

val bundle = Bundle()
bundle.putString("fragmentOrActivityHash", myFragmentOrActivity.hashCode().toString())
mReactRootView.startReactApplication(mReactInstanceManager, "MyRootComponent", bundle )

4) Use the prop in your component ( MyRootComponent in this example) to retrieve your specific bridge. 4)使用组件中的prop(在本例中为MyRootComponent )来检索您的特定桥。 (Javascript) (JavaScript)

NativeModules[this.props.fragmentOrActivityHash].showToast("It works")

Profit! 利润!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2025 STACKOOM.COM