专栏首页玩转全栈Flutter单引擎和外接纹理内存优化探索之路
原创

Flutter单引擎和外接纹理内存优化探索之路

背景

今年九月初,王者人生Android端及iOS端正式接入flutter跨平台方案来提升开发效率。

接入flutter之后,我们成功使用flutter上线了首页一起玩赢福利,上线之后,我们的优化工作也一直紧锣密鼓的进行着,其中最为突出的三个问题是【flutter热修复,flutter单引擎,flutter利用原生图片缓存来减少整体内存占用】。

flutter的热更新

着手研究flutter热更新是为了应对现网出现flutter相关的bug好紧急修复,这个在我前面的文章《带你不到80行代码搞定Flutter热更新》中已经提到,这个问题我们目前已经解决了,这里不再啰嗦。

flutter单引擎

着手研究flutter单引擎,是因为对于以原生接入flutter这种形式的项目来说,因为并不是一个纯粹的flutter项目,因此,可能会出现以下这样的导航方式中间过程,而且可能会存在多次。

而且,出现flutter通过调用原生jsbridge在开一个flutter也是有可能的发生的,当出现这样一种情况时,很明显,flutter会有多个实例,那么我们的flutter引擎内存占用是否会有多份呢?

带着这个问题,我研究了一下flutter的启动流程,也记录了一下过程《flutter启动流程简析》,而这个过程让我明白了我们起初的接入方式做不到单引擎,但是如果我们换另外一种方式,可以很巧妙的做到单引擎,下面来探索这个过程。

通过下图,可以看到,FlutterView存在两个版本,这还是在一个flutter版本中,如图所示:

而我们最初的接入方式是采用的io.flutter.view 包下的FlutterView

它是通过Flutter.createView()创建的,相关部分的省略代码可以参考:

public static FlutterView createView(@NonNull final Activity activity, @NonNull Lifecycle lifecycle, String initialRoute) 
。。。
if (nativeView == null) {
                this.mNativeView = new FlutterNativeView(activity.getApplicationContext());
            } else {
                this.mNativeView = nativeView;
            }

            this.dartExecutor = this.mNativeView.getDartExecutor();
            this.flutterRenderer = new FlutterRenderer(this.mNativeView.getFlutterJNI());
            this.mIsSoftwareRenderingEnabled = FlutterJNI.nativeGetIsSoftwareRenderingEnabled();

可以看到dartExecutorFlutterRendererNativeView等都是耦合在FlutterView中的。所以这个FlutterView在的话,引擎就会一直在。然后,我们关注一下Flutter.createView()的时候,flutter给加上的生命周期回调函数。

这里重点关注一下onPause,onStop,这里为了便于理解,我特意补充了fragment生命周期图

fragment生命周期

很明显,单我们栈里面出现两个flutter模块的时候,被覆盖的,就是前一个flutter模块会走

onPause

onStop

注意,这里并没有走onDestroyView,和onDetach,往下翻可以看到我贴的日志。

回过头来看看我们之前的接入方式,在生命周期回调中看看,跟进去,你会发现,并没有对DartExecutor等做释放操作。

所以,以**io.flutter.view** 包下的**FlutterView**接入flutter的方式,在有多个**flutter**实例的情况下,是会出现多分引擎内存占用的,而且因为引擎**代码耦合**在**FLutterView**中的原因,我们很难做到单引擎。

所以,我们另外一种接入方式可以做到单引擎吗?

那么,对比使用另外一个FlutterView的接入方式来看,我们发现有一个代理类FlutterActivityAndFragmentDelegate中,代理的生命周期函数:

有释放引擎的操作,这就意味着,这意味着,如果这个代理类派上用场,那么,如果我们出现两个flutter模块,前一个的引擎是否会释放呢?

用图说话最简单的了,如图所示

所以,biubiubiu,我将接入方式进行了重构,当然,用上了这个代理类,然后,走走上面的导航流程,用Profiler工具看看FLutterEngin实例:

恩,切换到第二个flutter模块,gc一下,看看发现确实只有一个引擎了。做法其实很简单。

  1. 继承io.flutter.embedding.android.FlutterFragment
  2. 写一个引擎提供者EngineProvider
  3. 串联起来就OK啦。 部分代码可以参考:
//引擎提供者
public class TipEngineProvider {
    public static FlutterEngine obtain() {
        return new FlutterEngine(IGameApplication.getIGameApplicationContext());
    }
}
。。。。。。
//fragment中-
@Override
    public void onAttach(@NonNull Context context) {
        FlutterMain.startInitialization(IGameApplication.getIGameApplicationContext());
        FlutterMain.ensureInitializationComplete(IGameApplication.getIGameApplicationContext(), (String[]) null);
        super.onAttach(context);
    }
    public FlutterEngine provideFlutterEngine(@NonNull Context context) {
        return TipEngineProvider.obtain();
    }
    public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
        ShimPluginRegistry pluginRegistry = new ShimPluginRegistry(flutterEngine);
        GeneratedPluginRegistrant.registerWith(pluginRegistry);
        new TipMethodChannelProxy().init(this, flutterEngine);
    }

图片优化,利用原生缓存

第一次爬坑,利用flutter平台提供的PlatFromView,

包装原生的ImageView,做到了利用原生图片缓存,详情可以参考我写的这篇文章

Flutter利用原生控件加载图片,馋原生的图片缓存

在图片较少时,这种方式固然可以,但是一旦出现像列表加载图片的场景,性能问题就出现了,当使用列表加载多张图片时,滑动起来会非常卡。所以PlatformView不适合用于列表,仅仅适合用户页面呈现单一控件的情景,比如地图,比如单个的视频播放器,有很多引用列表展示视频,使用PlatformView实现的那些视频播放插件很显然不适合,我们可以发现,flutter团队视频播放器https://pub.dev/packages/video_player的实现就不是platfomView,是使用的外接纹理。

第二次爬坑,利用Texture 外接纹理。

Google了一下,很不幸,flutter外接纹理渲染图片的demo非常少,仅仅找到了官方的VideoPlayer可以看看源码中联系texture和原生的代码,这里贴出比较重要的部分。

 @override
  Widget buildView(int textureId) {
    return Texture(textureId: textureId);
  }
  EventChannel _eventChannelFor(int textureId) {
    return EventChannel('flutter.io/videoPlayer/videoEvents$textureId');
  }
private void setupVideoPlayer(
      EventChannel eventChannel, TextureRegistry.SurfaceTextureEntry textureEntry, Result result) {
    ....
    surface = new Surface(textureEntry.surfaceTexture());
    exoPlayer.setVideoSurface(surface);
    setAudioAttributes(exoPlayer);
    ....
    Map<String, Object> reply = new HashMap<>();
    reply.put("textureId", textureEntry.id());
    result.success(reply);

我们可以看到最重要的两行代码是这个:

surface = new Surface(textureEntry.surfaceTexture());
exoPlayer.setVideoSurface(surface);

参考其原理,那我们要将bitmap渲染上去,是不是只要想办法把bitmap扔给surface,然后在合适的时机手动触发surface的一些回调,比如unlockCanvasAndPost就可以将bitmap渲染出来,既然视频都可以做样做不卡,一张bitmap应该不会存在性能问题才是,恩,这是理论上的,但是,这方面的这些方面的demo没有找到,不知道如何推进,可以留着后面继续研究。

那么还有其他方式吗?

继续在Google汪洋大海中寻找,发现讲原理倒是一堆一堆的,真正比较关键的地方缺没给出,直到我发现了这篇文章提到了如何去使用flutter的外接纹理,但是其实对于我来说,离贴bitmap有一定的距离,虽然只是贴了一个背景色而已,但是意义非凡,剩下的就是将原生缓存库中的bitmap拿到,并且贴上去就OK了。

所干就干,但是真的那么容易吗?经过研究,发现越陷越深,我不得不去了解一下**OpenGL**。

Android OpenGLES2.0(一)——了解OpenGLES2.0

Android OpenGLES2.0(八)——纹理贴图之显示图片

大致了解到 纹理映射是将2D的纹理映射到3D场景中的立体物体上 ,然后,OpenGL ES 的世界是3D的,但是手机屏幕能够给我展示的终究是一个平面,只不过是在绘制的过程中利用色彩和线条让画面呈现出3D的效果。OpenGL ES将这种从3D到2D的转换过程利用投影的方式使计算相对使用者来说变得简单可设置。

textureId->绘制->初始化glcontext->生成贴图->flutter

最终可以看看效果:

其中勾选导航栏按钮,表示使用flutter提供的image来加载图片,不勾选表示使用了外接纹理

可以看到,这次使用texture外接纹理,渲染图片,在列表加载多图情况下,滑动也非常流畅。

另外,这里也对比一下两种情况下帧率,发现在滑动列表时,外接纹理这种和flutter原生表现一致,基本上是可以满足性能要求的。:

使用外接纹理的方式:

使用flutter原生的Image

目前,插件仅仅实现了Android版本,已经开源了,目前支持webp,gif解析。iOS版本在开发中,相信很快就能出来。

在这个方案的实现过程中,请教过踩过这些坑的同事,特别鸣谢raymondguo,azraellong 。当然,我们的优化之路还将继续进行着,我知道我们并没将这个工作做到极致,只是目前可用而已,遇到新的问题,肯定还需要继续想办法突破。

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 现有项目集成flutter排坑指南

    1、如果选择,stable,我们遇到的情况是,IOS上接入之后是跑不了的。切到master上就OK了。

    brzhang
  • flutter接入现有的app详细介绍

    接入的方式,我是参考的官方的介绍文档,我这里尝试的是android的接入方式,还算比较顺利。

    brzhang
  • flutter图片加载内存优化,我只是很馋原生缓存的图片而已

    如果,你使用的是混栈开发模式,就是所谓的在原生的基础上接入flutter,那么在成功接入flutter之后,你肯定会碰到这样一个困扰,就是flutter这边的图...

    brzhang
  • Flutter基础篇(7)-- Flutter更新错误全面解决方案(图文+视频讲解)

    (我电脑里面已经安装flutter最新版了。为了演示flutter升级过程,我删除了flutter文件夹,重新去github下载flutter文件,并且执行更新...

    AWeiLoveAndroid
  • 转发 | 闲鱼公开多年 Flutter 实践经验

    flutter-boot核心解决了混合开发模式下的两个问题:flutter混合开发的工程化设计和混合栈。那flutter-boot是如何解决的呢?

    Java帮帮
  • 现有项目集成flutter排坑指南

    1、如果选择,stable,我们遇到的情况是,IOS上接入之后是跑不了的。切到master上就OK了。

    brzhang
  • 不得不看的Flutter与Android混合开发

    记得在flutter刚出来时,笔者就开始学习flutter。但由于当时嫌弃flutter复杂的层级组合且未推出稳定版,所以当时就放弃了深入学习,现如今随着flu...

    Android技术干货分享
  • Flutter开发:运行项目提示flutter packages get不成功的解决方法

    今天端午节,为了欢度佳节,就算再困也得起床发一篇博文,发完继续睡,顺便祝各路粉丝端午节快乐。

    三掌柜
  • flutter-Mac系统下安装之export PATH=`pwd`/flutter/bin:$PATH

    在执行export PATH=pwd/flutter/bin:$PATH命令的时候踩了一会儿坑,按教程执行完之后,在命令行输入 flutter doctor之后...

    coderZhen
  • Flutter 更新&升级

    https://flutter-io.cn/posts/announcing-flutter-1-7-9.html

    林小帅

扫码关注云+社区

领取腾讯云代金券