接觸到Flutter之後,我第一時間嚐了一下鮮。那時尚未詳細的中文文檔,只有Flutter的官網上的英文文檔可參考。邊看邊學,搗鼓了些Flutter app以及插件。而後把這個過程當中的一些心得體會總結出來幾篇文章:前端
總的感受就是Flutter是如此簡潔卻又如此的強大。在熟悉了FLutter app的開發以後,天然開始對Flutter框架如何運做產生了興趣。因而花了一些時間研究了一下Flutter框架的源碼,寫了一系列Flutter源碼分析的文章做爲一個總結:less
近日有掘友提出可否分享一下學習Flutter源碼的一些方法和技巧,我以爲這是個很好的建議,因而就有了這篇文章供你們參考。但願能幫到想研究一下框架源碼的同窗,也但願你們若是有本身的一些閱讀框架源碼的經驗也能分享出來,共同窗習提升。
本篇文章把學習Flutter框架源碼分爲3個階段,學習前,學習中和學習後。學習前的階段主要是介紹一些在開始看框架源碼以前我本身以爲有幫助的一些準備工做;學習中階段主要會介紹在看看框架源碼的時候一些可能有用的方法或技巧;學習後階段主要會介紹在理解了框架源碼的基礎上能夠作一些事情來鞏固提升本身對其的理解深度。
那麼咱們就開始吧。
在開始看框架源碼以前,我會作一些準備工做,直到這些條件都具有了,再開始正式學習源碼,會有事半功倍的效果。
要學習框架源碼首先要有強烈的興趣,也就是好奇心,說簡單點也就是要多問問題。爲何會有StatefulWidget
和StatelessWidget
?爲何調用setState()
之後Flutter會刷新你的頁面?是什麼在驅動Flutter框架運行?動畫又是如何動起來的?等等等等。有了這些問題,你纔會有動力去學習框架源碼來本身尋找答案。若是你對Flutter並無特別大的興趣,僅僅是爲了瞭解一下新東西,照着開發文檔能寫app以爲就ok了。或者是爲了完成領導下達的任務,爲了完成kpi而簡單的學習一下,淺嘗輒止。那我以爲這種狀況下一我的是很難有去學習框架源碼的念頭的。
除了要有興趣,還要有信心。相信本身能征服框架源碼,不會被龐大的代碼給嚇到。可能會有一些同窗在開發Flutter app的過程當中會點開StatefulWidget
的源碼去看看,可是跳轉來跳轉去很快就迷失在代碼的海洋裏找不着方向。又或者點開Flutter源碼的目錄看到滿屏的.dart文件不知所措,直接勸退。這就是缺少信心的表現。
這裏我送你們一句話:「How hard can it be?」 直譯過來就是「這能難到哪裏去呢?」 這句話來自我最喜歡的前著名汽車評測節目「Top Gear」主持人,「大猩猩」 Jeremy Clarkson。
雖然當他在節目中猥瑣的說出這句話以後一般會搞砸某件事,可是這種精神是值得咱們學習的。堅信本身能征服眼前的困難是很是重要的。因此在你以爲Flutter源碼很難的時候不如問問本身,How hard can it be?
Flutter框架的一個不一樣之處是使用了你們不太熟悉的dart語言。和Java,JS,OC等都有很大的區別。雖然dart能很快上手,短期以內你就能夠開發出像模像樣的Flutter app。可是若是要去學習框架源碼,則須要咱們對這門新語言的特性很是的習慣,注意我這裏說的是習慣而不是瞭解,也不是熟悉。若是你僅僅是知道新語言的特性,而不是習慣這些特性的話,去看框架源碼是很是困難的。
特別是當你已經習慣了一種語言,好比Java,你對Java越熟悉,就越會以爲dart彆扭。怎麼在一個.dart文件裏有好多個類?函數也能當參數?閉包是什麼鬼?async
和await
是怎麼個執行順序?要克服慣性思惟,特別是從本身熟悉的語言切換到不熟悉的語言帶來的不適。才能順利的開始新徵程。怎麼克服呢?惟有多看,多練,造成習慣。當你再也不被新語言的特性所阻滯的時候,才能順利的開啓框架源碼學習之旅。
在開始看框架源碼以前,我先看了一些介紹框架的文章資料。這些文章可能參差不齊,相互矛盾。也可能介紹的比較簡單粗暴,看不懂裏面在說啥,從而帶來更多的問題。Widget我知道,但是Element和RenderObject又是啥?它們有啥關係?怎麼會有那麼多的樹?Layer又是什麼東西?
可是這些文章須要反覆的看,特別是要以官網上的權威文檔爲主,相互比較,相互補充,這樣慢慢的在腦海中就會創建起粗淺的框架模型。有了這個模型,你就會知道哪些地方比較重要,哪些地方須要特別關注,從大的分塊上對框架有一個大致的認識。這個模型就是咱們學習框架源碼的指路明燈,避免咱們迷失方向。
特別是對渲染流水線的介紹,這張圖能夠說就是整個Flutter框架的核心,從個人分析系列文章能夠看出,我學習Flutter源碼就是牢牢圍繞這張圖進行的。
最後呢,就是你要對前端,對GUI編程有個更高層次的理解會極大的幫助你去理解Flutter源碼,無論是Android ,iOS,Web仍是Flutter,各類各樣的前端框架作的事情其實都是差很少的。因此它們面對的問題也是差很少的,它們各自框架的實現也是有共性的,互相也都在學習借鑑。全部要利用你以前之後的GUI開發經驗,你會發如今各個框架中其實均可以找到類似的地方,若是能總結出通常性的規律,會極大幫助你理解新的框架。好比咱們都知道爲了和用戶交互,GUI編程一般採用的是事件驅動模型,那麼你確定會去看Flutter的事件驅動模型是什麼樣的?咱們都知道16ms,那麼確定有系統Vsync信號來驅動框架嘍。GUI編程通常會有窗口(window)的概念,咱們是否是能夠有目的的去找一下新框架的窗口在哪裏。用戶界面編程確定是逃不開界面元素的佈局,繪製,光柵化。若是你已經有了這樣的概念,那麼剩下的只是去Flutter源碼中去尋找具體實現了。有了這種通常性規律的指導,任何新GUI框架你均可以理解掌握。
作好以上準備之後,就能夠進入源碼的學習環節了。首先就是要找準學習框架源碼的切入點。
通常來說,框架代碼的切入點有兩個,一個是咱們常見的是最上層的,也就是框架提供的那些API來入手,對於Flutter來說那就是StatefulWidget
,StatelessWidget
,State
,setState()
和runApp()
等等。另一個切入點就是框架最底層和engine交互的地方,也就是window
。這兩個點也是框架的邊界,入手的時候咱們能夠自上而下,也能夠自下而上,或者兼而有之。這個看本身的喜愛了。重要的是能找準框架的上界和下界。
框架是很複雜的,須要處理不少事情,因此其代碼也會很是龐雜。咱們須要抓住主要流程,也就是我上面說的渲染流水線,而暫時忽略次要流程,例如點擊事件的處理,輔助功能(Accessibility)等等,排除這些次要流程會使咱們專一於主要流程,從而減輕咱們的認知負擔。在源代碼中遇到touch,Accessibility等相關類,函數,代碼可直接跳過,避免其帶來干擾。這些次要流程能夠待咱們搞清楚主要流程以後再回過頭來熟悉,這樣會順暢不少。
Flutter框架源代碼中除了主要功能相關的代碼以外,還存在着不少和調試,異常處理等邏輯。特別是有大量的assert
斷言語句。這些邏輯也是須要暫時剔除的以免干擾咱們的學習進程。每每一個函數在剔除了斷言和異常處理等邏輯以後,關鍵代碼可能只有幾行甚至只有一行。
拿咱們熟悉的setState()
舉例,完整代碼以下(沒必要細看,只須要知道代碼量就好了),大概60多行的樣子。
void setState(VoidCallback fn) {
assert(fn != null);
assert(() {
if (_debugLifecycleState == _StateLifecycle.defunct) {
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('setState() called after dispose(): $this'),
ErrorDescription(
'This error happens if you call setState() on a State object for a widget that '
'no longer appears in the widget tree (e.g., whose parent widget no longer '
'includes the widget in its build). This error can occur when code calls '
'setState() from a timer or an animation callback.'
),
ErrorHint(
'The preferred solution is '
'to cancel the timer or stop listening to the animation in the dispose() '
'callback. Another solution is to check the "mounted" property of this '
'object before calling setState() to ensure the object is still in the '
'tree.'
),
ErrorHint(
'This error might indicate a memory leak if setState() is being called '
'because another object is retaining a reference to this State object '
'after it has been removed from the tree. To avoid memory leaks, '
'consider breaking the reference to this object during dispose().'
),
]);
}
if (_debugLifecycleState == _StateLifecycle.created && !mounted) {
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('setState() called in constructor: $this'),
ErrorHint(
'This happens when you call setState() on a State object for a widget that '
'hasn\'t been inserted into the widget tree yet. It is not necessary to call '
'setState() in the constructor, since the state is already assumed to be dirty '
'when it is initially created.'
)
]);
}
return true;
}());
final dynamic result = fn() as dynamic;
assert(() {
if (result is Future) {
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('setState() callback argument returned a Future.'),
ErrorDescription(
'The setState() method on $this was called with a closure or method that '
'returned a Future. Maybe it is marked as "async".'
),
ErrorHint(
'Instead of performing asynchronous work inside a call to setState(), first '
'execute the work (without updating the widget state), and then synchronously '
'update the state inside a call to setState().'
)
]);
}
// We ignore other types of return values so that you can do things like:
// setState(() => x = 3);
return true;
}());
_element.markNeedsBuild();
}
複製代碼
在剔除斷言,調試代碼,異常處理以後就只剩短短兩行了。
void setState(VoidCallback fn) {
final dynamic result = fn() as dynamic;
_element.markNeedsBuild();
}
複製代碼
在看源碼的時候經過這樣的操做能夠排除干擾,快速掌握關鍵邏輯。
Flutter框架源代碼中其實有一些套路在被廣泛的使用,只要瞭解了這些套路,那麼在看新的源碼的時候會有似曾相識是的感受,其大體邏輯也能夠被推測出來。能夠指引咱們理解源碼邏輯。例如,在渲染流水線的構建(build),佈局(layout)和繪製(paint)階段,都是類似的邏輯,首先把相應節點標記爲髒(dirty)
Element.markNeedsBuild()
RenderObject.markNeedsLayout()
RenderObject.markNeedsPaint()
而後後續真正執行相應操做。若是搞清楚了構建流程的套路,那麼在看佈局,繪製的源碼的時候就不會感受那麼的陌生了。這樣的例子還有不少,就不一一列舉了。
Flutter框架也提供了不少的工具給開發者,你們在學習源碼的的時候能夠充分利用這些工具來幫助本身。好比Flutter inspector。可讓你能清晰的看到element tree, renderObject tree。有助於理解它們之間的關係。
其次就是對於比較複雜的邏輯,要本身動手寫一些demo app打上斷點跑一跑,單步跟蹤一下,也是很是有幫助的。
最後就是Flutter也提供一些調試用的Flag。例如如下這些標誌位
debugPrintScheduleFrameStacks = true;
debugPrintBeginFrameBanner = true;
debugPrintEndFrameBanner = true;
debugPrintRebuildDirtyWidgets = true;
debugPrintBuildScope = true;
debugPrintScheduleBuildForStacks = true;
debugProfileBuildsEnabled = true;
debugPaintLayerBordersEnabled = true;
debugPrintMarkNeedsLayoutStacks = true;
debugPrintMarkNeedsPaintStacks = true;
複製代碼
有選擇的打開某些標誌位再跑一下本身的程序,查看log輸出,也有助於咱們理解Flutter框架。
俗話說好記性不如爛筆頭。框架代碼如此龐雜,若是咱們僅僅是在IDE裏跳轉來,跳轉去。那麼很快就會迷失方向,不知道本身身處何處。因此必要的記錄是必須的。特別是有多個分支的狀況,若是不作記錄,順着其中一個分支深刻下去,極可能就會忘記其餘分支尚未照顧到。或者是看完一個分支再去看其餘分支,也能把以前的分支幹了些啥都給忘了。在後期有這些筆記也有助於對框架代碼的總體理解。
最後就是必定要有耐心,不要中途放棄,也不要拖延,看了一部分剩下的過兩個月再看的時候就全忘了。前功盡棄。遇到比較難以理解的複雜邏輯的時候要反覆看,反覆理解,強制本身去理解,強制本身去排除不正確的認知,把思路向框架開發者靠攏。沒有翻不過去的山。
若是能堅持到這裏,那麼恭喜你,Flutter框架對你來說已經不是個陌生人了。剩下的,就是再作一些事情使大家更加熟悉。
在初步掌握了框架主流程以後,此時咱們能夠回頭再去看那些分支流程。繼續豐富本身對框架的認識,同時也能夠鞏固相關知識。個人體會是學無止境,每次再回頭看框架源碼都會有新的收穫,新的認識;再去看有關框架的文檔,文章的時候也會有一些提高,好比那些地方說的對,比我本身的理解好,哪些地方可能有一些問題,和做者共同探討一下都是很是好的。
更進一步,咱們能夠多問一些爲何,爲何這個地方的邏輯是這樣的?框架做者爲何要這樣設計?有沒有更好,更高效的辦法來實現一樣的邏輯?帶着這些問題去複習源碼會給你帶來更大的提升。
最後的最後呢。若是能把本身的收穫寫成文章分享出來那是「墜吼的」。寫文章的好處太多了,我就很少說了。誰寫誰知道。。。。。。
本文是應掘友要求寫的學習Flutter源碼的一點心得體會。其實裏面不少方法,技巧可能並不侷限於Flutter這個特定的框架,甚至不侷限於代碼。因爲是我的的一些經驗,可能有些東西說的也不是適合全部人,若是你們以爲有不對的地方或者有更好的經驗,也但願大家能分享出來咱們互相學習,一同成長。