一、移動跨平臺技術演進
1. 引言
移動網際網路發展十餘年,伴隨著 Android、iOS 等智慧手機的不斷普及,移動端已逐步取代 PC 端,成為兵家必爭之地。正所謂“得移動端者得天下”,移動端已成為網際網路領域最大的流量分發入口,一大批網際網路公司正是在這大趨勢下崛起。
2. 為什麼需要跨平臺技術
伴隨著移動網際網路的高速發展,公司間競爭越來越激烈,如何將好想法快速落地、快速試錯,成為備受關注的問題。提升研發效率、縮短研發週期,保障產品快速試錯並能快速迭代新功能,讓新產品新功能以最快的速度同時抵達 Android、iOS 等多端使用者。
眾所周知,Android 應用採用 Java 或 Kotlin 編寫,iOS 應用採用 Objective-C 或 Swift 編寫,Web 端採用 HTML /CSS/JavaScript 編寫。當需要開發支援多端的應用,每一端都需要獨立研發、測試,一直到上線,以及後續的維護工作,工作量成倍增漲,勢必延長研發週期。
為了解決多端獨立開發的問題,跨平臺技術便應運而生,各大網際網路公司為此都投入大量人力,於是出現了各種跨平臺技術框架,面對移動領域的跨平臺技術方案的層出不窮,又該如何做技術選型呢?
3. 移動端技術選型
作為移動端的跨端技術方案,所關注無外乎以下這4個方面:研發效率、動態性、多端一致性、效能體驗。
- 研發效率:最大化程式碼複用,減少多端差異的適配工作量,降低開發成本,專注業務開發,實現“write once,run everywhere”的終極目標。效率提升是貫穿整個業務的生命週期線,即便業務上線後,可持續降低後續的維護成本,加快新feature的迭代速度,這是一個持續的效率收益。當然,這裡不得不說,任何一門新技術在開發啟動學習階段會有一些成本,但上手後的收益是長期的。
- 動態化:突破渠道的更新頻率,可快速迭代新功能,這一點不僅是跨平臺技術的訴求,也是Native技術必備的殺手鐗,這也是評估跨端技術的一個重要考核點。
- 多端一致性:好產品在多端UI設計上,往往是整體風格統一,所以業務方採用原生各自獨立開發完成後,還需額外花不少時間來修改UI以保證多端一致性;可見,各端獨立實現開發方式,帶來的效率滯後,不僅僅是Android和iOS各開發一份程式碼的工作量,還有雙端UI的一致性對齊的工作。
- 效能體驗:一般地,跨端技術方案擁有以上多重優勢,但在效能方面比原生流暢更差些。犧牲部分體驗換來效率提升,這一點也是情理之中,試想一下,跨平臺技術方案同時兼得這4點,那麼原生技術恐怕已退出歷史舞臺,早已是跨平臺技術的天下,所以往往跨平臺技術的效能優劣便成為核心指標。
4. 跨平臺技術劃分
對研發效率和體驗的不斷追逐,移動端的跨平臺技術方框架層出不窮,然則天下武功眾多,萬變不離其宗,從其核心本質來劃分,可大致分為以下三大類:
- Web技術:主要依賴於WebView的技術,功能支援受限,效能體驗很差,比如PhoneGap、Cordova、小程式。
- 原生渲染:使用JavaScript作為程式語言,通過中間層轉化為原生控制元件來渲染UI介面,比如React Native、Weex。
- 自渲染技術:自行實現一套渲染框架,可通過呼叫skia等方式完成自渲染,而不依賴於原生控制元件,比如Flutter、Unity。
5. 跨平臺技術演進
跨平臺技術,一直以來是每一個有追求的開發者所追逐的夢想,同時也是守舊者的噩夢,跨平臺的多端一體化方案勢必顛覆現有的原生各端獨立開發模式,接下來列舉眾多的跨平臺技術中最為關鍵的幾個技術方案的演進階段。
從上圖可以看出,技術演進過程大致分以下三個階段: 第一階段,採用WebView技術繪製介面的Hybrid混合開發技術,通過JS Bridge 將系統部分能力暴露給 JS 呼叫,其缺點是效能較差,功能受限,擴充套件性差,不適合互動複雜的場景,比如Cordova。 第二階段,針對WebView介面效能等問題,於是繪製交還原生渲染,僅僅通過JS呼叫原生控制元件,相比WebView技術效能體驗更好,這是目前絕大部分跨平臺框架的設計思路,比如React Native、Weex。另外,最近小程式也比較火,第一和第二階段的融合,依然採用WebView作為渲染容器,通過限制Web技術棧的子集,規範化元件使用,並逐步引入原生控制元件代表WebView渲染,以提升效能。 第三階段,雖然通過橋接技術使用原生控制元件解決了功能受限問題,提升效能體驗,但相比原生體驗差距還是比較大,以及處理平臺差異性非常耗費人力。於是Flutter提出自帶渲染引擎的解決方案,儘可能減少不同平臺間的差異性, 同時媲美原生的高效能體驗,因此業界對 Flutter有著極高的關注度。
面對現有的如此多跨平臺方案,為何當下最火的跨平臺技術是Flutter,有哪些優勢呢?
RN、Weex均使用JavaScript作為程式語言,JavaScript作為前端開發語言,在跨平臺開發中可謂大放異彩,利用web技術不僅能開發出網站,也可以開發手機端web應用和移動端應用程式,似有一統三界(Android、iOS、Web)的趨勢,這就是大家常說的“大前端”時代。這些技術方案流暢度不太好,平臺一致性較差,至今還沒能大面積取代原生開發。
Flutter是以Dart語言編寫,開發體驗更接近客戶端,從大家使用反饋來看也是如此,Flutter開發環境這一套的流程對於前端開發來說並不太友好。Flutter的定位同樣是多端一體化,但是以客戶端為首,先磨平Android和iOS雙端開發體驗,再逐步向Web端滲透,從Flutter規劃的Roadmap也能看出,Flutter for web目前仍處於預覽版,Flutter客戶端方向都已經如火如荼上線了不少應用。
在此之前,大家常說“大前端”,對於Flutter技術,在筆者看來稱之為“大移動端”更貼切,Flutter的UI框架優先支援客戶端(Android/iOS)應用的同時,然後再適配Web端。移動網際網路時代,不少公司都制定“移動優先”的戰略,甚至只開發移動端,沒有Web端。移動網際網路的時代造就“大移動端”,Flutter作為一款能做到媲美原生的高效能跨平臺技術方案,或許一統天下。
在跨平臺技術領域,只要挑戰在,技術就不會停滯,伴隨著技術不斷演進與革新,終將走向美好。
6. Flutter技術優勢
Flutter是徹底的跨平臺方案,既沒有采用webView,也沒有采用JS橋接原生控制元件,而是自行實現一套UI框架,在引擎底層通過Skia渲染到螢幕。對於UI之外所需要使用的移動裝置自身提供的服務,比如相機、定位、螢幕觸控等,則採用Platform Channels跟原生系統通訊的方式來實現。
對於Flutter優勢,回到前面講到移動端技術選型的4要素,研發效率、動態性、多端一致性、效能體驗,分別對應下面這一組詞語。
- 高效率:採用dart語言編寫程式碼,雖然剛開始上手需要點時間,但熟練後效率比較高。一套程式碼適用多個平臺(Android、iOS、Web),以及高效的Hot Reload能快速輔助除錯;
- 動態化:2017年3月蘋果下發警告郵件,禁止JSPatch等 iOS App熱更新方案,從此iOS動態化成為一個不宜公開討論的話題。同樣地,Flutter引擎在某一個官方版本對動態化做過一些嘗試,但後續基於風險考慮移除,當然並沒有阻礙大家對技術的探索,這裡不方便展開討論;
- 高一致性:實現UI畫素級的控制,Flutter渲染引擎依靠跨平臺Skia圖形庫來實現,僅依賴系統圖形繪製相關的介面,比如未來Android會支援vulkan,iOS會支援metal,這些都是通過skia封裝呼叫。可最大程度上保證不同平臺的體驗一致性,見下圖所示。
- 高效能:渲染效能優於現有的各種跨平臺框架,可媲美原生效能的跨平臺技術方案,Dart程式碼執行效率比JS高,通過AOT編譯成平臺原生程式碼,渲染採用自渲染skia方案,既不需要JS Bridge橋接,也不需要Art虛擬機器參與。再從渲染原理來看看Flutter的高效能的底氣在哪裡。
圖解:
- Android原生框架,通過呼叫Java Framework層,再呼叫到skia來渲染介面;
- 其他跨平臺方案(如RN),通過JSBridge中間層來將JS寫的APP轉換成相應的原生渲染邏輯,可見比Native程式碼增加了更多邏輯,效能遜色差於原生框架;
- Flutter框架,APP通過呼叫Dart Framework層,再直接呼叫到skia來渲染介面,並沒有經過原生Framework過程,可見其渲染效能並不會弱於Native技術,這是一個效能上限很高的跨平臺技術。
當然,不得不說目前的Flutter確實不夠盡善盡美,會存在一些不夠盡善盡美之處,比如生態不夠健全,包體積問題,但其該方案的上限比較高,想象空間比較大,相信更多開發者參與進來,經過更多打磨,未來會做得更好。
7. 業界發展近況
2017年5月Google I/O大會正式對外公佈Flutter,到2018年12月釋出Flutter1.0,引發全球大量的開發者和企業開始研究Flutter。StackOverflow 2019年的全球開發者檔案調查中,Flutter被評選為最受開發者歡迎的框架之一,超過了TensorFlow和Node.js。
到目前,全球越來越多的公司已經在大家耳熟能詳的知名APP中使用Flutter技術並落地,尤其國內知名網際網路公司對Flutter投入度很大,社群也是非常活躍。
8. Flutter未來趨勢
目前Flutter主要在移動端Android/iOS雙端跨端,Flutter 的願景是成為一個多端執行的 UI 框架,能夠支援不僅僅是移動端,還包括Web、桌面、甚至嵌入式裝置。在2019 Google I/O 開發者大會上推出的使用 Flutter 開發 Web 應用的框架,同年9月釋出Flutter 1.9,並將Flutter web合入Flutter主倉庫。
從架構圖看,Flutter採用同一個Dart Framework層來統一Flutter C++引擎和Web引擎,最終可以執行在Android,iOS,Browser上,從Flutter引擎程式碼不難看出Flutter也是支援Fuchsia作業系統。
Fuchsia是Google內部正在開發的一款新的作業系統,採用Flutter作為系統預設的UI框架,也就是說Flutter天然支援Fuchsia,這無疑讓Flutter在眾多的跨平臺方案更有優勢。
從Fuchsia技術架構來看,核心層zircon的基礎LK是專為嵌入式應用中小型系統設計的核心,程式碼簡潔,適合嵌入式裝置和高效能裝置,比如IOT、移動可穿戴裝置等,目前這些領域還沒有標準化級別的壟斷者。以及在框架層中有著語音互動、雲端以及智慧化等模組,由此筆者揣測未來Fuchsia率先應用在音控等智慧嵌入式裝置。
目前大家普遍比較看好的未來兩個技術就是5G和IoT時代。對於5G的需求,很大程度上是因為移動網際網路發展到“IoT時代”的階段。這個發展階段,全球上網裝置的數量可能會達到500億個。隨著5G+IOT時代的到來,現在大家比較關注的Flutter包大小也同樣不再是一個問題,或許Flutter技術的生命期比客戶端更長,或許Fuchsia正在馳騁IOT疆場,你所掌握的Flutter技術棧可以無縫遷移,一次彎道超車的機會。
到此,介紹完跨平臺技術演進以及Flutter的優勢。看到這,相信你可能對Flutter技術有一定興趣,為了能讓大家快速瞭解Flutter內部原理而不枯燥,Gityuan通過一系列圖來幫大家從整體架構來快速理解Flutter。
二、Flutter引擎架構
1. Flutter技術架構
先來看看Flutter整體的技術架構,分為四層,從上之下依次是Dart APP,Dart Framework, C++ Engine,Platform。
Flutter架構最核心的便是Framework(框架)和Engine(引擎):
- Flutter Framework層:用Dart編寫,封裝整個Flutter架構的核心功能,包括Widget、動畫、繪製、手勢等功能,有Material(Android風格UI)和Cupertino(iOS風格)的UI介面, 可構建Widget控制元件以及實現UI佈局。
- Flutter Engine層:用C++編寫,用於高質量移動應用的輕量級執行時環境,實現了Flutter的核心庫,包括Dart虛擬機器、動畫和圖形、文字渲染、通訊通道、事件通知、外掛架構等。引擎渲染採用的是2D圖形渲染庫Skia,虛擬機器採用的是面嚮物件語言Dart VM,並將它們託管到Flutter的嵌入層。shell實現了平臺相關的程式碼,比如跟螢幕鍵盤IME和系統應用生命週期事件的互動。不同平臺有不同的shell,比如Android和iOS的shell。
2. Flutter編譯產物
看完Flutter內部架構,或許你好奇,Flutter不用Android/iOS的本地語言技術開發,Dart編寫完的程式碼如何讓不同系統可以識別,最終編譯後得到的產物是什麼呢?
Flutter產物分為Dart業務程式碼和Engine程式碼各自生成的產物,圖中的Dart Code包含開發者編寫的業務程式碼,Engine Code是引擎程式碼,如果並沒有定製化引擎,則無需重新編譯引擎程式碼。
一份Dart程式碼,可編譯生成雙端產物,實現跨平臺的能力。經過編譯工具處理後可生成雙端產物,圖中便是release模式的編譯產物,Android產物是由vm、isolate各自的指令段和資料段以及flutter.jar組成的app.apk,iOS產物是由App.framework和Flutter.framework組成的Runner.app。
這個過程涉及frontend_server、gen_snapshot、xcrun、ninja編譯工具。frontend_server前端編譯器會進行詞法分析、語法分析以及相關全域性轉換等工作,將dart程式碼轉換為AST(抽象語法樹),並生成app.dill格式的dart kernel。gen_snapshot經過CHA、內聯等一系列執行流的優化,根據中間程式碼生成優化後的FlowGraph物件,再轉換為具體相應系統架構(arm/arm64等)的二進位制指令。
3. Flutter引擎啟動
既然瞭解了Flutter的編譯產物,那你或許又好奇,Flutter這臺引擎如何發動的,怎麼跟Native銜接呢?
這裡以Android為例,熟悉Android的開發者,應該都瞭解APP啟動過程,會執行Application和Activity的onCreate()方法,FlutterApplication和FlutterActivity的onCreate()方法正是連線Native和Flutter的樞紐。
- FlutterApplication.java的onCreate過程主要完成初始化配置、載入引擎libflutter.so、註冊JNI方法;
- FlutterActivity.java的onCreate過程,通過FlutterJNI的AttachJNI()方法來初始化引擎Engine、Dart虛擬機器、Isolate、taskRunner等物件。再經過層層處理最終呼叫main.dart中main()方法,執行runApp(Widget app)來處理整個Dart業務程式碼。
Flutter引擎啟動中會建立有4個TaskRunner以及建立虛擬機器,分別來看看它們的工作原理。
4. TaskRunner工作原理
Flutter引擎啟動過程,會建立UI/GPU/IO這3個執行緒,會為這些執行緒依次建立MessageLoop物件,啟動後處於epoll_wait等待狀態。對於Flutter的訊息機制跟Android原生的訊息機制有很多相似之處,都有訊息(或者任務)、訊息佇列(或任務佇列)以及Looper;有一點不同的是Android有一個Handler類,用於傳送訊息以及執行回撥方法,相對應Flutter中有著相近功能的便是TaskRunner。
上圖是從原始碼中提煉而來的任務處理流程,比官方流程圖更容易理解一些複雜流程的時序問題,後續會專門講解箇中原由。Flutter的任務佇列處理機制跟Android的訊息佇列處理相通,只不過Flutter分為Task和MicroTask兩種型別,引擎和Dart虛擬機器的事件以及Future都屬於Task,Dart層執行scheduleMicrotask()所產生的屬於Microtask。
每次Flutter引擎在消費任務時呼叫FlushTasks()方法,遍歷整個延遲任務佇列delayed_tasks_,將已到期的任務加入task佇列,然後開始處理任務。
- Step 1: 檢查task,當task佇列不為空,先執行一個task;
- Step 2: 檢查microTask,當microTask不為空,則執行microTask;不斷迴圈Step 2 直到microTask佇列為空,再回到執行Step 1;
可簡單理解為先處理完所有的Microtask,然後再處理Task。因為scheduleMicrotask()方法的呼叫自身就處於一個Task,執行完當前的task,也就意味著馬上執行該Microtask。
瞭解了其工作機制,再來看看這4個Task Runner的具體工作內容。
- Platform Task Runner:執行在Android或者iOS的主執行緒,儘管阻塞該執行緒並不會影響Flutter渲染管道,平臺執行緒建議不要執行耗時操作;否則可能觸發watchdog來結束該應用。比如Android、iOS都是使用平臺執行緒來傳遞使用者輸入事件,一旦平臺執行緒被阻塞則會引起手勢事件丟失。
- UI Task Runner: 執行在ui執行緒,比如1.ui,用於引擎執行root isolate中的所有Dart程式碼,執行渲染與處理Vsync訊號,將widget轉換生成Layer Tree。除了渲染之外,還有處理Native Plugins訊息、Timers、Microtasks等工作;
- GPU Task Runner:執行在gpu執行緒,比如1.gpu,用於將Layer Tree轉換為具體GPU指令,執行裝置GPU相關的skia呼叫,轉換相應平臺的繪製方式,比如OpenGL, vulkan, metal等。每一幀的繪製需要UI Runner和GPU Runner配合完成,任何一個環節延遲都可能導致掉幀;
- IO Task Runner:執行在io執行緒,比如1.io,前3個Task Runner都不允許執行耗時操作,該Runner用於將圖片從磁碟讀取出來,解壓轉換為GPU可識別的格式後,再上傳給GPU執行緒。為了能訪問GPU,IO Runner跟GPU Runner的Context在同一個ShareGroup。比如ui.image通過非同步呼叫讓IO Runner來非同步載入圖片,該執行緒不能執行其他耗時操作,否則可能會影響圖片載入的效能。
5. Dart虛擬機器工作
Flutter引擎啟動會建立Dart虛擬機器以及Root Isolate。DartVM自身也擁有自己的Isolate,完全由虛擬機器自己管理的,Flutter引擎也無法直接訪問。Dart的UI相關操作,是由Root Isolate通過Dart的C++呼叫,或者是傳送訊息通知的方式,將UI渲染相關的任務提交到UIRunner執行,這樣就可以跟Flutter引擎相關模組進行互動。
何為Isolate,從字面上理解是“隔離”,isolate之間是邏輯隔離的。Isolate中的程式碼也是按順序執行,因為Dart沒有共享記憶體的併發,沒有競爭的可能性,故不需要加鎖,也沒有死鎖風險。對於Dart程式的併發則需要依賴多個isolate來實現。
圖解:
- isolate堆是運該isolate中程式碼分配的所有物件的GC管理的記憶體儲存;
- vm isolate是一個偽isolate,裡面包含不可變物件,比如null,true,false;
- isolate堆能引用vm isolate堆中的物件,但vm isolate不能引用isolate堆;
- isolate彼此之間不能相互引用;
- 每個isolate都有一個執行dart程式碼的Mutator thread,一個處理虛擬機器內部任務(比如GC, JIT等)的helper thread; 可見,isolate是擁有記憶體堆和控制執行緒,虛擬機器中可以有很多isolate,但彼此之間記憶體不共享,無法直接訪問,只能通過dart特有的Port埠通訊;isolate除了擁有一個mutator控制執行緒,還有一些其他輔助執行緒,比如後臺JIT編譯執行緒、GC清理/併發標記執行緒;
6. Widget架構概覽
Flutter引擎啟動後執行Dart業務,是通過runApp(Widget app)方法,那Widget又是什麼呢?
Widget是所有Flutter應用程式的基石,Widget可以是一個按鈕,一種字型或者顏色,一個佈局屬性等,在Flutter的UI世界可謂是“萬物皆Widget”。常見的Widget子類為StatelessWidget(無狀態)和StatefulWidget(有狀態);
- StatelessWidget:內部沒有儲存狀態,UI介面建立後不會發生改變;
- StatefulWidget:內部有儲存狀態,當狀態發生改變,呼叫setState()方法會觸發StatefulWidget的UI發生更新,對於自定義繼承自StatefulWidget的子類,必須要重寫createState()方法。
三棵樹
圖解:
- Widget是為Element描述需要的配置, 負責建立Element,決定Element是否需要更新。Flutter Framework通過差分演算法比對Widget樹前後的變化,決定Element的State是否改變。當重建Widget樹後並未發生改變, 則Element不會觸發重繪,則就是Widget樹的重建並不一定會觸發Element樹的重建。
- Element表示Widget配置樹的特定位置的一個例項,同時持有Widget和RenderObject,負責管理Widget配置和RenderObject渲染。Element狀態由Flutter Framework管理, 開發人員只需更改Widget即可。
- RenderObject表示渲染樹的一個物件,負責真正的渲染工作,比如測量大小、位置、繪製等都由RenderObject完成。
可見,開發者通過Widget配置,Framework通過比對Widget配置來更新Element,最後排程RenderObject Tree完成佈局排列和繪製。
7. 渲染原理
Dart的UI採用Widget來實現,最終轉換為RenderObject,那介面又是如何渲染的呢?
渲染過程,UI執行緒完成佈局、繪製操作,生成Layer Tree;GPU執行緒執行合成並光柵化後交給GPU來處理,其中幾個關鍵步驟:
- Animate: 遍歷_transientCallbacks,執行動畫回撥方法;
- Build: 對於dirty的元素會執行build構造,沒有dirty元素則不會執行,對應於buildScope()
- Layout: 計算渲染物件的大小和位置,對應於flushLayout(),這個過程可能會巢狀再呼叫build操作;
- Compositing bits: 更新具有髒合成位的任何渲染物件, 對應於flushCompositingBits();
- Paint: 將繪製命令記錄到Layer, 對應於flushPaint();
- Compositing: 將Compositing bits傳送給GPU, 對應於compositeFrame();
GPU執行緒通過skia向GPU硬體繪製一幀的資料,GPU將幀資訊儲存到FrameBuffer裡面,然後視訊控制器會根據VSync訊號從FrameBuffer取幀資料傳遞給顯示器,從而顯示出最終的畫面。
8. Platform Channels
Flutter框架提供了UI的控制元件支援,對於APP除了UI還有其他依賴於Native平臺的支援,比如呼叫Camera的功能,該怎麼辦呢?為此,Flutter通過提供Platform Channel的功能,使得Dart程式碼具備與Native互動的能力。
Platform Channel用於Flutter與Native之間的訊息傳遞,整個過程的訊息與響應是非同步執行,不會阻塞使用者介面。Flutter引擎框架已完成橋接的通道,這樣開發者只需在Native層編寫定製的Android/iOS程式碼,即可在Dart程式碼中直接呼叫,這也就是Flutter Plugin外掛的一種形式。
三、結束語
科技不斷在進步,技術不斷髮展,移動跨平臺技術幾乎從Android、iOS誕生不久便出現,已發展快10年。時至今日,兼具跨端高效率與高效能體驗的Flutter力壓群雄,嶄露頭角,已然成為當下最熱門的移動端新技術,全球越來越多的公司在Flutter技術佈局並落地產品應用,社群也非常活躍。
筆者Gityuan之前一直從事於Android作業系統底層研發工作,今年剛接觸Flutter,Flutter作為一門全新的跨平臺技術框架,不斷深究會發現這是一個小型系統,涉及到的技術很廣:
- 編譯技術如何將dart程式碼轉換為AST(抽象語法樹),如何彙編轉換為機器碼,打包成產物是什麼?
- Flutter這臺引擎如何發動的,怎麼跟Native原生系統銜接執行,如何識別產物並載入到記憶體?
- 引擎啟動後,TaskRunner如何分發任務,跟原生系統訊息機制有什麼關係?
- Dart虛擬機器如何管理記憶體,跟isolate又有什麼關係?
- 開發者編寫的Widget控制元件如何渲染到螢幕上?
- Flutter如何通過plugin支援移動裝置提供的服務?
這些疑問本文都逐一解讀,如果僅僅是用Flutter做業務開發,並不需要掌握這麼深度技術,不過,知其然知其所以然,能讓你遊刃有餘。本文講述跨平臺技術的過去與未來,以及從巨集觀架構解讀Flutter內部原理,後續有時間將更深入的技術細節以及實戰經驗角度來跟大家揭祕更多Flutter技術。
隨著5G+IOT時代的到來,Fuchsia系統或許發力IOT新戰場,你所掌握的Flutter技術棧可以無縫遷移,這是一次彎道超車的機會。即便Fuchsia落敗,相信只要深扎Flutter系統技術的精髓,其他任何的移動端新技術都可以輕鬆快速地掌握。
最後,用一句話來結束本次分享,“有時候,你選擇一個方向,不是因為它一定會成為未來,而是它有可能成為不一樣的未來。”