在Flutter中嵌入Native組件的解決方案

来源:https://www.cnblogs.com/chengxyyh/archive/2020/06/23/13182113.html
-Advertisement-
Play Games

摘要:在漫長的從Native向Flutter過渡的混合工程時期,要想平滑地過渡,在Flutter中使用Native中較為完善的控制項會是一個很好的選擇。本文希望向大家介紹AndroidView的使用方式以及在此基礎之上拓展的雙端嵌入Native組件的解決方案。 引言 在漫長的從Native向Flutt ...


摘要:在漫長的從Native向Flutter過渡的混合工程時期,要想平滑地過渡,在Flutter中使用Native中較為完善的控制項會是一個很好的選擇。本文希望向大家介紹AndroidView的使用方式以及在此基礎之上拓展的雙端嵌入Native組件的解決方案。

引言

在漫長的從Native向Flutter過渡的混合工程時期,要想平滑地過渡,在Flutter中使用Native中較為完善的控制項會是一個很好的選擇。本文希望向大家介紹AndroidView的使用方式以及在此基礎之上拓展的雙端嵌入Native組件的解決方案。

1. 使用教程

1.1. DemoRun

嵌入地圖這一場景可能在很多App中都會存在,但是現在的地圖SDK都沒有提供Flutter的庫,而自己開發一套地圖顯然不太現實。這種場景下,使用混合棧的形式是一個比較好的選擇。我們可以直接在Native的繪圖樹中嵌入一個Map,但是這個方案嵌入的View並不在Flutter的繪圖樹中,是一種比較暴力且不優雅的方式,使用起來也很費勁。

這時候,使用Flutter官方提供的控制項AndroidView就是一種比較優雅的解決方案了。這裡做了一個簡單的嵌入高德地圖的demo,就讓我們跟著這個應用場景,看一下AndroidView的使用方式和實現原理。

1.2. AndroidView使用方式

AndroidView的使用方式和MethodChannel類似,比較簡單,主要分為三個步驟:

第一步:在dart代碼的相應位置使用AndroidView,使用時需要傳入一個viewType,這個String將用於唯一標識該Widget,用於和Native的View建立關聯。

第二步:在native側添加代碼,寫一個PlatformViewFactory,PlatformViewFactory的主要任務是,在create()方法中創建一個View並把它傳給Flutter(這個說法並不准確,但是我們姑且可以這麼理解,後續會進行解釋)

第三步:使用registerViewFactory()方法註冊剛剛寫好的PlatformViewFactory,該方法需要傳入兩個參數,第一個參數需要和之前在Flutter端寫的viewType對應,第二個參數是剛剛寫好的的PlatformViewFactory。

配置高德地圖的部分這裡就省略不說了,官方有比較詳細的文檔,可以去高德開發者平臺進行查閱。

以上便是使用AndroidView的所有操作,總體看起來還是比較簡單的,但是真正要用起來,還是有兩個無法忽視的問題:

  1. View最終的顯示尺寸由誰決定?
  2. 觸摸事件是如何處理的?

下麵就讓小閑魚來給各位一一解答。

這是我的iOS開發交流群:519832104不管你是小白還是大牛歡迎入駐,可以一起分享經驗,討論技術,共同學習成長!
另附上一份各好友收集的大廠面試題,需要iOS開發學習資料、面試真題,可以進群可自行下載!

2. 原理講解

想要解決上面的兩個問題,首先必須得理解所謂"傳View"的本質是什麼?

2.1. 所謂"傳View"的本質是什麼?

要解決這個問題,自然避免不了的需要去閱讀源碼,從更深的層面去看這個傳遞的整個過程,可以整理出一張這樣的流程圖:

我們可以看到,Flutter最終拿到的是native層返回的一個textureId。根據native的知識ky h這個textureId是已經在native側渲染好了的view的繪圖數據對應的ID,通過這個ID可以直接在GPU中找到相應的繪圖數據並使用,那麼Flutter是如何去利用這個ID的呢?

在之前的深入瞭解Flutter界面開發中,也給大家介紹了Flutter的繪圖流程。我這裡也給大家再簡單整理一下

Flutter的Framework層最後會遞交給Engine層一個layerTree,在管線中會遍歷layertree的每一個葉子節點,每一個葉子節點最終會調用Skia引擎完成界面元素的繪製,在遍歷完成後,在調用glPresentRenderBuffer(IOS)或者glSwapBuffer(Android)按完成上屏操作。

Layer的種類有很多,而AndroidView則使用的是其中的TextureLayer。TextureLayer在之前的《Flutter外接紋理》中有更為詳細的介紹,這裡就不再贅述。TextureLayer在被遍歷到時,會調用一個engine層的方法SceneBuilder::addTexture() 將textureId作為參數傳入。最終在繪製的時候,skia會直接在GPU中根據textureId找到相應的繪製數據,並將其繪製到屏幕上。

那麼是不是誰拿到這個ID都可以進行這樣的操作呢?答案當然是否定的,Texture數據存儲在創建它的EGLContext對應的線程中,所以如果在別的線程進行操作是無法獲取到對應的數據的。這裡需要引入幾個概念:

  • 顯示屏對象(Display):提供合理的顯示器的像素密度和大小的信息
  • Presentation:它給Android提供了在對應的上下文(Context)和顯示屏對象(Display)上繪製的能力,通常用於雙屏異顯。

這裡不展開講解Presentation,我們只需要明白Flutter是通過Presentation實現了外接紋理,在創建Presentation時,傳入FlutterView對應的Context和創建出來的一個虛擬顯示屏對象,使得Flutter可以直接通過ID找到並使用Native創建出來的紋理數據。

2.2. View最終的顯示尺寸由誰決定?

通過上面的流程大家應該都能想到,顯示尺寸看起來像是由兩部分決定的:AndroidView的大小,Android端View的大小。那麼實際上到底是有誰來決定的呢,讓我們來做一個實驗?

直接新建一個Flutter工程,並把中間改成一個AndroidView。

//Flutter
class _MyHomePageState extends State<MyHomePage> {
  double size = 200.0;

  void _changeSize() {
    setState(() {
      size = 100.0;
    });
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text(widget.title),
      ),
      body: Container(
        color: Color(0xff0000ff),
        child: SizedBox(
          width: size,
          height: size,
          child: AndroidView(
            viewType: 'testView',
          ),
        ),
      ),
      floatingActionButton: new FloatingActionButton(
        onPressed: _changeSize,
        child: new Icon(Icons.add),
      ),
    );
  }
}

在Android端也要加上對應的代碼,為了更好地看出裁切效果,這裡使用ImageView。

//Android
@Override
public PlatformView create(final Context context, int i, Object o) {
    final ImageView imageView = new ImageView(context);
    imageView.setLayoutParams(new ViewGroup.LayoutParams(500,500));
    imageView.setBackground(context.getResources().getDrawable(R.drawable.idle_fish));
    return new PlatformView() {
        @Override
        public View getView() {
            return imageView;
        }

        @Override
        public void dispose() {

        }
    };
}

首先先看AndroidView,AndroidView對應的RenderObject是RenderAndroidView,而一個RenderObject的最終大小的確定是存在兩種可能,一種是由父節點所指定,還有一種是在父節點指定的範圍中根據自身情況確定大小。打開對應的源碼,可以看到其中有個很重要的屬性sizedByParent = true,也就是說AndroidView的大小是由其父節點所決定的,我們可以使用Container、SizedBox等控制項控制AndroidView的大小。

AndroidView的繪圖數據是Native層所提供的,那麼當Native中渲染的View的實際像素大小大於AndroidView的大小時,會發生什麼呢?通常情況下,這種情況的處理思路無非就兩種選擇,一種是裁切,另一種是縮放。Flutter保持了其一貫的做法,所有out of the bounds的Widget統一使用裁切的方式進行展示,上面所描述的情況就被當作是一種out of the bounds。

當這個View的實際像素大小小於AndroidView的時候,會發現View並不會相應地變小(Container的背景色並沒有顯露出來),沒有內容的地方會被白色填充。這其中的原因是SingleViewPresentation::onCreate中,會使用一個FrameLayout作為rootView。

2.3. 觸摸事件如何傳遞

Android的事件流大家應該都很熟悉了,自頂向下傳遞,自底向上處理或迴流。Flutter同樣是使用這一規則,但是其中AndroidView通過兩個類來去處理手勢:

MotionEventsDispatcher:負責將事件封裝成Native的事件並向Native傳遞;

AndroidViewGestureRecognizer:負責識別出相應的手勢,其中有兩個屬性:

cachedEventsforwardedPointers,只有當PointerEvent的pointer屬性在forwardedPointers中時才會去進行分發,否則會存在cacheEvents中。這裡的實現主要是為瞭解決一些事件的衝突,比如滑動事件,可以通過gestureRecognizers來進行處理,這裡可以參考官方註釋。

/// For example, with the following setup vertical drags will not be dispatched to the Android view as the vertical drag gesture is claimed by the parent [GestureDetector].
/// 
/// GestureDetector(
///   onVerticalDragStart: (DragStartDetails d) {},
///   child: AndroidView(
///     viewType: 'webview',
///     gestureRecognizers: <OneSequenceGestureRecognizer>[],
///   ),
/// )
/// 
/// To get the [AndroidView] to claim the vertical drag gestures we can pass a vertical drag gesture recognizer in [gestureRecognizers] e.g:
/// 
/// GestureDetector(
///   onVerticalDragStart: (DragStartDetails d) {},
///   child: SizedBox(
///     width: 200.0,
///     height: 100.0,
///     child: AndroidView(
///       viewType: 'webview',
///       gestureRecognizers: <OneSequenceGestureRecognizer>[ new VerticalDragGestureRecognizer() ],
///     ),
///   ),
/// )

所以總結起來,這部分流程總結起來其實也很簡單:事件最初從Native到Flutter這一階段不在本文的討論範圍之內,Flutter按照自己的規則去處理事件,如果AndroidView贏得了事件,事件就會被封裝成相應的Native端的事件並且通過方法通道傳回Native,Native再根據自己的處理事件的規則去處理。

3. 總結

3.1. 方案局限性

往大里說:這套方案是Google為瞭解決開發者日益增長的業務需求與落後的生態環境之間的矛盾而產生的,這一矛盾是一個新生態必然需要去面對的主要矛盾。為瞭解決這一個問題,最簡單的方式當然就是允許開發者使用老生態中已經非常成熟的控制項。當然,這樣是可以臨時解決Flutter生態發展不全面的問題,但是使用這套方案不可避免的需要去編寫雙端代碼(甚至現在iOS還沒有對應的控制項,當然之後肯定會更新),不能做到真正的跨端。

往小里說:這套方案存在著性能上的缺陷,在AndroidView這個類的第三句註釋中,官方就已經提到了這是一套比較昂貴的方案,避免在使用Flutter控制項也能實現的情況下去使用它。如果之前有看過《Flutter外接紋理》這一文章的同學應該知道,Flutter實現外接紋理的方案中,數據從GPU->CPU->GPU的過程代價是比較大的,在大量使用的場景會造成明顯的性能缺陷。我們通過一些手段繞過了中間CPU這一步,並且將這項技術在APP中落地,用於處理圖片資源。

3.2. 實際應用

目前閑魚從Native向Flutter的遷移工作遇到了Native的本地圖片資源在Flutter側無法訪問的問題,在現在Flutter和Native必將長期共存的情況下,重新拷貝一份資源以Flutter的規則來存儲當然可以,但是不可避免地增大了包體積,而且不好管理。

面對這個問題,我們的解法便是借鑒了AndroidView使用Texture的思路併在將其優化。實現了Native和Flutter的圖片資源歸一化。除了用於載入位於Native資源目錄下的本地圖片之外,還可以利用Native的圖片庫來載入網路圖片。

點擊此處,立即與iOS大牛交流學習


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 註意:PostgreSQL 12對主從複製實現和配置做了重大改進,如廢棄了recovery.conf,並將參數轉換為普通的PostgreSQL配置參數,使得配置群集和複製更加簡單。 PostgreSQL資料庫支持多種複製解決方案,以構建高可用性,可伸縮,容錯的應用程式,其中之一是預寫日誌(WAL)傳 ...
  • 插件 sup A Flutter widget which displays an image, a title, and a subtitle for errors, empty states, or just fancy custom messages. pub-rules simple yet ...
  • SwiftUI是一種新穎的構建UI方式和全新的編碼風格,本文以通俗易懂的語言,從Swift 5.1語法新特性和SwiftUI的優勢方面進行分享,希望對熱愛移動端的同學有一定的幫助,讓大家儘可能快速、全面和透徹地理解SwiftUI。 一、背景 蘋果於2019年度WWDC全球開發者大會上,發佈了基於Sw ...
  • 參數說明 (必填) 源碼文件夾絕對路徑(如:/Users/kelei/Documents/work/git/projectName/source) -modifyProjectName [原名稱]>[新名稱] 修改工程名。程式會修改原名稱-Swift.h、Podfile、原名稱-Bridging-H ...
  • ##layout_weight屬性 layout_weight屬性我們常常用到,但有時候會發現它還有一些奇怪的屬性,比如大多數使用時會把寬度設置成0,但要是寬度不設置成0會有什麼效果? layout_weight的屬性意義為權重大於零的控制項會分配剩餘控制項 意義為如控制項屬性設置為wrap_conten ...
  • 萬眾期待的蘋果年度開發者大會這一次雖然只能以線上方式進行,但依舊吸引了大量用戶的關註,當然更多的是開發者和第三方廠商的關註。因為蘋果各個系統的升級和變化,對於未來的開發又有了新的需求。目前,蘋果全球應用開發者已經有2300萬了。 作為軟體開發領域的盛事,蘋果全球開發者大會(WWDC)一直吸引著全世界 ...
  • Flutter中的自定義控制項其實和Android中的很類似,都有組合控制項、自繪控制項。 組合控制項就是將通用的控制項封裝起來,其內部由多個小控制項組合起來實現的,比如說公用的title欄,其實和我們平時寫頁面的時候沒什麼區別。 自繪控制項就是繼承RenderObjectWidget,然後通過提供給我們的Pai... ...
  • 第一次寫博客,有點小激動嗷~ 寫博客的原因主要是在練手之餘,總結歸納以及供大家參考,見笑了嗷~ 接下來分四個大部分:經典藍牙(BT,BlueTooth)、低功耗藍牙(BLE,Bluetooth Low Energy)、Wifi直連(WiFiDirect)、WiFi熱點(WiFiHot)展開討論。 每 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...