前言
本文主要介紹 Flutter 與 Android 的混合開發,關于 Flutter 與 iOS 的混合開發后面再詳細介紹。
一、Flutter 和 Android 混合開發
開發步驟:
1. 創建 Flutter module
在做混合開發之前首先需要創建一個 Flutter module。
假如你的 Android項目路徑是這樣的:xxx/flutter_hybrid/Android項目,那么 flutter module 就要創建在 Android項目的上一級目錄下:xxx/flutter_hybrid/。
$ cd xxx/flutter_hybrid/
$ flutter create -t module flutter_module
flutter module 創建成功之后,看一下flutter_module 的文件結構,它里面包含兩個隱藏的文件 .android 與 .ios,這兩個文件是 flutter_module 的宿主工程:
- .android:flutter_module 的 Android 宿主工程
- .ios:flutter_module 的 iOS 宿主工程
- lib:flutter_module 的 Dart 部分的代碼
- pubspec.yaml:flutter_module 的項目依賴配置文件
因為宿主工程的存在,我們這個 flutter_module 在不加額外的配置的情況下是可以獨立運行的,通過安裝了 Flutter 與 Dart 插件的AndroidStudio 打開這個 flutter_module 項目,就可以直接運行。
2. 在Android項目中添加 Flutter module 依賴
首先在 Android項目根目錄下的 settings.gradle 文件中添加下面的代碼,構建 flutter 工程 module:
include ':app'
setBinding(new Binding([gradle: this]))
evaluate(new File(
settingsDir.parentFile,
'flutter_module/.android/include_flutter.groovy'
))
然后在 Android項目主工程app下的 build.gradle 中添加 flutter_module 的依賴:
dependencies {
...
implementation project(':flutter')
}
需要注意兩點:
(1)這里需要的 minSdkVersion 最小為16。
(2)Android項目編譯的時候需要設置 Java8
補充:怎樣設置Java8,在主工程 app下的build.gradle中添加如下代碼:
android {
...
compileOptions{
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
3. 在 Android 中調用 Flutter module
在 Android 中調用 Flutter模塊 有兩種方式:
- 使用 Flutter.createView API的方式
- 使用 FlutterFragment 的方式
(1)使用 Flutter.createView API 的方式
MainActivity.java 代碼:
findViewById(R.id.test).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
View flutterView = Flutter.createView(
MainActivity.this,
getLifecycle(),
"route2"
);
FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(600, 800);
layoutParams.gravity = Gravity.CENTER;
addContentView(flutterView, layoutParams);
}
});
(2)使用 FlutterFragment 的方式
xml布局中代碼:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<TextView
android:id="@+id/test"
android:layout_width="wrap_content"
android:layout_height="50dp"
android:background="@color/colorAccent"
android:gravity="center"
android:text="Hello World!" />
<FrameLayout
android:id="@+id/someContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
MainActivity.java 代碼:
findViewById(R.id.test).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
FragmentTransaction tx = getSupportFragmentManager().beginTransaction();
tx.replace(R.id.someContainer, Flutter.createFragment("route1"));
tx.commit();
}
});
上面代碼中的“route1”用來告訴 Dart 代碼在 Flutter 視圖中顯示哪個小部件。Flutter 模塊項目的 lib/main.dart文件需要通過window.defaultRouteName 來獲取 Native指定要顯示的路由名,以確定要創建哪個窗口小部件并傳遞給runApp,
main.dart 代碼
void main() => runApp(_widgetForRoute(window.defaultRouteName));
Widget _widgetForRoute(String route){
switch(route){
case 'route1':
return SomeWidget(...);
case 'route2':
return SomeOtherWidget(...);
default:
return MyApp(initParams: window.defaultRouteName,);
}
}
“route1”也可以換成需要傳遞的數據。
4. 調試與發布
前面在介紹 Flutter 開發的時候,我們知道它帶有熱重啟/重新加載的功能。但是在本文混合開發的示例中會發現,在 Android 項目中集成了 Flutter 項目,Flutter 的熱重啟/重新加載功能好像失效了,我們通過下面的方法來啟動混合開發中的熱重啟/重新加載:
- 連接一個設備到電腦上;
- 關閉我們的APP,然后運行 flutter attach:
(1)調試 Dart 代碼
混合開發模式下高效調試代碼的方式:
- 關閉APP
- 點擊 AndroidStudio 的 Flutter Attach 按鈕
- 啟動APP
(2)發布
第一步:生成 Android 簽名證書
簽名 APK 需要一個證書用于為 APP 簽名,生成簽名證書可以用 AndroidStudio 可視化的方式生成,也可以使用命令行的方式生成,這里就不在詳細介紹了。
第二步:設置 gradle 變量
① 將簽名證書放到 android/app 目錄下。
② 編輯~/.gradle/gradle.properties 或 ../android/gradle.properties,加入一下代碼:
MYAPP_RELEASE_STORE_FILE = your keystone filename
MYAPP_RELEASE_KEY_ALIAS = your keystone alias
MYAPP_RELEASE_STORE_PASSWORD = *****
MYAPP_RELEASE_KEY_PASSWORD = *****
③ 在 gradle 配置文件中添加簽名配置
編輯 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
二、Flutter 通信機制 & Dart 端講解
Flutter 和 Native 的通信是通過 PlatformChannel 來完成的。
消息使用 PlatformChannel 在客戶端和主機之間傳遞。
PlatformChannel 的三種不同類型的種類:
- BasicMessageChannel:用于傳遞字符串和半結構化的信息,持續通信,收到消息后可以回復本次消息。
- MethodChannel:用于傳遞方法調用(method invocation)一次性通信,如 Flutter 調用 Native 拍照。
- EventChannel:用于數據流(event streams)的通信,持續通信,收到消息后無法回復此次消息,通過長用于Native向Dart的通信,如:手機電量變化、網絡連接變化、陀螺儀、傳感器等。
這三種類型的 PlatformChannel 都是全雙工通信,即A<=>B,Flutter 可以主動發送消息給 platform 端,并且 platform 接收到消息后可以做出回應。同樣,platform 端可以主動發送消息給 Dart 端,Flutter 端接收消息后返回給 platform 端。
- BasicMessageChannel
Flutter端:
(1) 構造方法原型
const BasicMessageChannel(this.name,this.codec);
① String name:Channel 的名字,要和 Native端保持一致;
② MessageCodec<T> codec:消息的編解碼器,要和 Native端保持一致;
(2) setMessageHandler 方法原型
void setMessageHandler(Future<T> handler(T message))
① Future<T> handler(T message):消息處理器,配合BinaryMessager 完成消息的處理;
在創建好BasicMessageChannel 后,如果要讓其接收 Native 發來的消息,則需要調用它的 setMessageHandler 方法為其設置一個消息處理器。
(3)send 方法原型
Future<T> send(T message)
- T message:要傳遞給Native的具體信息;
- Future<T>:消息發出去后,收到Native回復的回調函數;
在創建好 BasicMessageChannel 后,如果要向 Native 發送消息,可以調用它的 send 方法向 Native 傳遞數據。
import 'package:flutter/services.dart';
...
static const BasicMessageChannel _basicMessageChannel =
const BasicMessageChannel('BasicMessageChannelPlugin', StringCodec());
...
//使用BasicMessageChannel接收來自Native的消息,并向Native回復
_basicMessageChannel.setMessageHandler((String message) => Future<String>((){
setState((){
showMessage = message;
});
return '收到Native的消息:'+ messgae;
}));
//使用BasicMessageChannel向Native發送消息,并接收到Native的回復
String response;
try{
response = await _basicMessageChannel.send(value);
} on PlatformException catch (e){
print(e);
}
...
- MethodChannel
Flutter端:
(1)構造方法原型
const MethodChannel(this.name,[this.codec = const StandardMethodCodec()])
- String name:Channel 的名字,要和 Native端保持一致;
- MethodCodec codec:消息的編解碼器,默認是 StandardMethodCodec,要和 Native端保持一致;
(2)invokeMethod 方法原型
Future<T> invokeMethod<T>(String method,[dynamic arguments])
- String method:要調用 Native的方法名;
- [dynamic arguments]:調用 Native方法傳遞的參數,可不傳;
import 'package:flutter/services.dart';
...
static const MethodChannel _methodChannelPlugin =
const MethodChannel('MethodChannelPlugin');
...
String response;
try{
response = await _methodChannelPlugin.invokeMethod('send',value);
}on PlatformException catch (e){
print(e);
}
...
- EventChannel
Flutter端:
(1)構造方法原型
const EventChannel(this.name,[this.codec = const StandardMethodCodec()]);
- String name:Channel的名字,要和 Native端保持一致;
- MethodCodec codec:消息的編解碼器,默認是 StandardMethodCodec,要和 Native端保持一致;
(2)receiveBroadcastStream 方法原型
Stream<dynamic> receiveBroadcastStream([dynamic arguments])
- dynamic arguments:監聽事件時向 Native傳遞的數據;
初始化一個廣播流用于從 channel 中接收數據,它返回一個 Stream 接下來需要調用 Stream的 listen 方法來完成注冊,另外需要在頁面銷毀時調用 Stream 的 cancel 方法來取消監聽;
import 'package:flutter/services.dart';
...
static const EventChannel _eventChannelPlugin =
EventChannel('EventChannelPlugin');
StreamSubscription _streamSubscription;
@override
void initState(){
_streamSubscription = _eventChannelPlugin
.receiveBroadcastStream()
.listen(_onToDart,onError: _onToDartError);
super.initState();
}
@override
void dispose(){
if(_streamSubscription != null){
_streamSubscription.cancel();
_streamSubscription = null;
}
super.dispose();
}
void _onToDart(message){
setState((){
showMessage = message;
});
}
void _onToDartError(error){
print(error);
}
三、Flutter 與 Android 通信
- BasicMessageChannel用法
Android端:
(1)構造方法原型
BasicMessageChannel(BinaryMessenger messenger,String name,MessageCodec<T> codec)
- BinaryMessenger messenger:消息信使,是消息的發送與接收的工具;
- String name:Channel的名字,也是其唯一標識符;
- MessageCodec<T> codec:消息的編解碼器,它有幾種不同類型的實現:
BinaryCodec:最為簡單的一種 Codec,因為其返回值類型和入參的類型相同,均為二進制格式(Android 中為 ByteBuffer)。實際上,BinaryCodec 在編解碼過程中什么都沒做,只是原封不動將二進制數據消息返回而已。或許你會因此覺得 BinaryCodec 沒有意義,但是在某些情況下它非常有用,比如使用 BinaryCodec 可以使傳遞內存數據塊時在編解碼階段免于內存拷貝;
StringCodec:用于字符串與二進制數據之間的編解碼,其編碼格式為 UTF-8;
JSONMessageCodec:用于基礎數據與二進制數據之間的編解碼,其支持基礎數據類型以及列表、字典。
StandardMessageCodec:是 BasicMessageChannel 的默認編解碼器,其支持基礎數據類型、二進制數據、列表、字典;
(2)setMessageHandler 方法原型
void setMessageHandler(BasicMessageChannel.MessageHandler<T> handler)
- BasicMessageChannel.MessageHandler<T> handler:消息處理器,配合 BinaryMessenger 完成消息的處理;
在創建好 BasicMessageChannel 后,如果要讓其接收 Flutter 發來的消息,則需要調用它的setMessageHandler 方法為其設置一個消息處理器。
(3)BasicMessageChannel.MessageHandler 原型
public interface MessageHandler<T>{
void onMessage(T var1,BasicMessageChannel.Reply<T> var2);
}
- onMessage(T var1,BasicMessageChannel.Reply<T> var2):用于接收消息,var1 是消息內容,var2 是回復此消息的回調函數;
(4)send方法原型
void send(T message)
void send(T message,BasicMessageChannel.Reply<T> callback)
- T message:要傳遞給 Flutter 的具體信息;
- BasicMessageChannel.Reply<T> callback:消息發出去后,收到 Flutter 的回復的回調函數;
在創建好BasicMessageChannel后,如果要向 Flutter 發送消息,可以調用它的send方法向 Flutter 傳遞數據。
public class BasicMessageChannelPlugin implements BasicMessageChannel.MessageHandler<String>,
BasicMessageChannel.Reply<String> {
private final Activity activity;
private final BasicMessageChannel<String> messageChannel;
public BasicMessageChannelPlugin(FlutterView flutterView) {
this.activity = (Activity) flutterView.getContext();
this.messageChannel = new BasicMessageChannel<>(flutterView,
"BasicMessageChannelPlugin", StringCodec.INSTANCE);
//設置消息處理器,處理來自Flutter的消息
messageChannel.setMessageHandler(this);
}
static BasicMessageChannelPlugin registerWith(FlutterView flutterView) {
return new BasicMessageChannelPlugin(flutterView);
}
@Override
public void onMessage(String s, BasicMessageChannel.Reply<String> reply) {//處理Flutter發來的消息
reply.reply("BasicMessageChannelPlugin收到:" + s);//可以通過reply進行回復
if (activity instanceof IShowMessage) {
((IShowMessage) activity).onShowMessage(s);
}
Toast.makeText(activity, s, Toast.LENGTH_SHORT).show();
}
/**
* 向Flutter發送消息,并接受Flutter的反饋
*
* @param message
* @param callback
*/
void send(String message, BasicMessageChannel.Reply<String> callback) {
messageChannel.send(message, callback);
}
@Override
public void reply(String s) {
}
}
- MethodChannel用法
Android端:
(1)構造方法原型
//會構造一個StandardMethodCodec.INSTANCE類型的MethodCodec
MethodChannel(BinaryMessager messenger,String name)
//或
MethodChannel(BinaryMessager messenger,String name,MethodCodec codec)
- BinaryMessenger messenger:消息信使,是消息的發送與接收的工具;
- String name:Channel的名字,也是其唯一標識符;
- MethodCodec codec:用作 MethodChannel 的解編碼器;
(2)setMethodCallHandler 方法原型
setMethodCallHandler(@Nullable MethodChannel.MethodCallHandler handler)
- @Nullable MethodChannel.MethodCallHandler handler:消息處理器,配合 BinaryMessenger 完成消息的處理;
在創建好 MethodChannel 后,需要調用它的 setMethodCallHandler方法為其設置一個消息處理器,以便能接收來自 Flutter 的消息。
(3)MethodChannel.MethodCallHandler原型
public interface MethodCallHandler{
void onMethodCall(MethodCall var1,MethodChannel.Result var2);
}
- onMethodCall(MethodCall call,MethodChannel.Result result):用于接收消息,call 是消息內容,它有兩個成員變量 String 類型的 call.method 表示調用的方法名,Object 類型的 call.arguments 表示調用方法所傳遞的入參;MethodChannel.Result result 是回復此消息的回調函數提供了result.success,result.error,result.notImplemented方法調用;
/**
* MethodChannelPlugin
* 用于傳遞方法調用(method invocation),一次性通信,通常用于 Flutter 調用 native 的方法:如拍照
*/
public class MethodChannelPlugin implements MethodChannel.MethodCallHandler {
private final Activity activity;
public MethodChannelPlugin(Activity activity) {
this.activity = activity;
}
public static void registerWith(FlutterView flutterView) {
MethodChannel channel = new MethodChannel(flutterView, "MethodChannelPlugin");
MethodChannelPlugin instance = new MethodChannelPlugin((Activity) flutterView.getContext());
channel.setMethodCallHandler(instance);
}
@Override
public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) {
switch (methodCall.method) {//處理來自Flutter的方法調用
case "showMessage":
showMessage(methodCall.arguments());
result.success("MethodChannelPlugin收到:" + methodCall.arguments);//返回結果給Flutter
break;
default:
result.notImplemented();
}
}
/**
* 展示來自Flutter的數據
*
* @param arguments
*/
private void showMessage(String arguments) {
if (activity instanceof IShowMessage) {
((IShowMessage) activity).onShowMessage(arguments);
}
Toast.makeText(activity, arguments, Toast.LENGTH_SHORT).show();
}
}
- EventChannel用法
Android端:
(1)構造方法原型
//會構造一個StandardMethodCodec.INSTANCE類型的MethodCodec
EventChannel(BinaryMessenger messenger,String name)
//或
EventChannel(BinaryMessenger messenger,String name,MethodCodec codec)
- BinaryMessenger messenger:消息信使,是消息的發送與接收的工具;
- String name:Channel的名字,也是其唯一標識符;
- MethodCodec:用作 EventChannel 的編解碼器;
(2)setStreamHandler 方法原型
void setStreamHandler(EventChannel.StreamHandler handler)
- EventChannel.StreamHandler handler:消息處理器,配合 BinaryMessenger 完成消息的處理;在創建好 EventChannel 后,如果要讓其接收 Flutter 發來的消息,則需要調用它的 setStreamHandler 方法為其設置一個消息處理器。
(3)EventChannel.StreamHandler原型
public interface StreamHandler{
void onListen(Object args,EventChannel.EventSink eventSink);
void onCancel(Object 0);
}
- void onListen(Object args,EventChannel.EventSink eventSink):Flutter Native監聽事件時調用,Object args 是傳遞的參數,EventChannel.EventSink eventSink 是 Native 回調 Flutter 時的回調函數,eventSink 提供success、error 與 endOfStream 三個回調方法分別對應事件的不同狀態;
- void onCancel(Object 0):Flutter取消監聽時調用;
public class EventChannelPlugin implements EventChannel.StreamHandler {
private List<EventChannel.EventSink> eventSinks = new ArrayList<>();
static EventChannelPlugin registerWith(FlutterView flutterView){
EventChannelPlugin plugin = new EventChannelPlugin();
new EventChannel(flutterView,"EventChannelPlugin").setStreamHandler(plugin);
return plugin;
}
void sendEvent(Object params){
for (EventChannel.EventSink eventSink : eventSinks){
eventSink.success(params);
}
}
@Override
public void onListen(Object o, EventChannel.EventSink eventSink) {
eventSinks.add(eventSink);
}
@Override
public void onCancel(Object o) {
}
}
總結
本文主要介紹了 Flutter 與 Native 的混合開發,它們之間的消息通信主要是通過 Channel 實現的。