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

React Native Android原生Fragment组件导航后创建实例报错求助

React Native Android 原生组件导航后创建Fragment报错问题

问题描述

在Android端构建原生视图供React Native使用,首次渲染可创建任意数量实例,但导航到新页面后,使用同一组件创建原生视图时出现以下错误:

java.lang.IllegalArgumentException: No view found for id 0x93c3 (unknown) for fragment MyFragment{4660a7b} (1fddb7f4-9894-4da7-a39c-ae82aadfd9aa id=0x93c3 tag=37827)

完全按照React Native官方文档的Fragment示例适配修改,仍触发该错误。

相关代码

Android 端 MyViewManager 代码

//class where i create my fragment

public class MyViewManager extends ViewGroupManager<FrameLayout> {

    public static final String REACT_CLASS = "MyViewManager";
    public final int COMMAND_CREATE = 1;
    private int propWidth;
    private int propHeight;
    private int old_value = 0;
    protected String zone_id;
    private String publisher_id;
    FragmentManager fragmet;
    private ReactApplicationContext reactContext;

    public MyViewManager(ReactApplicationContext reactContext) {
        this.reactContext = reactContext;
    }

    @Override
    public String getName() {
        return REACT_CLASS;
    }

    /**
     * Return a FrameLayout which will later hold the Fragment
     */
    @Override
    public FrameLayout createViewInstance(ThemedReactContext reactContext) {
        return new FrameLayout(reactContext);
    }

    /**
     * Map the "create" command to an integer
     */
    @Nullable
    @Override
    public Map<String, Integer> getCommandsMap() {
        return MapBuilder.of("create", COMMAND_CREATE);
    }

    /**
     * Handle "create" command (called from JS) and call createFragment method
     */
    @Override
    public void receiveCommand(
            @NonNull FrameLayout root,
            String commandId,
            @Nullable ReadableArray args
    ) {
        super.receiveCommand(root, commandId, args);
        int reactNativeViewId = args.getInt(0);
        int commandIdInt = Integer.parseInt(commandId);
        this.old_value = reactNativeViewId;

        switch (commandIdInt) {
            case COMMAND_CREATE:
                Log.e("MediumRectangle", "EJECUTAMOS AQUQUIQ" + Integer.toString(reactNativeViewId));
                createFragment(root, reactNativeViewId);
                break;
            default: {}
        }
    }

    @ReactPropGroup(names = {"width", "height", "backgroundColor"}, customType = "Style")
    public void setStyle(FrameLayout view, int index, Integer value) {
        if (index == 0) {
            propWidth = value;
        }

        if (index == 1) {
            propHeight = value;
        }
    }

    @ReactProp(name = "zone_id")
    public void setAdType(FrameLayout view, String zone_id) {
        this.zone_id = zone_id;
    }

    @ReactProp(name = "publisher_id")
    public void setPublisherId(FrameLayout view, String publisher_id) {
        this.publisher_id = publisher_id;
    }

    /**
     * Replace your React Native view with a custom fragment
     */
    public void createFragment(FrameLayout root, int reactNativeViewId) {

        Log.e("MediumRectangle", "CRAETETETT FRAGMEENTNETETTE fragment" + Integer.toString(reactNativeViewId));

        ViewGroup parentView = (ViewGroup) root.findViewById(reactNativeViewId);
        setupLayout(parentView);

        final MyFragment myFragment = new MyFragment(reactNativeViewId, this.reactContext, this.zone_id, publisher_id);
        FragmentActivity activity = (FragmentActivity) reactContext.getCurrentActivity();
        activity.getSupportFragmentManager()
            .beginTransaction()
            .replace(reactNativeViewId, myFragment, String.valueOf(reactNativeViewId))
            .commit();

        FragmentManager fragmentManager = activity.getSupportFragmentManager();
        MyFragment fragment = (MyFragment) fragmentManager.findFragmentById(reactNativeViewId);
        if (fragment ==  null) {
            Log.e("MediumRectangle", "existetet viwewe");
        }else {
            Log.e("MediumRectangle", "no existete view");
        }
    }

    public void setupLayout(View view) {
        Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() {
            @Override
            public void doFrame(long frameTimeNanos) {
                manuallyLayoutChildren(view);
                view.getViewTreeObserver().dispatchOnGlobalLayout();
                Choreographer.getInstance().postFrameCallback(this);
            }
        });
    }

    /**
     * Layout all children properly
     */
    public void manuallyLayoutChildren(View view) {
        // propWidth and propHeight coming from react-native props
        int width = propWidth;
        int height = propHeight;

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

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

Android 端 MyFragment 代码

// class of my fragment

public class MyFragment extends Fragment {

    CustomView customView;

    int viewID;
    ReactApplicationContext reactContext;
    String zone_id;
    String publisher_id;

    protected int getContentViewLayoutId() { return R.layout.activity_display_ad_test; }
    protected int getAdContainerLayoutId() { return R.id.engage_ad; }

    public MyFragment(int reactNativeViewId, ReactApplicationContext reactContext, String zone_id, String publisher_id) {
        this.viewID = reactNativeViewId;
        this.reactContext = reactContext;
        this.zone_id = zone_id;
        this.publisher_id = publisher_id;
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
        super.onCreateView(inflater, parent, savedInstanceState);
        customView = new CustomView(this.getContext(), this.reactContext, this.publisher_id);
        return customView; // this CustomView could be any view that you want to render
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        // do any logic that should happen in an `onCreate` method, e.g:
        //customView.onCreate(savedInstanceState);
    }

    @Override
    public void onPause() {
        super.onPause();
        // do any logic that should happen in an `onPause` method
        // e.g.: customView.onPause();
    }

    @Override
    public void onResume() {
        super.onResume();
        // do any logic that should happen in an `onResume` method
        // e.g.: customView.onResume();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        // do any logic that should happen in an `onDestroy` method
        // e.g.: customView.onDestroy();
    }

}

Android 端 CustomView 代码

public class CustomView extends FrameLayout implements AdExecutor.AdLoadCallback, IMAdEngage.AdEventCallback {

    private IMAdSdk sdk_;
    private ReactApplicationContext context;
    private EngageAd banner;
    private ViewGroup container;
    private String zone_id;

    protected int getContentViewLayoutId() { return R.layout.activity_display_ad_test; }
    protected int getAdContainerLayoutId() { return R.id.engage_ad; }

    public CustomView(@NonNull Context context, ReactApplicationContext reactContext, String publisher_id) {

        super(context);

        // set padding and background color
        this.setPadding(16,16,16,16);
        this.setBackgroundColor(Color.parseColor("#5FD3F3"));

        // add default text view
        TextView text = new TextView(context);
        text.setText("Welcome to Android Fragments with React Native.");
        this.addView(text);

     }
}

JavaScript 端代码

import React, { useEffect, useState, useRef } from "react";
import { requireNativeComponent, PixelRatio, UIManager, findNodeHandle, Dimensions, View } from 'react-native';

const MyViewManager = requireNativeComponent('MyViewManager');

const Innity = () => {

  const AD_HEIGHT = PixelRatio.getPixelSizeForLayoutSize(250);
  const AD_WIDTH = PixelRatio.getPixelSizeForLayoutSize(Dimensions.get("window").width);

  const ref = useRef(null);
  const id_ref = useRef(null);
  const [id, setID] = useState(null);

  useEffect(() => {
    create();
  }, []);

  const create = () => {
    const viewId = findNodeHandle(ref.current);
      UIManager.dispatchViewManagerCommand(
        viewId,
        // we are calling the 'create' command
        UIManager.MyViewManager.Commands.create.toString(),
        [viewId],
      );
    }
  }

  return (
    <View
      style = {{
        height: 250,
        backgroundColor: "transparent"
      }}
    >
      <MyViewManager
        style={{
            height: AD_HEIGHT,
            width: AD_WIDTH
        }}
        zone_id = {"medium_rectangle"}
        publisher_id = {"5613"}
        ref={ref}
      >
      </MyViewManager> 
    </View>
  );
}

export default Innity;

解决方案

问题根源

  1. FragmentManager 残留实例:导航返回后,FragmentManager仍保留旧Fragment实例,新Fragment挂载时找不到对应View容器。
  2. 视图ID冲突:React Native视图ID可能被复用,导致Fragment事务引用的ID对应的View已销毁或不属于当前层级。
  3. 全局成员变量污染:zone_id、publisher_id等变量属于MyViewManager全局实例,多组件实例会互相覆盖参数。

修复步骤

1. 清理旧Fragment实例

创建新Fragment前,先移除对应ID的旧实例:

public void createFragment(FrameLayout root, int reactNativeViewId) {
    FragmentActivity activity = (FragmentActivity) reactContext.getCurrentActivity();
    FragmentManager fragmentManager = activity.getSupportFragmentManager();
    
    // 移除旧Fragment
    Fragment oldFragment = fragmentManager.findFragmentById(reactNativeViewId);
    if (oldFragment != null) {
        fragmentManager.beginTransaction().remove(oldFragment).commitNow();
    }

    // 创建并挂载新Fragment
    final MyFragment myFragment = new MyFragment(reactNativeViewId, this.reactContext, this.zone_id, publisher_id);
    fragmentManager.beginTransaction()
        .replace(reactNativeViewId, myFragment, String.valueOf(reactNativeViewId))
        .commit();
}

2. 绑定参数到View实例

避免全局变量污染,将参数存储到FrameLayout的Tag中:

// 替换原有@ReactProp方法
@ReactProp(name = "zone_id")
public void setAdType(FrameLayout view, String zone_id) {
    view.setTag(R.id.tag_zone_id, zone_id);
}

@ReactProp(name = "publisher_id")
public void setPublisherId(FrameLayout view, String publisher_id) {
    view.setTag(R.id.tag_publisher_id, publisher_id);
}

// 在createFragment中获取参数
public void createFragment(FrameLayout root, int reactNativeViewId) {
    String zoneId = (String) root.getTag(R.id.tag_zone_id);
    String publisherId = (String) root.getTag(R.id.tag_publisher_id);
    
    final MyFragment myFragment = new MyFragment(reactNativeViewId, this.reactContext, zoneId, publisherId);
    // ... 后续代码
}

需在res/values/ids.xml中添加自定义Tag:

<resources>
    <item name="tag_zone_id" type="id"/>
    <item name="tag_publisher_id" type="id"/>
</resources>

3. 修正Fragment容器引用

原代码中root.findViewById(reactNativeViewId)错误,root本身就是目标容器,直接使用:

public void createFragment(FrameLayout root, int reactNativeViewId) {
    setupLayout(root);
    
    // ... 移除旧Fragment、创建新Fragment代码
    fragmentManager.beginTransaction()
        .replace(root.getId(), myFragment, String.valueOf(reactNativeViewId))
        .commit();
}

4. 视图销毁时清理Fragment

重写onDropViewInstance方法,在React Native视图销毁时移除对应Fragment:

@Override
public void onDropViewInstance(@NonNull FrameLayout root) {
    super.onDropViewInstance(root);
    FragmentActivity activity = (FragmentActivity) reactContext.getCurrentActivity();
    if (activity != null) {
        FragmentManager fragmentManager = activity.getSupportFragmentManager();
        Fragment fragment = fragmentManager.findFragmentById(root.getId());
        if (fragment != null) {
            fragmentManager.beginTransaction().remove(fragment).commitNow();
        }
    }
}

5. 修复JS语法错误

修正create方法的多余闭合花括号,并增加空值判断:

const create = () => {
    const viewId = findNodeHandle(ref.current);
    if (viewId) {
        UIManager.dispatchViewManagerCommand(
            viewId,
            UIManager.MyViewManager.Commands.create.toString(),
            [viewId],
        );
    }
}

验证

修改后,导航切换页面时旧Fragment会被正确清理,新Fragment能找到对应View容器,不再触发"no view found for id"错误。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 17:15:37