前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Flutter单引擎和外接纹理内存优化探索之路

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

原创
作者头像
brzhang
发布2020-01-15 10:06:56
5.5K4
发布2020-01-15 10:06:56
举报
文章被收录于专栏:玩转全栈玩转全栈

背景

今年九月初,王者人生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()创建的,相关部分的省略代码可以参考:

代码语言:txt
复制
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啦。 部分代码可以参考:
代码语言:txt
复制
//引擎提供者
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和原生的代码,这里贴出比较重要的部分。

代码语言:txt
复制
 @override
  Widget buildView(int textureId) {
    return Texture(textureId: textureId);
  }
  EventChannel _eventChannelFor(int textureId) {
    return EventChannel('flutter.io/videoPlayer/videoEvents$textureId');
  }
代码语言:txt
复制
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);

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

代码语言:txt
复制
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 。当然,我们的优化之路还将继续进行着,我知道我们并没将这个工作做到极致,只是目前可用而已,遇到新的问题,肯定还需要继续想办法突破。

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

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 背景
  • flutter的热更新
  • flutter单引擎
    • fragment生命周期
      • 所以,我们另外一种接入方式可以做到单引擎吗?
      • 图片优化,利用原生缓存
        • 第一次爬坑,利用flutter平台提供的PlatFromView,
          • 第二次爬坑,利用Texture 外接纹理。
            • 那么还有其他方式吗?
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档