問題背景
路由框架采用flutterboost盾剩,根視圖UITabbarController的其中一個item的頁面是flutter呵扛,該flutter頁面采用了FadeInImage顯示圖片添吗,其余都是native讳侨。在剩余的native中可以點擊進(jìn)入flutter頁面粘拾。直接看現(xiàn)象:
結(jié)果:只要切換了別的flutter頁面再回內(nèi)嵌在TabbarItem的flutter頁面憔晒,頁面中的圖片就會重新動畫。
開始以為是自己的項目哪里沒寫對明吩,想著放到flutterboost的example上嘗試一下间学,
在tabItem頁隨便添加一個widget:
FadeInImage.assetNetwork(
placeholder: "images/flutter.png",
image: "https://gw.alicdn.com/tfs/TB1aUlEYLb2gK0jSZK9XXaEgFXa-252-252.png",
width: 300,
height: 300,
fadeOutDuration: const Duration(milliseconds: 300),
fadeInDuration: const Duration(milliseconds: 700),
),
現(xiàn)象如下:
結(jié)果:同樣的場景,得到了一個更讓人奇怪的現(xiàn)象印荔,切換了tab就會重新動畫菱鸥,不切tab怎么玩都沒關(guān)系。
排查過程
1.首先先查明為什么會刷新圖片躏鱼,來到FadeinImage
內(nèi)部的build方法
通過斷點調(diào)試最終發(fā)現(xiàn)圖片是通過image
中framBuilder
回調(diào)創(chuàng)建視圖,而該回調(diào)中的wasSynchronouslyLoaded
決定了是返回網(wǎng)絡(luò)圖片殷绍,還是返回占位圖動畫染苛。所以再跟進(jìn)去看一下。
官方注解:如果framBuilder為null主到,一旦第一個圖像幀可用茶行,則此小部件將顯示繪制為的圖像。調(diào)用方也可以使用此生成器向圖像添加效果(例如在圖像變暗時淡入)或在加載圖像時顯示占位符小部件登钥。
2.看一下Image的build方法畔师,因為image是一個statefulwidget,所以這里是imageState
也驗證了剛才的說的官方注解牧牢。那么就看一下為什么tab切換這里返回的是wasSynchronouslyLoaded
是fasle
看锉,其他情況都是true
姿锭。
3.查看Widget樹,發(fā)現(xiàn)兩種切換方式層級都是一樣的伯铣。
眾所周知呻此,當(dāng)同級的子樹發(fā)生變化的時候,都用didChangeDependencies()
腔寡,每次都會進(jìn)入_updateSourceStream
關(guān)鍵可以看到這個方法焚鲜,關(guān)鍵在于如果_imageStream.key
不相等,wasSynchronouslyLoaded就設(shè)置成false了放前。如果正常加載過一次忿磅,該值默認(rèn)就是true,這個這里就不展開了凭语。這里的key
是stream的completer對象葱她。
4.到這就更奇怪了,為什么URL都是一樣的叽粹,_imageStream
為啥會不一樣呢览效?這里主要就要看下這個newStream是怎么拼出來的了。這塊代碼會有點深虫几,說結(jié)論:
imageCache的_cache屬性如果有key锤灿,直接返回image.completer。所以通過斷點發(fā)現(xiàn)辆脸,切換tab但校。
到這里的結(jié)論就是這里的_cache在某個時刻清空了,導(dǎo)致返回的key
(image.completer)不一致了啡氢。
5.看看_cache在何時清空的:
看起來是從native調(diào)過來的状囱,
channel
和type
分別是flutter/system
和memoryPressure
6.調(diào)試一下engine看看怎么傳過來的。
全局搜一下engine源碼倘是,看起來是這個亭枷。
lldb打上方法斷點看看什么時候調(diào):
快知道最終原因了:
因為flutter_boost把engine.viewController設(shè)置為了nil,觸發(fā)了memoryPressure
搀崭。
方法的源頭是detachFlutterEngineIfNeeded
而detachFlutterEngineIfNeeded
就是在vc dismiss的時候調(diào)用叨粘。
7.到這更奇怪了,兩種方式都是dismiss瘤睹,為什么切換tab升敲,會重新動畫呢?
結(jié)論
最終答案在這轰传,如果是當(dāng)前頁面push和pop會進(jìn)入attatchFlutterEngine
的邏輯驴党,而在簡單場景下例如A->B->A detachFlutterEngine方法內(nèi)部因為判斷self.viewController.engine != self,所以就不會執(zhí)行之后的邏輯获茬。而嵌tabbar頁中flutter vc在flutterboost看起來港庄,也做了相同的邏輯倔既,一旦切出,我也默認(rèn)這個頁面銷毀了攘轩,不再進(jìn)行管理叉存。切換tab,F(xiàn)adeInImage重新做動畫的根本原因就是度帮,F(xiàn)lutterBoost在detachFlutterEngine
把engine.viewcontroller置為了nil歼捏,觸發(fā)了memoryPressure
(內(nèi)存壓力),把ImageCache清空了笨篷,導(dǎo)致雖然網(wǎng)絡(luò)圖片的緩存還在瞳秽,但是讓flutter誤以為需要重新加載。
稍微改一下代碼
完美率翅。
暫時不知道官方這么寫的原因练俐,所以最后的解決方法,還是把動畫時間調(diào)成最短冕臭,讓人肉眼看不到圖片重新做動畫腺晾。
補充核心過程(假設(shè)A是tab-native,B是tab-flutter辜贵,B的圖片已經(jīng)加載完畢):
第一種情況(B->A->C->A->B)
第一步:回到A悯蝉,push一個flutter的C頁面,觸發(fā)memoryPressure
(init)托慨,并且因為層級變化image觸發(fā)didChangeDependencies
可以看到_cache
清空了鼻由,但_liveImages
中的網(wǎng)絡(luò)圖片對象還在,這個時候會把_liveImages的對象取出厚棵,重新放到_cache
中蕉世。
并且返回對象。這個時候_cache
又恢復(fù)正常婆硬。
之后因為image不需要在響應(yīng)動畫狠轻,所以移除通知
再移除的過程中將liveImage
清掉
所以進(jìn)入C頁面的最后結(jié)果是
第二步.C dismiss,觸發(fā)memoryPressure
(engineDetach)彬犯,清了一把_cache
這個時候什么緩存都沒了向楼,Image的key需要重新創(chuàng)建初始化,所以回到B會重新動畫躏嚎。
第二種情況:(B->C->B)
第一步同上。
第二步.C dismiss菩貌,不會engineDetach
卢佣,所以不會觸發(fā)memoryPressure
,所以回到B箭阶,直接拿緩存虚茶,不用重新創(chuàng)建戈鲁。
后續(xù)
給官方提了一個PR,新增vc keepalive屬性嘹叫,針對這種tab內(nèi)嵌的頁面進(jìn)行特殊管理婆殿。
https://github.com/alibaba/flutter_boost/pull/1422?w=1