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;
解决方案
问题根源
- FragmentManager 残留实例:导航返回后,FragmentManager仍保留旧Fragment实例,新Fragment挂载时找不到对应View容器。
- 视图ID冲突:React Native视图ID可能被复用,导致Fragment事务引用的ID对应的View已销毁或不属于当前层级。
- 全局成员变量污染:
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
相关产品推荐
相关产品推荐

