Flutter坑之共享FlutterEngine页面切换无法点击

背景

最近在研究做Flutter一块相关的内容,方案上采用的是单FlutterEngine,全局Flutter元素共用一个FlutterEngine,对于使用单Engine遇到一个很大的坑,页面切换之后无法点击,页面就像卡死了一样,以下三种情况都会发生:

第一种:两个FlutterFragment在tab中进行切换,假如AB代表两个FlutterFragment,当A切换到B,再从B切换到A的时候,页面就无法点击。

第二种:在FlutterActivity中打开新的FlutterActivity,新的FlutterActivity页面跟上述的情况一样,也是无法点击。

第三种:在Tab中打开FlutterFragment之后再打开FlutterActivity,情况一样,依然无法点击。

如下动图所示:

错误情况

(来自issuehttps://github.com/flutter/flutter/issues/49950)

但他们有一个共同的特点:当页面卡死的时候,手动进入后台(打开任务管理或者home键退出)再回到前台,页面就会“刷新”,一切又变得正常了。那么推测:多半与跟页面Fragment和Activity的生命周期有关。我尝试了在进入页面后,再手动模拟“页面退出再回来的周期”,也就是先调用一次onPause()再调用一次onResume(),然而并没有什么卵用……

回到后台再回来

可这是为什么呢??太坑了……踏遍了千山万水也没有找到有人有解决方案,很多人都说别用共享引擎,但是想到 闲鱼Flutter_boost 和HelloBike的thrio框架也都是用的共享引擎啊,他们为什么没有问题?找了很久的解决方法,两天,甚至晚上做梦都梦在关于这个问题。真的是……难受。加了各种各样的群,也没有人能解答这个问题。终于……我去打印了两个Activity的生命周期,才发现事情的端倪。

关于Activity与Fragment的切换的生命周期

这里向大家在简单介绍一下Activity和Fragment切换生命周期,相信大家都有被面试问过:现在有两个Activity A和B,在A打开B这一段时间Activity的生命周期变化情况:

A.onPause() ->B.onCreate()-> B.onStart() ->B.onResume() ->A.onStop()

A的onStop() 的调用情况分为两种:当设置Activity A的主题windowIsTranslucent属性为true,A Activity并不会调用onStop方法,只会调用onPause()方法。

ok说完了Activity再说说Fragment,对于我们的问题:Fragment生命周期考虑tab间切换(也就是两个FlutterFragment之间的切换),两个Fragment的切换,并不会导致Fragment的onPause()和onStop()调用,只会调用onHiddenChanged(boolean hidden),hiden为true表示该Fragment被隐藏了,false表示当前Fragment可见。

关于Flutter单Engine方案

现在关于讲解Flutter单Engine方案也比较多,推荐去看这篇–> 为追求高性能,我必须告诉你Flutter引擎线程的事实… 关于对单Engine的讲解,单Engine方案,我们可以简单理解为:所有应用中的FlutterView都是由同一个FlutterEngine来渲染的。当然这看起来是废话,但这就是问题的关键,那么对于所有的FlutterView都是用的同一个FlutterEngine渲染,那么FlutterEngine是怎么去控制的呢?如果让你去设计,你会怎么设计呢?

这篇博客–>flutter单引擎方案讲解了一种单Engine的实现方案,可以参考,不过其中也需要对Engine进行多次new,不过这并不是最重要的,我们需要明白的是:当FlutterEngine去渲染FlutterView B的时候,它需要attach再在B上,从FlutterView A detach掉,再返回FlutterView A的时候,它需要从FlutterView B上detach掉,再attach到A上。

Flutter坑!

现在我们明白了生命周期的变化,我们接下来去看FlutterActivity中响应生命周期中的源码。对于FlutterActivity A打开FlutterActivity B,他们依次会调用:

A.onPause() ->B.onCreate()-> B.onStart() ->B.onResume() ->A.onStop()

我们来看看FlutterActivity在对应的生命周期里面做了什么,

对于A.onPause()

1
2
3
4
5
6
@Override
protected void onPause() {
super.onPause();
delegate.onPause();
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE);
}

我们知道:FlutterActivity和Fragment主要由FlutterActivityAndFragmentDelegate来进行管理,这里我们主要关注delegate里面的内容

1
2
3
4
5
void onPause() {
Log.v(TAG, "onPause()");
ensureAlive();
flutterEngine.getLifecycleChannel().appIsInactive();
}

这里是关键我们看到了flutterEngine与生命周期相关的代码,接下来我们看其他几个生命周期里面对于flutterEngine的Lifecycle管理情况

对于B.onCreate()只进行了相关的view创建工作.

对于B.onStart()中有执行doInitialFlutterViewRun();,其中比较关键的一句就是:

1
2
3
if (host.getInitialRoute() != null) {
flutterEngine.getNavigationChannel().setInitialRoute(host.getInitialRoute());
}

对于B.onResume()

1
2
3
4
5
void onResume() {
Log.v(TAG, "onResume()");
ensureAlive();
flutterEngine.getLifecycleChannel().appIsResumed();
}

最后是A.onStop()

1
2
3
4
5
void onStop() {
Log.v(TAG, "onStop()");
ensureAlive();
flutterEngine.getLifecycleChannel().appIsPaused();
}

看到了A.onStop(),聪明的人应该都看出来问题了,我们重新整理一下从Activity A启动到B,flutterEngine相关的生命周期主要执行了以下流程:

1
2
3
4
5
6
7
8
9
A.onPause() -> flutterEngine.getLifecycleChannel().appIsInactive()

B.onCreate()-> nothing.

B.onStart() -> flutterEngine.getNavigationChannel().setInitialRoute(host.getInitialRoute());

B.onResume() -> flutterEngine.getLifecycleChannel().appIsResumed();

A.onStop()-> flutterEngine.getLifecycleChannel().appIsPaused();

其实问题已经出来了:由于我们使用的是单FlutterEngine方案,那么上面生命周期中的flutterEngine为同一实例!,由于Activity的生命周期机制,前一个Activity的生命周期的onStop是在最后调用的,也就是这时候告诉了FlutterEngine: 这时候appIsPaused,你不用在渲染了,那么这时页面就会成一种“卡死”的状态!正常的生命周期这时候FlutterEngine应该是appIsResumed()。这也就能解释为什么退出到后台(调用了onPause())再回来(调用onResume())最终的FlutterEngine是调用了.appIsResumed();显示正常。

于是找到问题了,那么如何解决呢?这还不简单,当然是去绕过不用去调用A.onStop()呀!怎么可能不用调用A.onStop() 呢?错了,不用去调用其中的delegate中的flutterEngine.getLifecycleChannel().appIsPaused();就好了,我这边的方案与Flutter_boost的方案一样,也对FlutterActivty的代码进行了重写,所以能比较灵活的去改动FlutterActivityAndFragmentDelegate。

1
2
3
4
5
void onStop() {
Log.v(TAG, "onStop()");
ensureAlive();
// flutterEngine.getLifecycleChannel().appIsPaused();
}

对于Fragment的切换也是同样一个思路,就留着大家想一下吧。

总结

1、Activity A切换到B的生命周期(A不透明的情况下):A.onPause() ->B.onCreate()-> B.onStart() ->B.onResume() ->A.onStop()

2、至于flutterEngine.getLifecycleChannel().appIsPaused();内部具体做了什么事,还得具体去研究一下,字面上理解就是。

3、Flutter混合原生做开发坑实在是太多了,官方也没有做相应的解决方案,有什么问题,一定要大胆的想,大胆的去尝试!

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×