Flutter正式發佈1.0release版本,其中有Platform Views能夠支持iOS,Android原生View嵌入Flutter中進行展現,如 developers.googleblog.com/2018/12/flu… 這篇文章所述。javascript
對應issue:github.com/flutter/flu… Google已經merge:github.com/flutter/eng…html
業務需求須要Youtube播放器,考慮到有Platform Views支持,採用了原生實現Youtube播放功能,將View嵌入到Flutter中使用。iOS端在實現了視頻列表之後,發現看了幾個視頻之後內存就會爆掉,問題很嚴重。java
初步懷疑仍是原生UI建立後的循環引用問題,參考 github.com/flutter/plu… 實現了一個簡單的UIView,核心代碼以下:git
int i = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Flutter WebView example'),
actions: <Widget>[
],
),
body: (i%2 == 0)? Container(color: Colors.red,) : WebView(
initialUrl: 'https://flutter.io',
javascriptMode: JavascriptMode.unrestricted,
onWebViewCreated: null,
),
floatingActionButton: favoriteButton(),
);
}
複製代碼
經過不斷的點擊按鈕setState刷新UI達到PlatformView的建立銷燬,發現最簡單的UIView在通過重複的建立銷燬後依然會存在內存泄露的問題,而不使用PlatformView,一樣的代碼建立銷燬Flutter的Widget不會出現問題,這樣能夠判定業務實現上沒有問題,代碼出在Flutter engine的底層實現上。github
關於如何使用Flutter engine能夠參考 juejin.im/post/5c24ac… 引入engine進行調查,很是方便。web
同時看到flutter engine的issue中有 github.com/flutter/flu… ,實際上是有人遇到一樣的問題了,官方沒有解決問題把問題關了,只能靠咱們本身進行調查了。bash
由於有以前解決Flutter engine內存泄露的經驗,相關內容能夠參考 juejin.im/post/5c24ad… 。app
開始調查engine的代碼想固然的覺得會是Google在實現PlatformView時有相似的循環引用問題致使建立的原生UI沒法被釋放。Google在實現PlatformView時會建立FlutterPlatformView和FlutterOverlayView兩個View,經過在dealloc添加log方式能夠發現建立的View都獲得了釋放,基本能夠判斷UIKit這部分沒有問題。ide
這時沒有什麼好辦法才使用了Apple的查內存泄露的工具,調查結果以下圖所示:函數
能夠看到主要的內存泄露來源都是IOSurface,查看堆棧能夠看到是 renderbufferStorage:fromDrawable:方法形成的,能夠查看蘋果官方文檔 developer.apple.com/documentati… 知道這個方法實際上是將framebuffer與CAEAGLLayer進行綁定。
熟悉OpenGL的同窗應該能夠知道問題基本上能夠定位到是Flutter engine使用CAEAGLLayer渲染時,申請的內存沒有獲得釋放致使的。
找到調用 renderbufferStorage:fromDrawable:方法的地方,是在IOSGLRenderTarget中,大概看了一下是基本的OpenGL渲染模塊,經過向上查找,在FlutterOverlayView中找到了引用的地方與UIKit方向調查的結果吻合,在實現PlatformView時會一層層傳下來建立了IOSGLRenderTarget,層級關係以下所示:
經過調查相關節點的內存釋放狀況,發現路徑中建立的東西都獲得了釋放,這時候就很困惑了,好像整個調查卡主了。再返回去看IOSGLRenderTarget的渲染流程,最終發如今析構函數中釋放建立的OpenGL Framebuffer代碼以下:
IOSGLRenderTarget::~IOSGLRenderTarget() {
FML_DCHECK(glGetError() == GL_NO_ERROR);
// Deletes on GL_NONEs are ignored
glDeleteFramebuffers(1, &framebuffer_);
glDeleteRenderbuffers(1, &colorbuffer_);
FML_DCHECK(glGetError() == GL_NO_ERROR);
}
複製代碼
終於發現了問題所在,在DeleteFramebuffers時,Google沒有先設置上下文,在Flutter這種使用多個context進行渲染的的結構中,進行gl操做時最好都提早設置一下當前對應的上下文,避免其餘代碼更改了上下文,這邊再進行gl操做時操做無效。因此最後咱們只須要加一行代碼就能夠解決這個大問題,解決代碼以下:
IOSGLRenderTarget::~IOSGLRenderTarget() {
[EAGLContext setCurrentContext:context_];
FML_DCHECK(glGetError() == GL_NO_ERROR);
// Deletes on GL_NONEs are ignored
glDeleteFramebuffers(1, &framebuffer_);
glDeleteRenderbuffers(1, &colorbuffer_);
FML_DCHECK(glGetError() == GL_NO_ERROR);
}
複製代碼
編譯後,工程中使用咱們編譯出來的framework,內存問題就獲得瞭解決!之後能夠方便的在Flutter工程中使用原生View了!
flutter通用基礎庫flutter_luakit_plugin
持續更新中...