Flutter路由管理程式碼這麼長長長長長,阿里工程師怎麼高效解決?(實用)
01 背景
在Flutter的業務開發過程中,Flutter側會逐漸豐富自己的路由管理。一個輕量的路由管理本質上是頁面標識(或頁面路徑)與頁面例項的對映。本文閒魚工程師將基於dart註解提供了一個輕量路由管理方案。
不論是在native與Flutter的混合工程,還是純Flutter開發的工程,當我們實現一個輕量路由的時候一般會有以下幾種方法:
1. 較差的實現,if-else的邏輯堆疊:
做對映時較差的實現是透過if-else的邏輯判斷把url對映到對應的widget例項上。
class Router { Widget route(String url, Map params) { if(url == 'myapp://apage') { return PageA(url); } else if(url == 'myapp://bpage') { return PageB(url, params); } } }
這樣做的弊端比較明顯:
1)每個對映的維護影響全域性對映配置的穩定性,每次維護對映管理時需要腦補所有的邏輯分支。
2)無法做到頁面的統一抽象,頁面的構造器和構造邏輯被開發者自定義。
3)對映配置無法與頁面聯動,把頁面級的配置進行中心化的維護,導致維護責任人缺失。
2. 一般的實現,手動維護的對映表:
稍微好一點的是將對映關係透過一個配置資訊和一個工廠方法來表現。
class Router { Map<String, dynamic> mypages = <String, dynamic> { 'myapp://apage': 'pagea', 'myapp://bpage': 'pageb' } Widget route(String url, Map params) { String pageId = mypages[url]; return getPageFromPageId(pageId); } Widget getPageFromPageId(String pageId) { switch(pageId) { case 'pagea': return PageA(); case 'pageb': return PageB(); } return null; }
在Flutter側這種做法仍然比較麻煩,首先是問題3仍然存在,其次是由於Flutter目前不支援反射,必須有一個類似工廠方法的方式來建立頁面例項。
為了解決以上的問題,我們需要一套能在頁面級使用、自動維護對映的方案,註解就是一個值得嘗試的方向。我們的路由註解方案annotation_route應運而生,整個註解方案的執行系統如圖所示:
讓我們從dart註解開始,瞭解這套系統的運作。
02 dart註解
註解,實際上是程式碼級的一段配置,它可以作用於編譯時或是執行時,由於目前Flutter不支援執行時的反射功能,我們需要在編譯期就能獲取到註解的相關資訊,透過這些資訊來生成一個自動維護的對映表。那我們要做的,就是在編譯時透過分析dart檔案的語法結構,找到檔案內的註解塊和註解的相關內容,對註解內容進行收集,最後生成我們想要的對映表,這套方案的構想如圖示:
在調研中發現,dart的部分內建庫加速了這套方案的落地。
03 Source_gen
dart提供了build、analyser、source_gen這三個庫,其中source_gen利用build庫和analyser庫,給到了一層比較好的註解攔截的封裝。從註解功能的角度來看,這三個庫分別給到了如下的功能:
build庫:整套資原始檔的處理
analyser庫:對dart檔案生成完備的語法結構
source_gen庫:提供註解元素的攔截
這裡簡要介紹下source_gen和它的上下游,先看看我們捋出來的它註解相關的類圖:
source_gen的源頭是build庫提供的Builder基類,該類的作用是讓使用者自定義正在處理的資原始檔,它負責提供資原始檔資訊,同時提供生成新資原始檔的方法。source_gen從build庫提供的Builder類中派生出了一個自己的builder,同時自定義了一套生成器Generator的抽象,派生出來的builder接受Generator類的集合,然後收集Generator的產出,最後生成一份檔案,不同的派生builder對generator的處理各異。這樣source_gen就把一個檔案的構造過程交給了自己定義的多個Generator,同時提供了相對build庫而言比較友好的封裝。
在抽象的生成器Generator基礎上,source_gen提供了註解相關的生成器GeneratorForAnnotation,一個註解生成器例項會接受一個指定的註解型別,由於analyser提供了語法節點的抽象元素Element和其metadata欄位,即註解的語法抽象元素ElementAnnotation,註解生成器即可透過檢查每個元素的metadata型別是否匹配宣告的註解型別,從而篩選出被註解的元素及元素所在上下文的資訊,然後將這些資訊包裝給使用者,我們就可以利用這些資訊來完成路由註解。
04 annotation_route
在瞭解了source_gen之後,我們開始著手自己的註解解析方案annotation_route剛開始介入時,我們遇到了幾個問題:
只需要生成一個檔案:由於一個輸入檔案對應了一個生成檔案字尾,我們需要避免多餘的檔案生成
需要知道在什麼時候生成檔案:我們需要在所有的備選檔案掃描收集完成後再能進行對映表的生成
source_gen對一個類只支援了一個註解,但存在多個url對映到一個頁面
在一番思索後我們有了如下產出
首先將註解分成兩類,一類用於註解頁面@ARoute,另一類用於註解使用者自己的router@ARouteRoot。routeBuilder擁有RouteGenerator例項,RouteGenerator例項,負責@ARoute註解;routeWriteBuilder擁有RouteWriterGenerator例項,負責@ARouteRoot註解。透過build庫支援的配置檔案build.yaml,控制兩類builder的構造順序,在routeBuilder執行完成後去執行routeWriteBuilder,這樣我們就能準確的在所有頁面註解掃描完成後開始生成自己的配置檔案。
在註解解析工程中,對於@ARoute註解的頁面,透過RouteGenerator將其配置資訊交給擁有靜態儲存空間的Collector處理,同時將其輸出內容設為null,即不會生成對應的檔案。在@ARoute註解的所有頁面掃描完成後,RouteWriteGenerator則會呼叫Writer,它從Collector中提取資訊,並生成最後的配置檔案。對於使用者,我們提供了一層友好的封裝,在使用annotation_route配置到工程後,我們的路由程式碼發生了這樣的變化:
使用前:
import 'testa.dart' import 'testb.dart' import 'testc.dart' import 'testd.dart' import 'teste.dart' import 'testf.dart' class Router { Widget pageFromUrlAndQuery(String urlString, Map<String, dynamic> query) { if(urlString == 'myapp://testa') { return TestA(urlString, query); } else if(urlString == 'myapp://testb') { String absoluteUrl = Util.join(urlString, query); return TestB(url: absoluteUrl); } else if(urlString == 'myapp://testc') { String absoluteUrl = Util.join(urlString, query); return TestC(config: absoluteUrl); } else if(urlString == 'myapp://testd') { return TestD(PageDOption(urlString, query)); } else if(urlString == 'myapp://teste') { return TestE(PageEOption(urlString, query)); } else if(urlString == 'myapp://testf') { return TestF(PageFOption(urlString, query)); } return DefaultWidget; } }
使用後:
import 'package:annotation_route/route.dart'; class MyPageOption { String url; Map<String, dynamic> query; MyPageOption(this.url, this.query); } class Router { ARouteInternal internal = ARouteInternalImpl(); Widget pageFromUrlAndQuery(String urlString, Map<String, dynamic> query) { ARouteResult routeResult = internal.findPage(ARouteOption(url: urlString, params: query), MyPageOption(urlString, query)); if(routeResult.state == ARouteResultState.FOUND) { return routeResult.widget; } return DefaultWidget; } }
目前該方案已在閒魚app內穩定執行,我們提供了基礎的路由引數,隨著Flutter業務場景越來越複雜,我們也會在註解的自由度上進行更深的探索。關於annotation_route更加詳細的安裝和使用說明參見github地址:route ,在使用中遇到任何問題,歡迎向我們反饋。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31555606/viewspace-2285299/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 商品標題這麼長長長長長,阿里工程師如何解決?阿里工程師
- 如何增長程式碼長度
- Google 怎麼解決長尾延遲問題Go
- 資料庫,主鍵為何不宜太長長長長長長長長?資料庫
- 程式設計師垃圾簡歷長什麼樣?程式設計師
- excel太長了怎麼截圖 excel如何滾動截長圖Excel
- 我天天curd,怎麼才能成長?
- MySQL主從延時這麼長,要怎麼最佳化?MySql
- 前端工程師成長之讀好書前端工程師
- 前端工程師成長之多讀好書前端工程師
- 前端工程師成長之多看好書前端工程師
- 程式媛成長紀:從DBA到研發工程師工程師
- 阿里畢玄:程式設計師的成長路線阿里程式設計師
- 解決遷移資料庫錯誤,索引長度過長資料庫索引
- 【公式】怎麼確定是不是長龍公式
- win10副檔名太長怎麼辦_win10檔名太長無法刪除的解決方法Win10
- 阿里一年,聊聊我成長了什麼阿里
- cv演算法工程師成長路線演算法工程師
- [譯] Javascript 中最長的關鍵字序列長什麼樣子?JavaScript
- 程式設計師天天 CURD,怎麼才能成長,職業發展的思考 ?程式設計師
- 為什麼《夢幻西遊》能這麼長壽?大型複雜遊戲如何長時間經營?遊戲
- cad延伸快捷鍵命令 延長快捷鍵cad怎麼用
- 快手APP家長控制模式開啟方法 快手APP家長控制模式怎麼開啟?APP模式
- win10截長圖的方法_win10電腦怎麼截長圖片Win10
- win10如何開啟家長控制_win10家長控制怎麼設定Win10
- win10家長控制怎麼設定_win10家長控制如何設定Win10
- shell 怎麼獲取引數的長度
- angular路由高亮--長明燈RouterLinkActiveAngular路由
- 什麼是長尾關鍵詞?如何找到長尾關鍵詞?
- 為什麼要做長文字、長圖文、長語音的大模型?深度解讀訊飛星火V3.5春季上新大模型
- TCP協議長什麼樣TCP協議
- 生活服務商家如何實現從“網紅”到“長紅”的長效增長?
- 程式設計師天天 CURD,怎麼才能成長,職業發展的思考(2)程式設計師
- 一個測試工程師的成長覆盤工程師
- 一個 SAP 開發工程師的成長史工程師
- 技術之外的工程師另類成長指南工程師
- 初、中、高階測試工程師成長指南工程師
- eas_批次更新組織長編碼,長名稱