手把手教你解決 Flutter engine 內(nèi)存泄漏

Flutter/engine 1.0 修復版介紹

tags: flutter engine memory leak fix natoto 內(nèi)存泄漏 循環(huán)引用

團隊在0.9.4 版本解決了一系列循環(huán)引用宙拉,但是代碼不能上傳津畸,由于flutter和engine的限制逞敷,必須flutter版本號和engine的commit號要保持一致绎狭,提交了將導致commit號不一致的問題,導致運行失敗侥涵,官方號稱1.0解決了循環(huán)引用沼撕,下載了一看,令人失望芜飘,還是沒有徹底解決务豺,于是決定自己動手,豐衣足食 ,經(jīng)過了幾天痛苦的下載編譯嗦明,調(diào)試笼沥,測試,終于把內(nèi)存降下來了。

廣告一下
紙上得來終覺淺敬拓,實踐之后才真懂
建了一個flutter qq群邻薯,群號:217429001 有興趣的加入哦

歡迎關(guān)注
姊妹篇《手把手教你編譯Flutter engine》

最新消息: flutter內(nèi)存泄漏問題,現(xiàn)在可以使用官方清理方式了

見 flutter.framework FlutterEngine里面的 destroyContext

-------------------------------------------------------- 歷史的分割線 -------------------------------------

官方flutter的大麻煩

google團隊的大bug,個人認為內(nèi)存是很重要的乘凸,尤其是集成到現(xiàn)有app中的方式厕诡。

https://github.com/flutter/flutter/issues/24714
https://github.com/flutter/flutter/issues/23231
https://github.com/flutter/flutter/issues/25255
https://github.com/flutter/engine/pull/6879
https://github.com/flutter/flutter/issues/16995

image.png
image.png

flutter 1.0 解決了FlutterViewController的循環(huán)引用問題,但是把循環(huán)引用的問題轉(zhuǎn)嫁到了FlutterEngine上面营勤,下面手把手教你如何解決1.0的循環(huán)引用灵嫌。

如何找到內(nèi)存泄漏

為什么google難以解決

由于整個FlutterEngine是用MRC的方式編寫,所以內(nèi)存管理比較困難葛作,每個變量生成retain寿羞,都需要被release,如果一個實例retain了兩次赂蠢,只release一次绪穆,也會導致無法釋放,如果設(shè)置了autorelease虱岂,就有可能提前釋放玖院,導致badasses,訪問野指針第岖。

客觀原因难菌,一般的應用只會創(chuàng)建一個flutter應用,或者干脆就直接都是flutter應用蔑滓,不釋放就不釋放郊酒,多點內(nèi)存也無所謂,不影響崩潰键袱,不影響使用燎窘,所以google照常發(fā)布1.0版。

現(xiàn)狀

每次進去都會新增幾十M然而當退出flutterViewController的時候杠纵,內(nèi)存僅僅下降2m左右荠耽,還有幾十兆保留在內(nèi)存中钩骇。下降的部分就是flutterviewcontroller比藻,從程序運行到了dealloc可以證明。

修改結(jié)果

這是修改后的文件

修改后的framework下載列表

下面將一步步帶領(lǐng)大家找到循環(huán)引用倘屹,解決循環(huán)引用银亲,這一步可能比較繁瑣,如果不愿看推理過程纽匙,可以直接跳到文末獲取構(gòu)建后的framework务蝠。

使用flutterViewController

/**
* 加載boundle資源
*/
- (void)handleBoundleResource {    
    NSString * path = [[NSBundle mainBundle] pathForResource:@"flutter_assets" ofType:@""];
    NSURL * url = [NSURL URLWithString:path];
    FlutterDartProject * dart = [[FlutterDartProject alloc] init];
    if (!self.engine) {
        FlutterEngine * engine = [[FlutterEngine alloc] initWithName:path.lastPathComponent project:dart];
        [engine runWithEntrypoint:nil];
        self.engine = engine;
    }
    FlutterViewController* flutterViewController = [[FlutterViewController alloc] initWithEngine:self.engine nibName:nil bundle:nil];    
    [GeneratedPluginRegistrant registerWithRegistry:flutterViewController];    
    [self addBackButton:flutterViewController]; 
     [flutterViewController setInitialRoute:@"route1"];
    [self presentViewController:flutterViewController animated:YES completion:nil];    
}

flutterEngine, flutter官方推薦方式是自己管理flutterEngine,然后flutterviewcontroller是可以獨自創(chuàng)建和釋放烛缔,FlutterEngine是新1.0引進馏段,用于管理所有的metchodChannel轩拨,就是維護所有的方法消息,生命周期等作用院喜。也是要解決它的引用問題亡蓉。
執(zhí)行engine runwithEntrypoint可以啟動engine vm。

FlutterDartProject 用于配置啟動參數(shù)喷舀,默認可以直接new砍濒,或者從bundle啟動,找boundle下面的的flutter_assets文件夾硫麻,或直接導入App.framework

FlutterViewController用來顯示flutter工程的爸邢,所有的界面都是在其上面渲染出來的。跟普通的UIViewController一樣拿愧,可以present出來杠河,或者push出來。

Flutter的入口

plugin是flutter的入口浇辜,這個入口可以連接flutter和原生代碼
比如自帶的GeneratedPluginRegistrant感猛,將flutter工程中用到的插件都集中注冊到原生

@implementation GeneratedPluginRegistrant

+ (void)registerWithRegistry:(NSObject<FlutterPluginRegistry>*)registry {
  [FlutterWebviewPlugin registerWithRegistrar:[registry registrarForPlugin:@"FlutterWebviewPlugin"]];
  [FLTPathProviderPlugin registerWithRegistrar:[registry registrarForPlugin:@"FLTPathProviderPlugin"]];
  [FLTSharedPreferencesPlugin registerWithRegistrar:[registry registrarForPlugin:@"FLTSharedPreferencesPlugin"]];
}

@end

介紹上面兩個是為了找到斷點入口

如何設(shè)flutter斷點

為了方便調(diào)試,可以在編譯現(xiàn)有工程的時候替換flutter.framework
具體做法是在build phases 中創(chuàng)建一個腳本奢赂,用自己編譯的engine中的flutter.framework替換 .ios/engine/Flutter.framework

替換腳本

替換完之后則可以設(shè)置symbol斷點了陪白,如下

設(shè)置斷點

或者用lldb命令設(shè)置斷點 br xxxx lldb傳送門

幾個疑點

從目前的情況來看engine沒有被釋放,就是FlutterEngine.mm FlutterChannel.mmPluginRegister直接的關(guān)系混亂了
有兩點情況

  1. 互相引用
  2. 內(nèi)部引用沒有release
  3. 多次retain
  4. block是否使用的__block引用
  5. 主動destory
  6. 編譯膳灶,看dealloc
    從0.9.4經(jīng)驗來看咱士,需要手動destory一下,destory完成這個類中變量的release
    以上五個過程需要不斷的循環(huán)重試轧钓,過程比較長序厉。。

方法論說完了毕箍,下面直接說我的幾天試錯結(jié)果弛房。共計10個文件,核心的地方貼一下

maybesetupPlatformViewChannels

maybesetupPlatformViewChannels在flutterengine里面而柑,啟動默認平臺changnle和方法回調(diào)

image.png

重心文捶,F(xiàn)lutterChannel.mm里面 FlutterMethodChannel,flutter每個plugin的方法都會經(jīng)過通過register注冊一個channel媒咳,然而粹排,messger是register,不應該被持有或autorelease的涩澡,所以這樣做是會造成提前釋放或無法釋放的

對應于其他的channel也是用了messager顽耳,會有相同的問題。改造這些基本上就能解除大循環(huán)了。

總結(jié)engine的大循環(huán)引用

循環(huán)引用示例

如何使用

有兩種方法

  1. 替換flutter里面的framework
    路徑如下
    /Users/boo/Documents/flutter/bin/cache/artifacts/engine/ios
    不用擔心是否會破壞之前發(fā)flutter.framework射富,如果想用回官方的直接解壓同文件夾里面的.zip文件即可

  2. 工程中用zip膝迎,解壓成framework替換掉真正從官方復制過來的flutter.framework
    適用于 arm64真機

工程配置添加sh腳本

#R.replace.engine
cd "${SRCROOT}/flutterOnExistApp/Resources/"
  
unzip -u Flutter.framework.zip

cp -rf "${SRCROOT}/flutterOnExistApp/Resources/Flutter.framework" "${SRCROOT}/myflutter/.ios/Flutter/engine"

image.png

修改后代碼

修改的文件放在1.0engine修改的文件,可以直接替換使用并構(gòu)建自己的framework

打包framework

看一下構(gòu)建后結(jié)果

engine的文件大小
一種模式的工程大小

如構(gòu)建debug版的engine如下路徑或生產(chǎn)一個framework胰耗,這個通過執(zhí)行all.wxworkspace生成的弄抬,注意arm架構(gòu),如果選target是真機宪郊,則會生成arm64架構(gòu)framework
/Users/boo/Documents/engine/src/out/ios_debug/Flutter.framework

這個可以直接替換掉工程中原有的framework

構(gòu)建release framework類似掂恕。
詳細步驟可以參考《手把手教你編譯Flutter engine》


YY Flutter技術(shù)積累相關(guān)鏈接

一行代碼教你解決FlutterPlatformViews內(nèi)存泄露 by
AShawn

手把手教你在Flutter項目優(yōu)雅的使用ORM數(shù)據(jù)庫 by
williamwen1986

flutter通用基礎(chǔ)庫flutter_luakit_plugin by
williamwen1986

github - flutter_luakit_plugin使用例子 by
williamwen1986

手把手教你編譯Flutter engine by 共田君

手把手教你解決 Flutter engine 內(nèi)存泄漏 by 共田君

github - 編譯產(chǎn)物下載 修復內(nèi)存泄漏后的flutter engine(可直接使用)by 共田君</font>

github demo - 修復內(nèi)存泄漏后的flutter engine by 共田君

持續(xù)更新中...

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市弛槐,隨后出現(xiàn)的幾起案子懊亡,更是在濱河造成了極大的恐慌,老刑警劉巖乎串,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件店枣,死亡現(xiàn)場離奇詭異,居然都是意外死亡叹誉,警方通過查閱死者的電腦和手機鸯两,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來长豁,“玉大人钧唐,你說我怎么就攤上這事〗辰螅” “怎么了钝侠?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長酸舍。 經(jīng)常有香客問我帅韧,道長,這世上最難降的妖魔是什么啃勉? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任忽舟,我火速辦了婚禮,結(jié)果婚禮上淮阐,老公的妹妹穿的比我還像新娘叮阅。我一直安慰自己,他們只是感情好枝嘶,可當我...
    茶點故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布帘饶。 她就那樣靜靜地躺著哑诊,像睡著了一般群扶。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天竞阐,我揣著相機與錄音缴饭,去河邊找鬼。 笑死骆莹,一個胖子當著我的面吹牛颗搂,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播幕垦,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼丢氢,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了先改?” 一聲冷哼從身側(cè)響起疚察,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎仇奶,沒想到半個月后貌嫡,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡该溯,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年岛抄,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片狈茉。...
    茶點故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡夫椭,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出氯庆,到底是詐尸還是另有隱情益楼,我是刑警寧澤,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布点晴,位于F島的核電站感凤,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏粒督。R本人自食惡果不足惜陪竿,卻給世界環(huán)境...
    茶點故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望屠橄。 院中可真熱鬧族跛,春花似錦、人聲如沸锐墙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽溪北。三九已至桐绒,卻和暖如春夺脾,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背茉继。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工咧叭, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人烁竭。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓菲茬,卻偏偏與公主長得像,于是被迫代替她去往敵國和親派撕。 傳聞我的和親對象是個殘疾皇子婉弹,可洞房花燭夜當晚...
    茶點故事閱讀 44,976評論 2 355

推薦閱讀更多精彩內(nèi)容