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

在useEffect取消订阅后,Android向JS发送事件仍引发内存泄漏

问题

按照React Native官方文档实现了Android原生向RN发送事件的功能:定义RecyclerViewScrollListener,在滚动时将当前可见项索引发送至RN(类似FlatList的onViewableItemsChanged);监听器设置在TikTokScreenFragment的onViewCreated方法中,事件发送逻辑在自定义ViewManager的EventEmitter实现里。

RN侧通过useEffect使用NativeEventEmitter订阅事件,且在清理函数中移除了监听器,但滚动页面时Android内存分析器显示内存暴涨,注释addListener后问题消失,求排查解决建议。

相关代码:

RecyclerViewScrollListener 代码

scrollListener = object : RecyclerViewScrollListener() {
    override fun onItemIsFirstVisibleItem(index: Int) {
        Log.d("visible item index", index.toString())
        // play just visible item
        if (index != -1) {
            PlayerViewAdapter.playIndexThenPausePreviousPlayer(index)
            eventEmitter.emitEvent("onVisibleItemChanged", index) 
        }
    }
}

recyclerView!!.addOnScrollListener(scrollListener)

TikTokScreenFragment 的 onViewCreated 代码

override
fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    setAdapter() // this is where the scroll listener is set up

    // load data
    val model: MediaViewModel by activityViewModels()
    model.getMedia().observe(requireActivity(), Observer {
        mAdapter?.updateList(arrayListOf(*it.toTypedArray()))
    })
}

MyViewManager 代码

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

    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)
        }
    }

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

        val eventEmitter = object : EventEmitter {
            override fun emitEvent(eventName: String) {
                reactContext
                    .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
                    .emit(eventName, null)
            }

            override fun emitEvent(eventName: String, eventData: Int) {
                reactContext
                    .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
                    .emit(eventName, eventData)
            }
        }
        val myFragment = TikTokScreenFragment(eventEmitter)
        val activity = reactContext.currentActivity as FragmentActivity

        activity.supportFragmentManager
            .beginTransaction()
            .replace(reactNativeViewId, myFragment, reactNativeViewId.toString())
            .commit()
    }
}

RN 侧 useEffect 代码

useEffect(() => {
    const eventEmitter = new NativeEventEmitter();
    const onVisibleItemChanged = eventEmitter.addListener(
      "onVisibleItemChanged",
      function (e: Event) {
        // handle event.
        setVisibleIndex(e as unknown as number);
      }
    );
    console.log("listening to onVisibleItemChanged");

    return () => {
      console.log("removing onVisibleItemChanged listener");
      onVisibleItemChanged.remove();
    };
  }, []);
排查解决建议
  • 控制事件发送频率:RecyclerView滚动时onItemIsFirstVisibleItem可能被频繁触发(快速滚动时每秒可达几十次),大量事件会导致JS线程处理压力剧增,同时Native侧积累大量未处理的事件对象。添加防抖逻辑,比如设置200ms间隔,同一时间段内仅发送最后一次的索引值。
  • 清理Fragment与监听器的引用:自定义EventEmitter匿名类持有reactContext强引用,若Fragment销毁时未移除滚动监听器,会导致ReactApplicationContext无法被回收。在TikTokScreenFragment的onDestroyView方法中调用recyclerView?.removeOnScrollListener(scrollListener),同时确保Fragment销毁时清理eventEmitter相关引用。
  • 验证RN侧监听器的移除逻辑:确认组件卸载时是否打印了removing onVisibleItemChanged listener日志,确保监听器确实被移除。同时检查useEffect的依赖项,避免因依赖变化导致监听器被重复添加。
  • 优化跨线程数据传输:频繁发送int类型索引时,Native到JS的跨线程数据传输会产生大量临时对象。可合并事件或采用批量发送方式,减少传输次数。
  • 用Android Profiler定位泄漏点:打开Android Studio Profiler进行内存分析,滚动后生成Heap Dump,重点排查:
    • React相关对象(如RCTDeviceEventEmitter事件队列、JSBridge相关实例)
    • 自定义EventEmitter、TikTokScreenFragment是否被意外持有引用
    • RecyclerView的Adapter相关对象是否存在泄漏
  • 替换事件传递方式:对于自定义组件,推荐使用RCTEventEmitter(继承BaseJavaModule或SimpleViewManager)发送事件,而非直接获取RCTDeviceEventEmitter,这种方式更贴合RN生命周期管理,能降低内存泄漏风险。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 11:46:07