Open liuyicheng3 opened 2 years ago
因为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;
}
目前BoostFragment中的时序如下:
onCreateView(attachEngine -> detachEngine) -> onResume或者onHiddenChanged (detachEngineIfNeed -> attachEngine)
这个时许中,会出现crash的其实有两个点:
上述问题的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);
}
最后在FlutterBoostFragment
的onCreateView
方法开始调用removePlatformWrapperOrParents
把PlatformView
给移掉。
@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
中加了空判定。
为什么不能在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());
}
大概是,无法保证多个Fragment的情况下,可见Fragment的onResume方法和需要hide的fragment的didFragmentHide的方法的调用顺序把。
确实有这方面的问题。不过如果是在一个FragmentTransaction 顺序 操作多个fragment 就没问题了
needRemoveViews
这个我试过,可以解决单个fragment的跳转问题。 但是,如果是左右2个fragment,右边去加载新的 flutter,会让左边加载的flutter,view remove掉,导致左边不能滚动
needRemoveViews
这个我试过,可以解决单个fragment的跳转问题。 但是,如果是左右2个fragment,右边去加载新的 flutter,会让左边加载的flutter,view remove掉,导致左边不能滚动
FlutterBoost的目标场景中,应该不包含这种界面上同时显示两个Fragment的场景
这样会导致,返回Frgament1 时,platformView不能的触摸响应的
补充下,是从Frgament2返回Frgament1
目前FlutterBoost对PlatformView的支持在生命周期上有比较大的问题,最好还是不要用PlatfomrView吧。
调试发现返回Frgament1 时,Frgament2对应FlutterBoostActivity在执行onDestroy时,PlatformViewsController又执行一次detach方法,导致platformViewsChannel被置为空,触摸事件没法传递过来 /**
这边的临时解决方案 背景: flutter 的activity1 frament1 跳转activity2 frament2,frament1 包含platformView,按照上面老哥解决崩溃问题的基础上,出现activity2 frament2返回activity1 frament1时,platformView触摸事件没有响应。 问题流程:activity2 frament2返回时,在activity1 frament1 已经在PlatformViewsController已经attach,activity2的onDestroy触发flutterboostfragment的onDetach,最终调用了PlatformViewsController的detach,导致platformViewsChannel销毁了,中断了触摸事件的传递。 解决: 把platformViewsChannel重新attach,恢复platformViewsChannel,能让activity1 frament1 的platformView重新获取触摸事件。 实现: activity2的onDestroy时,异步去调用 flutterEngine.getPlatformViewsController().attach(getContextActivity(), flutterEngine.getRenderer(), flutterEngine.getDartExecutor()); 前提是判断frament1处于可见状态,且处于detach状态 兼容: PlatformViewsController在attach时有个判断context是否为空,如下所示: public void attach( @Nullable Context context, @NonNull TextureRegistry textureRegistry, @NonNull DartExecutor dartExecutor) { if (this.context != null) { throw new AssertionError( "A PlatformViewsController can only be attached to a single output target.\n"
说明: 项目比较急,没时间再一一跟进为什么ondestroy为什么调用PlatformViewsController的detach(),把刚刚attach的东西detach掉。 当前测试看起来正常,platformView能正常拿到触摸事件,暂没发现别的问题,如果有发现其他新问题,大家讨论下解决方法。 后续等解决了,再替换官方的方法
这个官方有后续的安排吗?
官方应该不跟进了吧,因为咸鱼没这种场景。
FlutterBoostActivity也有类似的问题
这个我一直想改,但没找到通用合适的方法。Flutter SDK版本太多,对PlatformView的修改也是频繁,又都是私有方法和私有变量,所以改起来非常麻烦。
目前我们线上收到了很多FlutterBoostActivity的问题
这个问题目前能查到的线索是FlutterBoostActivity使用了translucent模式也就是背景是transparent导致了flutterview渲染模式使用了texture,在activity在后台被杀死重启后 就会出现该问题
结合之前 @crh2017 这位兄弟的办法,我觉得,可以不用去调用PlatformViewsController.detach
方法,这样能够使内部的channelHandler
继续响应,解决 #1834 里的内存泄漏问题,同时也能使触摸事件正常响应。
public class FBPlatformViewsController extends PlatformViewsController {
private Context appCtx;
/**
* 记录PlatformViewsController绑定使用的FlutterView
*/
private FlutterView curFlutterView = null;
/**
* 占位FlutterView,用于防止不执行完整detach后,内部channelHandler继续响应时,出现空指针异常。
*/
private FlutterView dummyFlutterView = null;
public FBPlatformViewsController() {
super();
}
@Override
public void attach(@Nullable Context context, @NonNull TextureRegistry textureRegistry,
@NonNull DartExecutor dartExecutor) {
if (appCtx == null && context != null) {
appCtx = context.getApplicationContext();
dummyFlutterView = new FlutterView(appCtx);
}
super.attach(context, textureRegistry, dartExecutor);
}
@Override
public void detach() {
// 不执行完整的detach,这样就使内部channelHandler正确响应,同时避免platformView触摸事件无法响应
// super.detach();
// 使用反射将内部context变量设置为null,一方面解决重新attach时的异常,另一方面解决内存泄漏
try {
Field contextF = getClass().getSuperclass().getDeclaredField("context");
contextF.setAccessible(true);
contextF.set(this, null);
} catch (Exception ignore) {
}
}
@Override
public void attachToView(@NonNull FlutterView newFlutterView) {
if (curFlutterView == null) {
super.attachToView(newFlutterView);
curFlutterView = newFlutterView;
} else if (newFlutterView != curFlutterView) {
removePlatformWrapperOrParents();
super.attachToView(newFlutterView);
curFlutterView = newFlutterView;
}
}
@Override
public void detachFromView() {
if (curFlutterView != null) {
super.detachFromView();
curFlutterView = null;
//将占位FlutterView绑定上去
attachToView(dummyFlutterView);
}
}
public void removePlatformWrapperOrParents() {
if (curFlutterView != null) {
List<View> needRemoveViews = new ArrayList<>();
int childCount = curFlutterView.getChildCount();
for (int i = 0; i < childCount; i++) {
View view = curFlutterView.getChildAt(i);
if (view.getClass().getName().contains("PlatformViewWrapper") || view instanceof FlutterMutatorView) {
needRemoveViews.add(view);
}
}
if (!needRemoveViews.isEmpty()) {
for (View needRemoveView : needRemoveViews) {
curFlutterView.removeView(needRemoveView);
}
}
}
}
}
Steps to Reproduce
1.在 flutterboost example工程 修改 “main dart”, 把tab_message修改调到example工程里面的NativeViewExample页面 `
`
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)