8.1-Flutter混合開發(fā)

混合開發(fā)Flutter集成步驟

  • 創(chuàng)建Flutter module
  • 添加Flutter module依賴
  • 在Java/Object-c中調用Flutter module
  • 編寫Dart代碼
  • 運行項目
  • 熱重啟/重新加載
  • 調試Dart代碼
  • 發(fā)布應用

創(chuàng)建Flutter module

項目目錄xxx/flutter/Navive項目:

cd xxx/flutter/
flutter create -t module flutter_module

上面的代碼會切換到Android/iOS項目的上一級目錄,并創(chuàng)建flutter模塊

flutter中文件用途

  • .android - flutter_module的Android宿主工程;
  • .ios - flutter_module的iOS宿主工程
  • lib - flutter_module的Dart部分代碼
  • pubspec.yaml - flutter_module的項目依賴配置文件
image

Flutter_module是上面創(chuàng)建的flutter module,Android Hybrid是Android工程

Flutter Android混合開發(fā)

添加flutter依賴

在Android工程中的settings.gradle文件中進行如下配置

include ':app'
setBinding(new Binding([gradle:this]))
evaluate(new File(
        settingsDir.parentFile,
        'flutter_module/.android/include_flutter.groovy'
))

同步后會在工程目錄中顯示我們的flutter module

image

在app的build.gradle中添加對flutter的依賴

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

使用flutter要求minSDK最小應該>=16

另外使用flutter也需要設置支持Java8特性

android {
  ...
  compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
  }
}

在Java中調用Flutter module

Java中調用Flutter模塊有倆種方式

  • 使用Flutter.createView API方式
  • 使用FlutterFragment的方式
使用Flutter.createView API的方式
View flutterView = Flutter.createView(
    MainActivity.this,
    getLifecycle(),
    "route1"
);
使用FlutterFragment的方式
FragmentTransaction tx = getSupportFragmentManager().beginTransaction();
tx.replace(R.id.someContainer,Flutter.createFragment('route1'));
tx.commit();

字符串"route1"用來告訴Dart代碼在Flutter視圖中顯示哪個小部件,Flutter模塊項目的lib/main.dart文件需要通過window.defaultRouteName來獲取Native指定要顯示的路由名,以確定要創(chuàng)建哪個窗口小部件并傳遞給runAPP:

調用Flutter module時傳遞數(shù)據(jù)

無論通過Flutter.createView的方式,還是通過Flutter.createFragment的方式,都允許我們加載Flutter module時傳遞一個String類型的initialRoute參數(shù),也可以穿Json字符串.

Native代碼

        mBtnCreate.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
                //為dart模塊初始化所設置的參數(shù)
                ft.replace(R.id.container, Flutter.createFragment("{name:'yangdxg',dataList:['aa','bb','cc']}"));
                ft.commit();
            }
        });
    <Button
        android:id="@+id/btn_create"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Test" />

    <FrameLayout
        android:id="@+id/container"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

    </FrameLayout>

Dart代碼

import 'package:flutter/material.dart';
import 'dart:ui';

//使用window.defaultRouteName獲取Native傳遞過來的參數(shù)
void main() => runApp(MyApp(initParams: window.defaultRouteName,));

class MyApp extends StatelessWidget {

  final String initParams;

  const MyApp({Key key, this.initParams}) :super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter 混合開發(fā)',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter 混合開發(fā)', initParams: initParams),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title, this.initParams}) : super(key: key);

  final String title;
  final String initParams;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'initParams:${widget.initParams}',
            ),
            Text(
              '$_counter',
              style: Theme
                  .of(context)
                  .textTheme
                  .display1,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

Android調用Flutter

熱重啟/重新加載

混合開發(fā)后熱重啟/重新加載會失效,我們可以通過把flutter_moudle和Android工程進行關聯(lián)恢復熱重啟/重新加載

  • 打開模擬器或將設備連接到電腦上

  • 關閉App,運行flutter attach

    cd flutter_hybrid/flutter_module
    flutter attach
    

    注:如果同時有多個模擬器或連接的設備,運行flutter attach時會提示選擇設備

    flutter attach -d 'emulator-5554'
    

    出現(xiàn)等待連接后運訓Android程序Waiting for a connection from Flutter on Android SDK built for x86...

    flutter部分運行后會出現(xiàn)如下提示

    Done.
    Syncing files to device Android SDK built for x86...                    
     2,243ms (!)                                       
    
    ??  To hot reload changes while running, press "r". To hot restart (and rebuild state), press "R".
    An Observatory debugger and profiler on Android SDK built for x86 is available at: http://127.0.0.1:55352/QMwyyeCLcSc=/
    For a more detailed help message, press "h". To detach, press "d"; to quit, press "q".
    
    • 熱加載按r
    • 熱重啟按R
    • 請求幫助按h
    • 斷開鏈接按d
    • 推出按q

    更改代碼后按下r設備就會更新顯示

調試Dart代碼

  • 關閉APP
  • 點擊AndroidStudio的Flutter Attach按鈕(必須安裝好Flutter與Dart插件)
  • 啟動APP

發(fā)布應用

打包
生成Android簽名證書

為Android項目生成簽名證書(就是原生操作)

配置gradle
  • 將簽名證書copy到android/app目錄下

  • 編輯~/.gradle/gradle.properties或../android/gradle.properties(一個全局的,一個項目中的),加入如下代碼

    MYAPP_RELEASE_STORE_FILE = your keystore filename
    MYAPP_RELEASE_KEY_ALIAS = your keystore alias
    MYAPP_RELEASE_STORE_PASSWORD = ******
    MYAPP_RELEASE_KEY_PASSWORD = ******
    
  • 編輯 android/app/build.gradle文件

    android{
      defaultConfig{...}
      signingConfigs{
          release{
              storeFile file(MYAPP_RELEASE_STORE_FILE)
              storePassword MYAPP_RELEASE_STORE_PASSWORD
              keyAlias MYAPP_RELEASE_KEY_ALIAS
              keyPassword MYAPP_RELEASE_KEY_PASSWORD
          }
      }
      buildTypes{
          release{
              ...
              signingConfig signingConfigs.release
          }
      }
    }
    
簽名打包APK

terminal進入項目下的Android目錄,運行如下代碼

./gradlew assembleRelease

打包完成apk在app/build/outputs/apk

Flutter iOS混合開發(fā)

稍后補充

Flutter與Native通信

Flutter與Native的通信是通過Channel來完成的

消息使用Channel(平臺通道)再客戶端(UI)和主機(平臺之間傳遞)

Flutter中的消息傳遞完全是異步的

Channel所支持的數(shù)據(jù)類型

Dart Android iOS
null null nil (NSNull when nested)
bool java.lang.Boolean NSNumber numberWithBool:
int java.lang.Integer NSNumber numberWithInt:
int, if 32 bits not enough java.lang.Long NSNumber numberWithLong:
double java.lang.Double NSNumber numberWithDouble:
String java.lang.String NSString
Uint8List byte[] FlutterStandardTypedData typedDataWithBytes:
Int32List int[] FlutterStandardTypedData typedDataWithInt32:
Int64List long[] FlutterStandardTypedData typedDataWithInt64:
Float64List double[] FlutterStandardTypedData typedDataWithFloat64:
List java.util.ArrayList NSArray
Map java.util.HashMap NSDictionary

Flutter定義了三種不同類型的Channel:

  • BasicMessageChannel:用于傳遞字符串和半結構化的信息,持續(xù)通信,接收到消息后可以回復此消息,
  • MethodChannel:用于傳遞方法調用,一次性通信,如Flutter調用Native拍照
  • EventChannel:用于數(shù)據(jù)流的通信,持續(xù)通信,收到消息后無法回復此消息,通常用于Native向Dart的通信,如網(wǎng)絡變化,傳感儀檢測

BasicMessageChannel用法

Dart端
const BasicMessageChannel(this.name,this.codec);
  • String name — Channel 的名字,要和Native端保持一致;
  • MessageCodec<T> codec — 消息的編解碼器,要和Native端保持一致,有四種實現(xiàn)(見Native端講解)

sendMessageHandler方法

void    sendMessageHandle(Future<T> handler(T message))
  • Future<T> handler(T message) — 消息處理器,配置BinaryMessage完成消息的處理

在創(chuàng)建好BasicMessageChannel后,如果要讓其接收Native發(fā)來的消息,則需要調用它的setMessageHandler方法為其設置一個消息處理器

Future<T> send(T message)
  • T message — 要傳遞給Native的具體消息
  • Future<T> — 消息發(fā)出去后,收到Native回復的回調函數(shù)

MessageChannel方法

Dart端
const MethodChannel(this.name[this.codec=const StandardMethodCodec])
  • name — Channel的名字,和Native端保持一致
  • MethodCodec codec — 消息的編解碼器,默認時StandardMethodCodec,和Native端保持一致

invokeMethod方法

Future<T> invokeMethod<T>(String method,[dynamic arguments])
  • method:要調用Native的方法名

EvnetChannel

Dart端
const EventChannel(this.name,[this.codec = const StandardMethodCodec()])
  • name — Channel的名字,要和Native端保持一致
  • MethodCodec codec — 消息的編解碼器,默認是StandardMethodCodec,要和Native端保持一致

receiveBroadcastStream

Stream<dynamic> receivedBroadcastStream([dynamic arguments])
  • dynamic arguments — 監(jiān)聽事件時向Native傳遞的數(shù)據(jù);

Flutter與Android通信

BasicMessageChannel用法

Native端
BasicMessageChannel(BinaryMessenger messenger, String name, MessageCodec<T> codec) {
  • BinaryMessenger — 消息信使,是消息的發(fā)送與接收的工具
  • String name — Channel的名字,也是其唯一標識符
  • MessageCodec<T> — 消息的編解碼器,它有幾種不同類型的實現(xiàn)
    • BinaryCodec — 最為簡單的一種Codec,其返回值類型和入?yún)⒌念愋拖嗤?均為二進制格式(Android 中為ByteBuffer,iOS中為NSData),實際上,BinarCodec在編解碼過程中什么都沒做,只是原封不動將二進制數(shù)據(jù)消息返回而已,BinaryCodec并非沒有意義,在某些情況下非常有用,比如使用BinaryCodec可以傳遞內存數(shù)據(jù)塊時在編解碼階段等于內存拷貝
    • StringCodec — 用于字符串與二進制數(shù)據(jù)之間的編解碼,其編碼格式為UTF-8
    • JSONMessageCodec — 用于基礎樹與二進制數(shù)據(jù)之間的編解碼,其支持基礎數(shù)據(jù)類型以及列表,字典,其在iOS端使用NSJSONSerialization作為序列化的工具,而在Android端則使用了其自定義的JSONUTIL與StringCodec作為序列化工具;
    • StandardMessageCode — 是BasicMessageChannel的默認編解碼器,支持基礎數(shù)據(jù)類型,二進制數(shù)據(jù),列表,字典,

setMessageHandle接收消息

void setMessageHandler(BasicMessageChannel.MessageHandler<T> handler)
  • Handler — 消息處理器,配合BinaryMessenger完成消息的處理

創(chuàng)建好BasicMessageChannel后,如果要讓其接收Dart發(fā)來的消息,則需要調用它的setMessageHandler方法為其設置一個消息處理器

public interface MessageHandler<H>{
    void onMessage(T var1,BasicMessageChannel.Repl<T> var2);
}
  • onMessage(T var,BasicMessageChannel Reply<T> var2) — 用于接收消息,var1是消息內容,var2是回復此消息的回調函數(shù)

send方法,向Dart端發(fā)送消息

void send(T message);
void send(T message,BasicMessageChannel.Reply<T> callback)
  • T message — 要傳遞給Dart的具體信息
  • BasicMessageChannel.Reply<T> callback — 消息發(fā)出后,收到Dart 回復的回調函數(shù)

MethodChannel用法

Native端
//會構造一個StandardMethodCodec.INSTANCE類型的MethodCodec
MethodChannel(BinaryMessage messager,String name)
//或
MethodChannel(BinaryMessage message,String name,MethodCodec codec)
  • messager — 消息信使
  • name — Channel名字
  • codec — 編解碼器
setMethodCallhandler(@Nullable MethodChannel.MethodCallHandler handler)
  • Handler — 消息處理器,配合BinaryMessenger完成消息的處理;
public interface MethodCallHandler{
    void onMethodCall(MethodCall var1,MethodChannel.Result var2);
}
  • onMessageCall — 用于接收消息,call是消息內容,call.method表示調用的方法名,Object類型的call.arguments表示調用方法所傳遞的入餐;result是回復此消息的回調函數(shù)提供了success,error,noImplemented方法調用

EventChannel用法

Native端
EventChannel(BinaryMessenger messenger, String name) 
EventChannel(BinaryMessenger messenger, String name, MethodCodec codec)
  • 參數(shù)意義同上

接收消息

setStreamHandler(EventChannel.StreamHandler handler)
public interface StreamHandler{
    void onListen(Object args,EventChannel.EventSink eventSink);
    void onCancle(Object o);
}
  • eventSink回調函數(shù)
  • onCancle取消監(jiān)聽檢測

Flutter與Android通信實戰(zhàn)

Flutter與iOS通信

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • 本文先介紹一下現(xiàn)有工程如何集成 Flutter 實現(xiàn)混合開發(fā),以及混合項目如何打包,再探索下如何降低原生和 Flu...
    koin447閱讀 5,830評論 1 37
  • 文|拾憶年 中午下班沒有回家,直接和辦公室的同事一起去了外面餐館吃飯。 屁股剛剛落在椅子上,餐還沒有點完,同事就開...
    句號與逗號閱讀 1,355評論 6 3
  • 人潮擁擠 我已失散 人潮已散 我心擁擠 獨守原地 空自悲
    sugar和圖圖閱讀 164評論 1 4
  • 本想著,今日不寫的。 凌晨十一點半左右,下班。跑過步,回住處沖冷水澡,嘗試是歡喜的,過程是顫抖著。 工作十四時又兩...
    昉之閱讀 227評論 2 3

友情鏈接更多精彩內容