前言
本文主要介紹 Flutter 與 Android 的混合開發(fā),關(guān)于 Flutter 與 iOS 的混合開發(fā)后面再詳細(xì)介紹饿序。
一团赏、Flutter 和 Android 混合開發(fā)
開發(fā)步驟:
1. 創(chuàng)建 Flutter module
在做混合開發(fā)之前首先需要創(chuàng)建一個 Flutter module府框。
假如你的 Android項(xiàng)目路徑是這樣的:xxx/flutter_hybrid/Android項(xiàng)目,那么 flutter module 就要創(chuàng)建在 Android項(xiàng)目的上一級目錄下:xxx/flutter_hybrid/。
$ cd xxx/flutter_hybrid/
$ flutter create -t module flutter_module
flutter module 創(chuàng)建成功之后迅皇,看一下flutter_module 的文件結(jié)構(gòu)从铲,它里面包含兩個隱藏的文件 .android 與 .ios,這兩個文件是 flutter_module 的宿主工程:
- .android:flutter_module 的 Android 宿主工程
- .ios:flutter_module 的 iOS 宿主工程
- lib:flutter_module 的 Dart 部分的代碼
- pubspec.yaml:flutter_module 的項(xiàng)目依賴配置文件
因?yàn)樗拗鞴こ痰拇嬖谛牵覀冞@個 flutter_module 在不加額外的配置的情況下是可以獨(dú)立運(yùn)行的珠插,通過安裝了 Flutter 與 Dart 插件的AndroidStudio 打開這個 flutter_module 項(xiàng)目,就可以直接運(yùn)行颖对。
2. 在Android項(xiàng)目中添加 Flutter module 依賴
首先在 Android項(xiàng)目根目錄下的 settings.gradle 文件中添加下面的代碼捻撑,構(gòu)建 flutter 工程 module:
include ':app'
setBinding(new Binding([gradle: this]))
evaluate(new File(
settingsDir.parentFile,
'flutter_module/.android/include_flutter.groovy'
))
然后在 Android項(xiàng)目主工程app下的 build.gradle 中添加 flutter_module 的依賴:
dependencies {
...
implementation project(':flutter')
}
需要注意兩點(diǎn):
(1)這里需要的 minSdkVersion 最小為16。
(2)Android項(xiàng)目編譯的時候需要設(shè)置 Java8
補(bǔ)充:怎樣設(shè)置Java8缤底,在主工程 app下的build.gradle中添加如下代碼:
android {
...
compileOptions{
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
3. 在 Android 中調(diào)用 Flutter module
在 Android 中調(diào)用 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 模塊項(xiàng)目的 lib/main.dart文件需要通過window.defaultRouteName 來獲取 Native指定要顯示的路由名,以確定要創(chuàng)建哪個窗口小部件并傳遞給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”也可以換成需要傳遞的數(shù)據(jù)个唧。
4. 調(diào)試與發(fā)布
前面在介紹 Flutter 開發(fā)的時候江解,我們知道它帶有熱重啟/重新加載的功能。但是在本文混合開發(fā)的示例中會發(fā)現(xiàn)徙歼,在 Android 項(xiàng)目中集成了 Flutter 項(xiàng)目犁河,F(xiàn)lutter 的熱重啟/重新加載功能好像失效了,我們通過下面的方法來啟動混合開發(fā)中的熱重啟/重新加載:
- 連接一個設(shè)備到電腦上魄梯;
- 關(guān)閉我們的APP桨螺,然后運(yùn)行 flutter attach:
(1)調(diào)試 Dart 代碼
混合開發(fā)模式下高效調(diào)試代碼的方式:
- 關(guān)閉APP
- 點(diǎn)擊 AndroidStudio 的 Flutter Attach 按鈕
- 啟動APP
(2)發(fā)布
第一步:生成 Android 簽名證書
簽名 APK 需要一個證書用于為 APP 簽名,生成簽名證書可以用 AndroidStudio 可視化的方式生成酿秸,也可以使用命令行的方式生成灭翔,這里就不在詳細(xì)介紹了。
第二步:設(shè)置 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 通信機(jī)制 & Dart 端講解
Flutter 和 Native 的通信是通過 PlatformChannel 來完成的稀蟋。
消息使用 PlatformChannel 在客戶端和主機(jī)之間傳遞煌张。
PlatformChannel 的三種不同類型的種類:
- BasicMessageChannel:用于傳遞字符串和半結(jié)構(gòu)化的信息,持續(xù)通信退客,收到消息后可以回復(fù)本次消息骏融。
- MethodChannel:用于傳遞方法調(diào)用(method invocation)一次性通信,如 Flutter 調(diào)用 Native 拍照。
- EventChannel:用于數(shù)據(jù)流(event streams)的通信绎谦,持續(xù)通信管闷,收到消息后無法回復(fù)此次消息,通過長用于Native向Dart的通信窃肠,如:手機(jī)電量變化包个、網(wǎng)絡(luò)連接變化、陀螺儀冤留、傳感器等碧囊。
這三種類型的 PlatformChannel 都是全雙工通信,即A<=>B纤怒,F(xiàn)lutter 可以主動發(fā)送消息給 platform 端糯而,并且 platform 接收到消息后可以做出回應(yīng)。同樣泊窘,platform 端可以主動發(fā)送消息給 Dart 端熄驼,F(xiàn)lutter 端接收消息后返回給 platform 端。
- BasicMessageChannel
Flutter端:
(1) 構(gòu)造方法原型
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 完成消息的處理祭芦;
在創(chuàng)建好BasicMessageChannel 后,如果要讓其接收 Native 發(fā)來的消息憔鬼,則需要調(diào)用它的 setMessageHandler 方法為其設(shè)置一個消息處理器龟劲。
(3)send 方法原型
Future<T> send(T message)
- T message:要傳遞給Native的具體信息;
- Future<T>:消息發(fā)出去后轴或,收到Native回復(fù)的回調(diào)函數(shù)昌跌;
在創(chuàng)建好 BasicMessageChannel 后,如果要向 Native 發(fā)送消息侮叮,可以調(diào)用它的 send 方法向 Native 傳遞數(shù)據(jù)避矢。
import 'package:flutter/services.dart';
...
static const BasicMessageChannel _basicMessageChannel =
const BasicMessageChannel('BasicMessageChannelPlugin', StringCodec());
...
//使用BasicMessageChannel接收來自Native的消息悼瘾,并向Native回復(fù)
_basicMessageChannel.setMessageHandler((String message) => Future<String>((){
setState((){
showMessage = message;
});
return '收到Native的消息:'+ messgae;
}));
//使用BasicMessageChannel向Native發(fā)送消息囊榜,并接收到Native的回復(fù)
String response;
try{
response = await _basicMessageChannel.send(value);
} on PlatformException catch (e){
print(e);
}
...
- MethodChannel
Flutter端:
(1)構(gòu)造方法原型
const MethodChannel(this.name,[this.codec = const StandardMethodCodec()])
- String name:Channel 的名字,要和 Native端保持一致亥宿;
- MethodCodec codec:消息的編解碼器卸勺,默認(rèn)是 StandardMethodCodec,要和 Native端保持一致烫扼;
(2)invokeMethod 方法原型
Future<T> invokeMethod<T>(String method,[dynamic arguments])
- String method:要調(diào)用 Native的方法名曙求;
- [dynamic arguments]:調(diào)用 Native方法傳遞的參數(shù),可不傳;
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)構(gòu)造方法原型
const EventChannel(this.name,[this.codec = const StandardMethodCodec()]);
- String name:Channel的名字悟狱,要和 Native端保持一致静浴;
- MethodCodec codec:消息的編解碼器,默認(rèn)是 StandardMethodCodec挤渐,要和 Native端保持一致苹享;
(2)receiveBroadcastStream 方法原型
Stream<dynamic> receiveBroadcastStream([dynamic arguments])
- dynamic arguments:監(jiān)聽事件時向 Native傳遞的數(shù)據(jù);
初始化一個廣播流用于從 channel 中接收數(shù)據(jù)浴麻,它返回一個 Stream 接下來需要調(diào)用 Stream的 listen 方法來完成注冊得问,另外需要在頁面銷毀時調(diào)用 Stream 的 cancel 方法來取消監(jiān)聽;
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)構(gòu)造方法原型
BasicMessageChannel(BinaryMessenger messenger,String name,MessageCodec<T> codec)
- BinaryMessenger messenger:消息信使宫纬,是消息的發(fā)送與接收的工具;
- String name:Channel的名字膏萧,也是其唯一標(biāo)識符漓骚;
- MessageCodec<T> codec:消息的編解碼器,它有幾種不同類型的實(shí)現(xiàn):
BinaryCodec:最為簡單的一種 Codec榛泛,因?yàn)槠浞祷刂殿愋秃腿雲(yún)⒌念愋拖嗤暇常鶠槎M(jìn)制格式(Android 中為 ByteBuffer)。實(shí)際上挟鸠,BinaryCodec 在編解碼過程中什么都沒做叉信,只是原封不動將二進(jìn)制數(shù)據(jù)消息返回而已∷蚁#或許你會因此覺得 BinaryCodec 沒有意義硼身,但是在某些情況下它非常有用,比如使用 BinaryCodec 可以使傳遞內(nèi)存數(shù)據(jù)塊時在編解碼階段免于內(nèi)存拷貝覆享;
StringCodec:用于字符串與二進(jìn)制數(shù)據(jù)之間的編解碼佳遂,其編碼格式為 UTF-8;
JSONMessageCodec:用于基礎(chǔ)數(shù)據(jù)與二進(jìn)制數(shù)據(jù)之間的編解碼撒顿,其支持基礎(chǔ)數(shù)據(jù)類型以及列表丑罪、字典。
StandardMessageCodec:是 BasicMessageChannel 的默認(rèn)編解碼器凤壁,其支持基礎(chǔ)數(shù)據(jù)類型吩屹、二進(jìn)制數(shù)據(jù)、列表拧抖、字典煤搜;
(2)setMessageHandler 方法原型
void setMessageHandler(BasicMessageChannel.MessageHandler<T> handler)
- BasicMessageChannel.MessageHandler<T> handler:消息處理器,配合 BinaryMessenger 完成消息的處理唧席;
在創(chuàng)建好 BasicMessageChannel 后擦盾,如果要讓其接收 Flutter 發(fā)來的消息嘲驾,則需要調(diào)用它的setMessageHandler 方法為其設(shè)置一個消息處理器。
(3)BasicMessageChannel.MessageHandler 原型
public interface MessageHandler<T>{
void onMessage(T var1,BasicMessageChannel.Reply<T> var2);
}
- onMessage(T var1,BasicMessageChannel.Reply<T> var2):用于接收消息迹卢,var1 是消息內(nèi)容辽故,var2 是回復(fù)此消息的回調(diào)函數(shù);
(4)send方法原型
void send(T message)
void send(T message,BasicMessageChannel.Reply<T> callback)
- T message:要傳遞給 Flutter 的具體信息腐碱;
- BasicMessageChannel.Reply<T> callback:消息發(fā)出去后榕暇,收到 Flutter 的回復(fù)的回調(diào)函數(shù);
在創(chuàng)建好BasicMessageChannel后喻杈,如果要向 Flutter 發(fā)送消息彤枢,可以調(diào)用它的send方法向 Flutter 傳遞數(shù)據(jù)。
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);
//設(shè)置消息處理器筒饰,處理來自Flutter的消息
messageChannel.setMessageHandler(this);
}
static BasicMessageChannelPlugin registerWith(FlutterView flutterView) {
return new BasicMessageChannelPlugin(flutterView);
}
@Override
public void onMessage(String s, BasicMessageChannel.Reply<String> reply) {//處理Flutter發(fā)來的消息
reply.reply("BasicMessageChannelPlugin收到:" + s);//可以通過reply進(jìn)行回復(fù)
if (activity instanceof IShowMessage) {
((IShowMessage) activity).onShowMessage(s);
}
Toast.makeText(activity, s, Toast.LENGTH_SHORT).show();
}
/**
* 向Flutter發(fā)送消息缴啡,并接受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)構(gòu)造方法原型
//會構(gòu)造一個StandardMethodCodec.INSTANCE類型的MethodCodec
MethodChannel(BinaryMessager messenger,String name)
//或
MethodChannel(BinaryMessager messenger,String name,MethodCodec codec)
- BinaryMessenger messenger:消息信使瓷们,是消息的發(fā)送與接收的工具业栅;
- String name:Channel的名字,也是其唯一標(biāo)識符谬晕;
- MethodCodec codec:用作 MethodChannel 的解編碼器碘裕;
(2)setMethodCallHandler 方法原型
setMethodCallHandler(@Nullable MethodChannel.MethodCallHandler handler)
- @Nullable MethodChannel.MethodCallHandler handler:消息處理器,配合 BinaryMessenger 完成消息的處理攒钳;
在創(chuàng)建好 MethodChannel 后帮孔,需要調(diào)用它的 setMethodCallHandler方法為其設(shè)置一個消息處理器,以便能接收來自 Flutter 的消息不撑。
(3)MethodChannel.MethodCallHandler原型
public interface MethodCallHandler{
void onMethodCall(MethodCall var1,MethodChannel.Result var2);
}
- onMethodCall(MethodCall call,MethodChannel.Result result):用于接收消息文兢,call 是消息內(nèi)容,它有兩個成員變量 String 類型的 call.method 表示調(diào)用的方法名焕檬,Object 類型的 call.arguments 表示調(diào)用方法所傳遞的入?yún)⒛芳幔籑ethodChannel.Result result 是回復(fù)此消息的回調(diào)函數(shù)提供了result.success,result.error实愚,result.notImplemented方法調(diào)用兼呵;
/**
* MethodChannelPlugin
* 用于傳遞方法調(diào)用(method invocation),一次性通信腊敲,通常用于 Flutter 調(diào)用 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的方法調(diào)用
case "showMessage":
showMessage(methodCall.arguments());
result.success("MethodChannelPlugin收到:" + methodCall.arguments);//返回結(jié)果給Flutter
break;
default:
result.notImplemented();
}
}
/**
* 展示來自Flutter的數(shù)據(jù)
*
* @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)構(gòu)造方法原型
//會構(gòu)造一個StandardMethodCodec.INSTANCE類型的MethodCodec
EventChannel(BinaryMessenger messenger,String name)
//或
EventChannel(BinaryMessenger messenger,String name,MethodCodec codec)
- BinaryMessenger messenger:消息信使击喂,是消息的發(fā)送與接收的工具;
- String name:Channel的名字兔仰,也是其唯一標(biāo)識符茫负;
- MethodCodec:用作 EventChannel 的編解碼器;
(2)setStreamHandler 方法原型
void setStreamHandler(EventChannel.StreamHandler handler)
- EventChannel.StreamHandler handler:消息處理器乎赴,配合 BinaryMessenger 完成消息的處理忍法;在創(chuàng)建好 EventChannel 后,如果要讓其接收 Flutter 發(fā)來的消息榕吼,則需要調(diào)用它的 setStreamHandler 方法為其設(shè)置一個消息處理器饿序。
(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監(jiān)聽事件時調(diào)用,Object args 是傳遞的參數(shù)羹蚣,EventChannel.EventSink eventSink 是 Native 回調(diào) Flutter 時的回調(diào)函數(shù)原探,eventSink 提供success、error 與 endOfStream 三個回調(diào)方法分別對應(yīng)事件的不同狀態(tài)顽素;
- void onCancel(Object 0):Flutter取消監(jiān)聽時調(diào)用咽弦;
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) {
}
}
總結(jié)
本文主要介紹了 Flutter 與 Native 的混合開發(fā),它們之間的消息通信主要是通過 Channel 實(shí)現(xiàn)的胁出。