6.Flutte3.0 遙遙領先系列|一文教你完全掌握原生交互(Platform Channel)

目錄:

1.3種方式比較, BasicMessageChannel, MethodChannel, EventChannel
2.原生交互的缺點
3.Pigeon插件的使用
4.fluttter 嵌套原生的view視圖
5.fluttter 動態嵌套原生的view視圖

解決問題: Flutter和原生交互分為2種, 數據傳輸和view的控制顯示

形象的舉例:

一次典型的方法調用過程類似網絡調用!
由作為客戶端的 Flutter,通過方法通道向作為服務端的原生代碼宿主發送方法調用請求,原生代碼宿主在監聽到方法調用的消息后,調用平臺相關的 API 來處理 Flutter 發起的請求,最后將處理完畢的結果通過方法通道回發至 Flutter
dart與Native之間多了一層C++寫的Engine,至于flutter的C++源碼,

架構圖:


原生交互的類型.jpg
  1. 3種方式比較, BasicMessageChannel, MethodChannel, EventChannel
    Flutter為開發者提供了一個輕量級的解決方案,即邏輯層的方法通道(Method Channel)機制。基于方法通道,我們可以將原生代碼所擁有的能力,以接口形式暴露給Dart,從而實現Dart代碼與原生代碼的交互,就像調用了一個普通的Dart API一樣。
    Flutter定義了三種不同類型的Channel

1>. BasicMessageChannel:用于傳遞字符串和半結構化的信息。BasicMessageChannel支持數據雙向傳遞,有返回值。

2> .MethodChannel:用于傳遞方法調用(method invocation)。MethodChannel支持數據雙向傳遞,有返回值。通常用來調用 native 中某個方法

3>. EventChannel: 用于數據流(event streams)的通信。 EventChannel僅支持數據單向傳遞,無返回值。有監聽功能,比如電量變化之后直接推送數據給flutter端
對比表格:

3種方式的對比.jpg

實現調用步驟 :
1). Flutter能夠通過輕量級的異步方法調用,實現與原生代碼的交互。一次典型的調用過程由Flutter發起方法調用請求開始,
2). 請求經由唯一標識符指定的方法通道到達原生代碼宿主,
3). 而原生代碼宿主則通過注冊對應方法實現、響應并處理調用請求,
4). 最后將執行結果通過消息通道,回傳至Flutter。
5). 方法調用請求的處理和響應,在Android中是通過FlutterView

plantview.png
1.1 問題:Flutter如何實現一次方法調用請求?

方法調用過程是異步的,所以我們需要使用非阻塞(或者注冊回調)來等待原生代碼給予響應
非阻塞: 就是單線程中的處理方案
1.1 .1 案例演示: flutter調用原生的方法, MethodChannel

Flutter代碼

  void getBatteryInfo() async {
      
    // 聲明 MethodChannel
     const platform = MethodChannel('samples.chenhang/utils');
    // 核心代碼二
    final int result = await platform.invokeMethod("getBatteryInfo");
     // 返回值的類型是啥????,可以統一定義為json
    setState(() {
      _result = result;
    });
  }
}

偽代碼: flutter發起原生調用

// 聲明 MethodChannel
const platform = MethodChannel('samples.chenhang/utils');
 
// 處理按鈕點擊
handleButtonClick() async{
  int result;
  // 異常捕獲
  try {
    // 異步等待方法通道的調用結果
    result = await platform.invokeMethod('openAppMarket');
  }
  catch (e) {
    result = -1;
  }
  print("Result:$result");
}
1.1.2 原生通過MethodChannel調用flutter的案例:

在原生代碼中完成方法調用的響應(flutter監聽原生調用)
android代碼
考慮到打開應用市場的過程可能會出錯,我們也需要增加 try-catch 來捕獲可能的異常:
核心類:
· ) MethodChannel
· ) DartExecutor
· ) BinaryMessenger:是一個接口,在FlutterView中實現了該接口,在BinaryMessenger的方法中通過JNI來與系統底層溝通
· ) FlutterEngine: 發送數據必然要通過
內部都是通過DartMessenger來調用FlutterJNI的相關API完成通信的

public class MainActivity extends FlutterActivity {
  private static final String CHANNEL = "coderwhy.com/battery";

  @Override
  public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
    // 1.創建MethodChannel對象
    MethodChannel methodChannel = new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL);

    // 2.添加調用方法的回調
    methodChannel.setMethodCallHandler(
        (call, result) -> {
          // 2.1.如果調用的方法是getBatteryInfo,那么正常執行
          if (call.method.equals("getBatteryInfo")) {

            // 2.1.1.調用另外一個自定義方法回去電量信息
            int batteryLevel = getBatteryLevel();

            // 2.1.2. 判斷是否正常獲取到
            if (batteryLevel != -1) {
              // 獲取到返回結果
              result.success(batteryLevel);
            } else {
              // 獲取不到拋出異常
              result.error("UNAVAILABLE", "Battery level not available.", null);
            }
          } else {
            // 2.2.如果調用的方法是getBatteryInfo,那么正常執行
            result.notImplemented();
          }
        }
      );
  }

需要注意的是,方法通道是非線程安全的。這意味著原生代碼與 Flutter 之間所有接口調用必須發生在主線程

1.2. 1 MethodChannel(flutter----> 原生----(返回結果到)---> flutter)

源碼分析:
原理: 從flutter開始調用:
調用棧圖:

methodChannel調用流程圖.jpg
class MethodChannel {
  /// Creates a [MethodChannel] with the specified [name].
  ///
  /// The [codec] used will be [StandardMethodCodec], unless otherwise
  /// specified.
  ///
  /// The [name] and [codec] arguments cannot be null. The default [ServicesBinding.defaultBinaryMessenger]
  /// instance is used if [binaryMessenger] is null.
  const MethodChannel(this.name, [this.codec = const StandardMethodCodec(), BinaryMessenger? binaryMessenger ])
      : _binaryMessenger = binaryMessenger;
  @optionalTypeArgs
  Future<T?> _invokeMethod<T>(String method, { required bool missingOk, dynamic arguments }) async {
    final ByteData input = codec.encodeMethodCall(MethodCall(method, arguments)); // 編碼數據
    final ByteData? result =
      !kReleaseMode && debugProfilePlatformChannels ? 
        await (binaryMessenger as _ProfiledBinaryMessenger).sendWithPostfix(name, '#$method', input) :
        await binaryMessenger.send(name, input);    // binaryMessenger發送數據
    if (result == null) {
      if (missingOk) {
        return null;
      }
      throw MissingPluginException('No implementation found for method $method on channel $name');
    }
    return codec.decodeEnvelope(result) as T?;
  }

_DefaultBinaryMessenger

  @override
  Future<ByteData?> send(String channel, ByteData? message) {
    final Completer<ByteData?> completer = Completer<ByteData?>();
    ui.PlatformDispatcher.instance.sendPlatformMessage(channel, message, (ByteData? reply) {
      try {
        completer.complete(reply);
      } catch (exception, stack) {
        FlutterError.reportError(FlutterErrorDetails(
          exception: exception,
          stack: stack,
          library: 'services library',
          context: ErrorDescription('during a platform message response callback'),
        ));
      }
    });
    return completer.future;
  }
調用到PlatformDispatcher的_sendPlatformMessage方法,進入flutter engine層的_SendPlatformMessage。
@Native<Handle Function(Handle, Handle, Handle)>(symbol: 'PlatformConfigurationNativeApi::SendPlatformMessage')
  external static String? __sendPlatformMessage(String name, PlatformMessageResponseCallback? callback, ByteData? data);

PlatformConfiguration

void _SendPlatformMessage(Dart_NativeArguments args) {
  tonic::DartCallStatic(&SendPlatformMessage, args);
}
}
void PlatformConfiguration::RegisterNatives(
    tonic::DartLibraryNatives* natives) {
  natives->Register({
      {"PlatformConfiguration_sendPlatformMessage", _SendPlatformMessage, 4,
       true},
      ...
  });
}

Dart_Handle SendPlatformMessage(Dart_Handle window,
                                const std::string& name,
                                Dart_Handle callback,
                                Dart_Handle data_handle) {
  UIDartState* dart_state = UIDartState::Current();

  if (!dart_state->platform_configuration()) {
    return tonic::ToDart(
        "Platform messages can only be sent from the main isolate");
  }

  fml::RefPtr<PlatformMessageResponse> response;
  if (!Dart_IsNull(callback)) {
    //PlatformMessageResponseDart對象中采用的是UITaskRunner
    response = fml::MakeRefCounted<PlatformMessageResponseDart>(
        tonic::DartPersistentValue(dart_state, callback),
        dart_state->GetTaskRunners().GetUITaskRunner());
  }
  if (Dart_IsNull(data_handle)) {
    dart_state->platform_configuration()->client()->HandlePlatformMessage(
        std::make_unique<PlatformMessage>(name, response));
  } else {
    tonic::DartByteData data(data_handle);
    const uint8_t* buffer = static_cast<const uint8_t*>(data.data());
    dart_state->platform_configuration()->client()->HandlePlatformMessage(
        std::make_unique<PlatformMessage>(
            name, fml::MallocMapping::Copy(buffer, data.length_in_bytes()),
            response));
  }

  return Dart_Null();
}

RuntimeController //flutter/runtime/runtime_controller.cc

void RuntimeController::HandlePlatformMessage(
    std::unique_ptr<PlatformMessage> message) {
  client_.HandlePlatformMessage(std::move(message));
}

Engine

static constexpr char kAssetChannel[] = "flutter/assets";

void Engine::HandlePlatformMessage(fml::RefPtr<PlatformMessage> message) {
  if (message->channel() == kAssetChannel) {
    HandleAssetPlatformMessage(std::move(message));
  } else {
    delegate_.OnEngineHandlePlatformMessage(std::move(message));
  }
}

Shell //flutter/shell/common/shell.cc

constexpr char kSkiaChannel[] = "flutter/skia";

void Shell::OnEngineHandlePlatformMessage(
    fml::RefPtr<PlatformMessage> message) {
  if (message->channel() == kSkiaChannel) {
    HandleEngineSkiaMessage(std::move(message));
    return;
  }
  task_runners_.GetPlatformTaskRunner()->PostTask(
      [view = platform_view_->GetWeakPtr(), message = std::move(message)]() {
        if (view) {
          view->HandlePlatformMessage(std::move(message));
        }
      });
}

核心: # PlatformViewAndroid
//flutter/shell/platform/android/platform_view_android.cc
這個view對于Android平臺的實例為PlatformViewAndroid。

std::unordered_map<int, fml::RefPtr<flutter::PlatformMessageResponse>>
      pending_responses_;

// |PlatformView|
void PlatformViewAndroid::HandlePlatformMessage(
    std::unique_ptr<flutter::PlatformMessage> message) {
  int response_id = 0;
  if (auto response = message->response()) {
    response_id = next_response_id_++;
    pending_responses_[response_id] = response;
  }
  // This call can re-enter in InvokePlatformMessageXxxResponseCallback.
  jni_facade_->FlutterViewHandlePlatformMessage(std::move(message),
                                                response_id);
  message = nullptr;
}

c調用android ,對應FlutterJNI.java中的handlePlatformMessage()方法

void FlutterViewHandlePlatformMessage(JNIEnv* env, jobject obj,
                                      jstring channel, jobject message,
                                      jint responseId) {
  env->CallVoidMethod(obj, g_handle_platform_message_method, channel, message, responseId);
}

Android 端源碼分析:
FlutterJN

  @VisibleForTesting
  public void handlePlatformMessage(
      @NonNull final String channel,
      ByteBuffer message,
      final int replyId,
      final long messageData) {
    if (platformMessageHandler != null) {
      platformMessageHandler.handleMessageFromDart(channel, message, replyId, messageData);
    } else {
      nativeCleanupMessageData(messageData);
    }
    // TODO(mattcarroll): log dropped messages when in debug mode
    // (https://github.com/flutter/flutter/issues/25391)
  }
  public void handleMessageFromDart(
      @NonNull String channel, @Nullable ByteBuffer message, int replyId, long messageData) {
    Log.v(TAG, "Received message from Dart over channel '" + channel + "'");
    synchronized (handlersLock) {
      handlerInfo = messageHandlers.get(channel);
      messageDeferred = (enableBufferingIncomingMessages.get() && handlerInfo == null);
      if (messageDeferred) {
        if (!bufferedMessages.containsKey(channel)) {
          bufferedMessages.put(channel, new LinkedList<>());
        }
        List<BufferedMessageInfo> buffer = bufferedMessages.get(channel);
        buffer.add(new BufferedMessageInfo(message, replyId, messageData));
      }
    }
    if (!messageDeferred) {
      dispatchMessageToQueue(channel, handlerInfo, message, replyId, messageData);
    }
  }

把消息添加到隊列中去!

  private void dispatchMessageToQueue(
      @NonNull String channel,
      @Nullable HandlerInfo handlerInfo,
      @Nullable ByteBuffer message,
      int replyId,
      long messageData) {
    // Called from any thread.
    final DartMessengerTaskQueue taskQueue = (handlerInfo != null) ? handlerInfo.taskQueue : null;
    TraceSection.beginAsyncSection("PlatformChannel ScheduleHandler on " + channel, replyId);
    Runnable myRunnable =
        () -> {
          TraceSection.endAsyncSection("PlatformChannel ScheduleHandler on " + channel, replyId);
          TraceSection.begin("DartMessenger#handleMessageFromDart on " + channel);
          try {
            invokeHandler(handlerInfo, message, replyId); // 調用
            if (message != null && message.isDirect()) {
              // This ensures that if a user retains an instance to the ByteBuffer and it
              // happens to be direct they will get a deterministic error.
              message.limit(0);
            }
          } finally {
            // This is deleting the data underneath the message object.
            flutterJNI.cleanupMessageData(messageData);
            TraceSection.end();
          }
        };
    final DartMessengerTaskQueue nonnullTaskQueue =
        taskQueue == null ? platformTaskQueue : taskQueue;
    nonnullTaskQueue.dispatch(myRunnable);
  }

所有的Channel都會走上面的邏輯,從這里的handlerInfo.handler.onMessage開始有所不一樣了,因為不同的Channel的handler不同。

  private void invokeHandler(
      @Nullable HandlerInfo handlerInfo, @Nullable ByteBuffer message, final int replyId) {
    // Called from any thread.
    if (handlerInfo != null) {
      try {
        Log.v(TAG, "Deferring to registered handler to process message.");
        handlerInfo.handler.onMessage(message, new Reply(flutterJNI, replyId));
      } catch (Exception ex) {
        Log.e(TAG, "Uncaught exception in binary message listener", ex);
        flutterJNI.invokePlatformMessageEmptyResponseCallback(replyId);
      } catch (Error err) {
        handleError(err);
      }
    } else {
      Log.v(TAG, "No registered handler for message. Responding to Dart with empty reply message.");
      flutterJNI.invokePlatformMessageEmptyResponseCallback(replyId);
    }
  }

   public void onMessage(ByteBuffer message, final BinaryReply reply) {
      final MethodCall call = codec.decodeMethodCall(message);
      try {
        handler.onMethodCall(
            call,
            new Result() {
              @Override
              public void success(Object result) {
                reply.reply(codec.encodeSuccessEnvelope(result));
              }

              @Override
              public void error(String errorCode, String errorMessage, Object errorDetails) {
                reply.reply(codec.encodeErrorEnvelope(errorCode, errorMessage, errorDetails));
              }

              @Override
              public void notImplemented() {
                reply.reply(null);
              }
            });
      } catch (RuntimeException e) {
        Log.e(TAG + name, "Failed to handle method call", e);
        reply.reply(
            codec.encodeErrorEnvelopeWithStacktrace(
                "error", e.getMessage(), null, Log.getStackTraceString(e)));
      }
    }
  }
}

MethodChannel的執行流程涉及到主線程和UI線程的交互,代碼從Dart到C++再到Java層,執行完相應邏輯后原路返回,從Java層到C++層再到Dart層。

1.2.2 EventChannel (原生------>flutter)

不存在: 原生向flutter調用, 然后flutter把結果返回給原生!
EventChannel 可以由 Android 原生主動向 Flutter 發起交互請求
相對于原生為主動式交互,類似于 Android 發送一個廣播在 Flutter 端進行接收
android端:

new EventChannel(flutterView, CHANNEL).setStreamHandler(new EventChannel.StreamHandler() {
    @Override
    public void onListen(Object arguments, final EventChannel.EventSink events) {
        events.success("我來自 " + TAG +" !! 使用的是 EventChannel 方式");
    }

    @Override
    public void onCancel(Object arguments) {
    }
});

flutter部分:

class _MyHomePageState extends State<MyHomePage> {
  static const eventChannel = const EventChannel('ace_demo_android_flutter');
  String _result = '';
  StreamSubscription _streamSubscription;

  @override
  void initState() {
    super.initState();
    _getEventResult();
  }

  @override
  void dispose() {
    super.dispose();
    if (_streamSubscription != null) {
      _streamSubscription.cancel();
    }
  }

  _getEventResult() async {
    try {
      _streamSubscription =
          eventChannel.receiveBroadcastStream().listen((data) {
        setState(() {
          _result = data;
        });
      });
    } on PlatformException catch (e) {
      setState(() {
        _result = "event get data err: '${e.message}'.";
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(title: Text(widget.title)),
        body: Center(
            child: Text('${_result}',
                style: TextStyle(color: Colors.blueAccent, fontSize: 18.0))),
        floatingActionButton: FloatingActionButton(
            onPressed: _incrementCounter, child: Icon(Icons.arrow_back)));
  }
}

1.2. 3 BasicMessageChannel

BasicMessageChannel 主要傳遞字符串和半結構化的數據交互;其編解碼有多種類型,在使用時建議 Android 與 Flutter 兩端一致;

1).BinaryCodec:基本二進制編碼類型;
2).StringCodec:字符串與二進制之間的編碼類型;
3).JSONMessageCodec:Json 與二進制之間的編碼類型;
4).StandardMessageCodec:默認編碼類型,包括基礎數據類型、二進制數據、列表、字典等與二進制之間等編碼類型;
Flutter -> Android
用途: Flutter 端向 Android 端發送 send 數據請求,Android 端接收到后通過 replay 向 Flutter 端發送消息,從而完成一次消息交互;

// Flutter 端
static const basicChannel = BasicMessageChannel<String>('ace_demo_android_flutter', StringCodec());

@override
void initState() {
  super.initState();
  _getBasicResult();
}
  
_getBasicResult() async {
  final String reply = await basicChannel.send('ace_demo_user');
  setState(() {
    _result = reply;
  });
}

\// Android 端
final BasicMessageChannel channel = new BasicMessageChannel<String> (flutterView, CHANNEL, StringCodec.INSTANCE);
channel.setMessageHandler(new BasicMessageChannel.MessageHandler() {
    @Override
    public void onMessage(Object o, BasicMessageChannel.Reply reply) {
        reply.reply("我來自 " + TAG +" !! 使用的是 BasicMessageChannel 方式");
    }
});

Android -> Flutter
根據上述繼續由 Android 端主動向 Flutter 端發送數據,Android 通過 send 向 Flutter 發送數據請求,Flutter 通過 setMessageHandler 接收后向 Android 端 return 返回結果,再由 Android 回調接收,從而完成一次數據交互;

public void send(T message) {
    this.send(message, (BasicMessageChannel.Reply)null);
}

public void send(T message, BasicMessageChannel.Reply<T> callback) {
    this.messenger.send(this.name, this.codec.encodeMessage(message), callback == null ? null : new BasicMessageChannel.IncomingReplyHandler(callback));
}

分析源碼 send 有兩個構造函數,有兩個參數的構造方法用來接收 Flutter 回調的數據;

// Flutter 端
static const basicChannel = BasicMessageChannel<String>('ace_demo_android_flutter', StringCodec());

@override
void initState() {
  super.initState();
  _getBasicResult();
}

_getBasicResult() async {
  final String reply = await
  channel.setMessageHandler((String message) async {
    print('Flutter Received: ${message}');
    setState(() {
      _result = message;
    });
    return "{'name': '我不是老豬', 'gender': 1}";
  });
}

// Android 端
channel.setMessageHandler(new BasicMessageChannel.MessageHandler() {
    @Override
    public void onMessage(Object o, BasicMessageChannel.Reply reply) {
        reply.reply("我來自 " + TAG +" !! 使用的是 BasicMessageChannel 方式");
        channel.send("ace_demo_user");
        //channel.send("ace_demo_user", new BasicMessageChannel.Reply() {
        //    @Override
        //    public void reply(Object o) {
        //        Intent intent = new Intent();
        //        intent.putExtra("data", o!=null?o.toString():"");
        //        setResult(REQUEST_CODE, intent);
        //        MyFlutterViewActivity.this.finish();
        //    }
        //});
    }
});

3者的通信原理總結:

消息信使:BinaryMessenger

以ByteBuffer為數據載體,然后通過BinaryMessenger來發送與接收數據。整體設計如下。
在Android側,BinaryMessenger是一個接口,在FlutterView中實現了該接口,在BinaryMessenger的方法中通過JNI來與系統底層溝通。在Flutter側,BinaryMessenger是一個類,該類的作用就是與類window溝通,而類window才真正與系統底層溝通。

methodchannel原理.jpg

雖然三種Channel各有用途,但是他們與Flutter通信的工具卻是相同的,均為BinaryMessager。

BinaryMessenger是Platform端與Flutter端通信的工具,其通信使用的消息格式為二進制格式數據。當我們初始化一個Channel,并向該Channel注冊處理消息的Handler時,實際上會生成一個與之對應的BinaryMessageHandler,并以channel name為key,注冊到BinaryMessenger中。當Flutter端發送消息到BinaryMessenger時,BinaryMessenger會根據其入參channel找到對應的BinaryMessageHandler,并交由其處理。

Binarymessenger在Android端是一個接口,其具體實現為FlutterNativeView。而其在iOS端是一個協議,名稱為FlutterBinaryMessenger,FlutterViewController遵循了它。

Binarymessenger并不知道Channel的存在,它只和BinaryMessageHandler打交道。而Channel和BinaryMessageHandler則是一一對應的。由于Channel從BinaryMessageHandler接收到的消息是二進制格式數據,無法直接使用,故Channel會將該二進制消息通過Codec(消息編解碼器)解碼為能識別的消息并傳遞給Handler進行處理。

當Handler處理完消息之后,會通過回調函數返回result,并將result通過編解碼器編碼為二進制格式數據,通過BinaryMessenger發送回Flutter端。

1.3. Platform Channel的代碼運行在什么線程

在文章《深入理解Flutter引擎線程模型》中提及,Flutter Engine自己不創建線程,其線程的創建于管理是由enbedder提供的,并且Flutter Engine要求Embedder提供四個Task Runner,分別是Platform Task Runner,UI Task Runner,GPU Task Runner和IO Task Runner。

實際上,在Platform側執行的代碼運行在Platform Task Runner中,而在Flutter app側的代碼則運行在UI Task Runner中。在Android和iOS平臺上,Platform Task Runner跑在主線程上。因此,不應該在Platform端的Handler中處理耗時操作。

1.4. Platform Channel是否線程安全

Platform Channel并非是線程安全的,這一點在官方的文檔也有提及。Flutter Engine中多個組件是非線程安全的,故跟Flutter Engine的所有交互(接口調用)必須發生在Platform Thread。故我們在將Platform端的消息處理結果回傳到Flutter端時,需要確保回調函數是在Platform Thread(也就是Android和iOS的主線程)中執行的。

1.5. 是否支持大內存數據塊的傳遞

Platform Channel實際上是支持大內存數據塊的傳遞,當需要傳遞大內存數據塊時,需要使用BasicMessageChannel以及BinaryCodec。而整個數據傳遞的過程中,唯一可能出現數據拷貝的位置為native二進制數據轉化為Dart語言二進制數據。若二進制數據大于閾值時(目前閾值為1000byte)則不會拷貝數據,直接轉化,否則拷貝一份再轉化。

1.6. 如何將Platform Channel原理應用到開發工作中

實際上Platform Channel的應用場景非常多,我們這里舉一個例子:

在平常的業務開發中,我們需要使用到一些本地圖片資源,但是Flutter端是無法使用Platform端已存在的圖片資源的。當Flutter端需要使用一個Platform端已有的圖片資源時,只有將該圖片資源拷貝一份到Flutter的Assert目錄下才能使用。實際上,讓Flutter端使用Platform端的資源并不是一件難事。

我們可以使用BasicMessageChannel來完成這個工作。Flutter端將圖片資源名name傳遞給Platform端,Native端使用Platform端接收到name后,根據name定位到圖片資源,并將該圖片資源以二進制數據格式,通過BasicMessageChannel,傳遞回Flutter端。

2. 混合交互的缺點:

1). android 還是得寫代碼,ios端還是得寫代碼, 要維護!
2). 運行多個Flutter實例,或在屏幕局部上運行Flutter可能會導致不可以預測的行為;
3). 在后臺模式使用Flutter的能力還在開發中(目前不支持);
4).將Flutter庫打包到另一個可共享的庫或將多個Flutter庫打包到同一個應用中,都不支持;
5).添加到應用在Android平臺的實現基于 FlutterPlugin 的 API,一些不支持 FlutterPlugin 的插件可能會有不可預知的行為。
混合開發的2種情況

  1. .flutter項目,里面用android或者ios
    2).android項目中,加入flutter進行混合

3. 插件Pigeon

Pigeon工具生成的代碼是基于BasicMessageChannel實現通信的。

  • 生成的代碼是 Java/Objective-C,但是由于 Kotlin 可以調用 Java,Swift 可以調用 Objective-C

4.fluttter 嵌套原生的view視圖

Flutter提供了一個平臺視圖(Platform View)的概念。它提供了一種方法,允許開發者在Flutter里面嵌入原生系統(Android和iOS)的視圖,并加入到Flutter的渲染樹中,實現與Flutter一致的交互體驗。

嵌套原生的view:
由于Flutter與原生渲染方式完全不同,因此轉換不同的渲染數據會有較大的性能開銷。如果在一個界面上同時實例化多個原生控件,就會對性能造成非常大的影響,所以我們要避免在使用Flutter控件也能實現的情況下去使用內嵌平臺視圖。

一方面, 需要分別在Android和iOS端寫大量的適配橋接代碼,違背了跨平臺技術的本意,也增加了后續的維護成本;
另一方面, 畢竟除去地圖、WebView、相機等涉及底層方案的特殊情況外,大部分原生代碼能夠實現的UI效果,完全可以用Flutter實現。

平臺視圖PlatformView
PlatformView跟add to app怎么選
FlutterView的創建:

public class MyFlutterViewActivity extends FlutterFragmentActivity {

    private static final String CHANNEL = "ace_demo_android_flutter";
    private static final String TAG = "MyFlutterViewActivity";
    private static final int REQUEST_CODE = 1000;
    FlutterView flutterView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_flutter);

        DisplayMetrics outMetrics = new DisplayMetrics();
        getWindowManager().getDefaultDisplay().getMetrics(outMetrics);
        int widthPixels = outMetrics.widthPixels;
        int heightPixels = outMetrics.heightPixels;

        flutterView = Flutter.createView(MyFlutterViewActivity.this, getLifecycle(), "/");
        FrameLayout.LayoutParams layout = new FrameLayout.LayoutParams(widthPixels, heightPixels);
        addContentView(flutterView, layout);

在Flutter中嵌套原生視圖: 通過平臺視圖,我們就可以將一個原生控件包裝成Flutter控件,嵌入到Flutter頁面中,
就像使用一個普通的Widget一樣。把原生視圖組裝成一個 Flutter 控件
一次典型的平臺視圖使用過程與方法通道類似:
首先,由作為客戶端的Flutter,通過向原生視圖的Flutter封裝類(在iOS和Android平臺分別是UIKitView和AndroidView)傳入視圖標識符,用于發起原生視圖的創建請求;
然后,原生代碼側將對應原生視圖的創建交給平臺視圖工廠(PlatformViewFactory)實現;
最后,在原生代碼側將視圖標識符與平臺視圖工廠進行關聯注冊,讓Flutter發起的視圖創建請求可以直接找到對應的視圖創建工廠。
架構原理圖:

channel.jpg

實現步驟:
1. android 端,把view封裝一個.PlatformView !
2. android端創建一個工廠
3. android通過高方法通道, 綁定注冊view
flutter端: 直接返回一個綁定方法通道標識的view,AndroidView!
案例demo2:
問題: flutter如何實現原生視圖的接口調用?
flutter端:

class SampleView extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // 使用 Android 平臺的 AndroidView,傳入唯一標識符 sampleView
    if (defaultTargetPlatform == TargetPlatform.android) {
      return AndroidView(viewType: 'sampleView');
    } else {
      // 使用 iOS 平臺的 UIKitView,傳入唯一標識符 sampleView
      return UiKitView(viewType: 'sampleView');
    }
  }
}
Scaffold(
        backgroundColor: Colors.yellowAccent,
        body:  Container(width: 200, height:200,
            child: SampleView(controller: controller)
        ));

如何在原生系統實現接口?
android端:

// 視圖工廠類
class SampleViewFactory extends PlatformViewFactory {
    private final BinaryMessenger messenger;
    // 初始化方法
    public SampleViewFactory(BinaryMessenger msger) {
        super(StandardMessageCodec.INSTANCE);
        messenger = msger;
    }
    // 創建原生視圖封裝類,完成關聯
    @Override
    public PlatformView create(Context context, int id, Object obj) {
        return new SimpleViewControl(context, id, messenger);
    }
}
// 原生視圖封裝類
class SimpleViewControl implements PlatformView {
    private final View view;// 緩存原生視圖
    // 初始化方法,提前創建好視圖
    public SimpleViewControl(Context context, int id, BinaryMessenger messenger) {
        view = new View(context);
        view.setBackgroundColor(Color.rgb(255, 0, 0));
    }
    
    // 返回原生視圖
    @Override
    public View getView() {
        return view;
    }
    // 原生視圖銷毀回調
    @Override
    public void dispose() {
    }
}
protected void onCreate(Bundle savedInstanceState) {
  ...
  Registrar registrar =    registrarFor("samples.chenhang/native_views");// 生成注冊類
  SampleViewFactory playerViewFactory = new SampleViewFactory(registrar.messenger());// 生成視圖工廠
 
registrar.platformViewRegistry().registerViewFactory("sampleView", playerViewFactory);// 注冊視圖工廠
}

5.如何在程序運行時,動態地調整原生視圖的樣式?

flutter端我們會用到原生視圖的一個初始化屬性,即 onPlatformViewCreated
原生端: 會用到MethodChannel, 方法通道
實現原理:
flutter:
1.創建一個controller

  1. 初始化的時候在controller中創建方法通道
    3.在controller中, invoke通道方法(聲明方法)
    android端實現: 方法通道, 動態調用flutter的方法
    案例demo3:
    flutter端的代碼
// 原生視圖控制器
class NativeViewController {
  MethodChannel _channel;
  // 原生視圖完成創建后,通過 id 生成唯一方法通道
  onCreate(int id) {
    _channel = MethodChannel('samples.chenhang/native_views_$id');
  }
  // 調用原生視圖方法,改變背景顏色
  Future<void> changeBackgroundColor() async {
    return _channel.invokeMethod('changeBackgroundColor');
  }
}
 
// 原生視圖 Flutter 側封裝,繼承自 StatefulWidget
class SampleView extends StatefulWidget {
  const SampleView({
    Key key,
    this.controller,
  }) : super(key: key);
 
  // 持有視圖控制器
  final NativeViewController controller;
  @override
  State<StatefulWidget> createState() => _SampleViewState();
}
 
class _SampleViewState extends State<SampleView> {
  // 根據平臺確定返回何種平臺視圖
  @override
  Widget build(BuildContext context) {
    if (defaultTargetPlatform == TargetPlatform.android) {
      return AndroidView(
        viewType: 'sampleView',
        // 原生視圖創建完成后,通過 onPlatformViewCreated 產生回調
        onPlatformViewCreated: _onPlatformViewCreated,
      );
    } else {
      return UiKitView(viewType: 'sampleView',
        // 原生視圖創建完成后,通過 onPlatformViewCreated 產生回調
        onPlatformViewCreated: _onPlatformViewCreated
      );
    }
  }
  // 原生視圖創建完成后,調用 control 的 onCreate 方法,傳入 view id
  _onPlatformViewCreated(int id) {
    if (widget.controller == null) {
      return;
    }
    widget.controller.onCreate(id);
  }
}

Android 端的代碼

class SimpleViewControl implements PlatformView, MethodCallHandler {
    private final MethodChannel methodChannel;
    ...
    public SimpleViewControl(Context context, int id, BinaryMessenger messenger) {
        ...
        // 用 view id 注冊方法通道
        methodChannel = new MethodChannel(messenger, "samples.chenhang/native_views_" + id);
        // 設置方法通道回調
        methodChannel.setMethodCallHandler(this);
    }
    // 處理方法調用消息
    @Override
    public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) {
        // 如果方法名完全匹配
        if (methodCall.method.equals("changeBackgroundColor")) {
            // 修改視圖背景,返回成功
            view.setBackgroundColor(Color.rgb(0, 0, 255));
            result.success(0);
        }else {
            // 調用方發起了一個不支持的 API 調用
            result.notImplemented();
        }
    }
  ...
}
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容