Android Flutter 混合開發(fā)高仿大廠App

自上篇 Flutter 10天高仿大廠App及小技巧積累總結(jié) 的續(xù)篇澄峰,這次更是干貨滿滿昔驱。

這篇文章將概述 Android組件化的架構(gòu)搭建FlutterAndroid 如何混合開發(fā) (整個App只有首頁是用原生Android完成型酥,其他頁面都是引入之前的做好的Flutter頁面) 诸尽,主宿主程序由 Android 搭建劳翰,采用了組件化的架構(gòu)搭建整個 App ,不同業(yè)務事甜,對應不同的 module 工程谬泌,業(yè)務之間采用接口通信 (ARouter) ,以 module 的形式混入 Flutter逻谦,通過 MethodChannelFlutter 端進行數(shù)據(jù)通信等掌实,且這些功能實現(xiàn)源碼開源,感興趣的小伙伴可以移步至 GitHub邦马。
以下博文會分為4個部分概述:

  • 項目完成的功能預覽
  • 項目組件化結(jié)構(gòu)分析
  • 項目功能詳細概述(所用知識點)
  • Android Flutter 混合開發(fā)

項目完成的功能預覽

首先贱鼻,我們還是通過一個視頻來快速預覽下項目完成的功能和運行效果,如下

大家也可以滋将, 移步這里點擊觀看 (點擊齒輪 --> 更多播放設置邻悬,可以隱藏黑邊)

看完視頻后,其實大部分功能和之前的 純flutter項目 功能相同随闽,只是首頁新增了4個tab推薦頁面及攜程二樓和布局改變父丰。

大家也可掃描,安裝體驗:

手機掃描二維碼安裝

項目組件化結(jié)構(gòu)分析

項目結(jié)構(gòu)圖預覽

其次掘宪,分析梳理下項目結(jié)構(gòu)蛾扇,項目的結(jié)構(gòu)大致如圖,還有一些細枝末節(jié)的沒有體現(xiàn)在圖里:

project structure

項目結(jié)構(gòu)分析

業(yè)務工程

把具體獨立的業(yè)務都拆分成單獨的 module 減小項目的維護壓力

  • ft_home: 首頁模塊添诉,這個模塊其實還可以繼續(xù)拆分屁桑,可把4個 tab (精選、附近栏赴、景點蘑斧、美食) 頁都拆成模塊,這里我暫時沒有拆分须眷,后續(xù)會完成
  • ft_destination: 目的地模塊竖瘾,其實并沒有建立這個模塊,因為直接引入了之前做好的 flutter 頁面
  • ft_travel: 旅拍模塊花颗,同樣也使用了 flutter 頁面
  • flutter: flutter模塊捕传,這個模塊是從 flutter_module 中自動生成的,后面介紹到

基礎庫工程

把具體的功能都封裝成獨立的庫供業(yè)務模塊使用扩劝,降低項目的維護成本及代碼之間耦合性

  • lib_network: 網(wǎng)絡庫庸论,使用 okhttp 插件二次封裝职辅,業(yè)務層簡單的調(diào)用即可
  • lib_webview: 打開網(wǎng)頁的webview庫,使用了 agentweb 插件二次封裝聂示,業(yè)務層只需要一句代碼即可完成網(wǎng)頁的跳轉(zhuǎn)
  • lib_image_loader: 圖片加載庫域携,使用了 glide 插件二次封裝,業(yè)務層只需一句代碼即可加載不同參數(shù)的圖片
  • lib_asr: 百度AI語音庫鱼喉,通過 Android 集成好供 Flutter 端使用
  • lib_common_ui: 公共UI庫秀鞭,重復多次使用的頁面集中管理
  • lib_base: 基礎庫,通過 ARouter 的 service 功能暴露接口提供服務給業(yè)務層扛禽,當然業(yè)務層也可以在這里暴露接口供外界使用

這里有一些使用的插件并沒有在項目結(jié)構(gòu)圖里體現(xiàn)出來(結(jié)構(gòu)圖空間有限)锋边。

插件

在這里把項目使用的插件整理列舉出來供大家參考:

  • magicindicator 強大、可定制编曼、易擴展的 ViewPager 指示器框架豆巨,首頁的4個 tab (精選、附近灵巧、景點搀矫、美食) 就是用這個實現(xiàn)的抹沪。
  • immersionbar 一句代碼輕松實現(xiàn)狀態(tài)欄刻肄、導航欄沉浸式管理
  • pagerBottomTabStrip 頁面底部和側(cè)邊的導航欄,首頁融欧、目的地敏弃、旅拍、我的頁面切換就是用這個實現(xiàn)的噪馏。
  • rxjava/rxandroid 異步和鏈式編程
  • butterknife view注入插件麦到,配合Android插件使用,可快速自動生成 init view的代碼欠肾,不用寫一句 findViewById 的代碼瓶颠。
  • gson json解析,配合Android插件使用刺桃,可快速生成實體類
  • smartRefreshLayout 智能下拉刷新框架粹淋,攜程二樓及下拉刷新加載更多就是用這個實現(xiàn)的
  • eventbus 發(fā)布/訂閱事件總線,優(yōu)雅的完成組件之間通信
  • arouter 依賴注入瑟慈、路由跳轉(zhuǎn)桃移、注冊service,優(yōu)雅的完成模塊之間的通信
  • okhttp 網(wǎng)絡請求插件
  • agentweb webview框架葛碧,進行簡單的二次封裝可優(yōu)雅的進行網(wǎng)頁跳轉(zhuǎn)
  • glide 高性能借杰、可擴展的圖片加載插件
  • banner 圖片輪播控件

基本就是這些了,應該沒有漏的进泼,插件的詳細使用蔗衡,請進入各插件的 GitHub 主頁纤虽。

在此,把我項目的插件引入代碼及版本管理的 gradle 代碼貼出來绞惦,如下:

插件引入代碼:

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])

    implementation rootProject.depsLibs.appcompat
    implementation rootProject.depsLibs.legacy
    implementation rootProject.depsLibs.recyclerview
    implementation rootProject.depsLibs.constraintlayout
    implementation rootProject.depsLibs.cardview

    //tab指示器
    implementation rootProject.depsLibs.magicindicator
    //沉浸式
    implementation rootProject.depsLibs.immersionbar
    //導航欄
    implementation rootProject.depsLibs.pagerBottomTabStrip
    //rxjava
    implementation rootProject.depsLibs.rxjava
    //rxandroid
    implementation rootProject.depsLibs.rxandroid
    //view 注入
    implementation rootProject.depsLibs.butterknife
    //view 注入
    annotationProcessor rootProject.depsLibs.butterknifeCompiler
    //gson
    implementation rootProject.depsLibs.gson
    //banner
    implementation rootProject.depsLibs.banner
    //smartRefreshLayout 上下拉刷新
    implementation rootProject.depsLibs.smartRefreshLayout
    implementation rootProject.depsLibs.refreshHeader
    implementation rootProject.depsLibs.refreshHeaderTwoLevel
    implementation rootProject.depsLibs.refreshFooter
    //eventbus
    implementation rootProject.depsLibs.eventbus
    //arouter庫
    implementation(rootProject.depsLibs.arouterapi) {
        exclude group: 'com.android.support'
    }
    annotationProcessor rootProject.depsLibs.aroutercompiler

    //引入home模塊
    implementation project(':ft_home')
    //引入圖片加載庫
    implementation project(':lib_image_loader')
    //引入網(wǎng)絡庫
    implementation project(':lib_network')
    //webview
    implementation project(':lib_webview')
    //引入基礎ui庫
    implementation project(':lib_common_ui')
    //base庫
    implementation project(':lib_base')
    //引入flutter模塊
    implementation project(':flutter')
    //引入百度AI語音庫
    implementation project(':lib_asr')
}

版本管理代碼 (統(tǒng)一管理版本號) :

ext {
    android = [
            compileSdkVersion: 29,
            buildToolsVersion: "29.0.0",
            minSdkVersion    : 19,
            targetSdkVersion : 29,
            applicationId    : 'net.lishaoy.android_ctrip',
            versionCode      : 1,
            versionName      : '1.0',
            multiDexEnabled  : true,
    ]

    depsVersion = [
            appcompat            : '1.1.0',
            legacy               : '1.0.0',
            recyclerview         : '1.0.0',
            constraintlayout     : '1.1.3',
            cardview             : '1.0.0',
            magicindicator       : '1.5.0',
            immersionbar         : '3.0.0',
            pagerBottomTabStrip  : '2.3.0X',
            glide                : '4.11.0',
            glidecompiler        : '4.11.0',
            butterknife          : '10.2.1',
            butterknifeCompiler  : '10.2.1',
            rxjava               : '3.0.0',
            rxandroid            : '3.0.0',
            okhttp               : '4.7.2',
            okhttpLogging        : '4.7.2',
            gson                 : '2.8.6',
            banner               : '2.0.10',
            smartRefreshLayout   : '2.0.1',
            refreshHeader        : '2.0.1',
            refreshFooter        : '2.0.1',
            refreshHeaderTwoLevel: '2.0.1',
            eventbus             : '3.2.0',
            agentweb             : '4.1.3',
            arouterapi           : '1.5.0',
            aroutercompiler      : '1.2.2',

    ]

    depsLibs = [
            appcompat            : "androidx.appcompat:appcompat:${depsVersion.appcompat}",
            legacy               : "androidx.legacy:legacy-support-v4:${depsVersion.legacy}",
            recyclerview         : "androidx.recyclerview:recyclerview:${depsVersion.recyclerview}",
            constraintlayout     : "androidx.constraintlayout:constraintlayout:${depsVersion.constraintlayout}",
            cardview             : "androidx.cardview:cardview:${depsVersion.cardview}",
            magicindicator       : "com.github.hackware1993:MagicIndicator:${depsVersion.magicindicator}",
            immersionbar         : "com.gyf.immersionbar:immersionbar:${depsVersion.immersionbar}",
            pagerBottomTabStrip  : "me.majiajie:pager-bottom-tab-strip:${depsVersion.pagerBottomTabStrip}",
            glide                : "com.github.bumptech.glide:glide:${depsVersion.glide}",
            glidecompiler        : "com.github.bumptech.glide:compiler:${depsVersion.glidecompiler}",
            butterknife          : "com.jakewharton:butterknife:${depsVersion.butterknife}",
            butterknifeCompiler  : "com.jakewharton:butterknife-compiler:${depsVersion.butterknifeCompiler}",
            rxjava               : "io.reactivex.rxjava3:rxjava:${depsVersion.rxjava}",
            rxandroid            : "io.reactivex.rxjava3:rxandroid:${depsVersion.rxandroid}",
            okhttp               : "com.squareup.okhttp3:okhttp:${depsVersion.okhttp}",
            okhttpLogging        : "com.squareup.okhttp3:logging-interceptor:${depsVersion.okhttpLogging}",
            gson                 : "com.google.code.gson:gson:${depsVersion.gson}",
            banner               : "com.youth.banner:banner:${depsVersion.banner}",
            smartRefreshLayout   : "com.scwang.smart:refresh-layout-kernel:${depsVersion.smartRefreshLayout}",
            refreshHeader        : "com.scwang.smart:refresh-header-classics:${depsVersion.refreshHeader}",
            refreshHeaderTwoLevel: "com.scwang.smart:refresh-header-two-level:${depsVersion.refreshHeader}",
            refreshFooter        : "com.scwang.smart:refresh-footer-classics:${depsVersion.refreshFooter}",
            eventbus             : "org.greenrobot:eventbus:${depsVersion.eventbus}",
            agentweb             : "com.just.agentweb:agentweb:${depsVersion.agentweb}",
            arouterapi           : "com.alibaba:arouter-api:${depsVersion.arouterapi}",
            aroutercompiler      : "com.alibaba:arouter-compiler:${depsVersion.aroutercompiler}",
    ]
}

項目功能詳細概述(所用知識點)

這里主要對首頁功能及知識點進行概述廓推,由于其他頁面是引入了之前的 Flutter 頁面, 具體功能在 Flutter 10天高仿大廠App及小技巧積累總結(jié) 已經(jīng)介紹過了翩隧,在這就不再闡述樊展。

首頁重點概述以下功能的實現(xiàn):

  • 下拉刷新、攜程二樓
  • 搜索appBar
  • 漸變色網(wǎng)格導航
  • banner組件
  • 多狀態(tài)的tab指示器 (滾動固定頂部)

下拉刷新堆生、攜程二樓

首先专缠,看看具體的效果圖,如圖:

second floor

下拉刷新和攜程二樓是使用 smartRefreshLayout 插件完成的淑仆,實現(xiàn)代碼如下:

private void initRefreshMore() {
    homeHeader.setRefreshHeader(new ClassicsHeader(getContext()), -1, (int) Utils.dp2px(76)); //設置下拉刷新及二樓header的高度
    homeHeader.setFloorRate(1.6f); //設置二樓觸發(fā)比率
    homeRefreshContainer.setPrimaryColorsId(R.color.colorPrimary, R.color.white); //設置下拉刷新及二樓提示文字顏色
    homeRefreshContainer.setOnMultiListener(new SimpleMultiListener() {
        @Override
        public void onLoadMore(@NonNull RefreshLayout refreshLayout) {
            loadMore(refreshLayout); //加載更多
        }

        @Override
        public void onRefresh(@NonNull RefreshLayout refreshLayout) {
            refreshLayout.finishRefresh(1600); //設置下拉刷新延遲
        }

        @Override
        public void onHeaderMoving(RefreshHeader header, boolean isDragging, float percent, int offset, int headerHeight, int maxDragHeight) {
            homeSecondFloorImg.setVisibility(View.VISIBLE);  //隱藏二樓背景圖
            homeSearchBarContainer.setAlpha(1 - Math.min(percent, 1)); //改變searchBar透明度
        }

        @Override
        public void onStateChanged(@NonNull RefreshLayout refreshLayout, @NonNull RefreshState oldState, @NonNull RefreshState newState) {
            if (oldState == RefreshState.ReleaseToTwoLevel) {  //即將去往二樓狀態(tài)處理
                homeSecondFloorImg.setVisibility(View.GONE);
                homeHeaderContent.animate().alpha(1).setDuration(666);
            } else if (newState == RefreshState.PullDownCanceled) { //下拉取消狀態(tài)處理
                homeHeaderContent.animate().alpha(0).setDuration(666);
            } else if (newState == RefreshState.Refreshing) { //正在刷新狀態(tài)處理
                homeHeaderContent.animate().alpha(0).setDuration(666);
            } else if (oldState == RefreshState.TwoLevelReleased) { // 準備去往二樓完成狀態(tài)處理涝婉,這里打開webview
                WebViewImpl.getInstance().gotoWebView("https://m.ctrip.com/webapp/you/tsnap/secondFloorIndex.html?isHideNavBar=YES&s_guid=feb780be-c55a-4f92-a6cd-2d81e04d3241", true);
                homeHeader.finishTwoLevel();
            } else if (oldState == RefreshState.TwoLevel) { //到達二樓狀態(tài)處理
                homeCustomScrollView.setVisibility(View.GONE);
                homeHeaderContent.animate().alpha(0).setDuration(666);
            } else if (oldState == RefreshState.TwoLevelFinish) { //二樓完成狀態(tài)處理
                homeCustomScrollView.setVisibility(View.VISIBLE);
                homeCustomScrollView.animate().alpha(1).setDuration(666);
            }
        }

    });

}

XML 頁面布局文件代碼如下:

<com.scwang.smart.refresh.layout.SmartRefreshLayout
    android:id="@+id/home_refresh_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:clipChildren="false"
    app:srlAccentColor="@color/colorPrimary"
    app:srlPrimaryColor="@color/colorPrimary">
    <com.scwang.smart.refresh.header.TwoLevelHeader
        android:id="@+id/home_header"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="top">

        <ImageView
            android:id="@+id/home_second_floor_img"
            android:layout_width="match_parent"
            android:layout_height="460dp"
            android:layout_alignTop="@+id/home_header"
            android:scaleType="fitXY"
            android:src="@drawable/second_floor"
            android:visibility="gone"/>
        <FrameLayout
            android:id="@+id/home_header_content"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:alpha="0">
            <ImageView
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:scaleType="fitXY"
                android:src="@drawable/second_floor" />
        </FrameLayout>

    </com.scwang.smart.refresh.header.TwoLevelHeader>
    
    ...

    <com.scwang.smart.refresh.footer.ClassicsFooter
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</com.scwang.smart.refresh.layout.SmartRefreshLayout>

具體實現(xiàn)詳情,可移步 GitHub 查看源碼蔗怠。

搜索appBar

搜索欄的滾動的 placeholder 文字是使用 banner 插件實現(xiàn)的墩弯,點擊搜索框可跳轉(zhuǎn)到搜索頁面 (flutter寫的搜索頁面) ,跳轉(zhuǎn)頁面后可以把 placeholder 文字帶到 flutter 搜索頁面寞射。

效果如圖:

search bar

滾動的placeholder文字實現(xiàn)代碼如下 (搜索框的實現(xiàn)就不再這里展示都是一些XML布局代碼)

        homeSearchBarPlaceholder
                .setAdapter(new HomeSearchBarPlaceHolderAdapter(homeData.getSearchPlaceHolderList())) // 設置適配器
                .setOrientation(Banner.VERTICAL) // 設置滾動方向
                .setDelayTime(3600) // 設置間隔時間
                .setOnBannerListener(new OnBannerListener() {
                    @Override
                    public void OnBannerClick(Object data, int position) {  //點擊打開 flutter 搜索頁面
                        ARouter.getInstance()
                                .build("/home/search")
                                .withString("placeHolder", ((Home.SearchPlaceHolderListBean) data).getText())
                                .navigation();
                    }
                });
    }

searchBar的具體功能不過多闡述渔工,和之前的項目一致。

漸變色網(wǎng)格導航

漸變色網(wǎng)格導航基本都是一些 XML 頁面布局代碼桥温,只是我把它封裝成了單獨的組件引矩,效果如圖

gridBar.png

封裝之后的引入就非常簡單,代碼如下:

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:background="@color/white">

    <!-- 網(wǎng)格導航 -->
    <net.lishaoy.ft_home.GridNavView
        android:id="@+id/home_grid_nav_container"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    ...

</LinearLayout>

具體實現(xiàn)詳情侵浸,可移步 GitHub 查看源碼旺韭。

banner組件

banner組件也是用 banner 插件實現(xiàn)的,如圖

banner

實現(xiàn)代碼如下:

private void initBanner() {
    homeBanner.addBannerLifecycleObserver(this)
            .setAdapter(new HomeBannerAdapter(homeData.getBannerList())) //設置適配器
            .setIndicator(new EllipseIndicator(getContext()))           //設置指示器掏觉,如圖的指示器是我自定義的插件里并沒有提供
            .setIndicatorSelectedColorRes(R.color.white)                //設置指示器顏色
            .setIndicatorSpace((int) BannerUtils.dp2px(10))             //設置間距
            .setBannerRound(BannerUtils.dp2px(6));                      //設置圓角

}

多狀態(tài)的tab指示器

多狀態(tài)的tab指示器的實現(xiàn)需要注意很多細節(jié)区端,因為它是在首頁的 fragmentScrollView 里嵌入 viewPaper,首先你會發(fā)現(xiàn) viewPaper 不顯示的問題澳腹,其次是滾動不流暢的問題织盼,這兩個問題我的解決方案是:

  • viewPaper 不顯示的問題:使用自定義的 ViewPager 重寫 onMeasure 方法,重新計算高度
  • 滾動不流暢的問題:使用自定義的 ScrollView遵湖,重寫 computeScrollonScrollChanged 重新獲取滾動距離

實現(xiàn)效果如圖:

tab page

這個功能實現(xiàn)代碼過多不便在這里展示悔政,具體實現(xiàn)詳情,可移步 GitHub 查看源碼延旧。

Android Flutter 混合開發(fā)

這個項目的實現(xiàn)只有首頁是用 Android 原生實現(xiàn)谋国,其他的頁面均是 Flutter 實現(xiàn)的,之前 純Flutter項目迁沫。

Android 引入 Flutter 進行混合開發(fā)芦瘾,需要以下幾個步驟

  • 建立一個flutter module
  • 編寫flutter代碼 (創(chuàng)建 flutter 路由)
  • flutter 和 android 之間相互通信

下面依次概述這幾部分是如何操作實現(xiàn)的捌蚊。

建立一個flutter module

這個應該不用過多描述,基本操作大家都會 File --> New --> New Module 如圖:

flutter module

新建完成之后近弟,android studio 會自動生成配置代碼到 gradle 配置文件里缅糟,且生成一個 flutter 的 library 模塊。

Tips:
新建的時候最好 flutter module 和 android 項目放到同級目錄下祷愉;
新版的 android studio 才會自動生成 gradle 配置代碼窗宦,老版本貌似需要手動配置

如,沒有生成 gradle 配置代碼二鳄,你需要在根項目的 settings.gradle 文件里手動加入如下配置:

setBinding(new Binding([gradle: this]))
evaluate(new File(
  settingsDir, //設置根路徑赴涵,根據(jù)具體flutter module路徑配置
  'flutter_module/.android/include_flutter.groovy'
))

include ':flutter_module'

還需在宿主工程 (沒改名的話都是app)build.gradle 引入 flutter, 如下:

dependencies {
    ...
    //引入flutter模塊
    implementation project(':flutter')
    ...
}

編寫flutter代碼

編寫flutter代碼订讼,在 flutter module 里按照正常 flutter 開發(fā)流程編寫 flutter 代碼即可髓窜。 (我項目里的 flutter 的代碼是之前項目都寫好的,復制過來欺殿,改改包的引入問題寄纵,就可以運行了。)

這里需要注意的是脖苏,flutter 有且只有一個入口程拭,就是 main() 函數(shù),我們需要在這里處理好 flutter 頁面的跳轉(zhuǎn)問題帆阳。

在 android 端哺壶,創(chuàng)建 flutter 頁面屋吨,代碼如下:

    Flutter.createView(getActivity(),getLifecycle(),"destination");

Flutter.createView 需要3個參數(shù) activity 蜒谤、lifecycleroute 至扰,這個 route 就是要傳遞到 flutter 端的鳍徽,當然,它是 String 類型的敢课,我們可以自由發(fā)揮傳遞普通字符串或 json 字符串等阶祭。

我們也可以通過其他的方式創(chuàng)建 flutter 頁面,如: Flutter.createFragment() 直秆、 FlutterActivity.withNewEngine()濒募、 FlutterFragment.createDefault() 等。

具體的使用圾结,可前往 Flutter官方文檔 查閱瑰剃。

那么,flutter 端如何接收這個 route 參數(shù)筝野,是通過 window.defaultRouteName晌姚,此項目里管理 flutter 端路由代碼如下:

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Flutter model',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        fontFamily: 'PingFang',
      ),
      home: _widgetRoute(window.defaultRouteName), // 通過 window.defaultRouteName 接收 android 端傳來的參數(shù)
    );
  }
}

Widget _widgetRoute(String defaultRouteName) {
    Map<String, dynamic> params = convert.jsonDecode(defaultRouteName); //解析參數(shù)
    defaultRouteName = params['routeName'];
    placeHolder = params['placeHolder'];

    switch (defaultRouteName) { // 根據(jù)參數(shù)返回對應的頁面
        ...
        case 'destination/search':
            return DestinationSearchPage(
                hideLeft: false,
        );
        ...
        default:
            return Center(
                child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                    Text('not found $defaultRouteName',
                        textDirection: TextDirection.ltr),
                ],
                ),
            );
    }
}

其實粤剧,flutter 端接收這個 route 參數(shù),還有一種方法挥唠,就是通過 onGenerateRoute抵恋,它是 MaterialApp 里的一個方法。

代碼如下:

onGenerateRoute: (settings){ //通過 settings.name 獲取android端傳來的參數(shù)
    return _widgetRoute(settings.name);
},

flutter 和 android 之間相互通信

flutter 端可以調(diào)用 android 端的方法及相互傳遞數(shù)據(jù)是如何實現(xiàn)的宝磨,flutter 官方提供了3個方法可以實現(xiàn)弧关,分別是:

  • EventChannel:單向的持續(xù)通信,如:網(wǎng)絡變化唤锉、傳感器等梯醒。
  • MethodChannel:一次性通信,一般適用如方法的調(diào)用腌紧。
  • BasicMessageChannel:持續(xù)的雙向通信茸习。

此項目里采用了 MethodChannel 方法進行通信,如:flutter 端調(diào)用 android 端的AI智能語音方法以及 flutter 打開 android 端頁面就是用 MethodChannel 實現(xiàn)的壁肋。

flutter 端調(diào)用 android 端的AI智能語音方法代碼如下:

class AsrManager {
  static const MethodChannel _channel = const MethodChannel('lib_asr');
  //開始錄音
  static Future<String> start({Map params}) async {
    return await _channel.invokeMethod('start', params ?? {});
  }
  //停止錄音
    ...
  //取消錄音
    ...
  //銷毀
    ...
}

flutter 打開 android 端頁面代碼如下:

class MethodChannelPlugin {

  static const MethodChannel methodChannel = MethodChannel('MethodChannelPlugin');

  static Future<void> gotoDestinationSearchPage() async {
    try {
      await methodChannel.invokeMethod('gotoDestinationSearchPage'); //gotoDestinationSearchPage 參數(shù)會傳到android端
    } on PlatformException {
      print('Failed go to gotoDestinationSearchPage');
    }
  }
    ...
}

android 接收也是通過 MethodChannel 号胚,具體實現(xiàn)代碼如下:

public class MethodChannelPlugin implements MethodChannel.MethodCallHandler {

    private static MethodChannel methodChannel;
    private Activity activity;

    private MethodChannelPlugin(Activity activity) {
        this.activity = activity;
    }

    //調(diào)用方通過 registerWith 來注冊flutter頁面
    public static void registerWith(FlutterView flutterView) {
        methodChannel = new MethodChannel(flutterView, "MethodChannelPlugin");
        MethodChannelPlugin instance = new MethodChannelPlugin((Activity) flutterView.getContext());
        methodChannel.setMethodCallHandler(instance);
    }

    @Override
    public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) {
        if (methodCall.method.equals("gotoDestinationSearchPage")) { // 收到消息進行具體操作
            EventBus.getDefault().post(new GotoDestinationSearchPageEvent());
            result.success(200);
        } 
        ...
        else {
            result.notImplemented();
        }
    }
}

android flutter 混合開發(fā)基本就是這3個步驟,其他一些細節(jié)及具體的流程請參考 GitHub 項目源碼浸遗。

最后附上項目和博客地址:

項目地址:https://github.com/persilee/android_ctrip
博客地址:https://h.lishaoy.net/androidctrip

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末猫胁,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子跛锌,更是在濱河造成了極大的恐慌弃秆,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件髓帽,死亡現(xiàn)場離奇詭異菠赚,居然都是意外死亡,警方通過查閱死者的電腦和手機郑藏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門衡查,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人必盖,你說我怎么就攤上這事拌牲。” “怎么了歌粥?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵塌忽,是天一觀的道長。 經(jīng)常有香客問我失驶,道長土居,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮装盯,結(jié)果婚禮上坷虑,老公的妹妹穿的比我還像新娘。我一直安慰自己埂奈,他們只是感情好迄损,可當我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著账磺,像睡著了一般芹敌。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上垮抗,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天氏捞,我揣著相機與錄音,去河邊找鬼冒版。 笑死液茎,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的辞嗡。 我是一名探鬼主播捆等,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼续室!你這毒婦竟也來了栋烤?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤挺狰,失蹤者是張志新(化名)和其女友劉穎明郭,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體丰泊,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡薯定,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了趁耗。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片沉唠。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖苛败,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情径簿,我是刑警寧澤罢屈,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站篇亭,受9級特大地震影響缠捌,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一曼月、第九天 我趴在偏房一處隱蔽的房頂上張望谊却。 院中可真熱鬧,春花似錦哑芹、人聲如沸炎辨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽碴萧。三九已至,卻和暖如春末购,著一層夾襖步出監(jiān)牢的瞬間破喻,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工盟榴, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留曹质,地道東北人。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓擎场,卻偏偏與公主長得像咆繁,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子顶籽,可洞房花燭夜當晚...
    茶點故事閱讀 44,577評論 2 353