前端技術:一文帶你掌握Flutter插件開發新姿勢

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"概述"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"隨着開發技術的發展,幾乎所有主流的開發語言都有自己的包管理工具。Node開發有npm、Android開發有Gradle,Flutter也有自己的Dart Packages倉庫。"},{"type":"text","marks":[{"type":"strong"}],"text":"插件的開發和複用能夠提高開發效率,降低工程的耦合度"},{"type":"text","text":",像網絡請求(http)、用戶授權(permission_handler)等客戶端開發常用的功能模塊,我們只需要引入對應插件就可以爲項目快速集成相關能力,從而專注於具體業務功能的實現。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"除了使用倉庫中的流行組件以外,在Flutter項目開發過程中面對通用業務邏輯拆分、或者需要對原生能力封裝等場景時,開發者仍然需要開發新的組件。本文以一個具體的native_image_view插件爲例,將從Flutter組件的創建、開發、測試和發佈等多個方面進行介紹,力圖完整的展示整個Flutter組件的開發和發佈流程。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"Flutter與Native通信"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在Flutter插件開發過程中,幾乎都會需要進行Flutter與Native端的數據交互,因此在進行插件開發之前,我們先簡單瞭解下"},{"type":"text","marks":[{"type":"strong"}],"text":"Platform Channel機制"},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Flutter與Native的通信是通過Platform Channel實現的,它是一種C\/S模型,其中Flutter作爲Client,iOS和Android平臺作爲Host,Flutter通過該機制向Native發送消息,Native在收到消息後調用平臺自身的API進行實現,然後將處理結果再返回給Flutter頁面。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/44\/44ba875beee123c6a204de35714a4f12.png","alt":"Image","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Flutter中的Platform Channel機制提供了三種交互方式:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":"br"}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"BasicMessageChannel :用於傳遞字符串和半結構化信息;"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"MethodChannel :用於傳遞方法調用和處理回調;"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"EventChannel:用於數據流的監聽與發送。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這三種channel雖然用途不同,但都包含了三個重要的成員變量:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"(1)String name"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"表示channel的名字,在一個項目中可能會有很多的channel,每個channel都應該使用唯一的命名標識,否則可能會被覆蓋。推薦的命名方式是組織名稱加插件的名稱,例如:com.tencent.game\/native_image_view,如果一個插件中包含了多個channel可再根據功能模塊進一步進行區分。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"(2)BinaryMessager messager"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"作爲Native與Flutter通信的載體,能夠將codec轉換後的二進制數據在Native與Flutter之間進行傳遞。每個channel在初始化時都要生成或提供對應的messager,如果channel註冊了對應的handler,則messager會維護一個name與handler的映射關係。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Native平臺在收到對方發來的消息後,meesager會將消息內容分發給對應的handler進行處理,在處理完成後還可以通過回調方法result將處理結果返回給Flutter。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":"br"}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/2e\/2e8a6641cbaba353248acbbd06ed23a8.png","alt":"Image","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"(3)MessageCodec\/MethodCodec codec"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"用於Native與Flutter通信過程中的編解碼,在發送方能夠將Flutter(或Native)的基礎類型編碼爲二進制進行數據傳輸,在接收方Native(或Flutter)將二進制轉換爲handler能夠識別的基礎類型。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"注:本文實現的native_image_share插件僅用到了最爲常用的MethodChannel通信,Flutter通過MethodChannel將遠程圖片地址或本地圖片文件名傳遞給原生側,iOS和Android平臺獲取到圖片後轉換爲二進制並通過result返回。更多關於MessageChannel和EventChannel的示例可以文末提供參考擴展閱讀。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"插件創建"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":"br"}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Flutter組件根據是否包含原生代碼可分爲兩種:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"Flutter Package(包)"},{"type":"text","text":":僅包含dart代碼,一般是對flutter特定功能的封裝實現,例如用於網絡請求的http包。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"Flutter Plugin(插件)"},{"type":"text","text":":除了dart代碼之外,還包含了Android和iOS平臺的代碼實現,常用於將客戶端原生的能力進行封裝,然後提供給flutter項目使用。例如用於判斷鍵盤可見狀態的flutter_keyboard_visibility插件,就是分別在iOS和Android端監聽了鍵盤的打開和關閉事件,然後將對應事件通過Platform Channel傳遞給Flutter項目。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/98\/984b71f7f8eb92892807df1bb5eeef0e.png","alt":"Image","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Flutter插件可以通過Android Studio創建(需要在Android Studio中先安裝Dart和Flutter插件),或者使用命令行創建。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"1. 創建Dart包"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"使用--template=package聲明創建的是隻包含dart代碼的package。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"flutter create --template=package hello"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/63\/637382deaac51303a505f4c787981447.png","alt":"Image","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"lib目錄用於存放package的代碼實現,Flutter腳手架會自動生成一個與package同名的dart文件。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"pubspec.yaml文件想必做過Flutter開發的同學都非常熟悉,我們開發package所依賴的package或者plugin都需要在該文件中聲明。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"2. 創建Flutter插件"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"使用--template=plugin聲明創建的是同時包含了iOS和Android代碼的plugin;"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"使用--org選項指定組織,一般採用反向域名錶示法;"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"使用-i選項指定iOS平臺開發語言,objc或者swift;"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"使用-a選項指定Android平臺開發語言,java或者kotlin。"}]}]}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"flutter create --template=plugin --org com.tencent.game -i objc -a java native_image_view"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/79\/792d14974dca41a850290a76060651d8.webp","alt":"Image","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"相比Package,我們可以看到Plugin多出了一些目錄,android目錄用於Android平臺的代碼實現,ios目錄用於iOS平臺的代碼實現,example目錄用於該組件的調試。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":"br"}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"注:Flutter腳手架在創建Plugin時默認實現了一個獲取系統版本號的示例,該示例的原理是分別在iOS和Android平臺獲取到系統版本號,然後通過MethodChannel調用返回給Flutter平臺顯示。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"插件開發"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":"br"}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Plugin和Package的開發和發佈流程基本一致,相比之下Plugin還涉及到iOS和Android的開發,實現起來要更加複雜一些。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在Flutter嵌入原生項目的場景中,比較常見的一個問題是:Flutter和原生項目中都使用了同一張圖片時,兩側會分別進行存儲,即該圖片會被存儲兩次。不同於Weex、Hippy等基於JS的跨平臺框架是依賴於原生進行圖片的獲取和顯示,Flutter是自行進行圖片的管理並直接通過Skia引擎直接進行繪製的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"針對這一問題,本文將開發一個Flutter插件(native_image_view),把Flutter圖片的下載和緩存工作交給Native實現,Flutter端則僅負責圖片的繪製。此外,我們還可以定義一個特殊協議,用於處理本地圖片的調用,同時解決Flutter無法複用原生項目本地圖片的問題。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/83\/83bd4b49868315049d16c747ced932f4.png","alt":"Image","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"注:本文開發的插件僅用於介紹插件的開發和發佈流程,不建議在生成環境中直接使用,關於圖片二次緩存問題還可以參考擴展閱讀中關於Texture(外接紋理)的文章。"}]}]},{"type":"horizontalrule"},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"頭圖:Unsplash"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"作者:趙哲"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"原文:https:\/\/mp.weixin.qq.com\/s\/mRXDKvyj_3pDjxM_axTDmQ"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"原文:前端技術:一文帶你掌握Flutter插件開發新姿勢"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"來源:雲加社區 - 微信公衆號 [ID:QcloudCommunity]"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"轉載:著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。"}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章