_clickable = false;
}
if (vCode.isEmpty || vCode.length < 6) {
_clickable = false;
}
if (password.isEmpty || password.length < 6) {
_clickable = false;
}
setState(() {
});
}
MyButton(
onPressed: _clickable ? _register : null,
text: ‘注冊’,
)
其實(shí)這里可以優(yōu)化一下。因?yàn)楝F(xiàn)在的每次輸入都必定刷新,我們可以在_clickable
參數(shù)有變化時再刷新,避免無效的刷新。優(yōu)化的代碼如下:
void _verify() {
String phone = _phoneController.text;
String vCode = _vCodeController.text;
String password = _passwordController.text;
bool clickable = true;
if (phone.isEmpty || phone.length < 11) {
clickable = false;
}
if (vCode.isEmpty || vCode.length < 6) {
clickable = false;
}
if (password.isEmpty || password.length < 6) {
clickable = false;
}
/// 狀態(tài)不一致時刷新
if (clickable != _clickable) {
setState(() {
_clickable = clickable;
});
}
}
就這樣一個簡單的處理,試想一下可以減少多少次的刷新。
類似的,在CustomPainter
中有個shouldRepaint
的重寫方法,我們可以根據(jù)需求控制CustomPainter
是否進(jìn)行重繪。
預(yù)構(gòu)建Widget
動畫的使用在實(shí)際開發(fā)中很常見,但是一旦使用不當(dāng)也會造成不必要的刷新,甚至?xí)砜D。
舉一個deer中的例子,商品列表頁中有一個商品操作菜單的呼入呼出動畫(這里就不談具體的實(shí)現(xiàn)效果了,有興趣的可以去看源碼)。一開始的寫法如下:
AnimatedBuilder(
animation: animation,
builder:(_, __) {
return MenuReveal(
revealPercent: animation.value,
child: _buildGoodsMenu(context),
);
}
)
效果如下: 這個動畫看起來還是比較流暢的。頂部的性能圖表(Performance Overlay
)中,UI花費(fèi)的時間平均在7.2ms/frame。比起16ms的安全標(biāo)準(zhǔn)來說已經(jīng)非常好了。
但是我們來看看構(gòu)建次數(shù)(呼入呼出各一次):
這里仔細(xì)看就有點(diǎn)問題,動畫執(zhí)行時我們只希望可變的部分刷新(MenuReveal),但實(shí)際上連菜單中的按鈕也一起刷新構(gòu)建了。
那么優(yōu)化的方法就是預(yù)構(gòu)建菜單中的按鈕,將_buildGoodsMenu(context)
方法放在AnimatedBuilder
之前執(zhí)行再傳入或是放在AnimatedBuilder
的child
中。
AnimatedBuilder(
animation: animation,
child: buildGoodsMenuContent(context), // <-----放在這里
builder:(, child) {
return MenuReveal(
revealPercent: animation.value,
child: child // <----這里使用
);
}
)
效果如下:
可以看到UI線程花費(fèi)的時間在6ms/frame左右。這個提升還是比較大的(16%左右),雖然對于用戶來說是無感知的。
再次看一下構(gòu)建次數(shù):
那么提升的原因也就找到了,因?yàn)楸苊饬瞬槐匾臉?gòu)建。所以針對這類不依賴于動畫的子Widget,預(yù)構(gòu)建它可以顯著提高性能。
類似這種builder/child的模式還有不少,你可以多多留意一下。
復(fù)用
- 盡量使用
const
來定義一些不變的Widget,這相當(dāng)于緩存一個Widget并復(fù)用它。
我之前看到過一篇博客,作者測試一個頁面上構(gòu)建1000個重復(fù)圖標(biāo),結(jié)果使用const
構(gòu)造函數(shù)的,F(xiàn)PS大約高8.4%,內(nèi)存使用量降低約20%。
當(dāng)然作者也說了,實(shí)際一個頁面上有1000個Widget也不現(xiàn)實(shí)。其實(shí)說這個點(diǎn)的原因也是希望大家能養(yǎng)成一個好習(xí)慣。
- 添加
GlobalKey
也能復(fù)用widget。這個使用場景相對較少,可以了解一下。相關(guān)內(nèi)容鏈接:說說Flutter中的Key
RepaintBoundary
這個我之前有詳細(xì)介紹過,可以直接查看:說說Flutter中的RepaintBoundary,這里我就不重復(fù)說了。合理的使用RepaintBoundary
可以減少不必要的刷新提升性能。
4.加載策略
按需加載
推薦使用ListView.builder
來動態(tài)實(shí)現(xiàn)列表,而不是直接使用ListView
靜態(tài)創(chuàng)建。注意這里在使用ListView.builder
的itemBuilder
來構(gòu)建item時,可不要預(yù)構(gòu)建Widget了。類似的Widget還有PageView.builder
和 GridView.builder
。
PS:按需加載是一種策略,并不是僅僅依靠這幾個類型的Widget。比如之前阿里AliFlutter的分享中,就有提到列表中加載圖片的優(yōu)化。通過判斷圖片的在屏和離屏,來合理回收圖片,這樣減小了內(nèi)存的波動,同樣也可以帶來性能的提升。
錯峰加載
錯峰加載的目的是為了避免因同一時間的大量構(gòu)建,而產(chǎn)生卡頓現(xiàn)象。這里我舉一個例子:
在使用PageView.builder
這個Widget時,我發(fā)現(xiàn)在左右滑動切換頁面時會有卡頓的現(xiàn)象。使用timeline
來分析發(fā)現(xiàn)兩個問題,一是切換的頁面比較復(fù)雜,比較耗時。二是頁面構(gòu)建的時間點(diǎn)在滑動中。
頁面復(fù)雜的問題我進(jìn)行了一定的優(yōu)化,雖然有效果,但還是有卡頓發(fā)生。那么只能針對第二點(diǎn)再進(jìn)行優(yōu)化,我們先看一下PageView.
相關(guān)源碼:
return NotificationListener(
onNotification: (ScrollNotification notification) {
if (notification.depth == 0 && widget.onPageChanged != null && notification is ScrollUpdateNotification) {
final PageMetrics metrics = notification.metrics;
final int currentPage = metrics.page.round();
if (currentPage != _lastReportedPage) {
_lastReportedPage = currentPage;
widget.onPageChanged(currentPage);
}
}
return false;
},
child: Scrollable(),
);
代碼很簡單,如果我們設(shè)置了onPageChanged
的監(jiān)聽,那么在滑動中(ScrollUpdateNotification
)計(jì)算當(dāng)前頁的頁碼并返回(round方法,四舍五入)。所以在滑動到一半的時候,onPageChanged
就會回調(diào)結(jié)果,我因?yàn)樵谶@里觸發(fā)了頁面的刷新代碼,導(dǎo)致了卡頓的發(fā)生。
其實(shí)在我熟知的安卓中,默認(rèn)行為都是在滑動結(jié)束后才去加載頁面數(shù)據(jù)。所以按照這個思路處理,調(diào)整一下加載策略。
修改代碼如下:
NotificationListener(
onNotification: (ScrollNotification notification) {
if (notification.depth == 0 && notification is ScrollEndNotification) {
final PageMetrics metrics = notification.metrics;
final int currentPage = metrics.page.round();
if (currentPage != _lastReportedPage) {
_lastReportedPage = currentPage;
_onPageChange(currentPage);
}
}
return false;
},
child: PageView.builder(),
)
我們在PageView.builder
上添加一個NotificationListener
,同時修改ScrollUpdateNotification
為ScrollEndNotification
。這樣就自定義了我們的滑動監(jiān)聽事件,通過錯峰加載保證了UI的流暢。
PS:在Flutter 1.17的重要改動中就有一條:在高速滾動時推遲圖像解碼。這也是運(yùn)用了錯峰加載的策略。
5.耗時計(jì)算
避免將一些耗時計(jì)算放在UI線程,我們可以把耗時計(jì)算放到Isolate
去執(zhí)行(多線程)。
舉一個Flutter源碼中的例子:
Future loadString(String key, { bool cache = true }) async {
final ByteData data = await load(key);
if (data == null)
throw FlutterError(‘Unable to load asset:
k
e
y
′
)
;
i
f
(
d
a
t
a
.
l
e
n
g
t
h
I
n
B
y
t
e
s
<
10
?
1024
)
/
/
10
K
B
t
a
k
e
s
a
b
o
u
t
3
m
s
t
o
p
a
r
s
e
o
n
a
P
i
x
e
l
2
X
L
.
/
/
S
e
e
:
h
t
t
p
s
:
/
/
g
i
t
h
u
b
.
c
o
m
/
d
a
r
t
?
l
a
n
g
/
s
d
k
/
i
s
s
u
e
s
/
31954
r
e
t
u
r
n
u
t
f
8.
d
e
c
o
d
e
(
d
a
t
a
.
b
u
f
f
e
r
.
a
s
U
i
n
t
8
L
i
s
t
(
)
)
;
r
e
t
u
r
n
c
o
m
p
u
t
e
(
u
t
f
8
d
e
c
o
d
e
,
d
a
t
a
,
d
e
b
u
g
L
a
b
e
l
:
′
U
T
F
8
d
e
c
o
d
e
f
o
r
"
key'); if (data.lengthInBytes < 10 * 1024) { // 10KB takes about 3ms to parse on a Pixel 2 XL. // See: https://github.com/dart-lang/sdk/issues/31954 return utf8.decode(data.buffer.asUint8List()); } return compute(_utf8decode, data, debugLabel: 'UTF8 decode for "
key′);if(data.lengthInBytes<10?1024)//10KBtakesabout3mstoparseonaPixel2XL.//See:https://github.com/dart?lang/sdk/issues/31954returnutf8.decode(data.buffer.asUint8List());returncompute(u?tf8decode,data,debugLabel:′UTF8decodefor"key"’);
}
static String _utf8decode(ByteData data) {
return utf8.decode(data.buffer.asUint8List());
}
因?yàn)?code>utf8.decode方法處理10KB數(shù)據(jù)大約需要3ms的時間(手機(jī)Pixel 2 XL),所以在超過10KB的數(shù)據(jù)就使用了compute
方法將耗時計(jì)算放到Isolate
。這里根據(jù)數(shù)據(jù)大小選擇不同的方式,是因?yàn)?code>Isolate的創(chuàng)建使用也是有空間和時間上的消耗,所以Isolate雖好,可不要濫用哦!
同樣的,我們項(xiàng)目中的json解析操作也可以這樣處理,以保證在一些性能較差的機(jī)子上可以不造成UI的卡頓。具體實(shí)現(xiàn)可以看:在后臺處理 JSON 數(shù)據(jù)解析
這里我簡單說明一下原因:Flutter應(yīng)用中的Dart
代碼執(zhí)行在UI Runner
中,而Dart
是單線程的,我們平時使用的異步任務(wù)Future
都是在這個單線程的Event Queue
之中,通過Event Loop
來按順序執(zhí)行。(這個單線程模型和js是一樣的)
也就是說即使我們是異步執(zhí)行這段計(jì)算代碼,但由于這段代碼耗時過長,那么這段時間內(nèi)線程沒有空閑(可以理解為任務(wù)代碼都是插空執(zhí)行?),也就是線程過載了。導(dǎo)致期間Widget的layout等計(jì)算遲遲無法執(zhí)行,那么時間越長,卡頓的現(xiàn)象就越明顯。
因此使用Isolate
來處理耗時計(jì)算,利用多線程來做到代碼的并行執(zhí)行。
可能這里你會有疑問,那我網(wǎng)絡(luò)請求也是Dart代碼而且有時也挺耗時的,怎么不見頁面卡頓?其實(shí)這是因?yàn)榫W(wǎng)絡(luò)請求在io線程,不會占用ui線程。且實(shí)際的網(wǎng)絡(luò)請求也并不是在Dart層做的,Dart代碼部分只是一層封裝,真正的請求是由底層的操作系統(tǒng)去實(shí)現(xiàn)的。
6.GPU
上面的幾點(diǎn)大都是關(guān)于UI線程的優(yōu)化。其實(shí)在觀察Performance Overlay
時,我們發(fā)現(xiàn)有時UI很流暢,但是GPU卻會很耗時。這里主要是繪制上的壓力比較大(GPU Runner
)導(dǎo)致的,可能包括對Skia
的saveLayer
、clipPath
等耗時函數(shù)調(diào)用。
saveLayer
會在GPU中分配一塊新的繪圖緩沖區(qū)(離屏渲染),切換繪圖目標(biāo),這些操作是在GPU中非常的耗時,尤其在比較老的設(shè)備上。
使用
clipPath
會影響接下來每一個繪圖指令。尤其這個Path比較復(fù)雜的時候都需要和這個復(fù)雜的Path做相交操作,而且把Path之外的部分剔除掉。
在Flutter源碼中搜索canvas.saveLayer
可以發(fā)現(xiàn)一些需要注意的:
-
當(dāng)
Text
的overflow
屬性為TextOverflow.fade
,且文字超出范圍時,會調(diào)用saveLayer
。 -
使用
Clip.antiAliasWithSaveLayer
作為剪切行為時,會調(diào)用saveLayer
(據(jù)說早期Flutter版本中大都使用這一方式)。建議優(yōu)先使用Clip.hardEdge
和Clip.antiAlias
。這部分屬性一般在ClipRect
、ClipOval
和ClipPath
等裁剪功能Widget中用到。 -
修改
RawChip
的isEnabled
屬性,觸發(fā)enableAnimation
動畫時,會調(diào)用saveLayer
。
而對于clipPath
,相對沒有saveLayer
耗時。但需要注意對于裁剪行為。優(yōu)先考慮使用BoxDecoration
的borderRadius
屬性來解決。比如Inkwell
的borderRadius
屬性就可以裁剪它的外形,如果borderRadius
實(shí)在不能滿足,可以使用customBorder
屬性(使用clipPath
)。
自我介紹一下,小編13年上海交大畢業(yè),曾經(jīng)在小公司待過,也去過華為、OPPO等大廠,18年進(jìn)入阿里一直到現(xiàn)在。
深知大多數(shù)初中級Android工程師,想要提升技能,往往是自己摸索成長或者是報班學(xué)習(xí),但對于培訓(xùn)機(jī)構(gòu)動則近萬的學(xué)費(fèi),著實(shí)壓力不小。自己不成體系的自學(xué)效果低效又漫長,而且極易碰到天花板技術(shù)停滯不前!
因此收集整理了一份《2024年Android移動開發(fā)全套學(xué)習(xí)資料》,初衷也很簡單,就是希望能夠幫助到想自學(xué)提升又不知道該從何學(xué)起的朋友,同時減輕大家的負(fù)擔(dān)。
既有適合小白學(xué)習(xí)的零基礎(chǔ)資料,也有適合3年以上經(jīng)驗(yàn)的小伙伴深入學(xué)習(xí)提升的進(jìn)階課程,基本涵蓋了95%以上Android開發(fā)知識點(diǎn),真正體系化!
由于文件比較大,這里只是將部分目錄截圖出來,每個節(jié)點(diǎn)里面都包含大廠面經(jīng)、學(xué)習(xí)筆記、源碼講義、實(shí)戰(zhàn)項(xiàng)目、講解視頻,并且會持續(xù)更新!
如果你覺得這些內(nèi)容對你有幫助,可以掃碼獲取?。。▊渥ⅲ篈ndroid)

結(jié)語
- 現(xiàn)在隨著短視頻,抖音,快手的流行NDK模塊開發(fā)也顯得越發(fā)重要,需要這塊人才的企業(yè)也越來越多,隨之學(xué)習(xí)這塊的人也變多了,音視頻的開發(fā),往往是比較難的,而這個比較難的技術(shù)就是NDK里面的技術(shù)。
- 音視頻/高清大圖片/人工智能/直播/抖音等等這年與用戶最緊密,與我們生活最相關(guān)的技術(shù)一直都在尋找最終的技術(shù)落地平臺,以前是windows系統(tǒng),而現(xiàn)在則是移動系統(tǒng)了,移動系統(tǒng)中又是以Android占比絕大部分為前提,所以AndroidNDK技術(shù)已經(jīng)是我們必備技能了。
- 要學(xué)習(xí)好NDK,其中的關(guān)于C/C++,jni,Linux基礎(chǔ)都是需要學(xué)習(xí)的,除此之外,音視頻的編解碼技術(shù),流媒體協(xié)議,ffmpeg這些都是音視頻開發(fā)必備技能,而且
- OpenCV/OpenGl/這些又是圖像處理必備知識,下面這些我都是當(dāng)年自己搜集的資料和做的一些圖,因?yàn)楫?dāng)年我就感覺視頻這塊會是一個大的趨勢。所以提前做了一些準(zhǔn)備。現(xiàn)在拿出來分享給大家。
《Android學(xué)習(xí)筆記總結(jié)+移動架構(gòu)視頻+大廠面試真題+項(xiàng)目實(shí)戰(zhàn)源碼》,點(diǎn)擊傳送門即可獲?。?/strong>
又是圖像處理必備知識,下面這些我都是當(dāng)年自己搜集的資料和做的一些圖,因?yàn)楫?dāng)年我就感覺視頻這塊會是一個大的趨勢。所以提前做了一些準(zhǔn)備?,F(xiàn)在拿出來分享給大家。
[外鏈圖片轉(zhuǎn)存中…(img-6o8hAGqA-1711856811770)]文章來源:http://www.zghlxwxcb.cn/news/detail-859553.html
[外鏈圖片轉(zhuǎn)存中…(img-l1bA8ZbU-1711856811770)]文章來源地址http://www.zghlxwxcb.cn/news/detail-859553.html
《Android學(xué)習(xí)筆記總結(jié)+移動架構(gòu)視頻+大廠面試真題+項(xiàng)目實(shí)戰(zhàn)源碼》,點(diǎn)擊傳送門即可獲?。?/strong>
到了這里,關(guān)于Flutter性能優(yōu)化實(shí)踐 —— UI篇的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!