你們好!今天給你們安利一個自認爲比較重磅的Flutter開源項目。java
Flutter的產品定義是一個高性能的跨平臺的移動UI框架,可以用一套代碼同時構建出Android/iOS/Web/MacOS應用。做爲一套UI框架,它不具有一些系統的接口,天然仍是避免不了跟原生打交道。因而乎,它提出了名爲platform channel
的東西,用於flutter和原生靈活的交換數據。如下爲了描述方便,用Android代指原生。git
燃鵝,燃鵝,燃鵝,它只支持一些基礎的數據類型和數據結構的傳輸,例如bool/int/long/byte/char/String/byte[]/List/Map等。github
所以,當你想傳輸複雜點的數據,你只能包裝成Map,相似這樣:shell
await _channel.invokeMethod('initUser', {'name': 'Oscar', 'age': 16, 'gender': 'MALE', 'country': 'China'});
而後再在Android層hard code,解析出不一樣的key對應的不一樣數據。若是你是一個純fluter項目,且之後也沒有和原生打交道的打算,或者只是須要進行簡單的交互,那這種作法也無可厚非。而當你的項目已經有很大的一部分原生代碼或者你須要使用第三方不支持flutter的lib庫的時候,就意味着你須要編寫大量向上面那樣的模板代碼。可見效率低下,且可維護性差。這時,你會想,能傳輸對象就行了!json
而當你想傳輸對象時:數據結構
抱歉,沒門,只能給你一個尷尬又不是禮貌的危笑。固然,也不是不能夠,咱們能夠在原生上層把對象序列化成json對象,而後在flutter層再把json轉成flutter的對象,一樣效率不好。框架
學過Android的應該都知道AIDL(Android Interface Defination Language),即Android接口定義語言。Android中有一種高級的跨進程通訊方式——Binder,可是想要使用Binder須要瞭解一些Binder的機制和API,須要編寫大量的模板代碼。Android爲了解決這個問題,嘗試把使用Binder的方法作的小白一點。因而定義了AIDL,告訴開發者,你的接口文件必須按照我規定的來寫,你要跨進程傳輸的對象必須實現Parcelable接口。而後,Android給你生成了一個Service.Stub類,偷偷的在背後把對象的序列化、反序列化的工做都給作了。開發者使用這個Stub類就能輕鬆上手Binder這種高級的跨進程通信方法。(😋😋😋我編的,差很少啦)ide
FIDL(Flutter Interface Defination Language)即Flutter接口定義語言,它的使命和AIDL很相似,悄悄把對象的序列化、反序列化、自動生成代碼這種「髒活累活」給作了。開發者在原生代碼中看到的類,能經過@FIDL註解標記,自動在Dart側生成和原生代碼中同樣的類。FIDL是一面鏡子,把各類原平生臺的類影射到Dart中,把Dart中的類影射到各個原平生臺。oop
一、首先是Java類:性能
public class User { String name; int age; String country; Gender gender; } enum Gender { MALE, FEMALE }
二、定義FIDL接口
@FIDL public interface IUserService { void initUser(User user); }
三、執行幾個命令
四、Android側在合適的地方打開IUserServiceStub通道(IUserServiceStub是IUserService的實現類,自動生成的)
FidlChannel.openChannel(getFlutterEngine().getDartExecutor(), new IUserServiceStub() { @Override void initUser(User user){ System.out.println(user.name + " is " + user.age + "years old!"); } }
五、Flutter側使用IUserService 通道
// 綁定通道(IUserService類是自動生產的哦) await Fidl.bindChannel(IUserService.CHANNEL_NAME, _channelConnection); // 使用User類(`User類`以及它使用的`Gender枚舉`是自動生成的哦) User user = User(); user.name = 'Oscar'; user.age = 18; user.gender = Gender.MALE; user.country = 'China'; // 調用通道方法 await IUserService.initUser(user);
編譯,運行,你將能在Logcat中看到Oscar is 18 years old!。
這一部分是對少囉嗦,先看東西
部分的補充解釋,觀衆姥爺們能夠自行跳過。
上面的例子中的Map,通常來講,在Java中會對應一個類:
public class User { String name; int age; String country; Gender gender; } enum Gender { MALE, FEMALE }
若是想讓flutter傳輸這個對象而不用在flutter層手動去編寫User這個類,以及編寫fromJson/toJson方法,你能夠這樣作:
一、定義一個接口,添加註解@FIDL。這個註解將告知annotationProcessor生成一些接口和類的描述文件。
@FIDL public interface IUserService { void initUser(User user); }
二、Android Studio點擊sync,或者執行:
./gradlew assembleDebug
而後就會產生一堆json文件,以下:
這些json文件就是FIDL和類的描述文件。沒錯,也會同時生成User引用的Gender類的描述文件。
同時,還會生成IUserService的實現IUserServiceStub。即:
三、進入到你的flutter項目,在lib目錄下建立fidl目錄,把上面的json文件拷貝到這個目錄,而後執行:
flutter packages pub run fidl_model
而後就能在fidl目錄下自動生成相關的dart類:
即:
四、使用
a. Android側在合適的地方打開IUserServiceStub通道
FidlChannel.openChannel(getFlutterEngine().getDartExecutor(), new IUserServiceStub() { @Override void initUser(User user){} }
b. Flutter側綁定IUserService通道
await Fidl.bindChannel(IUserService.CHANNEL_NAME, _channelConnection);
c、Flutter調用通道方法
await IUserService.initUser(User());
d、Flutter能夠在合適的時候接觸綁定
await Fidl.unbindChannel(IUserService.CHANNEL_NAME, _channelConnection);
e、Android側能夠在合適的時候關閉通道
FidlChannel.closeChannel(userServiceStub);
一、多個參數的FIDL接口
void init(String name, Integer age, Gender gender, Conversation conversation);
二、帶返回值的FIDL接口
UserInfo getUserInfo();
三、支持泛型類的生成
public class User<T> { T country; } public class AUser<String>{}
FIDL接口:
void initUser(AUser user);
將能在dart側生成AUser和User類,且能保持繼承關係。
四、傳遞枚舉
void initEnum0(EmptyEnum e); String initEnum1(MessageStatus status);
五、傳遞集合、Map
void initList0(List<String> ids); void initList1(Collection<String> ids); void initList7(Stack<String> ids); void initList10(BlockingQueue ids);
六、傳遞複雜對象。繼承、抽象、泛型、枚舉和混合類,來一個打一個。
如今,FIDL項目只實現了從Dart側調用Android側的方法。還有如下工做要作:
搞定了對象傳輸,這些問題,都是小case啦。
爲了能知足大佬們的定製化需求,我分別在Java側和Flutter側定義了序列化/反序列化的接口類。
Java:
public interface ObjectCodec { List<byte[]> encode(Object... objects); <T> T decode(byte[] input, TypeLiteral<T> type); }
Dart:
abstract class ObjectCodec { dynamic decode(Uint8List input); List<Uint8List> encode(List objects); }
目前使用的是JsonObjectCodec,通過JSON的編解碼,性能會稍差。後面還但願和小夥伴們一塊兒努力,實現更高效的編解碼。
上述提到的功能,只要是從Flutter側調用Java側的方法相關的,大部分都已經實現了。
我作了一個Demo,模擬了一個在Android側依賴了IM(即時通信)SDK,須要在Flutter側聊天、獲取消息、發消息的場景。如下是Demo的截圖:
一、首頁,點擊按鈕調用Android側方法,開啓聊天服務
二、聊天頁面
三、發一條消息給Lucy並獲取和Lucy的聊天記錄
四、調用Android側方法發送N條消息給Wilson並獲取聊天記錄
上次作開源項目已是3年前了,那是一個Android原生刷新控件,TwinklingRefreshLayout,github 3.7k stars。後來因爲工做的緣由,成天跟Android Framework、C/C++打交道,精力也都是放到了公司的業務上,也沒有時間和精力維護下去。
那麼今天我想發佈的這個Flutter開源項目,是想經過社區的力量,和你們一塊兒把項目維護下去。我在GayHub上創建了一個組織,https://github.com/flutterFIDL。稍晚一點時間,我會把項目開源出來,一兩天內,代碼會放在這裏,https://github.com/flutterFIDL/FIDL。你們記得投幣、點贊、收藏,一鍵3連(你們若是以爲這個項目能很好解決跨平臺通訊問題,給個star能夠嘛😶)。阿不,我須要一個團隊跟我一塊兒發展這個項目,但願你熟悉Flutter開發,瞭解Android和Java開發,熱愛開源,熟悉Flutter+iOS / Flutter + Web其中的一種,並有相關項目經歷,加我vx: w354850839。
這樣一個庫,香嗎?告訴我,有多香。😉
歡迎留言評論,告訴我你的Flutter和原生通訊的使用場景,以及遇到的痛點和問題~