Flutter 與 Native 混合開發

前言

本文主要介紹 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 端。

  1. 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);
}
...
  1. 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);
}
...
  1. 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 通信

  1. 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) {

    }
}
  1. 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();
    }
}
  1. 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 實現的。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容