MethodChannel 原理之 Native -> Java

通過上文分析,我們知道FlutterViewHandlePlatformMessage()實際上是通過JNI的方式最終調用了FlutterJNI.java中的handlePlatformMessage()方法,該方法接受三個來自Native層的參數:

  • channel: String類型,表示Channel名稱.
  • message: 字節數組,表示方法調用中的數據,如方法名和參數.
  • replyId: int類型,在將此次調用的響應數據從Java層寫回到Native層時用到
public class FlutterJNI {
  private PlatformMessageHandler platformMessageHandler;
    
      @UiThread
  public void setPlatformMessageHandler(@Nullable PlatformMessageHandler platformMessageHandler) {
    this.platformMessageHandler = platformMessageHandler;
  }
    
      // Called by native.
  @SuppressWarnings("unused")
  private void handlePlatformMessage(final String channel, byte[] message, final int replyId) {
    if (platformMessageHandler != null) {
      platformMessageHandler.handleMessageFromDart(channel, message, replyId);
    }
  }
}

FlutterJNI類定義了Java層和Flutter C/C++引擎之間的相關接口.此類目前處于實驗性質,隨著后續的發展可能會被不斷的重構和優化,不保證一直存在,不建議開發者調用該類.

為了建立Android應用和Flutter C/C++引擎的連接,需要創建FlutterJNI實例,然后將其attach到Native,常見的使用方法如下:

// 1.創建FlutterJNI實例
FlutterJNI flutterJNI = new FlutterJNI();
// 2.建立和Native層的連接
flutterJNI.attachToNative();
......
// 3.斷開和Native層的連接,并釋放資源
flutterJNI.detachFromNativeAndReleaseResources();

FlutterJNI中handlePlatformMessage(),在該方法中首先判斷platformMessageHandler是否為null,不為null,則調用其handleMessageFromDart()方法.其中platformMessageHandler需要通過FlutterJNI中的setPlatformMessageHandler()方法來設置.在FlutterNativeView中調用的。

public class FlutterNativeView implements BinaryMessenger {

    private final Map<String, BinaryMessageHandler> mMessageHandlers;
    private int mNextReplyId = 1;
    private final Map<Integer, BinaryReply> mPendingReplies = new HashMap<>();

    private final FlutterPluginRegistry mPluginRegistry;
    private FlutterView mFlutterView;
    private FlutterJNI mFlutterJNI;
    private final Context mContext;
    private boolean applicationIsRunning;

    public FlutterNativeView(Context context, boolean isBackgroundView) {
        mContext = context;
        mPluginRegistry = new FlutterPluginRegistry(this, context);
        // 創建FlutterJNI實例
        mFlutterJNI = new FlutterJNI();
        mFlutterJNI.setRenderSurface(new RenderSurfaceImpl());
        // 將PlatformMessageHandlerImpl實例賦值給FlutterJNI中的platformMessageHandler屬性
        mFlutterJNI.setPlatformMessageHandler(new PlatformMessageHandlerImpl());
        mFlutterJNI.addEngineLifecycleListener(new EngineLifecycleListenerImpl());
        attach(this, isBackgroundView);
        assertAttached();
        mMessageHandlers = new HashMap<>();
    }
    
    .......
}

在FlutterNativeView的構造函數中,首先創建FlutterJNI實例mFlutterJNI,然后調用setPlatformMessageHandler()并把PlatformMessageHandlerImpl實例作為參數傳入.因此在FlutterJNI的handlePlatformMessage()方法中,最終調用PlatformMessageHandlerImpl實例的handleMessageFromDart()來處理來自Flutter中的消息.

public class FlutterNativeView implements BinaryMessenger {
        private final Map<String, BinaryMessageHandler> mMessageHandlers;
    
        ......
            
        private final class PlatformMessageHandlerImpl implements PlatformMessageHandler {
        // Called by native to send us a platform message.
        public void handleMessageFromDart(final String channel, byte[] message, final int replyId) {
       // 1.根據channel名稱獲取對應的BinaryMessageHandler對象.每個Channel對應一個Handler對象
            BinaryMessageHandler handler = mMessageHandlers.get(channel);
            if (handler != null) {
                try {
                    // 2.將字節數組對象封裝為ByteBuffer對象
                    final ByteBuffer buffer = (message == null ? null : ByteBuffer.wrap(message));
                    // 3.調用handler對象的onMessage()方法來分發消息
                    handler.onMessage(buffer, new BinaryReply() {
                        private final AtomicBoolean done = new AtomicBoolean(false);
                        @Override
                        public void reply(ByteBuffer reply) {
                            // 4.根據reply的情況,調用FlutterJNI中invokePlatformMessageXXX()方法將響應數據發送給Flutter層
                            if (reply == null) {
                                mFlutterJNI.invokePlatformMessageEmptyResponseCallback(replyId);
                            } else {
                                mFlutterJNI.invokePlatformMessageResponseCallback(replyId, reply, reply.position());
                            }
                        }
                    });
                } catch (Exception exception) {
                    mFlutterJNI.invokePlatformMessageEmptyResponseCallback(replyId);
                }
                return;
            }
            mFlutterJNI.invokePlatformMessageEmptyResponseCallback(replyId);
        }
}

以Channel名稱作為key,以BinaryMessageHandler類型為value.在handleMessageFromDart()方法中,首先根據Channel名稱從mMessageHandlers取出對應的二進制消息處理器BinaryMessageHandler,然后將字節數組message封裝為ByteBuffer對象,然后調用BinaryMessageHandler實例的onMessage()方法處理ByteBuffer,并進行響應.

BinaryReply是一個接口,主要用來將ByteBuffer類型的響應數據reply從Java層寫回到Flutter層.根據reply是否為null,調用FlutterJNI實例不同的方法.

BinaryMessageHandler是如何添加到mMessageHandler中:

public class FlutterNativeView implements BinaryMessenger {
    private final Map<String, BinaryMessageHandler> mMessageHandlers;
    
    ......
        
    @Override
    public void setMessageHandler(String channel, BinaryMessageHandler handler) {
        if (handler == null) {
            mMessageHandlers.remove(channel);
        } else {
            mMessageHandlers.put(channel, handler);
        }
    }
    .......
}
public class MainActivity extends FlutterActivity {
    // 1.定義Channel的名稱,該名稱作為Channel的唯一標識符
    private static final String CHANNEL = "samples.flutter.io/battery";

    @Override
    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        // 2.創建MethodChannel對象channel
        MethodChannel channel = new MethodChannel(getFlutterView(), CHANNEL);
        // 3.調用MethodChannel實例的setMethodCallHandler()方法為當前channel設置Handler
        channel.setMethodCallHandler(
                new MethodCallHandler() {
                    @Override
                    public void onMethodCall(MethodCall call, Result result) {
                        // TODO
                    }
                });
    }
}

接下來是 MethodChannel 定義

public final class MethodChannel {
    // 二進制信使
    private final BinaryMessenger messenger;
    // Channel名稱
    private final String name;
    // 方法編碼
    private final MethodCodec codec;

    public MethodChannel(BinaryMessenger messenger, String name) {
        this(messenger, name, StandardMethodCodec.INSTANCE);
    }
        
    public MethodChannel(BinaryMessenger messenger, String name, MethodCodec codec) {
        assert messenger != null;
        assert name != null;
        assert codec != null;
        this.messenger = messenger;
        this.name = name;
        this.codec = codec;
    }    
    
    ......
        
    public void setMethodCallHandler(final @Nullable MethodCallHandler handler) {
        messenger.setMessageHandler(name,
            handler == null ? null : new IncomingMethodCallHandler(handler));
    }
    ......
    private final class IncomingMethodCallHandler implements BinaryMessageHandler {
        private final MethodCallHandler handler;

        IncomingMethodCallHandler(MethodCallHandler handler) {
            this.handler = handler;
        }

        @Override
        public void onMessage(ByteBuffer message, final BinaryReply reply) {
            // 1.使用codec對來自Flutter方法調用數據進行解碼,并將其封裝為MethodCall對象.
            // MethodCall中包含兩部分數據:method表示要調用的方法;arguments表示方法所需參數
            final MethodCall call = codec.decodeMethodCall(message);
            try {
                // 2.調用自定義MethodCallHandler中的onMethodCall方法繼續處理方法調用
                handler.onMethodCall(call, new Result() {
                    @Override
                    public void success(Object result) {
                        // 調用成功時,需要回傳數據給Flutter層時,使用codec對回傳數據result
                        // 進行編碼
                        reply.reply(codec.encodeSuccessEnvelope(result));
                    }

                    @Override
                    public void error(String errorCode, String errorMessage, Object errorDetails) {
                        // 調用失敗時,需要回傳錯誤數據給Flutter層時,使用codec對errorCode,
                        // errorMessage,errorDetails進行編碼
                        reply.reply(codec.encodeErrorEnvelope(errorCode, errorMessage, errorDetails));
                    }

                    @Override
                    public void notImplemented() {
                        // 方法沒有實現時,調用該方法后,flutter將會受到相應的錯誤消息
                        reply.reply(null);
                    }
                });
            } catch (RuntimeException e) {
                Log.e(TAG + name, "Failed to handle method call", e);
                reply.reply(codec.encodeErrorEnvelope("error", e.getMessage(), null));
            }
        }
    }
}

在上述代碼中,首先使用codec對來自Flutter層的二進制數據進行解碼,并將其封裝為MethodCall對象,然后調用MethodCallHandler.onMethodCall()方法.

調用過程

Java->Native

public class FlutterJNI {
  private Long nativePlatformViewId;
    
  ......  
  @UiThread
  public void invokePlatformMessageResponseCallback(int responseId, ByteBuffer message, int position) {
    // 1.檢查FlutterJNI是否已經attach到Native層,如若沒有則拋出異常  
    ensureAttachedToNative();
    // 2.繼續調用nativeInvokePlatformMessageResponseCallback()  
    nativeInvokePlatformMessageResponseCallback(
        nativePlatformViewId,
        responseId,
        message,
        position
    );
  }
    
  private native void nativeInvokePlatformMessageResponseCallback(
     long nativePlatformViewId,
     int responseId,
     ByteBuffer message,
     int position
  );   
    
  ......  
      
  private void ensureAttachedToNative() {
    // FlutterJNI attach到Native層后,會返回一個long類型的值用來初始化nativePlatformViewId  
    if (nativePlatformViewId == null) {
      throw new RuntimeException("Cannot execute operation because FlutterJNI is not attached to native.");
    }
  }

}

當數據需要寫回時,數據首先通過codec被編碼成ByteBuffer類型,然后調用reply的reply()方法.在reply()方法中,對于非null類型的ByteBuffer,會調用FlutterJNI中的invokePlatformMessageResponseCallback().
在上述invokePlatformMessageResponseCallback()方法中,首先檢查當前FlutterJNI實例是否已經attach到Native層,然后調用Native方法nativeInvokePlatformMessageResponseCallback()向JNI層寫入數據

void PlatformViewAndroid::InvokePlatformMessageResponseCallback(
    JNIEnv* env,
    jint response_id,
    jobject java_response_data,
    jint java_response_position) {
  if (!response_id)
    return;
  // 1.通過response_id從pending_responses_中取出response  
  auto it = pending_responses_.find(response_id);
  if (it == pending_responses_.end())
    return;
  // 2.GetDirectBufferAddress函數返回一個指向被傳入的ByteBuffer對象的地址指針  
  uint8_t* response_data =
      static_cast<uint8_t*>(env->GetDirectBufferAddress(java_response_data));
  std::vector<uint8_t> response = std::vector<uint8_t>(
      response_data, response_data + java_response_position);
  auto message_response = std::move(it->second);
  // 3.從pending_responses_中移除該response  
  pending_responses_.erase(it);
  // 4.調用response的Complete()方法將二進制結果返回
  message_response->Complete(
      std::make_unique<fml::DataMapping>(std::move(response)));
}

Native -> Dart

void PlatformMessageResponseDart::Complete(std::unique_ptr<fml::Mapping> data) {
  if (callback_.is_empty())
    return;
  FML_DCHECK(!is_complete_);
  is_complete_ = true;
  ui_task_runner_->PostTask(fml::MakeCopyable(
      [callback = std::move(callback_), data = std::move(data)]() mutable {
        std::shared_ptr<tonic::DartState> dart_state =
            callback.dart_state().lock();
        if (!dart_state)
          return;
        tonic::DartState::Scope scope(dart_state);
        // 將Native層的二進制數據data轉為Dart中的二進制數據byte_buffer
        Dart_Handle byte_buffer = WrapByteData(std::move(data));
        tonic::DartInvoke(callback.Release(), {byte_buffer});
      }));
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,505評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,556評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,463評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,009評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,778評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,218評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,281評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,436評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,969評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,795評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,993評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,537評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,229評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,659評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,917評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,687評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,990評論 2 374