IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> [-Flutter插件篇 -] 认识MethodChannel -> 正文阅读

[移动开发][-Flutter插件篇 -] 认识MethodChannel

上次从一个路径插件看来一下Flutter中如何调用iOS和Android中的方法以及平台如何返回值给Flutter框架。今天就来详细讲讲MethodChannel是如何连同另一个世界的。


1.从吐司弹框开始说起(Android端/Java)

想要达成的效果是这样使用可以弹出一个时间较长的吐司

这个示例要讲述的是Flutter中如何向平台传递参数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aNH1nR59-1629539325555)(https://user-gold-cdn.xitu.io/2019/8/12/16c83c52b7e9c7c4?imageView2/0/w/1280/h/960/ignore-error/1)]

var show = RaisedButton(
    onPressed: () {
      IaToast.show(msg: "hello",type: Toast.LENGTH_LONG);
    },
    child: Text("点击弹吐司"),
); 

1.1.Flutter/Dart端

定义一个IaToast的吐司类,根据枚举类型使用MethodChannel调用原生方法

import 'package:flutter/services.dart';

///吐司类型 [LENGTH_SHORT]短时间,[LENGTH_LONG]长时间
enum Toast { 
  LENGTH_SHORT,
  LENGTH_LONG
}

///吐司类
class IaToast {
  static const MethodChannel _channel =//方法渠道名
      const MethodChannel('www.toly1994.com.flutter_journey.toast');

  static show(//静态方法显示吐司
      {String msg, Toast type = Toast.LENGTH_SHORT}) {
    if (type == Toast.LENGTH_SHORT) {
      _channel.invokeMethod('showToast', {//渠道对象调用方法
        "msg": msg,
        "type": 0,
      });
    } else {
      _channel.invokeMethod('showToast', {
        "msg": msg,
        "type": 1,
      });
    }
  }
} 

1.2:Android/Java端

通过FlutterView和渠道名可以获取MethodChannel对象,对其进行方法调用监听

其中的两个回调参数分别储存着方法信息和返回信息。

public class MainActivity extends FlutterActivity {
  private static final String CHANNEL = "www.toly1994.com.flutter_journey.toast";//渠道名
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    GeneratedPluginRegistrant.registerWith(this);
      MethodChannel channel = new MethodChannel(getFlutterView(), CHANNEL);//获取渠道
      channel.setMethodCallHandler(this::handleMethod);//设置方法监听
  }
    /**
     * 处理方法回调监听
     * @param methodCall 方法的参数相关
     * @param result 方法的返回值相关
     */
    private void handleMethod(MethodCall methodCall, MethodChannel.Result result) {
        switch (methodCall.method){//根据方法名进行处理
            case "showToast":
                handleToast(this,methodCall);//具体处理
                break;
            default:
                result.notImplemented();
        }
    }
    
    public static void handleToast(Context context,MethodCall methodCall) {
        String msg=methodCall.argument("msg");
        int type=methodCall.argument("type");
        Toast.makeText(context, msg, type).show();
    }
} 

1.3:使用效果

这样对应Android端,在Flutter中就可以开心的弹吐司了

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TOg5nhNn-1629539325557)(https://user-gold-cdn.xitu.io/2019/8/12/16c83e575e5f1135?imageView2/0/w/1280/h/960/ignore-error/1)]

var show = RaisedButton(
  onPressed: () {
    IaToast.show(msg: "hello Flutter", type: Toast.LENGTH_LONG);//使用吐司
  },
  child: Text("点击弹吐司"),
);

var app = MaterialApp(
  title: 'Flutter Demo',
  theme: ThemeData(
    primarySwatch: Colors.blue,
  ),
  home: Scaffold(
    appBar: AppBar(
      title: Text('Flutter之旅'),
    ),
    body: show,
  ),
);

void main() => runApp(app); 

2.从吐司弹框开始说起(iOS端/Swift)

也简单的画了一幅Flutter和iOS沟通的图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EZbBFwoL-1629539325558)(https://user-gold-cdn.xitu.io/2019/8/12/16c8490f881aece8?imageView2/0/w/1280/h/960/ignore-error/1)]

2.1:创建插件类:

现在来看iOS端如何接受Flutter中的参数,和Android中基本一致,首先要获得渠道

在iOS里FlutterMethodChannel通过渠道标识和FlutterViewController来获取。
有了渠道方法之后,剩下的就几乎一致了,只是语法问题。
通过FlutterMethodCall回调中的call中的arguments值来获取参数,强转成NSDictionary
不过iOS系统并没有直接弹吐司的方法,所以需要自定义吐司。

import UIKit
import Flutter

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
    public static let channelId="www.toly1994.com.flutter_journey.toast"
    
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
   
    let controller:FlutterViewController = window.rootViewController as! FlutterViewController
    let messageChannel = FlutterMethodChannel.init(//获取方法渠道
        name: AppDelegate.channelId,
        binaryMessenger:controller)
    
    messageChannel.setMethodCallHandler{(call, result) in
        self.handle(call,result)
    }
    
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
    
    public func handle(_ call: FlutterMethodCall,_ result: @escaping FlutterResult) {
        let args: NSDictionary = call.arguments as! NSDictionary
        switch call.method {
        case "showToast":
            let msg:String = args["msg"] as! String
            let type:Int = args["type"] as! Int
            handleToast(msg:msg,type:type)
        default:
            result(FlutterMethodNotImplemented)
        }
    }
    
    public func handleToast(msg: String, type: Int) {
        Toast.toast(text: msg,type:type)
    }
} 

2.2:自定义吐司

使用UILabel和UIButton进行模拟一个吐司框

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s9YvyuYw-1629539325560)(https://user-gold-cdn.xitu.io/2019/8/12/16c848a60bfb4445?imageView2/0/w/1280/h/960/ignore-error/1)]

import UIKit
let toastDispalyDuration: CGFloat = 2.0
let toastBackgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.6)
class Toast: NSObject {
    var duration: CGFloat = toastDispalyDuration
     var contentView: UIButton//内容框
    
    init(text: String) {
        let rect = text.boundingRect(
            with: CGSize(width: 250, height: CGFloat.greatestFiniteMagnitude),
            attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 20)],//该值可调节边距
            context: nil)
    
        let textLabel = UILabel(//标签
            frame: CGRect(x: 0, y: 0, width: rect.size.width + 40, height: rect.size.height + 20))
        textLabel.backgroundColor = UIColor.clear
        textLabel.textColor = UIColor.white
        textLabel.textAlignment = .center
        textLabel.font = UIFont.systemFont(ofSize: 16)
        textLabel.text = text
        textLabel.numberOfLines = 0
        
       
        contentView = UIButton(type: .roundedRect)
        contentView.frame=CGRect(x: 0, y: 0,
            width: textLabel.frame.size.width,
            height: textLabel.frame.size.height)
        contentView.layer.cornerRadius = 15
        contentView.backgroundColor = toastBackgroundColor
        contentView.addSubview(textLabel)
        contentView.autoresizingMask = UIView.AutoresizingMask.flexibleWidth
        super.init()
        contentView.addTarget(self, action: #selector(toastTaped), for: .touchDown)
        NotificationCenter.default.addObserver(self, selector: #selector(toastTaped), name: UIDevice.orientationDidChangeNotification, object: UIDevice.current)
    }
    @objc func toastTaped() {
        self.hideAnimation()
    }
    func deviceOrientationDidChanged(notify: Notification) {
        self.hideAnimation()
    }
    @objc func dismissToast() {
        contentView.removeFromSuperview()
    }
    func setDuration(duration: CGFloat) {
        self.duration = duration
    }
    
    func showAnimation() {
        UIView.beginAnimations("show", context: nil)
        UIView.setAnimationCurve(UIView.AnimationCurve.easeIn)
        UIView.setAnimationDuration(0.3)
        contentView.alpha = 1.0
        UIView.commitAnimations()
    }
    @objc func hideAnimation() {
        UIView.beginAnimations("hide", context: nil)
        UIView.setAnimationCurve(UIView.AnimationCurve.easeOut)
        UIView.setAnimationDelegate(self)
        UIView.setAnimationDidStop(#selector(dismissToast))
        UIView.setAnimationDuration(0.3)
        contentView.alpha = 0.0
        UIView.commitAnimations()
    }

    func showFromBottomOffset(bottom: CGFloat) {
        let window: UIWindow = UIApplication.shared.windows.last!
        contentView.center = CGPoint(x: window.center.x, y: window.frame.size.height - (bottom + contentView.frame.size.height/2))
        window.addSubview(contentView)
        self.showAnimation()
        self.perform(#selector(hideAnimation), with: nil, afterDelay: TimeInterval(duration))
    }

    class func toast(text: String,type: Int) {
        let toast = Toast(text: text)
        var duration=0
        if type==0 {duration=1}else{duration=3}
        toast.setDuration(duration: CGFloat(duration))
        toast.showFromBottomOffset(bottom: 60)
    }
} 

现在应该对MethodChannel有了一个感性的认知了,它可以连通Flutter框架和平台。


3.Flutter视角看MethodChannel

在Flutter中MethodChannel是一个Dart类,

处于flutter/lib/src/services/platform_channel.dart文件中

3.1:MethodChannel的成员

其中有三个成员变量,我们在使用时只是传来一个字符串而已,其实还有两个是默认的

codec是消息的编解码器,类型MethodCodec,默认是StandardMethodCodec
binaryMessenger是二进制信使,类型BinaryMessenger,默认是defaultBinaryMessenger

class MethodChannel {
  const MethodChannel(this.name, [this.codec = const StandardMethodCodec(), this.binaryMessenger = defaultBinaryMessenger ])
    : assert(name != null),
      assert(binaryMessenger != null),
      assert(codec != null);

  final String name;
  final MethodCodec codec;
  final BinaryMessenger binaryMessenger; 

3.2:MethodChannel的invokeMethod方法

首先它是一个异步方法,传递方法名和参数,可以看出首先由codec编码MethodCall对象

然后通过binaryMessenger去发送信息,获取的结构是一个字节数据,
如果结果非空,通过codec去解码,然后进行返回,可见这个泛型便是期望的结果类型

 Future<T> invokeMethod<T>(String method, [ dynamic arguments ]) async {
    assert(method != null);
    final ByteData result = await binaryMessenger.send(
      name,
      codec.encodeMethodCall(MethodCall(method, arguments)),
    );
    if (result == null) {
      throw MissingPluginException('No implementation found for method $method on channel $name');
    }
    final T typedResult = codec.decodeEnvelope(result);
    return typedResult;
  } 

3.3:MethodCodec类及StandardMethodCodec

MethodCodec是一个抽象接口,定义了编解码的方法,所以具体逻辑还要看它的实现类

MethodCodec有两个实现类StandardMethodCodec和JSONMethodCodec

abstract class MethodCodec {

  ByteData encodeMethodCall(MethodCall methodCall);
  MethodCall decodeMethodCall(ByteData methodCall);
  dynamic decodeEnvelope(ByteData envelope);
  ByteData encodeSuccessEnvelope(dynamic result);
  ByteData encodeErrorEnvelope({ @required String code, String message, dynamic details });
} 
  • StandardMethodCodec的编码方法

可以看出StandardMethodCodec对MethodCall的编码是通过messageCodec实现的

messageCodec是StandardMessageCodec对象,其中的writeValue是编码的核心方法
将方法名和参数根据类型放入buffer中,从而将这些方法信息存储其中。

 class StandardMethodCodec implements MethodCodec {
  
  const StandardMethodCodec([this.messageCodec = const StandardMessageCodec()]);

  @override
  ByteData encodeMethodCall(MethodCall call) {
    final WriteBuffer buffer = WriteBuffer();
    messageCodec.writeValue(buffer, call.method);
    messageCodec.writeValue(buffer, call.arguments);
    return buffer.done();
  }
  //略...
}

---->[StandardMessageCodec#writeValue]----
void writeValue(WriteBuffer buffer, dynamic value) {
  if (value == null) {
    buffer.putUint8(_valueNull);
  } else if (value is bool) {
    buffer.putUint8(value ? _valueTrue : _valueFalse);
  } else if (value is int) {
    if (-0x7fffffff - 1 <= value && value <= 0x7fffffff) {
      buffer.putUint8(_valueInt32);
      buffer.putInt32(value);
    } else {
      buffer.putUint8(_valueInt64);
      buffer.putInt64(value);
    }
  //略...
  } else if (value is List) {
    buffer.putUint8(_valueList);
    writeSize(buffer, value.length);
    for (final dynamic item in value) {
      writeValue(buffer, item);
    }
  } else if (value is Map) {
    buffer.putUint8(_valueMap);
    writeSize(buffer, value.length);
    value.forEach((dynamic key, dynamic value) {
      writeValue(buffer, key);
      writeValue(buffer, value);
    });
  } else {
    throw ArgumentError.value(value);
  }
} 

3.5:BinaryMessages发送信息

BinaryMessenger是一个抽象接口,默认使用的实现了是defaultBinaryMessenger

_sendPlatformMessage方法进行对平台发送信息

const BinaryMessenger defaultBinaryMessenger = _DefaultBinaryMessenger._();

---->[BinaryMessenger]----
abstract class BinaryMessenger {
  const BinaryMessenger();
  Future<void> handlePlatformMessage(String channel, ByteData data, ui.PlatformMessageResponseCallback callback);
  Future<ByteData> send(String channel, ByteData message);
  void setMessageHandler(String channel, Future<ByteData> handler(ByteData message));
  void setMockMessageHandler(String channel, Future<ByteData> handler(ByteData message));
}

---->[_DefaultBinaryMessenger]----
@override
Future<ByteData> send(String channel, ByteData message) {
  final MessageHandler handler = _mockHandlers[channel];
  if (handler != null)
    return handler(message);
  return _sendPlatformMessage(channel, message);
} 
  • _sendPlatformMessage

这里使用Window对象进行信息发送,最终调用的是Window_sendPlatformMessage的native方法

final Window window = Window._();

Future<ByteData> _sendPlatformMessage(String channel, ByteData message) {
  final Completer<ByteData> completer = Completer<ByteData>();
  ui.window.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;
}

---->[Window#sendPlatformMessage]----
void sendPlatformMessage(String name,
                         ByteData data,
                         PlatformMessageResponseCallback callback) {
  final String error =
      _sendPlatformMessage(name, _zonedPlatformMessageResponseCallback(callback), data);
  if (error != null)
    throw Exception(error);
}
String _sendPlatformMessage(String name,
                            PlatformMessageResponseCallback callback,
                            ByteData data) native 'Window_sendPlatformMessage'; 

4.Android视角看MethodChannel

在Android中MethodChannel是一个Java类,处于io.flutter.plugin.common

主要的成员变量也是三位messenger,name和codec,在构造方法中需要传入BinaryMessenger
默认的MethodCodec是StandardMethodCodec.INSTANCE

public final class MethodChannel {
    private static final String TAG = "MethodChannel#";
    private final BinaryMessenger messenger;
    private final String name;
    private final MethodCodec codec;

    public MethodChannel(BinaryMessenger messenger, String name) {
        this(messenger, name, StandardMethodCodec.INSTANCE);
    } 

4.1:设置方法监听处理器

监听器是设置在了messenger的身上,如果监听器非空会使用IncomingMethodCallHandler

messenger需要的监听器的类型是BinaryMessenger.BinaryMessageHandler,所以关系如下

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9RriGmrV-1629539325560)(https://user-gold-cdn.xitu.io/2019/8/12/16c850e6e032bfe5?imageView2/0/w/1280/h/960/ignore-error/1)]

public void setMethodCallHandler(@Nullable MethodChannel.MethodCallHandler handler) {
    this.messenger.setMessageHandler(this.name,
    handler == null ? 
    null : new MethodChannel.IncomingMethodCallHandler(handler));
}

---->[BinaryMessenger]----
public interface BinaryMessenger {
    @UiThread
    void setMessageHandler(@NonNull String var1,
    @Nullable BinaryMessenger.BinaryMessageHandler var2); 

4.2:IncomingMethodCallHandler与回调参数的生成

IncomingMethodCallHandler实现了BinaryMessageHandler接口,必然实现其接口方法

onMessage中需要回调了ByteBuffer的方法字节信息以及BinaryReply对象
回调中的MethodCall对象是通过codec将字节信息解码生成的
MethodChannel.Result是一个接口,有三个接口方法,这里直接new对象并实现三个方法
通过codec编码success传入的对象,后通过reply对象的reply将返回值传给Flutter端

private final class IncomingMethodCallHandler implements BinaryMessageHandler {
    private final MethodChannel.MethodCallHandler handler;
    IncomingMethodCallHandler(MethodChannel.MethodCallHandler handler) {
        this.handler = handler;
    }
    @UiThread
    public void onMessage(ByteBuffer message, final BinaryReply reply) {
        MethodCall call = MethodChannel.this.codec.decodeMethodCall(message);
        try {
            this.handler.onMethodCall(call, new MethodChannel.Result() {
                public void success(Object result) {
                    reply.reply(MethodChannel.this.codec.encodeSuccessEnvelope(result));
                }
                public void error(String errorCode, String errorMessage, Object errorDetails) {
                    reply.reply(MethodChannel.this.codec.encodeErrorEnvelope(errorCode, errorMessage, errorDetails));
                }
                public void notImplemented() {
                    reply.reply((ByteBuffer)null);
                }
            });
        } catch (RuntimeException var5) {
            Log.e("MethodChannel#" + MethodChannel.this.name, "Failed to handle method call", var5);
            reply.reply(MethodChannel.this.codec.encodeErrorEnvelope("error", var5.getMessage(), (Object)null));
        }
    } 

5:信息发送追踪

到这里一切矛头指向BinaryMessenger,它是一个接口,定义了发生信息的三个方法。

和信息发送相关的类有四个:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wncLZrVj-1629539325561)(https://user-gold-cdn.xitu.io/2019/8/12/16c853e7cbfffcbb?imageView2/0/w/1280/h/960/ignore-error/1)]

public interface BinaryMessenger {
    @UiThread
    void send(@NonNull String var1, @Nullable ByteBuffer var2);

    @UiThread
    void send(@NonNull String var1, @Nullable ByteBuffer var2, @Nullable BinaryMessenger.BinaryReply var3);

    @UiThread
    void setMessageHandler(@NonNull String var1, @Nullable BinaryMessenger.BinaryMessageHandler var2);

    public interface BinaryReply {
        @UiThread
        void reply(@Nullable ByteBuffer var1);
    }

    public interface BinaryMessageHandler {
        @UiThread
        void onMessage(@Nullable ByteBuffer var1, @NonNull BinaryMessenger.BinaryReply var2);
    }
} 

5.1:FlutterView

我们在创建MethodChannel的时候传入的是getFlutterView()

追踪一下可以看到返回的是一个FlutterView,这也就说明FlutterView实现了BinaryMessenger
所以可以从实现的方法入手,最终发现是调用mNativeView的方法,其为FlutterNativeView类型

MethodChannel channel = new MethodChannel(getFlutterView(), CHANNEL);//获取渠道

---->[FlutterActivity]----
public FlutterView getFlutterView() {
    return this.viewProvider.getFlutterView();
}

---->[FlutterView]----
public interface Provider {
    FlutterView getFlutterView();
}

---->[FlutterView]----
@UiThread
public void send(String channel, ByteBuffer message) {
    this.send(channel, message, (BinaryReply)null);
}

@UiThread
public void send(String channel, ByteBuffer message, BinaryReply callback) {
    if (!this.isAttached()) {
        Log.d("FlutterView", "FlutterView.send called on a detached view, channel=" + channel);
    } else {
        this.mNativeView.send(channel, message, callback);
    }
} 

5.2:FlutterNativeView与DartExecutor

FlutterNativeView调用dartExecutor的方法,其为DartExecutor类型

在构造方法中创建了FlutterJNI对象来创建DartExecutor,
DartExecutor中通过DartMessenger对象messenger发送,这些DartMessenger跑不掉了

public class FlutterNativeView implements BinaryMessenger {
 
    private final DartExecutor dartExecutor;
    private final FlutterJNI mFlutterJNI;
    
    public FlutterNativeView(@NonNull Context context) {
        this(context, false);
    }
    public FlutterNativeView(@NonNull Context context, boolean isBackgroundView) {
        this.mContext = context;
        this.mPluginRegistry = new FlutterPluginRegistry(this, context);
        this.mFlutterJNI = new FlutterJNI();
        this.mFlutterJNI.setRenderSurface(new FlutterNativeView.RenderSurfaceImpl());
        this.dartExecutor = new DartExecutor(this.mFlutterJNI);

---->[FlutterNativeView]----
@UiThread
public void send(String channel, ByteBuffer message) {
    this.dartExecutor.send(channel, message);
}
@UiThread
public void send(String channel, ByteBuffer message, BinaryReply callback) {
    if (!this.isAttached()) {
        Log.d("FlutterNativeView", "FlutterView.send called on a detached view, channel=" + channel);
    } else {
        this.dartExecutor.send(channel, message, callback);
    }
}

---->[DartExecutor]----
@UiThread
public void send(@NonNull String channel, @Nullable ByteBuffer message) {
    this.messenger.send(channel, message, (BinaryReply)null);
}
@UiThread
public void send(@NonNull String channel, @Nullable ByteBuffer message, @Nullable BinaryReply callback) {
    this.messenger.send(channel, message, callback);
} 

5.3:DartMessenger

DartMessenger通过flutterJNI.dispatchPlatformMessage发送信息

最终到nativeDispatchPlatformMessage一个native方法,
然后那些C++里见不得人的勾当这里就不说了,有机会再细细道来。

@UiThread
public void send(@NonNull String channel, @NonNull ByteBuffer message) {
    Log.v("DartMessenger", "Sending message over channel '" + channel + "'");
    this.send(channel, message, (BinaryReply)null);
}
public void send(@NonNull String channel, @Nullable ByteBuffer message, @Nullable BinaryReply callback) {
    Log.v("DartMessenger", "Sending message with callback over channel '" + channel + "'");
    int replyId = 0;
    if (callback != null) {
        replyId = this.nextReplyId++;
        this.pendingReplies.put(replyId, callback);
    }
    if (message == null) {
        this.flutterJNI.dispatchEmptyPlatformMessage(channel, replyId);
    } else {
        this.flutterJNI.dispatchPlatformMessage(channel, message, message.position(), replyId);
    }
}

@UiThread
public void dispatchPlatformMessage(@NonNull String channel, @Nullable ByteBuffer message, int position, int responseId) {
    this.ensureRunningOnMainThread();
    if (this.isAttached()) {
        this.nativeDispatchPlatformMessage(this.nativePlatformViewId, channel, message, position, responseId);
    } else {
        Log.w("FlutterJNI", "Tried to send a platform message to Flutter, but FlutterJNI was detached from native C++. Could not send. Channel: " + channel + ". Response ID: " + responseId);
    }
} 

源码贴的有点多,整个关系看起来也不是非常复杂。虽然没啥大用,逻辑捋一捋对Flutter的整体认知也有所提升。



结语

本文到此接近尾声了,如果想快速尝鲜Flutter,《Flutter七日》会是你的必备佳品;如果想细细探究它,那就跟随我的脚步,完成一次Flutter之旅。
另外本人有一个Flutter微信交流群,欢迎小伙伴加入,共同探讨Flutter的问题,本人微信号:zdl1994328,期待与你的交流与切磋。

最后

按照国际惯例,给大家分享一套十分好用的Android进阶资料:《全网最全Android开发笔记》。

整个笔记一共8大模块、729个知识点,3382页,66万字,可以说覆盖了当下Android开发最前沿的技术点,和阿里、腾讯、字节等等大厂面试看重的技术。

图片

图片

因为所包含的内容足够多,所以,这份笔记不仅仅可以用来当学习资料,还可以当工具书用。

如果你需要了解某个知识点,不管是Shift+F 搜索,还是按目录进行检索,都能用最快的速度找到你要的内容。

相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照整个知识体系编排的。

(一)架构师必备Java基础

1、深入理解Java泛型

2、注解深入浅出

3、并发编程

4、数据传输与序列化

5、Java虚拟机原理

6、高效IO

……

图片

(二)设计思想解读开源框架

1、热修复设计

2、插件化框架设计

3、组件化框架设计

4、图片加载框架

5、网络访问框架设计

6、RXJava响应式编程框架设计

……

图片

(三)360°全方位性能优化

1、设计思想与代码质量优化

2、程序性能优化

  • 启动速度与执行效率优化
  • 布局检测与优化
  • 内存优化
  • 耗电优化
  • 网络传输与数据储存优化
  • APK大小优化

3、开发效率优化

  • 分布式版本控制系统Git
  • 自动化构建系统Gradle

……

图片

(四)Android框架体系架构

1、高级UI晋升

2、Android内核组件

3、大型项目必备IPC

4、数据持久与序列化

5、Framework内核解析

……

图片

(五)NDK模块开发

1、NDK开发之C/C++入门

2、JNI模块开发

3、Linux编程

4、底层图片处理

5、音视频开发

6、机器学习

……

图片

(六)Flutter学习进阶

1、Flutter跨平台开发概述

2、Windows中Flutter开发环境搭建

3、编写你的第一个Flutter APP

4、Flutter Dart语言系统入门

……

图片

(七)微信小程序开发

1、小程序概述及入门

2、小程序UI开发

3、API操作

4、购物商场项目实战

……

图片

(八)kotlin从入门到精通

1、准备开始

2、基础

3、类和对象

4、函数和lambda表达式

5、其他

……

图片

好啦,这份资料就给大家介绍到这了,有需要详细文档的小伙伴,可以微信扫下方二维码领取哈~

img

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章           查看所有文章
加:2021-08-23 16:48:38  更:2021-08-23 16:50:52 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 9:34:08-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码