欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Flutter共享FlutterEngine页面切换无法点击的问题

程序员文章站 2022-03-24 18:21:46
背景最近在研究做Flutter一块相关的内容,方案上采用的是单FlutterEngine,全局Flutter元素共用一个FlutterEngine,对于使用单Engine遇到一个很大的坑,页面切换之后无法点击,页面就像卡死了一样,以下三种情况都会发生:第一种:两个FlutterFragment在tab中进行切换,假如AB代表两个FlutterFragment,当A切换到B,再从B切换到A的时候,页面就无法点击。第二种:在FlutterActivity中打开新的FlutterActivity,新的Flu...

背景

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

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

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

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

如下动图所示:

Flutter共享FlutterEngine页面切换无法点击的问题

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

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

Flutter共享FlutterEngine页面切换无法点击的问题

可这是为什么呢??太坑了……踏遍了千山万水也没有找到有人有解决方案,很多人都说别用共享引擎,但是想到 闲鱼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()

@Override
protected void onPause() {
  super.onPause();
  delegate.onPause();
  lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE);
}

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

void onPause() {
  Log.v(TAG, "onPause()");
  ensureAlive();
  flutterEngine.getLifecycleChannel().appIsInactive();
}

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

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

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

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

对于B.onResume()

void onResume() {
  Log.v(TAG, "onResume()");
  ensureAlive();
  flutterEngine.getLifecycleChannel().appIsResumed();
}

最后是A.onStop()

void onStop() {
  Log.v(TAG, "onStop()");
  ensureAlive();
  flutterEngine.getLifecycleChannel().appIsPaused();
}

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

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。

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();内部具体做了什么事,还得具体去研究一下flutter层面的代码,这个之后再做解析。

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

本文地址:https://blog.csdn.net/u010107153/article/details/108720894

相关标签: Android