基于當(dāng)前的flutter項(xiàng)目功能基本實(shí)現(xiàn),項(xiàng)目框架結(jié)構(gòu)不清晰,代碼耦合嚴(yán)重,重復(fù)渲染耗費(fèi)資源過(guò)大等問(wèn)題影響項(xiàng)目接下去的迭代開發(fā),所以對(duì)當(dāng)前項(xiàng)目先進(jìn)行重構(gòu)變得尤為重要。
雖然 Flutter 技術(shù)構(gòu)建的應(yīng)用程序在默認(rèn)情況下已經(jīng)非常高性能,但一開始急于完成項(xiàng)目,并沒(méi)有就渲染上的性能問(wèn)題做過(guò)多的思考。參考flutter官網(wǎng),對(duì)頁(yè)面的渲染性能問(wèn)題需要做如下幾點(diǎn)的考慮。
1. 減少build()的耗時(shí)
1) 避免在build()方法中執(zhí)行重復(fù)且耗時(shí)的工作,因?yàn)楫?dāng)父widget重建時(shí),子widget的build()方法會(huì)被重復(fù)調(diào)用
2)避免在build()方法中返回一個(gè)超大widget,將widget拆分成多個(gè)小widget,各自控制UI的渲染,避免重復(fù)渲染一個(gè)大的widget。
實(shí)踐
1) 采用Provider庫(kù),分離數(shù)據(jù)及視圖代碼,隔絕widget。
2) 將原先在build()方法中的的一些邏輯代碼提取到provider model中處理,減少build()后多次執(zhí)行。
3)將大組件拆分成多個(gè)widget小組件。與業(yè)務(wù)代碼耦合的組件采用provider進(jìn)行數(shù)據(jù)管理,進(jìn)而驅(qū)動(dòng)頁(yè)面UI渲染,獨(dú)立小組件采用statefullwidget進(jìn)行各自數(shù)據(jù)的狀態(tài)管理。
4)采用Provider庫(kù)中的shouldRebuild來(lái)減少不必要的渲染
Selector<QualificationModel,Tuple2<Uint8List, Uint8List>>(
shouldRebuild: (previous, next) {
if (previous.item1 != next.item1 && next.item1 != null) {
...
}
return false;
},
selector: ((_, data) => Tuple2(
data.frontIdentity,
data.backIdentity)),
builder: (context, data, _) { return ...})
2. 在必要的時(shí)候才使用一些效果
類似web開發(fā)中css3實(shí)現(xiàn)一些3D效果會(huì)開啟GPU加速渲染,占用過(guò)多的資源,flutter中也有相同的問(wèn)題。
在flutter中實(shí)現(xiàn)某些效果會(huì)調(diào)用性能代價(jià)很大的saveLayer()方法。因?yàn)檎{(diào)用 saveLayer() 會(huì)開辟一片離屏緩沖區(qū)。將內(nèi)容繪制到離屏緩沖區(qū)可能會(huì)觸發(fā)渲染目標(biāo)切換,這些切換在較早期的 GPU 中特別慢。
避免使用一些會(huì)觸發(fā)saveLayer()的widget:
- 盡量不使用
Opacitywidget - 使用Clipping時(shí)避免使用Clip.antiAliasWithSaveLayer
- ShaderMask
- ColorFilter
- 使用Chip且disabledColorAlpha != oxff 時(shí)
- 使用Text且存在overfloShader時(shí)
避免調(diào)用 saveLayer() 的方式:
- 要在圖像中實(shí)現(xiàn)淡入淡出,請(qǐng)考慮使用
FadeInImagewidget,該 widget 使用 GPU 的片段著色器應(yīng)用漸變不透明度。 - 要?jiǎng)?chuàng)建帶圓角的矩形,而不是應(yīng)用剪切矩形,考慮使用很多 widget 都提供的
borderRadius屬性。
3. 對(duì)列表或者網(wǎng)格列表采用懶加載
在構(gòu)建大型網(wǎng)格或列表時(shí),使用帶有回調(diào)的惰性方法。這樣,只有屏幕的可見(jiàn)部分是在開始時(shí)構(gòu)建的。
ScrollController doneScrollController = ScrollController();
doneScrollController.addListener(() {
if (doneScrollController.position.pixels ==
doneScrollController.position.maxScrollExtent &&
!Provider.of<DoneTasksModel>(context, listen: false).isLastPage) {
Provider.of<DoneTasksModel>(context, listen: false).nextPageListData();
}
});
ListView.separated(
itemBuilder: (BuildContext context, int idx) {
return ...
},
separatorBuilder: (BuildContext context, int index) {
return ...
},
itemCount: 50,
controller: scrollController,
)