<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                企業??AI智能體構建引擎,智能編排和調試,一鍵部署,支持知識庫和私有化部署方案 廣告
                為了把 Flutter 引入到原生工程,我們需要把 Flutter 工程改造為原生工程的一個組件依賴,并以組件化的方式管理不同平臺的 Flutter 構建產物,即 Android 平臺使用 aar、iOS 平臺使用 pod 進行依賴管理。這樣,我們就可以在 Android 工程中通過 FlutterView,iOS 工程中通過 FlutterViewController,為 Flutter 搭建應用入口,實現 Flutter 與原生的混合開發方式。 我在[第 26 篇](https://time.geekbang.org/column/article/127601)文章中提到,FlutterView 與 FlutterViewController 是初始化 Flutter 的地方,也是應用的入口。可以看到,以混合開發方式接入 Flutter,與開發一個純 Flutter 應用在運行機制上并無任何區別,只需要原生工程為它提供一個畫板容器(Android 為 FlutterView,iOS 為 FlutterViewController),Flutter 就可以自己管理頁面導航棧,從而實現多個復雜頁面的渲染和切換。 關于純 Flutter 應用的頁面路由與導航,我已經在[第 21 篇文章](https://time.geekbang.org/column/article/118421)中與你介紹過了。今天這篇文章,我會為你講述在混合開發中,應該如何管理混合導航棧。 對于混合開發的應用而言,通常我們只會將應用的部分模塊修改成 Flutter 開發,其他模塊繼續保留原生開發,因此應用內除了 Flutter 的頁面之外,還會有原生 Android、iOS 的頁面。在這種情況下,Flutter 頁面有可能會需要跳轉到原生頁面,而原生頁面也可能會需要跳轉到 Flutter 頁面。這就涉及到了一個新的問題:如何統一管理原生頁面和 Flutter 頁面跳轉交互的混合導航棧。 接下來,我們就從這個問題入手,開始今天的學習吧。 ## 混合導航棧 混合導航棧,指的是原生頁面和 Flutter 頁面相互摻雜,存在于用戶視角的頁面導航棧視圖中。 以下圖為例,Flutter 與原生 Android、iOS 各自實現了一套互不相同的頁面映射機制,即原生采用單容器單頁面(一個 ViewController/Activity 對應一個原生頁面)、Flutter 采用單容器多頁面(一個 ViewController/Activity 對應多個 Flutter 頁面)的機制。Flutter 在原生的導航棧之上又自建了一套 Flutter 導航棧,這使得 Flutter 頁面與原生頁面之間涉及頁面切換時,我們需要處理跨引擎的頁面切換。 :-: ![](https://img.kancloud.cn/60/3d/603d3f3777ef09a420b7b794efe0c9dd_1056x842.png) 圖 1 混合導航棧示意圖 接下來,我們就分別看看從原生頁面跳轉至 Flutter 頁面,以及從 Flutter 頁面跳轉至原生頁面,應該如何處理吧。 ### 從原生頁面跳轉至 Flutter 頁面 從原生頁面跳轉至 Flutter 頁面,實現起來比較簡單。 因為 Flutter 本身依托于原生提供的容器(iOS 為 FlutterViewController,Android 為 Activity 中的 FlutterView),所以我們通過初始化 Flutter 容器,為其設置初始路由頁面之后,就可以以原生的方式跳轉至 Flutter 頁面了。 如下代碼所示。對于 iOS,我們初始化一個 FlutterViewController 的實例,為其設置初始化頁面路由后,將其加入原生的視圖導航棧中完成跳轉。 對于 Android 而言,則需要多加一步。因為 Flutter 頁面的入口并不是原生視圖導航棧的最小單位 Activity,而是一個 View(即 FlutterView),所以我們還需要把這個 View 包裝到 Activity 的 contentView 中。在 Activity 內部設置頁面初始化路由之后,在外部就可以采用打開一個普通的原生視圖的方式,打開 Flutter 頁面了。 ~~~ //iOS 跳轉至 Flutter 頁面 FlutterViewController *vc = [[FlutterViewController alloc] init]; [vc setInitialRoute:@"defaultPage"];// 設置 Flutter 初始化路由頁面 [self.navigationController pushViewController:vc animated:YES];// 完成頁面跳轉 //Android 跳轉至 Flutter 頁面 // 創建一個作為 Flutter 頁面容器的 Activity public class FlutterHomeActivity extends AppCompatActivity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 設置 Flutter 初始化路由頁面 View FlutterView = Flutter.createView(this, getLifecycle(), "defaultRoute"); // 傳入路由標識符 setContentView(FlutterView);// 用 FlutterView 替代 Activity 的 ContentView } } // 用 FlutterPageActivity 完成頁面跳轉 Intent intent = new Intent(MainActivity.this, FlutterHomeActivity.class); startActivity(intent); ~~~ ### 從 Flutter 頁面跳轉至原生頁面 從 Flutter 頁面跳轉至原生頁面,則會相對麻煩些,我們需要考慮以下兩種場景: * 從 Flutter 頁面打開新的原生頁面; * 從 Flutter 頁面回退到舊的原生頁面。 首先,我們來看看 Flutter 如何打開原生頁面。 Flutter 并沒有提供對原生頁面操作的方法,所以不可以直接調用。我們需要通過方法通道(你可以再回顧下[第 26 篇](https://time.geekbang.org/column/article/127601)文章的相關內容),在 Flutter 和原生兩端各自初始化時,提供 Flutter 操作原生頁面的方法,并注冊方法通道,在原生端收到 Flutter 的方法調用時,打開新的原生頁面。 接下來,我們再看看如何從 Flutter 頁面回退到原生頁面。 因為 Flutter 容器本身屬于原生導航棧的一部分,所以當 Flutter 容器內的根頁面(即初始化路由頁面)需要返回時,我們需要關閉 Flutter 容器,從而實現 Flutter 根頁面的關閉。同樣,Flutter 并沒有提供操作 Flutter 容器的方法,因此我們依然需要通過方法通道,在原生代碼宿主為 Flutter 提供操作 Flutter 容器的方法,在頁面返回時,關閉 Flutter 頁面。 Flutter 跳轉至原生頁面的兩種場景,如下圖所示: :-: ![](https://img.kancloud.cn/78/34/78349cea1db3f8eb94ddb28af244494b_1522x724.png) 圖 2 Flutter 頁面跳轉至原生頁面示意圖 **接下來,我們一起看看這兩個需要通過方法通道實現的方法,即打開原生頁面 openNativePage,與關閉 Flutter 頁面 closeFlutterPage,在 Android 和 iOS 平臺上分別如何實現。** 注冊方法通道最合適的地方,是 Flutter 應用的入口,即在 FlutterViewController(iOS 端)和 Activity 中的 FlutterView(Android 端)這兩個容器內部初始化 Flutter 頁面前。為了將 Flutter 相關的行為封裝到容器內部,我們需要分別繼承 FlutterViewController 和 Activity,在其 viewDidLoad 和 onCreate 初始化容器時,注冊 openNativePage 和 closeFlutterPage 這兩個方法。 iOS 端的實現代碼如下所示: ~~~ @interface FlutterHomeViewController : FlutterViewController @end @implementation FlutterHomeViewController - (void)viewDidLoad { [super viewDidLoad]; // 聲明方法通道 FlutterMethodChannel* channel = [FlutterMethodChannel methodChannelWithName:@"samples.chenhang/navigation" binaryMessenger:self]; // 注冊方法回調 [channel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) { // 如果方法名為打開新頁面 if([call.method isEqualToString:@"openNativePage"]) { // 初始化原生頁面并打開 SomeOtherNativeViewController *vc = [[SomeOtherNativeViewController alloc] init]; [self.navigationController pushViewController:vc animated:YES]; result(@0); } // 如果方法名為關閉 Flutter 頁面 else if([call.method isEqualToString:@"closeFlutterPage"]) { // 關閉自身 (FlutterHomeViewController) [self.navigationController popViewControllerAnimated:YES]; result(@0); } else { result(FlutterMethodNotImplemented);// 其他方法未實現 } }]; } @end ~~~ Android 端的實現代碼如下所示: ~~~ // 繼承 AppCompatActivity 來作為 Flutter 的容器 public class FlutterHomeActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 初始化 Flutter 容器 FlutterView flutterView = Flutter.createView(this, getLifecycle(), "defaultPage"); // 傳入路由標識符 // 注冊方法通道 new MethodChannel(flutterView, "samples.chenhang/navigation").setMethodCallHandler( new MethodCallHandler() { @Override public void onMethodCall(MethodCall call, Result result) { // 如果方法名為打開新頁面 if(call.method.equals("openNativePage")) { // 新建 Intent,打開原生頁面 Intent intent = new Intent(FlutterHomeActivity.this, SomeNativePageActivity.class); startActivity(intent); result.success(0); } // 如果方法名為關閉 Flutter 頁面 else if(call.method.equals("closeFlutterPage")) { // 銷毀自身 (Flutter 容器) finish(); result.success(0); } else { // 方法未實現 result.notImplemented(); } } }); // 將 flutterView 替換成 Activity 的 contentView setContentView(flutterView); } } ~~~ 經過上面的方法注冊,我們就可以在 Flutter 層分別通過 openNativePage 和 closeFlutterPage 方法,來實現 Flutter 頁面與原生頁面之間的切換了。 在下面的例子中,Flutter 容器的根視圖 DefaultPage 包含有兩個按鈕: * 點擊左上角的按鈕后,可以通過 closeFlutterPage 返回原生頁面; * 點擊中間的按鈕后,會打開一個新的 Flutter 頁面 PageA。PageA 中也有一個按鈕,點擊這個按鈕之后會調用 openNativePage 來打開一個新的原生頁面。 ~~~ void main() => runApp(_widgetForRoute(window.defaultRouteName)); // 獲取方法通道 const platform = MethodChannel('samples.chenhang/navigation'); // 根據路由標識符返回應用入口視圖 Widget _widgetForRoute(String route) { switch (route) { default:// 返回默認視圖 return MaterialApp(home:DefaultPage()); } } class PageA extends StatelessWidget { ... @override Widget build(BuildContext context) { return Scaffold( body: RaisedButton( child: Text("Go PageB"), onPressed: ()=>platform.invokeMethod('openNativePage')// 打開原生頁面 )); } } class DefaultPage extends StatelessWidget { ... @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("DefaultPage Page"), leading: IconButton(icon:Icon(Icons.arrow_back), onPressed:() => platform.invokeMethod('closeFlutterPage')// 關閉 Flutter 頁面 )), body: RaisedButton( child: Text("Go PageA"), onPressed: ()=>Navigator.push(context, MaterialPageRoute(builder: (context) => PageA())),// 打開 Flutter 頁面 PageA )); } } ~~~ 整個混合導航棧示例的代碼流程,如下圖所示。通過這張圖,你就可以把這個示例的整個代碼流程串起來了。 :-: ![](https://img.kancloud.cn/93/2e/932efcc59bcc0ee590e644e67288ba53_1338x402.png) 圖 3 混合導航棧示例 在我們的混合應用中,RootViewController 與 MainActivity 分別是 iOS 和 Android 應用的原生頁面入口,可以初始化為 Flutter 容器的 FlutterHomeViewController(iOS 端)與 FlutterHomeActivity(Android 端)。 在為其設置初始路由頁面 DefaultPage 之后,就可以以原生的方式跳轉至 Flutter 頁面。但是,Flutter 并未提供接口,來支持從 Flutter 的 DefaultPage 頁面返回到原生頁面,因此我們需要利用方法通道來注冊關閉 Flutter 容器的方法,即 closeFlutterPage,讓 Flutter 容器接收到這個方法調用時關閉自身。 在 Flutter 容器內部,我們可以使用 Flutter 內部的頁面路由機制,通過 Navigator.push 方法,完成從 DefaultPage 到 PageA 的頁面跳轉;而當我們想從 Flutter 的 PageA 頁面跳轉到原生頁面時,因為涉及到跨引擎的頁面路由,所以我們仍然需要利用方法通道來注冊打開原生頁面的方法,即 openNativePage,讓 Flutter 容器接收到這個方法調用時,在原生代碼宿主完成原生頁面 SomeOtherNativeViewController(iOS 端)與 SomeNativePageActivity(Android 端)的初始化,并最終完成頁面跳轉。 ## 總結 好了,今天的分享就到這里。我們一起總結下今天的主要內容吧。 對于原生 Android、iOS 工程混編 Flutter 開發,由于應用中會同時存在 Android、iOS 和 Flutter 頁面,所以我們需要妥善處理跨渲染引擎的頁面跳轉,解決原生頁面如何切換 Flutter 頁面,以及 Flutter 頁面如何切換到原生頁面的問題。 在原生頁面切換到 Flutter 頁面時,我們通常會將 Flutter 容器封裝成一個獨立的 ViewController(iOS 端)或 Activity(Android 端),在為其設置好 Flutter 容器的頁面初始化路由(即根視圖)后,原生的代碼就可以按照打開一個普通的原生頁面的方式,來打開 Flutter 頁面了。 而如果我們想在 Flutter 頁面跳轉到原生頁面,則需要同時處理好打開新的原生頁面,以及關閉自身回退到老的原生頁面兩種場景。在這兩種場景下,我們都需要利用方法通道來注冊相應的處理方法,從而在原生代碼宿主實現新頁面的打開和 Flutter 容器的關閉。 需要注意的是,與純 Flutter 應用不同,原生應用混編 Flutter 由于涉及到原生頁面與 Flutter 頁面之間切換,因此導航棧內可能會出現多個 Flutter 容器的情況,即多個 Flutter 實例。 Flutter 實例的初始化成本非常高昂,每啟動一個 Flutter 實例,就會創建一套新的渲染機制,即 Flutter Engine,以及底層的 Isolate。而這些實例之間的內存是不互相共享的,會帶來較大的系統資源消耗。 因此我們在實際業務開發中,應該盡量用 Flutter 去開發閉環的業務模塊,原生只需要能夠跳轉到 Flutter 模塊,剩下的業務都應該在 Flutter 內部完成,而**盡量避免 Flutter 頁面又跳回到原生頁面,原生頁面又啟動新的 Flutter 實例的情況**。 為了解決混編工程中 Flutter 多實例的問題,業界有兩種解決方案: * 以今日頭條為代表的[修改 Flutter Engine 源碼](https://mp.weixin.qq.com/s/-vyU1JQzdGLUmLGHRImIvg),使多 FlutterView 實例對應的多 Flutter Engine 能夠在底層共享 Isolate; * 以閑魚為代表的[共享 FlutterView](https://www.infoq.cn/article/VBqfCIuwdjtU_CmcKaEu),即由原生層驅動 Flutter 層渲染內容的方案。 坦白說,這兩種方案各有不足: * 前者涉及到修改 Flutter 源碼,不僅開發維護成本高,而且增加了線程模型和內存回收出現異常的概率,穩定性不可控。 * 后者涉及到跨渲染引擎的 hack,包括 Flutter 頁面的新建、緩存和內存回收等機制,因此在一些低端機或是處理頁面切換動畫時,容易出現渲染 Bug。 * 除此之外,這兩種方式均與 Flutter 的內部實現綁定較緊,因此在處理 Flutter SDK 版本升級時往往需要耗費較大的適配成本。 綜合來說,目前這兩種解決方案都不夠完美。所以,在 Flutter 官方支持多實例單引擎之前,我們還是盡量在產品模塊層面,保證應用內不要出現多個 Flutter 容器實例吧。 我把今天分享所涉及到的知識點打包到了 GitHub([flutter\_module\_page](https://github.com/cyndibaby905/29_flutter_module_page)、[android\_demo](https://github.com/cyndibaby905/29_android_hybrid_demo)、[iOS\_demo](https://github.com/cyndibaby905/29_ios_hybrid_demo))中,你可以下載下來,反復運行幾次,加深理解與記憶。 ## 思考題 最后,我給你留兩道思考題吧。 1. 請在 openNativePage 方法的基礎上,增加頁面 id 的功能,可以支持在 Flutter 頁面打開任意的原生頁面。 2. 混編工程中會出現兩種頁面過渡動畫:原生頁面之間的切換動畫、Flutter 頁面之間的切換動畫。請你思考下,如何能夠確保這兩種頁面過渡動畫在應用整體的效果是一致的。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看