混合開發(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的項目依賴配置文件

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

在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.
);
}
}

熱重啟/重新加載
混合開發(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)聽檢測