You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android React Native Native UI传Context引发ClassCastException求助

问题描述

在Android平台通过Fragment创建Native UI组件时,尝试从Native侧向React Native发送数据时触发崩溃。核心代码中,CealScanQrView里执行val reactContext = context as ReactContext时抛出类型转换异常,提示MainActivity无法转换为ReactContext。

相关代码

CealScanQrView.kt

class CealScanQrView(context: Context): FrameLayout(context) {
...
// 包含CameraX集成逻辑
...
// 向React Native提交数据时出错

val reactContext = context as ReactContext /// 该行导致崩溃
                        reactContext
                            .getJSModule(RCTEventEmitter::class.java)
                            .receiveEvent(id, topChange, event)
}

CealScanQrFragment.kt

class CealScanQrFragment: Fragment() {
    private lateinit var cealScanQrView: CealScanQrView

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
        super.onCreateView(inflater, container, savedInstanceState)
        cealScanQrView = CealScanQrView(requireNotNull(context))
        return cealScanQrView // 自定义View可替换为任意要渲染的视图
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        // 执行onCreate阶段的逻辑,例如:
        cealScanQrView.setUpCamera(requireActivity())
    }

    override fun onDestroy() {
        super.onDestroy()
        cealScanQrView.destroyCamera()
    }
}

CealScanQrViewManager.kt

class CealScanQrViewManager(
    private val reactContext: ReactApplicationContext
) : ViewGroupManager<FrameLayout>() {

    private val cealScanQrView = "CealScanQrView"
    private val topChange = "topChange"
    private val phasedRegistrationNames = "phasedRegistrationNames"
    private val bubbled = "bubbled"
    private val onChange = "onChange"
    private val create = "create"

    companion object {
        private const val COMMAND_CREATE = 1
    }

    private var propWidth: Int? = null
    private var propHeight: Int? = null

    override fun getName() = cealScanQrView

    override fun createViewInstance(reactContext: ThemedReactContext) = FrameLayout(reactContext)

    override fun getCommandsMap() = mapOf("create" to COMMAND_CREATE)

    override fun receiveCommand(root: FrameLayout, commandId: String?, args: ReadableArray?) {
        super.receiveCommand(root, commandId, args)
        val reactNativeViewId = requireNotNull(args).getInt(0)

        when (commandId?.toInt()) {
            COMMAND_CREATE -> createFragment(root, reactNativeViewId)
        }
    }

    private fun createFragment(root: FrameLayout, reactNativeViewId: Int) {
        val parentView = root.findViewById<ViewGroup>(reactNativeViewId)
        setupLayout(parentView)

        val myFragment = CealScanQrFragment()
        val activity = reactContext.currentActivity as FragmentActivity
        activity.supportFragmentManager
            .beginTransaction()
            .replace(reactNativeViewId, myFragment, reactNativeViewId.toString())
            .commit()
    }

    private fun setupLayout(view: View) {
        Choreographer.getInstance().postFrameCallback(object: Choreographer.FrameCallback {
            override fun doFrame(frameTimeNanos: Long) {
                manuallyLayoutChildren(view)
                view.viewTreeObserver.dispatchOnGlobalLayout()
                Choreographer.getInstance().postFrameCallback(this)
            }
        })
    }

    @ReactPropGroup(names = ["width", "height"], customType = "Style")
    fun setStyle(view: FrameLayout, index: Int, value: Int) {
        if (index == 0) propWidth = value
        if (index == 1) propHeight = value
    }

    private fun manuallyLayoutChildren(view: View) {
        // propWidth和propHeight来自React Native props
        val width = requireNotNull(propWidth)
        val height = requireNotNull(propHeight)

        view.measure(
            View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
            View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY))

        view.layout(0, 0, width, height)
    }


    override fun getExportedCustomBubblingEventTypeConstants(): Map<String, Any> {
        return mapOf(
            topChange to mapOf(
                phasedRegistrationNames to mapOf(
                    bubbled to onChange
                )
            )
        )
    }
}

错误信息

java.lang.ClassCastException: com.myapp.MainActivity cannot be cast to com.facebook.react.bridge.ReactContext
解决方案

崩溃原因是Fragment传入的Context是宿主Activity实例(MainActivity),而非ReactContext。要解决这个问题,需要正确获取React Native的上下文实例来发送事件,以下是两种可行方案:

方案一:传递ReactApplicationContext到View

  1. 修改CealScanQrView构造函数
    直接接收ReactApplicationContext作为参数,避免类型转换:

    class CealScanQrView(private val reactContext: ReactApplicationContext): FrameLayout(reactContext) {
        // 发送事件的方法
        fun sendRNEvent(viewId: Int, eventData: WritableMap) {
            reactContext
                .getJSModule(RCTEventEmitter::class.java)
                .receiveEvent(viewId, "topChange", eventData)
        }
    }
    
  2. 通过Fragment传递上下文
    给Fragment添加静态创建方法,接收并传递ReactApplicationContext:

    class CealScanQrFragment: Fragment() {
        private lateinit var cealScanQrView: CealScanQrView
        private lateinit var reactContext: ReactApplicationContext
    
        companion object {
            private const val KEY_REACT_CONTEXT = "react_context"
            fun newInstance(context: ReactApplicationContext): CealScanQrFragment {
                val fragment = CealScanQrFragment()
                val args = Bundle()
                args.putSerializable(KEY_REACT_CONTEXT, WeakReference(context))
                fragment.arguments = args
                return fragment
            }
        }
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            val ref = arguments?.getSerializable(KEY_REACT_CONTEXT) as WeakReference<ReactApplicationContext>
            reactContext = ref.get()!!
        }
    
        override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
            cealScanQrView = CealScanQrView(reactContext)
            return cealScanQrView
        }
    }
    
  3. 在ViewManager中传递上下文
    创建Fragment时使用newInstance方法传入ReactApplicationContext:

    private fun createFragment(root: FrameLayout, reactNativeViewId: Int) {
        val parentView = root.findViewById<ViewGroup>(reactNativeViewId)
        setupLayout(parentView)
    
        val myFragment = CealScanQrFragment.newInstance(reactContext)
        val activity = reactContext.currentActivity as FragmentActivity
        activity.supportFragmentManager
            .beginTransaction()
            .replace(reactNativeViewId, myFragment, reactNativeViewId.toString())
            .commit()
    }
    

方案二:从Application获取ReactInstanceManager

如果不想传递上下文,可通过Application获取React实例管理器,再获取事件发射器:

// 在CealScanQrView的事件发送逻辑中
val activity = context as AppCompatActivity
val reactApp = activity.application as ReactApplication
val reactInstanceManager = reactApp.reactNativeHost.reactInstanceManager
val currentContext = reactInstanceManager.currentReactContext

currentContext?.let {
    it.getJSModule(RCTEventEmitter::class.java)
        .receiveEvent(viewId, "topChange", eventData)
}

此方案要求你的Application类实现ReactApplication接口,这是React Native项目的标准配置。

注意事项
  • 禁止直接将Activity强转为ReactContext,二者属于不同类型层级。
  • 使用WeakReference传递ReactApplicationContext可降低内存泄漏风险。
  • 发送事件前需判断currentReactContext是否为null,避免空指针异常。

内容的提问来源于stack exchange,提问作者BraveEvidence

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 02:01:29