Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 在线亚洲精品国产成人二区,久久国产精品久久,日本高清com

          整合營(yíng)銷服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢熱線:

          Flutter的原理及美團(tuán)的實(shí)踐

          文作者:少杰

          原文出處:美團(tuán)技術(shù)團(tuán)隊(duì)

          導(dǎo)讀

          Flutter是Google開發(fā)的一套全新的跨平臺(tái)、開源UI框架,支持iOS、Android系統(tǒng)開發(fā),并且是未來(lái)新操作系統(tǒng)Fuchsia的默認(rèn)開發(fā)套件。自從2017年5月發(fā)布第一個(gè)版本以來(lái),目前Flutter已經(jīng)發(fā)布了近60個(gè)版本,并且在2018年5月發(fā)布了第一個(gè)“Ready for Production Apps”的Beta 3版本,6月20日發(fā)布了第一個(gè)“Release Preview”版本。

          初識(shí)Flutter

          Flutter的目標(biāo)是使同一套代碼同時(shí)運(yùn)行在Android和iOS系統(tǒng)上,并且擁有媲美原生應(yīng)用的性能,F(xiàn)lutter甚至提供了兩套控件來(lái)適配Android和iOS(滾動(dòng)效果、字體和控件圖標(biāo)等等),為了讓App在細(xì)節(jié)處看起來(lái)更像原生應(yīng)用。

          在Flutter誕生之前,已經(jīng)有許多跨平臺(tái)UI框架的方案,比如基于WebView的Cordova、AppCan等,還有使用HTML+JavaScript渲染成原生控件的React Native、Weex等。

          基于WebView的框架優(yōu)點(diǎn)很明顯,它們幾乎可以完全繼承現(xiàn)代Web開發(fā)的所有成果(豐富得多的控件庫(kù)、滿足各種需求的頁(yè)面框架、完全的動(dòng)態(tài)化、自動(dòng)化測(cè)試工具等等),當(dāng)然也包括Web開發(fā)人員,不需要太多的學(xué)習(xí)和遷移成本就可以開發(fā)一個(gè)App。同時(shí)WebView框架也有一個(gè)致命(在對(duì)體驗(yàn)&性能有較高要求的情況下)的缺點(diǎn),那就是WebView的渲染效率和JavaScript執(zhí)行性能太差。再加上Android各個(gè)系統(tǒng)版本和設(shè)備廠商的定制,很難保證所在所有設(shè)備上都能提供一致的體驗(yàn)。

          為了解決WebView性能差的問(wèn)題,以React Native為代表的一類框架將最終渲染工作交還給了系統(tǒng),雖然同樣使用類HTML+JS的UI構(gòu)建邏輯,但是最終會(huì)生成對(duì)應(yīng)的自定義原生控件,以充分利用原生控件相對(duì)于WebView的較高的繪制效率。與此同時(shí)這種策略也將框架本身和App開發(fā)者綁在了系統(tǒng)的控件系統(tǒng)上,不僅框架本身需要處理大量平臺(tái)相關(guān)的邏輯,隨著系統(tǒng)版本變化和API的變化,開發(fā)者可能也需要處理不同平臺(tái)的差異,甚至有些特性只能在部分平臺(tái)上實(shí)現(xiàn),這樣框架的跨平臺(tái)特性就會(huì)大打折扣。

          Flutter則開辟了一種全新的思路,從頭到尾重寫一套跨平臺(tái)的UI框架,包括UI控件、渲染邏輯甚至開發(fā)語(yǔ)言。渲染引擎依靠跨平臺(tái)的Skia圖形庫(kù)來(lái)實(shí)現(xiàn),依賴系統(tǒng)的只有圖形繪制相關(guān)的接口,可以在最大程度上保證不同平臺(tái)、不同設(shè)備的體驗(yàn)一致性,邏輯處理使用支持AOT的Dart語(yǔ)言,執(zhí)行效率也比JavaScript高得多。

          Flutter同時(shí)支持Windows、Linux和macOS操作系統(tǒng)作為開發(fā)環(huán)境,并且在Android Studio和VS Code兩個(gè)IDE上都提供了全功能的支持。Flutter所使用的Dart語(yǔ)言同時(shí)支持AOT和JIT運(yùn)行方式,JIT模式下還有一個(gè)備受歡迎的開發(fā)利器“熱刷新”(Hot Reload),即在Android Studio中編輯Dart代碼后,只需要點(diǎn)擊保存或者“Hot Reload”按鈕,就可以立即更新到正在運(yùn)行的設(shè)備上,不需要重新編譯App,甚至不需要重啟App,立即就可以看到更新后的樣式。

          在Flutter中,所有功能都可以通過(guò)組合多個(gè)Widget來(lái)實(shí)現(xiàn),包括對(duì)齊方式、按行排列、按列排列、網(wǎng)格排列甚至事件處理等等。Flutter控件主要分為兩大類,StatelessWidget和StatefulWidget,StatelessWidget用來(lái)展示靜態(tài)的文本或者圖片,如果控件需要根據(jù)外部數(shù)據(jù)或者用戶操作來(lái)改變的話,就需要使用StatefulWidget。State的概念也是來(lái)源于Facebook的流行Web框架React,React風(fēng)格的框架中使用控件樹和各自的狀態(tài)來(lái)構(gòu)建界面,當(dāng)某個(gè)控件的狀態(tài)發(fā)生變化時(shí)由框架負(fù)責(zé)對(duì)比前后狀態(tài)差異并且采取最小代價(jià)來(lái)更新渲染結(jié)果。

          Hot Reload

          在Dart代碼文件中修改字符串“Hello, World”,添加一個(gè)驚嘆號(hào),點(diǎn)擊保存或者熱刷新按鈕就可以立即更新到界面上,僅需幾百毫秒:

          Flutter通過(guò)將新的代碼注入到正在運(yùn)行的DartVM中,來(lái)實(shí)現(xiàn)Hot Reload這種神奇的效果,在DartVM將程序中的類結(jié)構(gòu)更新完成后,F(xiàn)lutter會(huì)立即重建整個(gè)控件樹,從而更新界面。但是熱刷新也有一些限制,并不是所有的代碼改動(dòng)都可以通過(guò)熱刷新來(lái)更新:

          1. 編譯錯(cuò)誤,如果修改后的Dart代碼無(wú)法通過(guò)編譯,F(xiàn)lutter會(huì)在控制臺(tái)報(bào)錯(cuò),這時(shí)需要修改對(duì)應(yīng)的代碼。
          2. 控件類型從StatelessWidget到StatefulWidget的轉(zhuǎn)換,因?yàn)镕lutter在執(zhí)行熱刷新時(shí)會(huì)保留程序原來(lái)的state,而某個(gè)控件從stageless→stateful后會(huì)導(dǎo)致Flutter重新創(chuàng)建控件時(shí)報(bào)錯(cuò)“myWidget is not a subtype of StatelessWidget”,而從stateful→stateless會(huì)報(bào)錯(cuò)“type 'myWidget' is not a subtype of type 'StatefulWidget' of 'newWidget'”。
          3. 全局變量和靜態(tài)成員變量,這些變量不會(huì)在熱刷新時(shí)更新。
          4. 修改了main函數(shù)中創(chuàng)建的根控件節(jié)點(diǎn),F(xiàn)lutter在熱刷新后只會(huì)根據(jù)原來(lái)的根節(jié)點(diǎn)重新創(chuàng)建控件樹,不會(huì)修改根節(jié)點(diǎn)。
          5. 某個(gè)類從普通類型轉(zhuǎn)換成枚舉類型,或者類型的泛型參數(shù)列表變化,都會(huì)使熱刷新失敗。

          熱刷新無(wú)法實(shí)現(xiàn)更新時(shí),執(zhí)行一次熱重啟(Hot Restart)就可以全量更新所有代碼,同樣不需要重啟App,區(qū)別是restart會(huì)將所有Dart代碼打包同步到設(shè)備上,并且所有狀態(tài)都會(huì)重置。

          Flutter插件

          Flutter使用的Dart語(yǔ)言無(wú)法直接調(diào)用Android系統(tǒng)提供的Java接口,這時(shí)就需要使用插件來(lái)實(shí)現(xiàn)中轉(zhuǎn)。Flutter官方提供了豐富的原生接口封裝:

          • android_alarm_manager,訪問(wèn)Android系統(tǒng)的AlertManager。
          • android_intent,構(gòu)造Android的Intent對(duì)象。
          • battery,獲取和監(jiān)聽系統(tǒng)電量變化。
          • connectivity,獲取和監(jiān)聽系統(tǒng)網(wǎng)絡(luò)連接狀態(tài)。
          • device info,獲取設(shè)備型號(hào)等信息。
          • image_picker,從設(shè)備中選取或者拍攝照片。
          • package_info,獲取App安裝包的版本等信息。
          • path_provider,獲取常用文件路徑。
          • quick_actions,App圖標(biāo)添加快捷方式,iOS的eponymous concept和Android的App Shortcuts。
          • sensors,訪問(wèn)設(shè)備的加速度和陀螺儀傳感器。
          • shared_preferences,App KV存儲(chǔ)功能。
          • url_launcher,啟動(dòng)URL,包括打電話、發(fā)短信和瀏覽網(wǎng)頁(yè)等功能。
          • video_player,播放視頻文件或者網(wǎng)絡(luò)流的控件。

          在Flutter中,依賴包由Pub倉(cāng)庫(kù)管理,項(xiàng)目依賴配置在pubspec.yaml文件中聲明即可(類似于NPM的版本聲明Pub Versioning Philosophy),對(duì)于未發(fā)布在Pub倉(cāng)庫(kù)的插件可以使用git倉(cāng)庫(kù)地址或文件路徑:

          dependencies: 
            url_launcher: ">=0.1.2 <0.2.0"
            collection: "^0.1.2"
            plugin1: 
              git: 
                url: "git://github.com/flutter/plugin1.git"
            plugin2: 
              path: ../plugin2/

          以shared_preferences為例,在pubspec中添加代碼:

          dependencies:
            flutter:
              sdk: flutter
          
            shared_preferences: "^0.4.1"

          脫字號(hào)“^”開頭的版本表示和當(dāng)前版本接口保持兼容的最新版,^1.2.3 等效于 >=1.2.3 <2.0.0 而^0.1.2 等效于 >=0.1.2 <0.2.0,添加依賴后點(diǎn)擊“Packages get”按鈕即可下載插件到本地,在代碼中添加import語(yǔ)句就可以使用插件提供的接口:

          import 'package:shared_preferences/shared_preferences.Dart';
          
          class _MyAppState extends State<MyAppCounter> {
            int _count = 0;
            static const String COUNTER_KEY = 'counter';
          
            _MyAppState() {
              init();
            }
          
            init() async {
              var pref = await SharedPreferences.getInstance();
              _count = pref.getInt(COUNTER_KEY) ?? 0;
              setState(() {});
            }
          
            increaseCounter() async {
              SharedPreferences pref = await SharedPreferences.getInstance();
              pref.setInt(COUNTER_KEY, ++_count);
              setState(() {});
            }
          ...

          Dart

          Dart是一種強(qiáng)類型、跨平臺(tái)的客戶端開發(fā)語(yǔ)言。具有專門為客戶端優(yōu)化、高生產(chǎn)力、快速高效、可移植(兼容ARM/x86)、易學(xué)的OO編程風(fēng)格和原生支持響應(yīng)式編程(Stream & Future)等優(yōu)秀特性。Dart主要由Google負(fù)責(zé)開發(fā)和維護(hù),在2011年10啟動(dòng)項(xiàng)目,2017年9月發(fā)布第一個(gè)2.0-dev版本。

          Dart本身提供了三種運(yùn)行方式:

          1. 使用Dart2js編譯成JavaScript代碼,運(yùn)行在常規(guī)瀏覽器中(Dart Web)。
          2. 使用DartVM直接在命令行中運(yùn)行Dart代碼(DartVM)。
          3. AOT方式編譯成機(jī)器碼,例如Flutter App框架(Flutter)。

          Flutter在篩選了20多種語(yǔ)言后,最終選擇Dart作為開發(fā)語(yǔ)言主要有幾個(gè)原因:

          1. 健全的類型系統(tǒng),同時(shí)支持靜態(tài)類型檢查和運(yùn)行時(shí)類型檢查。
          2. 代碼體積優(yōu)化(Tree Shaking),編譯時(shí)只保留運(yùn)行時(shí)需要調(diào)用的代碼(不允許反射這樣的隱式引用),所以龐大的Widgets庫(kù)不會(huì)造成發(fā)布體積過(guò)大。
          3. 豐富的底層庫(kù),Dart自身提供了非常多的庫(kù)。
          4. 多生代無(wú)鎖垃圾回收器,專門為UI框架中常見(jiàn)的大量Widgets對(duì)象創(chuàng)建和銷毀優(yōu)化。
          5. 跨平臺(tái),iOS和Android共用一套代碼。
          6. JIT & AOT運(yùn)行模式,支持開發(fā)時(shí)的快速迭代和正式發(fā)布后最大程度發(fā)揮硬件性能。

          在Dart中,有一些重要的基本概念需要了解:

          • 所有變量的值都是對(duì)象,也就是類的實(shí)例。甚至數(shù)字、函數(shù)和null也都是對(duì)象,都繼承自O(shè)bject類。
          • 雖然Dart是強(qiáng)類型語(yǔ)言,但是顯式變量類型聲明是可選的,Dart支持類型推斷。如果不想使用類型推斷,可以用dynamic類型。
          • Dart支持泛型,List表示包含int類型的列表,List則表示包含任意類型的列表。
          • Dart支持頂層(top-level)函數(shù)和類成員函數(shù),也支持嵌套函數(shù)和本地函數(shù)。
          • Dart支持頂層變量和類成員變量。
          • Dart沒(méi)有public、protected和private這些關(guān)鍵字,使用下劃線“_”開頭的變量或者函數(shù),表示只在庫(kù)內(nèi)可見(jiàn)。參考庫(kù)和可見(jiàn)性。

          DartVM的內(nèi)存分配策略非常簡(jiǎn)單,創(chuàng)建對(duì)象時(shí)只需要在現(xiàn)有堆上移動(dòng)指針,內(nèi)存增長(zhǎng)始終是線形的,省去了查找可用內(nèi)存段的過(guò)程:

          Dart中類似線程的概念叫做Isolate,每個(gè)Isolate之間是無(wú)法共享內(nèi)存的,所以這種分配策略可以讓Dart實(shí)現(xiàn)無(wú)鎖的快速分配。

          Dart的垃圾回收也采用了多生代算法,新生代在回收內(nèi)存時(shí)采用了“半空間”算法,觸發(fā)垃圾回收時(shí)Dart會(huì)將當(dāng)前半空間中的“活躍”對(duì)象拷貝到備用空間,然后整體釋放當(dāng)前空間的所有內(nèi)存:

          整個(gè)過(guò)程中Dart只需要操作少量的“活躍”對(duì)象,大量的沒(méi)有引用的“死亡”對(duì)象則被忽略,這種算法也非常適合Flutter框架中大量Widget重建的場(chǎng)景。

          Flutter Framework

          Flutter的框架部分完全使用Dart語(yǔ)言實(shí)現(xiàn),并且有著清晰的分層架構(gòu)。分層架構(gòu)使得我們可以在調(diào)用Flutter提供的便捷開發(fā)功能(預(yù)定義的一套高質(zhì)量Material控件)之外,還可以直接調(diào)用甚至修改每一層實(shí)現(xiàn)(因?yàn)檎麄€(gè)框架都屬于“用戶空間”的代碼),這給我們提供了最大程度的自定義能力。Framework底層是Flutter引擎,引擎主要負(fù)責(zé)圖形繪制(Skia)、文字排版(libtxt)和提供Dart運(yùn)行時(shí),引擎全部使用C++實(shí)現(xiàn),F(xiàn)ramework層使我們可以用Dart語(yǔ)言調(diào)用引擎的強(qiáng)大能力。

          分層架構(gòu)

          Framework的最底層叫做Foundation,其中定義的大都是非常基礎(chǔ)的、提供給其他所有層使用的工具類和方法。繪制庫(kù)(Painting)封裝了Flutter Engine提供的繪制接口,主要是為了在繪制控件等固定樣式的圖形時(shí)提供更直觀、更方便的接口,比如繪制縮放后的位圖、繪制文本、插值生成陰影以及在盒子周圍繪制邊框等等。

          Animation是動(dòng)畫相關(guān)的類,提供了類似Android系統(tǒng)的ValueAnimator的功能,并且提供了豐富的內(nèi)置插值器。Gesture提供了手勢(shì)識(shí)別相關(guān)的功能,包括觸摸事件類定義和多種內(nèi)置的手勢(shì)識(shí)別器。GestureBinding類是Flutter中處理手勢(shì)的抽象服務(wù)類,繼承自BindingBase類。

          Binding系列的類在Flutter中充當(dāng)著類似于Android中的SystemService系列(ActivityManager、PackageManager)功能,每個(gè)Binding類都提供一個(gè)服務(wù)的單例對(duì)象,App最頂層的Binding會(huì)包含所有相關(guān)的Bingding抽象類。如果使用Flutter提供的控件進(jìn)行開發(fā),則需要使用WidgetsFlutterBinding,如果不使用Flutter提供的任何控件,而直接調(diào)用Render層,則需要使用RenderingFlutterBinding。

          Flutter本身支持Android和iOS兩個(gè)平臺(tái),除了性能和開發(fā)語(yǔ)言上的“native”化之外,它還提供了兩套設(shè)計(jì)語(yǔ)言的控件實(shí)現(xiàn)Material & Cupertino,可以幫助App更好地在不同平臺(tái)上提供原生的用戶體驗(yàn)。

          渲染庫(kù)(Rendering)

          Flutter的控件樹在實(shí)際顯示時(shí)會(huì)轉(zhuǎn)換成對(duì)應(yīng)的渲染對(duì)象(RenderObject)樹來(lái)實(shí)現(xiàn)布局和繪制操作。一般情況下,我們只會(huì)在調(diào)試布局,或者需要使用自定義控件來(lái)實(shí)現(xiàn)某些特殊效果的時(shí)候,才需要考慮渲染對(duì)象樹的細(xì)節(jié)。渲染庫(kù)主要提供的功能類有:

          abstract class RendererBinding extends BindingBase with ServicesBinding, SchedulerBinding, HitTestable { ... }
          abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin implements HitTestTarget {
          abstract class RenderBox extends RenderObject { ... }
          class RenderParagraph extends RenderBox { ... }
          class RenderImage extends RenderBox { ... }
          class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, FlexParentData>,
                                                  RenderBoxContainerDefaultsMixin<RenderBox, FlexParentData>,
                                                  DebugOverflowIndicatorMixin { ... }

          RendererBinding是渲染樹和Flutter引擎的膠水層,負(fù)責(zé)管理幀重繪、窗口尺寸和渲染相關(guān)參數(shù)變化的監(jiān)聽。RenderObject渲染樹中所有節(jié)點(diǎn)的基類,定義了布局、繪制和合成相關(guān)的接口。RenderBox和其三個(gè)常用的子類RenderParagraph、RenderImage、RenderFlex則是具體布局和繪制邏輯的實(shí)現(xiàn)類。

          在Flutter界面渲染過(guò)程分為三個(gè)階段:布局、繪制、合成,布局和繪制在Flutter框架中完成,合成則交由引擎負(fù)責(zé):

          控件樹中的每個(gè)控件通過(guò)實(shí)現(xiàn)RenderObjectWidget#createRenderObject(BuildContext context) → RenderObject方法來(lái)創(chuàng)建對(duì)應(yīng)的不同類型的RenderObject對(duì)象,組成渲染對(duì)象樹。因?yàn)镕lutter極大地簡(jiǎn)化了布局的邏輯,所以整個(gè)布局過(guò)程中只需要深度遍歷一次:

          渲染對(duì)象樹中的每個(gè)對(duì)象都會(huì)在布局過(guò)程中接受父對(duì)象的Constraints參數(shù),決定自己的大小,然后父對(duì)象就可以按照自己的邏輯決定各個(gè)子對(duì)象的位置,完成布局過(guò)程。

          子對(duì)象不存儲(chǔ)自己在容器中的位置,所以在它的位置發(fā)生改變時(shí)并不需要重新布局或者繪制。子對(duì)象的位置信息存儲(chǔ)在它自己的parentData字段中,但是該字段由它的父對(duì)象負(fù)責(zé)維護(hù),自身并不關(guān)心該字段的內(nèi)容。同時(shí)也因?yàn)檫@種簡(jiǎn)單的布局邏輯,F(xiàn)lutter可以在某些節(jié)點(diǎn)設(shè)置布局邊界(Relayout boundary),即當(dāng)邊界內(nèi)的任何對(duì)象發(fā)生重新布局時(shí),不會(huì)影響邊界外的對(duì)象,反之亦然:

          布局完成后,渲染對(duì)象樹中的每個(gè)節(jié)點(diǎn)都有了明確的尺寸和位置,F(xiàn)lutter會(huì)把所有對(duì)象繪制到不同的圖層上:

          因?yàn)槔L制節(jié)點(diǎn)時(shí)也是深度遍歷,可以看到第二個(gè)節(jié)點(diǎn)在繪制它的背景和前景不得不繪制在不同的圖層上,因?yàn)榈谒膫€(gè)節(jié)點(diǎn)切換了圖層(因?yàn)椤?”節(jié)點(diǎn)是一個(gè)需要獨(dú)占一個(gè)圖層的內(nèi)容,比如視頻),而第六個(gè)節(jié)點(diǎn)也一起繪制到了紅色圖層。這樣會(huì)導(dǎo)致第二個(gè)節(jié)點(diǎn)的前景(也就是“5”)部分需要重繪時(shí),和它在邏輯上毫不相干但是處于同一圖層的第六個(gè)節(jié)點(diǎn)也必須重繪。為了避免這種情況,F(xiàn)lutter提供了另外一個(gè)“重繪邊界”的概念:

          在進(jìn)入和走出重繪邊界時(shí),F(xiàn)lutter會(huì)強(qiáng)制切換新的圖層,這樣就可以避免邊界內(nèi)外的互相影響。典型的應(yīng)用場(chǎng)景就是ScrollView,當(dāng)滾動(dòng)內(nèi)容重繪時(shí),一般情況下其他內(nèi)容是不需要重繪的。雖然重繪邊界可以在任何節(jié)點(diǎn)手動(dòng)設(shè)置,但是一般不需要我們來(lái)實(shí)現(xiàn),F(xiàn)lutter提供的控件默認(rèn)會(huì)在需要設(shè)置的地方自動(dòng)設(shè)置。

          控件庫(kù)(Widgets)

          Flutter的控件庫(kù)提供了非常豐富的控件,包括最基本的文本、圖片、容器、輸入框和動(dòng)畫等等。在Flutter中“一切皆是控件”,通過(guò)組合、嵌套不同類型的控件,就可以構(gòu)建出任意功能、任意復(fù)雜度的界面。它包含的最主要的幾個(gè)類有:

          class WidgetsFlutterBinding extends BindingBase with GestureBinding, ServicesBinding, SchedulerBinding,
                      PaintingBinding, RendererBinding, WidgetsBinding { ... }
          abstract class Widget extends DiagnosticableTree { ... }
          abstract class StatelessWidget extends Widget { ... }
          abstract class StatefulWidget extends Widget { ... }
          abstract class RenderObjectWidget extends Widget { ... }
          abstract class Element extends DiagnosticableTree implements BuildContext { ... }
          class StatelessElement extends ComponentElement { ... }
          class StatefulElement extends ComponentElement { ... }
          abstract class RenderObjectElement extends Element { ... }
          ...

          基于Flutter控件系統(tǒng)開發(fā)的程序都需要使用WidgetsFlutterBinding,它是Flutter的控件框架和Flutter引擎的膠水層。Widget就是所有控件的基類,它本身所有的屬性都是只讀的。RenderObjectWidget所有的實(shí)現(xiàn)類則負(fù)責(zé)提供配置信息并創(chuàng)建具體的RenderObjectElement。Element是Flutter用來(lái)分離控件樹和真正的渲染對(duì)象的中間層,控件用來(lái)描述對(duì)應(yīng)的element屬性,控件重建后可能會(huì)復(fù)用同一個(gè)element。RenderObjectElement持有真正負(fù)責(zé)布局、繪制和碰撞測(cè)試(hit test)的RenderObject對(duì)象。

          StatelessWidget和StatefulWidget并不會(huì)直接影響RenderObject創(chuàng)建,只負(fù)責(zé)創(chuàng)建對(duì)應(yīng)的RenderObjectWidgetStatelessElement和StatefulElement也是類似的功能。

          它們之間的關(guān)系如下圖:

          如果控件的屬性發(fā)生了變化(因?yàn)榭丶膶傩允侵蛔x的,所以變化也就意味著重新創(chuàng)建了新的控件樹),但是其樹上每個(gè)節(jié)點(diǎn)的類型沒(méi)有變化時(shí),element樹和render樹可以完全重用原來(lái)的對(duì)象(因?yàn)閑lement和render object的屬性都是可變的):

          但是,如果控件樹種某個(gè)節(jié)點(diǎn)的類型發(fā)生了變化,則element樹和render樹中的對(duì)應(yīng)節(jié)點(diǎn)也需要重新創(chuàng)建:

          外賣全品類頁(yè)面實(shí)踐

          在調(diào)研了Flutter的各項(xiàng)特性和實(shí)現(xiàn)原理之后,外賣計(jì)劃灰度上線Flutter版的全品類頁(yè)面。對(duì)于將Flutter頁(yè)面作為App的一部分這種集成模式,官方并沒(méi)有提供完善的支持,所以我們首先需要了解Flutter是如何編譯、打包并且運(yùn)行起來(lái)的。

          Flutter App構(gòu)建過(guò)程

          最簡(jiǎn)單的Flutter工程至少包含兩個(gè)文件:

          運(yùn)行Flutter程序時(shí)需要對(duì)應(yīng)平臺(tái)的宿主工程,在Android上Flutter通過(guò)自動(dòng)創(chuàng)建一個(gè)Gradle項(xiàng)目來(lái)生成宿主,在項(xiàng)目目錄下執(zhí)行flutter create .,F(xiàn)lutter會(huì)創(chuàng)建ios和android兩個(gè)目錄,分別構(gòu)建對(duì)應(yīng)平臺(tái)的宿主項(xiàng)目,Android目錄內(nèi)容如下:

          此Gradle項(xiàng)目中只有一個(gè)app module,構(gòu)建產(chǎn)物即是宿主APK。Flutter在本地運(yùn)行時(shí)默認(rèn)采用Debug模式,在項(xiàng)目目錄執(zhí)行flutter run即可安裝到設(shè)備中并自動(dòng)運(yùn)行,Debug模式下Flutter使用JIT方式來(lái)執(zhí)行Dart代碼,所有的Dart代碼都會(huì)打包到APK文件中assets目錄下,由libflutter.so中提供的DartVM讀取并執(zhí)行:

          kernel_blob.bin是Flutter引擎的底層接口和Dart語(yǔ)言基本功能部分代碼:

          third_party/dart/runtime/bin/*.dart
          third_party/dart/runtime/lib/*.dart
          third_party/dart/sdk/lib/_http/*.dart
          third_party/dart/sdk/lib/async/*.dart
          third_party/dart/sdk/lib/collection/*.dart
          third_party/dart/sdk/lib/convert/*.dart
          third_party/dart/sdk/lib/core/*.dart
          third_party/dart/sdk/lib/developer/*.dart
          third_party/dart/sdk/lib/html/*.dart
          third_party/dart/sdk/lib/internal/*.dart
          third_party/dart/sdk/lib/io/*.dart
          third_party/dart/sdk/lib/isolate/*.dart
          third_party/dart/sdk/lib/math/*.dart
          third_party/dart/sdk/lib/mirrors/*.dart
          third_party/dart/sdk/lib/profiler/*.dart
          third_party/dart/sdk/lib/typed_data/*.dart
          third_party/dart/sdk/lib/vmservice/*.dart
          flutter/lib/ui/*.dart

          platform.dill則是實(shí)現(xiàn)了頁(yè)面邏輯的代碼,也包括Flutter Framework和其他由pub依賴的庫(kù)代碼:

          flutter_tutorial_2/lib/main.dart
          flutter/packages/flutter/lib/src/widgets/*.dart
          flutter/packages/flutter/lib/src/services/*.dart
          flutter/packages/flutter/lib/src/semantics/*.dart
          flutter/packages/flutter/lib/src/scheduler/*.dart
          flutter/packages/flutter/lib/src/rendering/*.dart
          flutter/packages/flutter/lib/src/physics/*.dart
          flutter/packages/flutter/lib/src/painting/*.dart
          flutter/packages/flutter/lib/src/gestures/*.dart
          flutter/packages/flutter/lib/src/foundation/*.dart
          flutter/packages/flutter/lib/src/animation/*.dart
          .pub-cache/hosted/pub.flutter-io.cn/collection-1.14.6/lib/*.dart
          .pub-cache/hosted/pub.flutter-io.cn/meta-1.1.5/lib/*.dart
          .pub-cache/hosted/pub.flutter-io.cn/shared_preferences-0.4.2/*.dart

          kernel_blob.bin和platform.dill都是由flutter_tools中的bundle.dart中調(diào)用KernelCompiler生成。

          在Release模式(flutter run --release)下,F(xiàn)lutter會(huì)使用Dart的AOT運(yùn)行模式,編譯時(shí)將Dart代碼轉(zhuǎn)換成ARM指令:

          kernel_blob.bin和platform.dill都不在打包后的APK中,取代其功能的是(isolate/vm)_snapshot_(data/instr)四個(gè)文件。snapshot文件由Flutter SDK中的flutter/bin/cache/artifacts/engine/android-arm-release/darwin-x64/gen_snapshot命令生成,vm_snapshot_*是Dart虛擬機(jī)運(yùn)行所需要的數(shù)據(jù)和代碼指令,isolate_snapshot_*則是每個(gè)isolate運(yùn)行所需要的數(shù)據(jù)和代碼指令。

          Flutter App運(yùn)行機(jī)制

          Flutter構(gòu)建出的APK在運(yùn)行時(shí)會(huì)將所有assets目錄下的資源文件解壓到App私有文件目錄中的flutter目錄下,主要包括處理字符編碼的icudtl.dat,還有Debug模式的kernel_blob.bin、platform.dill和Release模式下的4個(gè)snapshot文件。默認(rèn)情況下Flutter在Application#onCreate時(shí)調(diào)用FlutterMain#startInitialization來(lái)啟動(dòng)解壓任務(wù),然后在FlutterActivityDelegate#onCreate中調(diào)用FlutterMain#ensureInitializationComplete來(lái)等待解壓任務(wù)結(jié)束。

          Flutter在Debug模式下使用JIT執(zhí)行方式,主要是為了支持廣受歡迎的熱刷新功能:

          觸發(fā)熱刷新時(shí)Flutter會(huì)檢測(cè)發(fā)生改變的Dart文件,將其同步到App私有緩存目錄下,DartVM加載并且修改對(duì)應(yīng)的類或者方法,重建控件樹后立即可以在設(shè)備上看到效果。

          在Release模式下Flutter會(huì)直接將snapshot文件映射到內(nèi)存中執(zhí)行其中的指令:

          在Release模式下,F(xiàn)lutterActivityDelegate#onCreate中調(diào)用FlutterMain#ensureInitializationComplete方法中會(huì)將AndroidManifest中設(shè)置的snapshot(沒(méi)有設(shè)置則使用上面提到的默認(rèn)值)文件名等運(yùn)行參數(shù)設(shè)置到對(duì)應(yīng)的C++同名類對(duì)象中,構(gòu)造FlutterNativeView實(shí)例時(shí)調(diào)用nativeAttach來(lái)初始化DartVM,運(yùn)行編譯好的Dart代碼。

          打包Android Library

          了解Flutter項(xiàng)目的構(gòu)建和運(yùn)行機(jī)制后,我們就可以按照其需求打包成AAR然后集成到現(xiàn)有原生App中了。首先在andorid/app/build.gradle中修改:

          簡(jiǎn)單修改后我們就可以使用Android Studio或者Gradle命令行工具將Flutter代碼打包到aar中了。Flutter運(yùn)行時(shí)所需要的資源都會(huì)包含在aar中,將其發(fā)布到maven服務(wù)器或者本地maven倉(cāng)庫(kù)后,就可以在原生App項(xiàng)目中引用。

          但這只是集成的第一步,為了讓Flutter頁(yè)面無(wú)縫銜接到外賣App中,我們需要做的還有很多。

          圖片資源復(fù)用

          Flutter默認(rèn)將所有的圖片資源文件打包到assets目錄下,但是我們并不是用Flutter開發(fā)全新的頁(yè)面,圖片資源原來(lái)都會(huì)按照Android的規(guī)范放在各個(gè)drawable目錄,即使是全新的頁(yè)面也會(huì)有很多圖片資源復(fù)用的場(chǎng)景,所以在assets目錄下新增圖片資源并不合適。

          Flutter官方并沒(méi)有提供直接調(diào)用drawable目錄下的圖片資源的途徑,畢竟drawable這類文件的處理會(huì)涉及大量的Android平臺(tái)相關(guān)的邏輯(屏幕密度、系統(tǒng)版本、語(yǔ)言等等),assets目錄文件的讀取操作也在引擎內(nèi)部使用C++實(shí)現(xiàn),在Dart層面實(shí)現(xiàn)讀取drawable文件的功能比較困難。Flutter在處理assets目錄中的文件時(shí)也支持添加多倍率的圖片資源,并能夠在使用時(shí)自動(dòng)選擇,但是Flutter要求每個(gè)圖片必須提供1x圖,然后才會(huì)識(shí)別到對(duì)應(yīng)的其他倍率目錄下的圖片:

          flutter:
            assets:
              - images/cat.png
              - images/2x/cat.png
              - images/3.5x/cat.png
          new Image.asset('images/cat.png');

          這樣配置后,才能正確地在不同分辨率的設(shè)備上使用對(duì)應(yīng)密度的圖片。但是為了減小APK包體積我們的位圖資源一般只提供常用的2x分辨率,其他分辨率的設(shè)備會(huì)在運(yùn)行時(shí)自動(dòng)縮放到對(duì)應(yīng)大小。針對(duì)這種特殊的情況,我們?cè)诓辉黾影w積的前提下,同樣提供了和原生App一樣的能力:

          1. 在調(diào)用Flutter頁(yè)面之前將指定的圖片資源按照設(shè)備屏幕密度縮放,并存儲(chǔ)在App私有目錄下。
          2. Flutter中使用時(shí)通過(guò)自定義的WMImage控件來(lái)加載,實(shí)際是通過(guò)轉(zhuǎn)換成FileImage并自動(dòng)設(shè)置scale為devicePixelRatio來(lái)加載。

          這樣就可以同時(shí)解決APK包大小和圖片資源缺失1x圖的問(wèn)題。

          Flutter和原生代碼的通信

          我們只用Flutter實(shí)現(xiàn)了一個(gè)頁(yè)面,現(xiàn)有的大量邏輯都是用Java實(shí)現(xiàn),在運(yùn)行時(shí)會(huì)有許多場(chǎng)景必須使用原生應(yīng)用中的邏輯和功能,例如網(wǎng)絡(luò)請(qǐng)求,我們統(tǒng)一的網(wǎng)絡(luò)庫(kù)會(huì)在每個(gè)網(wǎng)絡(luò)請(qǐng)求中添加許多通用參數(shù),也會(huì)負(fù)責(zé)成功率等指標(biāo)的監(jiān)控,還有異常上報(bào),我們需要在捕獲到關(guān)鍵異常時(shí)將其堆棧和環(huán)境信息上報(bào)到服務(wù)器。這些功能不太可能立即使用Dart實(shí)現(xiàn)一套出來(lái),所以我們需要使用Dart提供的Platform Channel功能來(lái)實(shí)現(xiàn)Dart→Java之間的互相調(diào)用。

          以網(wǎng)絡(luò)請(qǐng)求為例,我們?cè)贒art中定義一個(gè)MethodChannel對(duì)象:

          import 'dart:async';
          import 'package:flutter/services.dart';
          const MethodChannel _channel = const MethodChannel('com.sankuai.waimai/network');
          Future<Map<String, dynamic>> post(String path, [Map<String, dynamic> form]) async {
            return _channel.invokeMethod("post", {'path': path, 'body': form}).then((result) {
              return new Map<String, dynamic>.from(result);
            }).catchError((_) => null);
          }

          然后在Java端實(shí)現(xiàn)相同名稱的MethodChannel:

          public class FlutterNetworkPlugin implements MethodChannel.MethodCallHandler {
          
              private static final String CHANNEL_NAME = "com.sankuai.waimai/network";
          
              @Override
              public void onMethodCall(MethodCall methodCall, final MethodChannel.Result result) {
                  switch (methodCall.method) {
                      case "post":
                          RetrofitManager.performRequest(post((String) methodCall.argument("path"), (Map) methodCall.argument("body")),
                                  new DefaultSubscriber<Map>() {
                                      @Override
                                      public void onError(Throwable e) {
                                          result.error(e.getClass().getCanonicalName(), e.getMessage(), null);
                                      }
          
                                      @Override
                                      public void onNext(Map stringBaseResponse) {
                                          result.success(stringBaseResponse);
                                      }
                                  }, tag);
                          break;
          
                      default:
                          result.notImplemented();
                          break;
                  }
              }
          }

          在Flutter頁(yè)面中注冊(cè)后,調(diào)用post方法就可以調(diào)用對(duì)應(yīng)的Java實(shí)現(xiàn):

          loadData: (callback) async {
              Map<String, dynamic> data = await post("home/groups");
              if (data == null) {
                callback(false);
                return;
              }
              _data = AllCategoryResponse.fromJson(data);
              if (_data == null || _data.code != 0) {
                callback(false);
                return;
              }
              callback(true);
            }),

          SO庫(kù)兼容性

          Flutter官方只提供了四種CPU架構(gòu)的SO庫(kù):armeabi-v7a、arm64-v8a、x86和x86-64,其中x86系列只支持Debug模式,但是外賣使用的大量SDK都只提供了armeabi架構(gòu)的庫(kù)。

          雖然我們可以通過(guò)修改引擎src根目錄和third_party/dart目錄下build/config/arm.gni,third_party/skia目錄下的BUILD.gn等配置文件來(lái)編譯出armeabi版本的Flutter引擎,但是實(shí)際上市面上絕大部分設(shè)備都已經(jīng)支持armeabi-v7a,其提供的硬件加速浮點(diǎn)運(yùn)算指令可以大大提高Flutter的運(yùn)行速度,在灰度階段我們可以主動(dòng)屏蔽掉不支持armeabi-v7a的設(shè)備,直接使用armeabi-v7a版本的引擎。

          做到這點(diǎn)我們首先需要修改Flutter提供的引擎,在Flutter安裝目錄下的bin/cache/artifacts/engine下有Flutter下載的所有平臺(tái)的引擎:

          我們只需要修改android-arm、android-arm-profile和android-arm-release下的flutter.jar,將其中的lib/armeabi-v7a/libflutter.so移動(dòng)到lib/armeabi/libflutter.so即可:

          cd $FLUTTER_ROOT/bin/cache/artifacts/engine
          for arch in android-arm android-arm-profile android-arm-release; do
            pushd $arch
            cp flutter.jar flutter-armeabi-v7a.jar # 備份
            unzip flutter.jar lib/armeabi-v7a/libflutter.so
            mv lib/armeabi-v7a lib/armeabi
            zip -d flutter.jar lib/armeabi-v7a/libflutter.so
            zip flutter.jar lib/armeabi/libflutter.so
            popd
          done

          這樣在打包后Flutter的SO庫(kù)就會(huì)打到APK的lib/armeabi目錄中。在運(yùn)行時(shí)如果設(shè)備不支持armeabi-v7a可能會(huì)崩潰,所以我們需要主動(dòng)識(shí)別并屏蔽掉這類設(shè)備,在Android上判斷設(shè)備是否支持armeabi-v7a也很簡(jiǎn)單:

          public static boolean isARMv7Compatible() {
              try {
                  if (SDK_INT >= LOLLIPOP) {
                      for (String abi : Build.SUPPORTED_32_BIT_ABIS) {
                          if (abi.equals("armeabi-v7a")) {
                              return true;
                          }
                      }
                  } else {
                      if (CPU_ABI.equals("armeabi-v7a") || CPU_ABI.equals("arm64-v8a")) {
                          return true;
                      }
                  }
              } catch (Throwable e) {
                  L.wtf(e);
              }
              return false;
          }

          灰度和自動(dòng)降級(jí)策略

          Horn是一個(gè)美團(tuán)內(nèi)部的跨平臺(tái)配置下發(fā)SDK,使用Horn可以很方便地指定灰度開關(guān):

          在條件配置頁(yè)面定義一系列條件,然后在參數(shù)配置頁(yè)面添加新的字段flutter即可:

          因?yàn)樵诳蛻舳俗隽薃BI兜底策略,所以這里定義的ABI規(guī)則并沒(méi)有啟用。

          Flutter目前仍然處于Beta階段,灰度過(guò)程中難免發(fā)生崩潰現(xiàn)象,觀察到崩潰后再針對(duì)機(jī)型或者設(shè)備ID來(lái)做降級(jí)雖然可以盡量降低影響,但是我們可以做到更迅速。外賣的Crash采集SDK同時(shí)也支持JNI Crash的收集,我們專門為Flutter注冊(cè)了崩潰監(jiān)聽器,一旦采集到Flutter相關(guān)的JNI Crash就立即停止該設(shè)備的Flutter功能,啟動(dòng)Flutter之前會(huì)先判斷FLUTTER_NATIVE_CRASH_FLAG文件是否存在,如果存在則表示該設(shè)備發(fā)生過(guò)Flutter相關(guān)的崩潰,很有可能是不兼容導(dǎo)致的問(wèn)題,當(dāng)前版本周期內(nèi)在該設(shè)備上就不再使用Flutter功能。

          除了崩潰以外,F(xiàn)lutter頁(yè)面中的Dart代碼也可能發(fā)生異常,例如服務(wù)器下發(fā)數(shù)據(jù)格式錯(cuò)誤導(dǎo)致解析失敗等等,Dart也提供了全局的異常捕獲功能:

          import 'package:wm_app/plugins/wm_metrics.dart';
          
          void main() {
            runZoned(() => runApp(WaimaiApp()), onError: (Object obj, StackTrace stack) {
              uploadException("$obj\n$stack");
            });
          }

          這樣我們就可以實(shí)現(xiàn)全方位的異常監(jiān)控和完善的降級(jí)策略,最大程度減少灰度時(shí)可能對(duì)用戶帶來(lái)的影響。

          分析崩潰堆棧和異常數(shù)據(jù)

          Flutter的引擎部分全部使用C/C++實(shí)現(xiàn),為了減少包大小,所有的SO庫(kù)在發(fā)布時(shí)都會(huì)去除符號(hào)表信息。和其他的JNI崩潰堆棧一樣,我們上報(bào)的堆棧信息中只能看到內(nèi)存地址偏移量等信息:

          *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
          Build fingerprint: 'Rock/odin/odin:7.1.1/NMF26F/1527007828:user/dev-keys'
          Revision: '0'
          Author: collect by 'libunwind'
          ABI: 'arm64-v8a'
          pid: 28937, tid: 29314, name: 1.ui  >>> com.sankuai.meituan.takeoutnew <<<
          signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0
          
          backtrace:
              r0 00000000  r1 ffffffff  r2 c0e7cb2c  r3 c15affcc
              r4 c15aff88  r5 c0e7cb2c  r6 c15aff90  r7 bf567800
              r8 c0e7cc58  r9 00000000  sl c15aff0c  fp 00000001
              ip 80000000  sp c0e7cb28  lr c11a03f9  pc c1254088  cpsr 200c0030
              #00 pc 002d7088  /data/app/com.sankuai.meituan.takeoutnew-1/lib/arm/libflutter.so
              #01 pc 002d5a23  /data/app/com.sankuai.meituan.takeoutnew-1/lib/arm/libflutter.so
              #02 pc 002d95b5  /data/app/com.sankuai.meituan.takeoutnew-1/lib/arm/libflutter.so
              #03 pc 002d9f33  /data/app/com.sankuai.meituan.takeoutnew-1/lib/arm/libflutter.so
              #04 pc 00068e6d  /data/app/com.sankuai.meituan.takeoutnew-1/lib/arm/libflutter.so
              #05 pc 00067da5  /data/app/com.sankuai.meituan.takeoutnew-1/lib/arm/libflutter.so
              #06 pc 00067d5f  /data/app/com.sankuai.meituan.takeoutnew-1/lib/arm/libflutter.so
              #07 pc 003b1877  /data/app/com.sankuai.meituan.takeoutnew-1/lib/arm/libflutter.so
              #08 pc 003b1db5  /data/app/com.sankuai.meituan.takeoutnew-1/lib/arm/libflutter.so
              #09 pc 0000241c  /data/data/com.sankuai.meituan.takeoutnew/app_flutter/vm_snapshot_instr

          單純這些信息很難定位問(wèn)題,所以我們需要使用NDK提供的ndk-stack來(lái)解析出具體的代碼位置:

          ndk-stack -sym PATH [-dump PATH]
          Symbolizes the stack trace from an Android native crash.
            -sym PATH   sets the root directory for symbols
            -dump PATH  sets the file containing the crash dump (default stdin)

          如果使用了定制過(guò)的引擎,必須使用engine/src/out/android-release下編譯出的libflutter.so文件。一般情況下我們使用的是官方版本的引擎,可以在flutter_infra頁(yè)面直接下載帶有符號(hào)表的SO文件,根據(jù)打包時(shí)使用的Flutter工具版本下載對(duì)應(yīng)的文件即可。比如0.4.4 beta版本:

          $ flutter --version # version命令可以看到Engine對(duì)應(yīng)的版本 06afdfe54e
          Flutter 0.4.4 ? channel beta ? https://github.com/flutter/flutter.git
          Framework ? revision f9bb4289e9 (5 weeks ago) ? 2018-05-11 21:44:54 -0700
          Engine ? revision 06afdfe54e
          Tools ? Dart 2.0.0-dev.54.0.flutter-46ab040e58
          $ cat flutter/bin/internal/engine.version # flutter安裝目錄下的engine.version文件也可以看到完整的版本信息 06afdfe54ebef9168a90ca00a6721c2d36e6aafa
          06afdfe54ebef9168a90ca00a6721c2d36e6aafa

          拿到引擎版本號(hào)后在https://console.cloud.google.com/storage/browser/flutter_infra/flutter/06afdfe54ebef9168a90ca00a6721c2d36e6aafa/ 看到該版本對(duì)應(yīng)的所有構(gòu)建產(chǎn)物,下載android-arm-release、android-arm64-release和android-x86目錄下的symbols.zip,并存放到對(duì)應(yīng)目錄:

          執(zhí)行ndk-stack即可看到實(shí)際發(fā)生崩潰的代碼和具體行數(shù)信息:

          ndk-stack -sym flutter-production-syms/06afdfe54ebef9168a90ca00a6721c2d36e6aafa/armeabi-v7a -dump flutter_jni_crash.txt 
          ********** Crash dump: **********
          Build fingerprint: 'Rock/odin/odin:7.1.1/NMF26F/1527007828:user/dev-keys'
          pid: 28937, tid: 29314, name: 1.ui  >>> com.sankuai.meituan.takeoutnew <<<
          signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0
          Stack frame #00 pc 002d7088  /data/app/com.sankuai.meituan.takeoutnew-1/lib/arm/libflutter.so: Routine minikin::WordBreaker::setText(unsigned short const*, unsigned int) at /b/build/slave/Linux_Engine/build/src/out/android_release/../../flutter/third_party/txt/src/minikin/WordBreaker.cpp:55
          Stack frame #01 pc 002d5a23  /data/app/com.sankuai.meituan.takeoutnew-1/lib/arm/libflutter.so: Routine minikin::LineBreaker::setText() at /b/build/slave/Linux_Engine/build/src/out/android_release/../../flutter/third_party/txt/src/minikin/LineBreaker.cpp:74
          Stack frame #02 pc 002d95b5  /data/app/com.sankuai.meituan.takeoutnew-1/lib/arm/libflutter.so: Routine txt::Paragraph::ComputeLineBreaks() at /b/build/slave/Linux_Engine/build/src/out/android_release/../../flutter/third_party/txt/src/txt/paragraph.cc:273
          Stack frame #03 pc 002d9f33  /data/app/com.sankuai.meituan.takeoutnew-1/lib/arm/libflutter.so: Routine txt::Paragraph::Layout(double, bool) at /b/build/slave/Linux_Engine/build/src/out/android_release/../../flutter/third_party/txt/src/txt/paragraph.cc:428
          Stack frame #04 pc 00068e6d  /data/app/com.sankuai.meituan.takeoutnew-1/lib/arm/libflutter.so: Routine blink::ParagraphImplTxt::layout(double) at /b/build/slave/Linux_Engine/build/src/out/android_release/../../flutter/lib/ui/text/paragraph_impl_txt.cc:54
          Stack frame #05 pc 00067da5  /data/app/com.sankuai.meituan.takeoutnew-1/lib/arm/libflutter.so: Routine tonic::DartDispatcher<tonic::IndicesHolder<0u>, void (blink::Paragraph::*)(double)>::Dispatch(void (blink::Paragraph::*)(double)) at /b/build/slave/Linux_Engine/build/src/out/android_release/../../topaz/lib/tonic/dart_args.h:150
          Stack frame #06 pc 00067d5f  /data/app/com.sankuai.meituan.takeoutnew-1/lib/arm/libflutter.so: Routine void tonic::DartCall<void (blink::Paragraph::*)(double)>(void (blink::Paragraph::*)(double), _Dart_NativeArguments*) at /b/build/slave/Linux_Engine/build/src/out/android_release/../../topaz/lib/tonic/dart_args.h:198
          Stack frame #07 pc 003b1877  /data/app/com.sankuai.meituan.takeoutnew-1/lib/arm/libflutter.so: Routine dart::NativeEntry::AutoScopeNativeCallWrapperNoStackCheck(_Dart_NativeArguments*, void (*)(_Dart_NativeArguments*)) at /b/build/slave/Linux_Engine/build/src/out/android_release/../../third_party/dart/runtime/vm/native_entry.cc:198
          Stack frame #08 pc 003b1db5  /data/app/com.sankuai.meituan.takeoutnew-1/lib/arm/libflutter.so: Routine dart::NativeEntry::LinkNativeCall(_Dart_NativeArguments*) at /b/build/slave/Linux_Engine/build/src/out/android_release/../../third_party/dart/runtime/vm/native_entry.cc:348
          Stack frame #09 pc 0000241c  /data/data/com.sankuai.meituan.takeoutnew/app_flutter/vm_snapshot_instr

          Dart異常則比較簡(jiǎn)單,默認(rèn)情況下Dart代碼在編譯成機(jī)器碼時(shí)并沒(méi)有去除符號(hào)表信息,所以Dart的異常堆棧本身就可以標(biāo)識(shí)真實(shí)發(fā)生異常的代碼文件和行數(shù)信息:

          FlutterException: type '_InternalLinkedHashMap<dynamic, dynamic>' is not a subtype of type 'num' in type cast
          #0      _$CategoryGroupFromJson (package:wm_app/lib/all_category/model/category_model.g.dart:29)
          #1      new CategoryGroup.fromJson (package:wm_app/all_category/model/category_model.dart:51)
          #2      _$CategoryListDataFromJson.<anonymous closure> (package:wm_app/lib/all_category/model/category_model.g.dart:5)
          #3      MappedListIterable.elementAt (dart:_internal/iterable.dart:414)
          #4      ListIterable.toList (dart:_internal/iterable.dart:219)
          #5      _$CategoryListDataFromJson (package:wm_app/lib/all_category/model/category_model.g.dart:6)
          #6      new CategoryListData.fromJson (package:wm_app/all_category/model/category_model.dart:19)
          #7      _$AllCategoryResponseFromJson (package:wm_app/lib/all_category/model/category_model.g.dart:19)
          #8      new AllCategoryResponse.fromJson (package:wm_app/all_category/model/category_model.dart:29)
          #9      AllCategoryPage.build.<anonymous closure> (package:wm_app/all_category/category_page.dart:46)
          <asynchronous suspension>
          #10     _WaimaiLoadingState.build (package:wm_app/all_category/widgets/progressive_loading_page.dart:51)
          #11     StatefulElement.build (package:flutter/src/widgets/framework.dart:3730)
          #12     ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:3642)
          #13     Element.rebuild (package:flutter/src/widgets/framework.dart:3495)
          #14     BuildOwner.buildScope (package:flutter/src/widgets/framework.dart:2242)
          #15     _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding&PaintingBinding&RendererBinding&WidgetsBinding.drawFrame (package:flutter/src/widgets/binding.dart:626)
          #16     _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding&PaintingBinding&RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:208)
          #17     _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:990)
          #18     _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:930)
          #19     _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding._handleDrawFrame (package:flutter/src/scheduler/binding.dart:842)
          #20     _rootRun (dart:async/zone.dart:1126)
          #21     _CustomZone.run (dart:async/zone.dart:1023)
          #22     _CustomZone.runGuarded (dart:async/zone.dart:925)
          #23     _invoke (dart:ui/hooks.dart:122)
          #24     _drawFrame (dart:ui/hooks.dart:109)

          Flutter和原生性能對(duì)比

          雖然使用原生實(shí)現(xiàn)(左)和Flutter實(shí)現(xiàn)(右)的全品類頁(yè)面在實(shí)際使用過(guò)程中幾乎分辨不出來(lái):

          但是我們還需要在性能方面有一個(gè)比較明確的數(shù)據(jù)對(duì)比。

          我們最關(guān)心的兩個(gè)頁(yè)面性能指標(biāo)就是頁(yè)面加載時(shí)間和頁(yè)面渲染速度。測(cè)試頁(yè)面加載速度可以直接使用美團(tuán)內(nèi)部的Metrics性能測(cè)試工具,我們將頁(yè)面Activity對(duì)象創(chuàng)建作為頁(yè)面加載的開始時(shí)間,頁(yè)面API數(shù)據(jù)返回作為頁(yè)面加載結(jié)束時(shí)間。

          從兩個(gè)實(shí)現(xiàn)的頁(yè)面分別啟動(dòng)400多次的數(shù)據(jù)中可以看到,原生實(shí)現(xiàn)(AllCategoryActivity)的加載時(shí)間中位數(shù)為210ms,F(xiàn)lutter實(shí)現(xiàn)(FlutterCategoryActivity)的加載時(shí)間中位數(shù)為231ms。考慮到目前我們還沒(méi)有針對(duì)FlutterView做緩存和重用,F(xiàn)lutterView每次創(chuàng)建都需要初始化整個(gè)Flutter環(huán)境并加載相關(guān)代碼,多出的20ms還在預(yù)期范圍內(nèi):

          因?yàn)镕lutter的UI邏輯和繪制代碼都不在主線程執(zhí)行,Metrics原有的FPS功能無(wú)法統(tǒng)計(jì)到Flutter頁(yè)面的真實(shí)情況,我們需要用特殊方法來(lái)對(duì)比兩種實(shí)現(xiàn)的渲染效率。Android原生實(shí)現(xiàn)的界面渲染耗時(shí)使用系統(tǒng)提供的FrameMetrics接口進(jìn)行監(jiān)控:

          public class AllCategoryActivity extends WmBaseActivity {
          
              @Override
              protected void onCreate(Bundle savedInstanceState) {
                  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                      getWindow().addOnFrameMetricsAvailableListener(new Window.OnFrameMetricsAvailableListener() {
                          List<Integer> frameDurations = new ArrayList<>(100);
                          @Override
                          public void onFrameMetricsAvailable(Window window, FrameMetrics frameMetrics, int dropCountSinceLastInvocation) {
                              frameDurations.add((int) (frameMetrics.getMetric(TOTAL_DURATION) / 1000000));
                              if (frameDurations.size() == 100) {
                                  getWindow().removeOnFrameMetricsAvailableListener(this);
                                  L.w("AllCategory", Arrays.toString(frameDurations.toArray()));
                              }
                          }
                      }, new Handler(Looper.getMainLooper()));
                  }
                  super.onCreate(savedInstanceState);
                  // ...
              }
          }

          Flutter在Framework層只能取到每幀中UI操作的CPU耗時(shí),GPU操作在Flutter引擎內(nèi)部實(shí)現(xiàn),所以要修改引擎來(lái)監(jiān)控完整的渲染耗時(shí),在Flutter引擎目錄下src/flutter/shell/common/rasterizer.cc文件中添加:

          void Rasterizer::DoDraw(std::unique_ptr<flow::LayerTree> layer_tree) {
            if (!layer_tree || !surface_) {
              return;
            }
          
            if (DrawToSurface(*layer_tree)) {
              last_layer_tree_ = std::move(layer_tree);
          #if defined(OS_ANDROID)
              if (compositor_context_->frame_count().count() == 101) {
                std::ostringstream os;
                os << "[";
                const std::vector<TimeDelta> &engine_laps = compositor_context_->engine_time().Laps();
                const std::vector<TimeDelta> &frame_laps = compositor_context_->frame_time().Laps();
                size_t i = 1;
                for (auto engine_iter = engine_laps.begin() + 1, frame_iter = frame_laps.begin() + 1;
                     i < 101 && engine_iter != engine_laps.end(); i++, engine_iter++, frame_iter++) {
                  os << (*engine_iter + *frame_iter).ToMilliseconds() << ",";
                }
                os << "]";
                __android_log_write(ANDROID_LOG_WARN, "AllCategory", os.str().c_str());
              }
          #endif
            }
          }

          即可得到每幀繪制時(shí)真正消耗的時(shí)間。測(cè)試時(shí)我們將兩種實(shí)現(xiàn)的頁(yè)面分別打開100次,每次打開后執(zhí)行兩次滾動(dòng)操作,使其繪制100幀,將這100幀的每幀耗時(shí)記錄下來(lái):

          for (( i = 0; i < 100; i++ )); do
              openWMPage allcategory
              sleep 1
              adb shell input swipe 500 1000 500 300 900
              adb shell input swipe 500 1000 500 300 900
              adb shell input keyevent 4
          done

          將測(cè)試結(jié)果的100次啟動(dòng)中每幀耗時(shí)取平均値,得到每幀平均耗時(shí)情況(橫坐標(biāo)軸為幀序列,縱坐標(biāo)軸為每幀耗時(shí),單位為毫秒):

          Android原生實(shí)現(xiàn)和Flutter版本都會(huì)在頁(yè)面打開的前5幀超過(guò)16ms,剛打開頁(yè)面時(shí)原生實(shí)現(xiàn)需要?jiǎng)?chuàng)建大量View,F(xiàn)lutter也需要?jiǎng)?chuàng)建大量Widget,后續(xù)幀中可以重用大部分控件和渲染節(jié)點(diǎn)(原生的RenderNode和Flutter的RenderObject),所以啟動(dòng)時(shí)的布局和渲染操作都是最耗時(shí)的。

          10000幀(100次×100幀每次)中Android原生總平均値為10.21ms,F(xiàn)lutter總平均値為12.28ms,Android原生實(shí)現(xiàn)總丟幀數(shù)851幀8.51%,F(xiàn)lutter總丟幀987幀9.87%。在原生實(shí)現(xiàn)的觸摸事件處理和過(guò)度繪制充分優(yōu)化的前提下,F(xiàn)lutter完全可以媲美原生的性能。

          總結(jié)

          Flutter目前仍處于早期階段,也還沒(méi)有發(fā)布正式的Release版本,不過(guò)我們看到Flutter團(tuán)隊(duì)一直在為這一目標(biāo)而努力。雖然Flutter的開發(fā)生態(tài)不如Android和iOS原生應(yīng)用那么成熟,許多常用的復(fù)雜控件還需要自己實(shí)現(xiàn),有的甚至?xí)容^困難(比如官方尚未提供的ListView.scrollTo(index)功能),但是在高性能和跨平臺(tái)方面Flutter在眾多UI框架中還是有很大優(yōu)勢(shì)的。

          開發(fā)Flutter應(yīng)用只能使用Dart語(yǔ)言,Dart本身既有靜態(tài)語(yǔ)言的特性,也支持動(dòng)態(tài)語(yǔ)言的部分特性,對(duì)于Java和JavaScript開發(fā)者來(lái)說(shuō)門檻都不高,3-5天可以快速上手,大約1-2周可以熟練掌握。

          在開發(fā)全品類頁(yè)面的Flutter版本時(shí)我們也深刻體會(huì)到了Dart語(yǔ)言的魅力,Dart的語(yǔ)言特性使得Flutter的界面構(gòu)建過(guò)程也比Android原生的XML+JAVA更直觀,代碼量也從原來(lái)的900多行減少到500多行(排除掉引用的公共組件)。Flutter頁(yè)面集成到App后APK體積至少會(huì)增加5.5MB,其中包括3.3MB的SO庫(kù)文件和2.2MB的ICU數(shù)據(jù)文件,此外業(yè)務(wù)代碼1300行編譯產(chǎn)物的大小有2MB左右。

          Flutter本身的特性適合追求iOS和Android跨平臺(tái)的一致體驗(yàn),追求高性能的UI交互效果的場(chǎng)景,不適合追求動(dòng)態(tài)化部署的場(chǎng)景。Flutter在Android上已經(jīng)可以實(shí)現(xiàn)動(dòng)態(tài)化部署,但是由于Apple的限制,在iOS上實(shí)現(xiàn)動(dòng)態(tài)化部署非常困難,F(xiàn)lutter團(tuán)隊(duì)也正在和Apple積極溝通。

          于注解,相信大家都不陌生了,但是這種熟悉對(duì)于Android開發(fā)者來(lái)說(shuō),也就是僅僅是它認(rèn)識(shí)你,你不認(rèn)識(shí)它吧,因?yàn)椋覀儙缀醵纪A粼谟蒙狭恕O馾agger2,像retrofit,像greenDao,他們都是滿滿的使用了注解,而我們只是停留在知其然缺不知其所以然的層面,那么我本次分享講分成幾批博客共享給大家一個(gè)體系的又不失針對(duì)Android平臺(tái)需要的恰到好處的給大家整理真正需要掌握的知識(shí)體系。

          注解

          注解的定義

          Java 注解用于為 Java 代碼提供元數(shù)據(jù)。作為元數(shù)據(jù),注解不直接影響你的代碼執(zhí)行,但也有一些類型的注解實(shí)際上可以用于這一目的。Java 注解是從 Java5 開始添加到 Java 的。

          注解即標(biāo)簽

          如果把代碼想象成一個(gè)具有生命的個(gè)體,注解就是給這些代碼的某些個(gè)體打標(biāo)簽

          如何自定義注解

          • 注解通過(guò) @interface關(guān)鍵字進(jìn)行定義。
          public @interface Test {
          }
          

          它的形式跟接口很類似,不過(guò)前面多了一個(gè) @ 符號(hào)。上面的代碼就創(chuàng)建了一個(gè)名字為 Test 的注解。

          你可以簡(jiǎn)單理解為創(chuàng)建了一張名字為 Test的標(biāo)簽。

          • 使用注解
          @Test
          public class TestAnnotation {
          }
          

          創(chuàng)建一個(gè)類 TestAnnotation,然后在類定義的地方加上 @Test就可以用 Test注解這個(gè)類了

          你可以簡(jiǎn)單理解為將 Test 這張標(biāo)簽貼到 TestAnnotation這個(gè)類上面。

          元注解

          元注解是可以注解到注解上的注解,或者說(shuō)元注解是一種基本注解,但是它能夠應(yīng)用到其它的注解上面。

          如果難于理解的話,你可以這樣理解。元注解也是一張標(biāo)簽,但是它是一張?zhí)厥獾臉?biāo)簽,它的作用和目的就是給其他普通的標(biāo)簽進(jìn)行解釋說(shuō)明的。

          元標(biāo)簽有 @Retention、@Documented、@Target、@Inherited、@Repeatable 5 種。

          • @Retention
          • Retention 的英文意為保留期的意思。當(dāng) @Retention 應(yīng)用到一個(gè)注解上的時(shí)候,它解釋說(shuō)明了這個(gè)注解的的存活時(shí)間。
          • 它的取值如下:
          1. RetentionPolicy.SOURCE 注解只在源碼階段保留,在編譯器進(jìn)行編譯時(shí)它將被丟棄忽視。
          2. RetentionPolicy.CLASS 注解只被保留到編譯進(jìn)行的時(shí)候,它并不會(huì)被加載到 JVM 中。
          3. RetentionPolicy.RUNTIME 注解可以保留到程序運(yùn)行的時(shí)候,它會(huì)被加載進(jìn)入到 JVM 中,所以在程序運(yùn)行時(shí)可以獲取到它們
          • @Target
          • Target 是目標(biāo)的意思,@Target 指定了注解運(yùn)用的地方
          • 你可以這樣理解,當(dāng)一個(gè)注解被 @Target 注解時(shí),這個(gè)注解就被限定了運(yùn)用的場(chǎng)景。
          • 類比到標(biāo)簽,原本標(biāo)簽是你想張貼到哪個(gè)地方就到哪個(gè)地方,但是因?yàn)?@Target 的存在,它張貼的地方就非常具體了,比如只能張貼到方法上、類上、方法參數(shù)上等等。@Target 有下面的取值
          1. ElementType.ANNOTATION_TYPE 可以給一個(gè)注解進(jìn)行注解
          2. ElementType.CONSTRUCTOR 可以給構(gòu)造方法進(jìn)行注解
          3. ElementType.FIELD 可以給屬性進(jìn)行注解
          4. ElementType.LOCAL_VARIABLE 可以給局部變量進(jìn)行注解
          5. ElementType.METHOD 可以給方法進(jìn)行注解
          6. ElementType.PACKAGE 可以給一個(gè)包進(jìn)行注解
          7. ElementType.PARAMETER 可以給一個(gè)方法內(nèi)的參數(shù)進(jìn)行注解
          • @Documented
          • 顧名思義,這個(gè)元注解肯定是和文檔有關(guān)。它的作用是能夠?qū)⒆⒔庵械脑匕?Javadoc 中去。ElementType.TYPE 可以給一個(gè)類型進(jìn)行注解,比如類、接口、枚舉
          • @Inherited
          • Inherited 是繼承的意思,但是它并不是說(shuō)注解本身可以繼承,而是說(shuō)如果一個(gè)超類被 @Inherited 注解過(guò)的注解進(jìn)行注解的話,那么如果它的子類沒(méi)有被任何注解應(yīng)用的話,那么這個(gè)子類就繼承了超類的注解。
          • @Repeatable
          • Repeatable 自然是可重復(fù)的意思。@Repeatable 是 Java 1.8 才加進(jìn)來(lái)的,所以算是一個(gè)新的特性。
          • 什么樣的注解會(huì)多次應(yīng)用呢?通常是注解的值可以同時(shí)取多個(gè)。

          注解的屬性

          注解的屬性也叫做成員變量。注解只有成員變量,沒(méi)有方法。

          需要注意的是,在注解中定義屬性時(shí)它的類型必須是 8 種基本數(shù)據(jù)類型外加 類、接口、注解及它們的數(shù)組

          注解中屬性可以有默認(rèn)值,默認(rèn)值需要用 default 關(guān)鍵值指定

          @Target(ElementType.TYPE)
          @Retention(RetentionPolicy.RUNTIME)
          public @interface Test{
          int id() default -1;
          String msg() default "Hello";
          }
          

          上面代碼定義了 TestAnnotation 這個(gè)注解中擁有 id 和 msg 兩個(gè)屬性。在使用的時(shí)候,我們應(yīng)該給它們進(jìn)行賦值。

          賦值的方式是在注解的括號(hào)內(nèi)以 value="" 形式,多個(gè)屬性之前用 ,隔開

          @Test(id=1,msg="hello annotation")
          public class TestAnnotation {
          }
          

          注解的提取

          注解與反射。

          注解通過(guò)反射獲取。首先可以通過(guò) Class 對(duì)象的 isAnnotationPresent() 方法判斷它是否應(yīng)用了某個(gè)注解

          public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {}
          

          然后通過(guò) getAnnotation() 方法來(lái)獲取 Annotation 對(duì)象。

           public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {}
          

          或者是 getAnnotations() 方法。

          public Annotation[] getAnnotations() {}
          

          前一種方法返回指定類型的注解,后一種方法返回注解到這個(gè)元素上的所有注解。

          如果獲取到的 Annotation 如果不為 null,則就可以調(diào)用它們的屬性方法了。比如

          @Test()
          public class TestDemo{
          public static void main(String[] args) {
           boolean hasAnnotation = TestDemo.class.isAnnotationPresent(Test.class);
           if ( hasAnnotation ) {
           TestAnnotation testAnnotation = TestDemo.class.getAnnotation(Test.class);
           System.out.println("id:"+testAnnotation.id());
           System.out.println("msg:"+testAnnotation.msg());
           }
           }
          }
          

          注解的使用場(chǎng)景

          • 提供信息給編譯器: 編譯器可以利用注解來(lái)探測(cè)錯(cuò)誤和警告信息
          • 編譯階段時(shí)的處理: 軟件工具可以用來(lái)利用注解信息來(lái)生成代碼、Html文檔或者做其它相應(yīng)處理。
          • 運(yùn)行時(shí)的處理: 某些注解可以在程序運(yùn)行的時(shí)候接受代碼的提取
          • 值得注意的是,注解不是代碼本身的一部分。

          作為一個(gè)資歷不淺的Android開發(fā),這幾年我面試過(guò)不少人。發(fā)現(xiàn)大多數(shù)面試者,雖然看起來(lái)工作努力,但他們表現(xiàn)出來(lái)的能力水平,卻不足以通過(guò)面試,或拿不到期望的薪資。

          在我看來(lái),造成這種情況的原因,主要有這么兩方面:

          第一,“知其然不知其所以然”。做了幾年技術(shù),開發(fā)了一些業(yè)務(wù)應(yīng)用,但沒(méi)有思考過(guò)這些技術(shù)選擇背后的邏輯。所以,我很難定位他們?nèi)蘸蟮某砷L(zhǎng)潛力,也不會(huì)放心把有一定深度的任務(wù)交給他們。

          第二,知識(shí)碎片化,不成系統(tǒng)。事實(shí)上,當(dāng)面試者無(wú)法完整、清晰地描述自己所開發(fā)的系統(tǒng)或使用的相關(guān)技術(shù)時(shí),面試官就會(huì)懷疑他是否具備解決復(fù)雜問(wèn)題、設(shè)計(jì)復(fù)雜系統(tǒng)的能力。

          所以,如果你平時(shí)只知道埋頭苦干,或過(guò)于死磕某個(gè)實(shí)現(xiàn)細(xì)節(jié),沒(méi)有抬頭審視過(guò)這些技術(shù),那么在準(zhǔn)備面試時(shí),很有必要好好梳理一下 Android 知識(shí)體系,這樣才能拿下滿意的 Offer。

          那么,作為Android 開發(fā)者,該怎樣規(guī)劃自己的學(xué)習(xí)路線,然后一步一個(gè)腳印的向中高級(jí)進(jìn)階呢?本篇文章根據(jù)自己的一些見(jiàn)解梳理一下。

          Android開發(fā)初期之后怎么提升?怎么才能叫精通?方向在哪?

          我發(fā)現(xiàn)好多人始終停留在兩三年的水平上沒(méi)有突破。

          那么為什么很多人會(huì)一直停留在兩三年的水平上,而后一直在重復(fù)以往的經(jīng)驗(yàn)?

          我認(rèn)為最主要的一點(diǎn)就是主觀能動(dòng)性,或者說(shuō)興趣,如果你對(duì)Android開發(fā)沒(méi)有太大的興趣,那么還是盡早換方向吧。有了興趣,然后就是要有一個(gè)比較正確的鉆研路線,不要這也搞那也抓,最后什么都沒(méi)精通又好像什么知道。

          我覺(jué)得一個(gè)比較好的路線是,把日常開發(fā)常用的各種系統(tǒng)庫(kù),開源庫(kù),代碼好好研究一遍,比如我用了butternife就要了解背后的apt,以及apt衍生出來(lái)的一系列庫(kù)比如Hugo。當(dāng)然這大部分情況是初級(jí)進(jìn)階的第一步。這個(gè)階段應(yīng)該是盡量很好的用技術(shù)完成業(yè)務(wù)需求。

          第二個(gè)階段,我覺(jué)得可以嘗試去了解Android Framework比較細(xì)節(jié)的一些東西,比如activity啟動(dòng)流程,順便分析清除Activity stack的管理,比如了解Android的資源加載機(jī)制,順便了解aapt是如何打包Android資源的;又比如Java的類加載機(jī)制,這里配合資源的加載機(jī)制,很自然的就可以去了解Android的hotpatch機(jī)制,插件化的實(shí)現(xiàn),開一些這方便的開源庫(kù)或者自己擼一個(gè)也就自然而然。

          第三個(gè)階段,橫向擴(kuò)展,到這個(gè)階段并不是說(shuō)比第二個(gè)階段更加高級(jí)了,而是對(duì)第二個(gè)階段的一些補(bǔ)充,比如你是不是可以了解一下web開發(fā),這樣做hybrid開發(fā)的時(shí)候就會(huì)更順手。是不是要了解一下這么火爆的ReactNative&Weex技術(shù),甚至可以把他們的一些思想拿過(guò)來(lái)自己用,比如我司內(nèi)部就有很多項(xiàng)目是用了JSCore和CssLayout來(lái)實(shí)現(xiàn)一些更輕量的動(dòng)態(tài)化技術(shù)的。正如科學(xué)領(lǐng)域很多重大貢獻(xiàn)都是在交叉學(xué)科領(lǐng)域出現(xiàn)的。技術(shù)上到了這個(gè)階段甚至可以做到技術(shù)影響業(yè)務(wù),技術(shù)驅(qū)動(dòng)業(yè)務(wù)。

          不要學(xué)得太雜太亂!學(xué)習(xí)Android開發(fā)只要記住這幾點(diǎn)!

          我也已經(jīng)做了好幾年開發(fā)了,還記得剛出來(lái)工作的時(shí)候覺(jué)得自己很牛逼,現(xiàn)在回想起來(lái)也蠻好笑的。懂的越多的時(shí)候你才會(huì)發(fā)現(xiàn)懂的越少。因?yàn)槿绻愕闹R(shí)是一個(gè)圓,當(dāng)你的圓越大時(shí),圓外面的世界也就越大。

          最近看到很多Android新手問(wèn)Android學(xué)習(xí)路線,學(xué)習(xí)方法啊,如何入門啊,所以我以我的工作經(jīng)驗(yàn)給大家總結(jié)一下,讓大家少走彎路,提取一些工作中經(jīng)常用到的技術(shù)。當(dāng)然,說(shuō)一千道一萬(wàn),最重要的還是學(xué)以致用,把學(xué)過(guò)的知識(shí)融會(huì)貫通。

          如何知道我需要學(xué)什么

          相信大家在覺(jué)得迷茫的時(shí)候,經(jīng)常會(huì)選擇自暴自棄、或者完全按照自己的喜好去學(xué)習(xí),這是有利有弊的,總體來(lái)說(shuō)弊大于利。我一直認(rèn)同一個(gè)觀點(diǎn),就是“你的認(rèn)知比你的知識(shí)更加重要”,換句話說(shuō),也就是“你知道你需要去學(xué)什么,比你所學(xué)的知識(shí)更加重要”,如何知道自己缺乏什么,應(yīng)該去學(xué)習(xí)哪些技能,這才是從思維上、思想上改變你對(duì)學(xué)習(xí)的態(tài)度!

          如何知道自己應(yīng)該具備哪些技能呢?或者說(shuō),我自己還缺乏哪些技能呢?

          這個(gè)問(wèn)題一直是困擾著很多人,解答這個(gè)問(wèn)題其實(shí)很簡(jiǎn)單,這個(gè)問(wèn)題的回答,往往離不開行業(yè)的最新動(dòng)態(tài):從下面培訓(xùn)機(jī)構(gòu)的培訓(xùn)清單、招聘信息的招聘要求就可以看出來(lái)。下面先介紹如何從培訓(xùn)機(jī)構(gòu)的培訓(xùn)清單開出行業(yè)的動(dòng)態(tài)。

          培訓(xùn)機(jī)構(gòu)一直是互聯(lián)網(wǎng)行業(yè)的風(fēng)向標(biāo),培訓(xùn)機(jī)構(gòu)的嗅覺(jué)是十分敏銳的,他們必須關(guān)注行業(yè)的最新技術(shù)方向、最新技術(shù)。所以,在一定程度上面,雖然我們大多數(shù)人都挺抵觸培訓(xùn)機(jī)構(gòu),但是培訓(xùn)機(jī)構(gòu)的培訓(xùn)清單可以作為我們學(xué)習(xí)的風(fēng)向標(biāo)、學(xué)習(xí)清單

          例子1:(介紹一下往高級(jí)Android開發(fā)工程師進(jìn)階需要具備的哪些知識(shí))

          • 架構(gòu)師筑基必備技能:深入Java泛型+注解深入淺出+并發(fā)編程+數(shù)據(jù)傳輸與序列化+Java虛擬機(jī)原理+反射與類加載+動(dòng)態(tài)代理+高效IO
          • Android高級(jí)UI與FrameWork源碼:高級(jí)UI晉升+Framework內(nèi)核解析+Android組件內(nèi)核+數(shù)據(jù)持久化
          • 360°全方面性能調(diào)優(yōu):設(shè)計(jì)思想與代碼質(zhì)量?jī)?yōu)化+程序性能優(yōu)化+開發(fā)效率優(yōu)化
          • 解讀開源框架設(shè)計(jì)思想:熱修復(fù)設(shè)計(jì)+插件化框架解讀+組件化框架設(shè)計(jì)+圖片加載框架+網(wǎng)絡(luò)訪問(wèn)框架設(shè)計(jì)+RXJava響應(yīng)式編程框架設(shè)計(jì)+IOC架構(gòu)設(shè)計(jì)+Android架構(gòu)組件Jetpack
          • NDK模塊開發(fā):NDK基礎(chǔ)知識(shí)體系+底層圖片處理+音視頻開發(fā)
          • 微信小程序:小程序介紹+UI開發(fā)+API操作+微信對(duì)接
          • Hybrid 開發(fā)與Flutter:Html5項(xiàng)目實(shí)戰(zhàn)+Flutter進(jìn)階

          其實(shí)這個(gè)培訓(xùn)清單我個(gè)人覺(jué)得是目前來(lái)說(shuō)總結(jié)得比較好的Android進(jìn)階資料。基本涵蓋了各種企業(yè)開發(fā)必備的新技術(shù):RN、異步操作RXJava庫(kù)、熱修復(fù)、插件化、設(shè)計(jì)模式、性能優(yōu)化等知識(shí)。這些其實(shí)都是我們學(xué)習(xí)的方向標(biāo),按照這些清單,自己去找各種資料學(xué)習(xí)。

          其實(shí)對(duì)于Android基礎(chǔ)的話,不外乎四大組件的基本使用、UI控件、布局的使用、版本控制工具、NDK基礎(chǔ)等等。

          當(dāng)然隨著Android技術(shù)的日新月異,Android開發(fā)對(duì)開發(fā)人員的基礎(chǔ)知識(shí)的要求就更上一層樓了,比如說(shuō):動(dòng)態(tài)權(quán)限管理、Kotlin基礎(chǔ)、Gradle基礎(chǔ)、AndroidStudio基本使用等等。

          例子2:(從招聘信息上了解目前互聯(lián)網(wǎng)大廠需要學(xué)習(xí)的一些熱門技術(shù))

          我一直都強(qiáng)調(diào)要多看招聘信息,雖然我們不一定要找工作,但是按照企業(yè)的招聘要求,一定程度上也可以知道我們需要什么,并且同時(shí)可以知道,越高級(jí)的Android開發(fā)有什么更高的要求。

          我們常見(jiàn)的招聘網(wǎng)站有:智聯(lián)招聘、拉勾網(wǎng)、boss直聘等,這里大家手機(jī)上安裝一個(gè)對(duì)應(yīng)的APP,吃飯或者上班路上沒(méi)事可以翻一翻。那么如何通過(guò)招聘信息獲取我們需要什么呢?那么就需要我們仔細(xì)分析下面幾份招聘信息。

          大家第一時(shí)間關(guān)注的肯定是薪水問(wèn)題,20K到50K,可以看到,這是一份相對(duì)來(lái)說(shuō)比較高級(jí)Android開發(fā)工程師的招聘信息,根據(jù)上面的要求,其實(shí)我們可以知道更多。

          一般人可能會(huì)覺(jué)得這是一份很普通的招聘要求,但是在我的角度來(lái)看,可以看出很多有用的信息,下面我?guī)е蠹襾?lái)分析解讀一下這份招聘要求吧。

          1. Android底層的機(jī)制的熟悉,關(guān)于這個(gè)問(wèn)題,其實(shí)在這里我并不想談?wù)撎啵驗(yàn)楸鞠盗形恼碌暮罄m(xù)我會(huì)專門有一篇文章講這個(gè)怎么去學(xué)習(xí)。一句話總結(jié)就是,熟悉Android底層機(jī)制,或者說(shuō)Android源碼,對(duì)上層應(yīng)用的編程開發(fā)是有很多好處的,例如寫出高可復(fù)用性、高效率、高質(zhì)量的代碼。其中,面試常問(wèn)的源碼分析有消息機(jī)制、四大組件、進(jìn)程間通信、WMS、PMS等等。

          2. 各種架構(gòu)設(shè)計(jì)能力,這就需要大家多去學(xué)習(xí)設(shè)計(jì)模式、各種軟件架構(gòu)、設(shè)計(jì)思想上的東西,例如MVC、MVP、MVVM、重構(gòu)、代碼規(guī)范等等。

          3. 在一般的互聯(lián)網(wǎng)公司,網(wǎng)絡(luò)通信是最重要的,因此我們就有必要掌握基本的Socket編程、各種網(wǎng)絡(luò)請(qǐng)求框架,比如Retrofit2等等,進(jìn)階的話,就需要自己去研究分析這些框架的源碼、自己動(dòng)手用組件化的思想去封裝這些框架,防止代碼的侵入性等等。

          4. 新技術(shù)的調(diào)研與學(xué)習(xí),企業(yè)需要的是在盡量少的時(shí)間,開發(fā)出最優(yōu)秀的產(chǎn)品,因此新技術(shù)是必不可少的。因此我們也需要保持不斷學(xué)習(xí)充實(shí)自己的習(xí)慣。這里提到“研究新技術(shù)”,既然是研究,那么公司為了減少大家的學(xué)習(xí)成本,肯定是需要你將研究的成功通過(guò)文檔的形式輸出,以方便其他員工的快速上手,因此這里隱含了文檔的輸出能力。

          5. 經(jīng)驗(yàn),關(guān)于這個(gè)也沒(méi)有什么好說(shuō)的,很多東西,如果你遇到過(guò)就有經(jīng)驗(yàn)了,處理起來(lái)就比較快了。但是有一些難題你從來(lái)沒(méi)有遇到過(guò),那么難度就是無(wú)限大的,經(jīng)驗(yàn)需要一點(diǎn)一滴地積累,多向大牛取經(jīng)往往是實(shí)現(xiàn)彎道超車的最好辦法

          6. 成熟項(xiàng)目的工作經(jīng)驗(yàn),這是作為應(yīng)聘者一個(gè)最有閃光點(diǎn)的地方,也是在校生校招的一個(gè)最有說(shuō)服力的點(diǎn)。當(dāng)然,除了在公司工作,也推薦大家去一些培訓(xùn)網(wǎng)站上面學(xué)習(xí)一些項(xiàng)目的實(shí)戰(zhàn)視頻教程,這也是一種快速學(xué)習(xí)的方法,但是學(xué)習(xí)完一定要加以總結(jié),最好以文檔、博客的方式進(jìn)行輸出,并保持分享,互相交流才能知道自己的錯(cuò)誤、不足,才能獲取更多技術(shù),進(jìn)步更加快。

          7. 性能優(yōu)化,這是面試常見(jiàn)的問(wèn)題。性能優(yōu)化跟一個(gè)APP的用戶體驗(yàn)息息相關(guān),很多公司都十分重視這方面的知識(shí)。

          為什么高級(jí)程序員不必?fù)?dān)心自己的技術(shù)過(guò)時(shí)?

          程序員是吃青春飯的嗎?等我們老了,技術(shù)過(guò)時(shí)了,公司有什么理由不裁掉我們,去雇一些既有活力、薪資要求又低的年輕人呢?這個(gè)老生常談的問(wèn)題困擾著諸多漸入中年的程序員。接下來(lái)講解教你如何增強(qiáng)自己的核心競(jìng)爭(zhēng)力,在知識(shí)飛速更新的行業(yè)中站穩(wěn)腳跟,跨過(guò)“初級(jí)工程師”和“高級(jí)工程師”之間的鴻溝。

          我認(rèn)為開發(fā)者中有很多我們不太重視的軟技能,這些軟技能都有可能成倍地增加我們工作的影響力(作為個(gè)人貢獻(xiàn)者和技術(shù)負(fù)責(zé)人)。這些軟技能包括:

          • 代碼審查禮節(jié);
          • 如何優(yōu)雅地遏制范圍蔓延;
          • 如何向其他部門直觀的方式解釋高科技問(wèn)題;
          • 如何在生產(chǎn)任務(wù)爆滿和日以繼夜的比賽中保持鎮(zhèn)定自若等。

          作為一名程序員 ,編碼硬實(shí)力固然很重要,但是這些軟技能也同樣重要,這決定著你的核心競(jìng)爭(zhēng)力的強(qiáng)弱。

          高級(jí)開發(fā)者,會(huì)在工作中解決問(wèn)題,而非制造問(wèn)題。

          他們減少壓力。他們按時(shí)完成任務(wù)。他們知道如何編寫經(jīng)得起時(shí)間考驗(yàn)、可維護(hù)的代碼。他們值得更高的工資。他們對(duì)項(xiàng)目的方向可以有準(zhǔn)確的把控。他們可以發(fā)現(xiàn)當(dāng)前流程中的缺陷,并使每個(gè)人都接受他們的想法以進(jìn)行改進(jìn)。他們可以指導(dǎo)應(yīng)屆畢業(yè)生。他們處事冷靜,不會(huì)在周二與你的最大客戶的電話會(huì)議上情緒崩潰、破口大罵。

          Android學(xué)習(xí)的方向

          學(xué)習(xí)一線大廠的各項(xiàng)技術(shù):

          大廠是我們的技術(shù)先驅(qū),不僅僅是各種技術(shù),還有很多令你大開眼界的使用方法,學(xué)習(xí)這些有利于自己競(jìng)爭(zhēng)力的提高,深入理解后也可以提升你的學(xué)習(xí)效率。

          現(xiàn)在競(jìng)爭(zhēng)這么激烈,只有通過(guò)不斷學(xué)習(xí),提高自己,才能保持競(jìng)爭(zhēng)力。

          1.2020大廠面試高頻知識(shí)點(diǎn)

          • 圖片
          • 網(wǎng)絡(luò)和安全機(jī)制
          • 數(shù)據(jù)庫(kù)
          • 插件化、模塊化、組件化、熱修復(fù)、增量更新、Gradle
          • 架構(gòu)設(shè)計(jì)和設(shè)計(jì)模式
          • 性能優(yōu)化
          • Android Framework
          • Android優(yōu)秀三方庫(kù)源碼

          2.Jetpack架構(gòu)組件從入門到精通

          • Android Jetpack - Navigation
          • Android Jetpack - Data Binding
          • Android Jetpack - ViewModel & LiveData
          • Android Jetpack - Room
          • Android Jetpack - Paging
          • Android Jetpack - WorkManger
          • Android Jetpack架構(gòu)組件之Lifecycle
          • Android Jetpack Compose 最全上手指南

          3.Framework精編內(nèi)核解析

          主要內(nèi)容包含:

          • 深入解析Binder
          • 深入解析Handler
          • Dalvik VM 進(jìn)程系統(tǒng)
          • 深入解析 WMS
          • PackagerManagerService

          4.Kotlin強(qiáng)化實(shí)戰(zhàn)(附Demo)

          • 第一章 Kotlin入門教程
          • 第二章 Kotlin 實(shí)戰(zhàn)避坑指南
          • 第三章 項(xiàng)目實(shí)戰(zhàn)《Kotlin Jetpack 實(shí)戰(zhàn)》
            • 從一個(gè)膜拜大神的 Demo 開始
            • Kotlin 寫 Gradle 腳本是一種什么體驗(yàn)?
            • Kotlin 編程的三重境界
            • Kotlin 高階函數(shù)
            • Kotlin 泛型
            • Kotlin 擴(kuò)展
            • Kotlin 委托
            • 協(xié)程“不為人知”的調(diào)試技巧
            • 圖解協(xié)程:suspend

          5.Android設(shè)計(jì)思想解讀開源框架

          • 熱修復(fù)
          • 插件化
          • 組件化框架設(shè)計(jì)
          • 圖片加載框架
          • 網(wǎng)絡(luò)請(qǐng)求框架
          • RXJava 響應(yīng)式編程框架設(shè)計(jì)
          • IOC 架構(gòu)設(shè)計(jì)
          • Android架構(gòu)組件Jetpack

          6.NDK模塊開發(fā)

          • NDK 模塊開發(fā)
          • JNI 模塊
          • Native 開發(fā)工具
          • Linux 編程
          • 底層圖片處理
          • 音視頻開發(fā)
          • 機(jī)器學(xué)習(xí)

          定期面試:

          光學(xué)習(xí)了大廠的技術(shù)還不夠,如何了解當(dāng)下Android市場(chǎng)需要哪些技術(shù)呢?

          定期的面試足以讓你了解當(dāng)下市場(chǎng)需要哪些技術(shù),也更加容易確定自身的短板在哪,同時(shí),也可以督促自己學(xué)習(xí)提升(小伙子,你了解的技術(shù)有點(diǎn)落伍了呀…)

          萬(wàn)一拿到了高薪Offer,豈不是更賺?

          《379頁(yè)Android開發(fā)面試寶典》

          多看大佬的學(xué)習(xí)筆記,學(xué)習(xí)大佬的設(shè)計(jì)思想:

          只要是程序員,不管是Java還是Android,如果不去閱讀源碼,只看API文檔,那就只是停留于皮毛,這對(duì)我們知識(shí)體系的建立和完備以及實(shí)戰(zhàn)技術(shù)的提升都是不利的。

          真正最能鍛煉能力的便是直接去閱讀源碼,不僅限于閱讀各大系統(tǒng)源碼,還包括各種優(yōu)秀的開源庫(kù)。

          《486頁(yè)超全面Android開發(fā)相關(guān)源碼精編解析》

          以上這些內(nèi)容均免費(fèi)分享給大家,需要完整版的朋友,直接私信回復(fù)【資料】一鍵領(lǐng)取!!!


          主站蜘蛛池模板: 成人精品一区二区不卡视频| 奇米精品一区二区三区在| 无码人妻一区二区三区一| 国产精品一区二区三区高清在线 | 无码一区二区三区中文字幕| 国产品无码一区二区三区在线蜜桃 | 久久中文字幕无码一区二区| 久久精品国产亚洲一区二区三区 | 动漫精品一区二区三区3d| 亚洲一区二区三区久久久久| 国产剧情国产精品一区| 视频一区在线播放| 麻豆va一区二区三区久久浪 | 色老板在线视频一区二区| 一区二区三区在线免费观看视频| 欧美人妻一区黄a片| 99国产精品欧美一区二区三区| 精品国产一区二区三区四区| 无码免费一区二区三区免费播放 | 国产成人av一区二区三区在线| 日韩一区二区视频| 欧美人妻一区黄a片| 国内精品视频一区二区三区| 亚洲高清毛片一区二区| 国产精品香蕉一区二区三区| 亚洲AV一区二区三区四区| 亚洲欧美日韩一区二区三区在线| 亚洲欧美成人一区二区三区| 国产乱码精品一区二区三区麻豆| 在线观看一区二区三区视频| 国产精品一区二区三区久久 | 亚洲AV成人一区二区三区观看| 99精品国产一区二区三区2021| 亚洲丰满熟女一区二区哦| 国产精品综合一区二区| 视频一区二区在线播放| 中文字幕久久亚洲一区| 3d动漫精品啪啪一区二区免费| 亚洲欧美日韩一区二区三区在线 | 成人中文字幕一区二区三区| 久久精品免费一区二区喷潮|