flutter_boost Android:FlutterBoostFragment 里面有PlatformView时候,切到另外一个Tab的FlutterBoostFragment 会crash

tquggr8v  于 2022-11-19  发布在  Flutter
关注(0)|答案(5)|浏览(458)

Steps to Reproduce

1.在 flutterboost example工程 修改 “main dart”,
把tab_message修改调到example工程里面的NativeViewExample页面
`

tab_message': (settings, uniqueId) {
    return PageRouteBuilder<dynamic>(

      settings: settings,

      pageBuilder: (_, __, ___) =>

          NativeViewExample());

}

`
2. run example 工程起来 进入 “open flutter fragment page”
3. 切换到 朋友Tab 必崩

Flutter Boost Version
4.2.0
Target Platform:
Android
Target OS version/browser:
Android 10
Devices:
荣耀畅玩 9A

Logs

java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.
at android.view.ViewGroup.addViewInner(ViewGroup.java:5327)
at android.view.ViewGroup.addView(ViewGroup.java:5156)
at android.view.ViewGroup.addView(ViewGroup.java:5096)
at android.view.ViewGroup.addView(ViewGroup.java:5069)
at io.flutter.plugin.platform.PlatformViewsController.attachToView(PlatformViewsController.java:747)
at io.flutter.embedding.android.FlutterView.attachToFlutterEngine(FlutterView.java:1215)
at io.flutter.embedding.android.FlutterActivityAndFragmentDelegate.onCreateView(FlutterActivityAndFragmentDelegate.java:337)
at io.flutter.embedding.android.FlutterFragment.onCreateView(FlutterFragment.java:806)
at com.idlefish.flutterboost.containers.FlutterBoostFragment.onCreateView(FlutterBoostFragment.java:92)
at com.idlefish.flutterboost.example.tab.FriendFlutterFragment.onCreateView(FriendFlutterFragment.java:19)
at androidx.fragment.app.Fragment.performCreateView(Fragment.java:2600)
at androidx.fragment.app.FragmentManagerImpl.moveToState(FragmentManagerImpl.java:881)
at androidx.fragment.app.FragmentManagerImpl.moveFragmentToExpectedState(FragmentManagerImpl.java:1238)
at androidx.fragment.app.BackStackRecord.executeOps(BackStackRecord.java:434)
at androidx.fragment.app.FragmentManagerImpl.executeOps(FragmentManagerImpl.java:2079)
at androidx.fragment.app.FragmentManagerImpl.executeOpsTogether(FragmentManagerImpl.java:1869)
at androidx.fragment.app.FragmentManagerImpl.removeRedundantOperationsAndExecute(FragmentManagerImpl.java:1824)
at androidx.fragment.app.FragmentManagerImpl.execPendingActions(FragmentManagerImpl.java:1727)
at androidx.fragment.app.FragmentManagerImpl$2.run(FragmentManagerImpl.java:150)
at android.os.Handler.handleCallback(Handler.java:900)
at android.os.Handler.dispatchMessage(Handler.java:103)
at android.os.Looper.loop(Looper.java:219)
at android.app.ActivityThread.main(ActivityThread.java:8349)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:513)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1055)

• Flutter version 3.3.3 on channel stable at /Users/liuyc/fvm/versions/3.3.3
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision 18a827f393 (6 weeks ago), 2022-09-28 10:03:14 -0700
    • Engine revision 5c984c26eb
    • Dart version 2.18.2
    • DevTools version 2.15.0
brqmpdu1

brqmpdu11#

因为detachFromFlutterEngine是FlutterBoostFragment在onCreateView做的。

但是super.onCreateView(inflater, container, savedInstanceState) 里面最终会把当前engine的platformview attach上去的操作(也就导致了第二个tab 错误的把第一个tab的platformview再次attach,所以出问题了)
flutterEngine.getPlatformViewsController().attachToView(this)

FlutterBoostFragment

public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
            FlutterBoost.instance().getPlugin().onContainerCreated(this);
            View view = super.onCreateView(inflater, container, savedInstanceState);
            flutterView = FlutterBoostUtils.findFlutterView(view);
            // Detach FlutterView from engine before |onResume|.
            flutterView.detachFromFlutterEngine();
            if (DEBUG) Log.d(TAG, "#onCreateView: " + flutterView + ", " + this);
            if (view == flutterView) {
                // fix https://github.com/alibaba/flutter_boost/issues/1732
                FrameLayout frameLayout = new FrameLayout(view.getContext());
                frameLayout.addView(view);
                return frameLayout;
            }
            return view;
        }
bksxznpy

bksxznpy2#

目前BoostFragment中的时序如下:

onCreateView(attachEngine -> detachEngine)  -> onResume或者onHiddenChanged (detachEngineIfNeed -> attachEngine)

这个时许中,会出现crash的其实有两个点:

  1. Frgament1 attachEngine后,切换Fragment2,onCreateView中又attachEngine
  2. onCreateView中detachEngine后,onResume中执行detachEngineIfNeed

上述问题的crash点出在1上,解决方法其实就是两次 attachEngine 中间添加一次 detachEngine 。但时机不好控制,所以基于目前没有 PlatformView 的情况下,能够正常运行,那么一个折中的方案就是,在Fragment2 attachEngine 之前,把 PlatformView 从Fragment1的 flutterView 中移除掉。

但一般无法直接获取到Fragment1中的 flutterView ,所以这里可以通过继承 PlatformViewsController 的方法来实现:

public class FlutterBoostPlatformViewsController extends PlatformViewsController {

    public FlutterView mCurrentFlutterView;

    @Override
    public void attachToView(@NonNull FlutterView newFlutterView) {
        super.attachToView(newFlutterView);
        mCurrentFlutterView = newFlutterView;
    }

    @Override
    public void detachFromView() {
        if (mCurrentFlutterView == null) {
            return;
        }
        super.detachFromView();
        mCurrentFlutterView = null;
    }

    public void removePlatformWrapperOrParents() {
        if (mCurrentFlutterView != null) {
            List<View> needRemoveViews = new ArrayList<>();
            int childCount = mCurrentFlutterView.getChildCount();
            for (int i = 0; i < childCount; i++) {
                View view = mCurrentFlutterView.getChildAt(i);
                if (view.getClass().getName().contains("PlatformViewWrapper") || view instanceof FlutterMutatorView) {
                    needRemoveViews.add(view);
                }
            }
            if (!needRemoveViews.isEmpty()) {
                for (View needRemoveView : needRemoveViews) {
                    mCurrentFlutterView.removeView(needRemoveView);
                }
            }
        }
    }
}

然后在构建FlutterEngine的地方使用 FlutterBoostPlatformViewsController 代替 PlatformViewsController

public FlutterEngine provideFlutterEngine(@NonNull Context context) {
    return new FlutterEngine(
            context,
            null,
            null,
            new FlutterBoostPlatformViewsController(),
            null,
            true,
            false);
}

最后在 FlutterBoostFragmentonCreateView 方法开始调用 removePlatformWrapperOrParentsPlatformView 给移掉。

@Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        FlutterEngine flutterEngine = getFlutterEngine();
        if (flutterEngine != null) {
            PlatformViewsController platformViewsController =
                    flutterEngine.getPlatformViewsController();
            if (platformViewsController instanceof FlutterBoostPlatformViewsController) {
                ((FlutterBoostPlatformViewsController) platformViewsController).removePlatformWrapperOrParents();
            }
        }
        ......
    }

这种方法只能解决attachEngine后又attachEngine的crash,运行后就会出现detachEngine后detachEngineIfNeed的crash点,就是个NPE,解决方法也简单,上面的 FlutterBoostPlatformViewsController 中已经做了保护, detachFromView 中加了空判定。

0aydgbwb

0aydgbwb3#

为什么不能在FlutterBoostFragment里面的 didFragmentHide把 performDetach加回来?

protected void didFragmentHide() {
    FlutterBoost.instance().getPlugin().onContainerDisappeared(this);
    // We defer |performDetach| call to new Flutter container's |onResume|;
    // performDetach();
    if (DEBUG) Log.d(TAG, "#didFragmentHide: " + this + ", isOpaque=" + isOpaque());
}
dgsult0t

dgsult0t4#

大概是,无法保证多个Fragment的情况下,可见Fragment的onResume方法和需要hide的fragment的didFragmentHide的方法的调用顺序把。

3pmvbmvn

3pmvbmvn5#

确实有这方面的问题。不过如果是在一个FragmentTransaction 顺序 操作多个fragment 就没问题了

相关问题