Flutter Plugin调用Native APIs

简介:

01 开篇.png
作者:闲鱼技术-储睿
关键词:Flutter, Flutter Plugin, Platform Channel, Method Channel, Flutter Package, Flutter插件

Flutter是Google使用Dart语言开发的一套移动应用开发框架。它不同于其他开发框架:
(1)因为Flutter使用AOT预编译代码为机器码,所以它的运行效率更高。
(2)Flutter的UI控件并没有使用底层的原生控件,而是使用Skia渲染引擎绘制而成,因为不依赖底层控件,所以多端一致性非常好。
(3)Flutter的扩展性也非常强,开发者可以通过Plugin与Native进行通信。

闲鱼开发Flutter过程中,经常会需要各种Native的能力,如获取设备信息、使用基础网络库等,这时会使用Plugin来做桥接。本文将对Plugin进行详细的介绍,希望能给Flutter开发者一些帮助。


摘要:

本文首先对Flutter Plugin以及原理进行了介绍,然后对Plugin所依赖的Platform Channel进行了讲解,随后对“获取剩余电量Plugin”进行了分解,最后给大家分享一下之前踩过的坑。

1. Flutter Plugin

在介绍Plugin前,我们先简单了解一下Flutter:

02 框架.png

Flutter框架包括:Framework和Engine,他们运行在各自的Platform上。

Framework是Dart语言开发的,包括Material Design风格的Widgets和Cupertino(iOS-style)风格的Widgets,以及文本、图片、按钮等基础Widgets;还包括渲染、动画、绘制、手势等基础能力。

Engine是C++实现的,包括Skia(二维图形库);Dart VM(Dart Runtime);Text(文本渲染)等。

实际上,Flutter的上层能力都是Engine提供的。Flutter正是通过Engine将各个Platform的差异化抹平。而我们今天要讲的Plugin,正是通过Engine提供的Platform Channel实现的通信。

2. Platform Channel

2.1 Flutter App调用Native APIs:

03 Channel.png

通过上图,我们看到Flutter App是通过Plugin创建的Platform Channel调用的Native APIs。


2.2 Platform Channel 架构图:

04 Channel.png

Platform Channel:

  • Flutter App (Client),通过MethodChannel类向Platform发送调用消息;
  • Android Platform (Host),通过MethodChannel类接收调用消息;
  • iOS Platform (Host),通过FlutterMethodChannel类接收调用消息。

PS:消息编解码器,是JSON格式的二进制序列化,所以调用方法的参数类型必须是可JSON序列化的。

PS:方法调用,也可以反向发送调用消息。

Android Platform

FlutterActivity,是Android的Plugin管理器,它记录了所有的Plugin,并将Plugin绑定到FlutterView。

iOS Platform

FlutterAppDelegate,是iOS的Plugin管理器,它记录了所有的Plugin,并将Plugin绑定到FlutterViewController(默认是rootViewController)。

3. 获取剩余电量Plugin:

05 Plugin.png

3.1 创建Plugin

首先,我们创建一个Plugin(flutter_plugin_batterylevel)项目。Plugin也是项目,只是Project type不同。
(1)IntelliJ欢迎界面点击 Create New Project 或者 点击 File>New>Project…;
(2)在左侧菜单选择 Flutter, 然后点击 Next;
(3)输入 Project name 和 Project location,Project type 选择 "Plugin"
(4)最后点击 Finish。

06 Project.png

Project type:
(1)Application,Flutter应用;
(2)Plugin,暴漏Android和iOS的API给Flutter应用;
(3)Package,封装一个Dart组件,如“浏览大图Widget”。

PS:Plugin有Dart、Android、iOS,3部分代码组成。

3.2 Plugin Flutter部分

3.2.1 MethodChannel:Flutter App调用Native APIs

  /**
   * (1)MethodChannel:Flutter App调用Native APIs
   */
  static const MethodChannel _methodChannel = const MethodChannel('samples.flutter.io/battery');
  
  // 
  Future<String> getBatteryLevel() async {
    String batteryLevel;
    try {
      final int result = await _methodChannel.invokeMethod('getBatteryLevel',{'paramName':'paramVale'});
      batteryLevel = 'Battery level: $result%.';
    } catch(e) {
      batteryLevel = 'Failed to get battery level.';
    }
    return batteryLevel;
  }

首先,我们实例_methodChannel(Channel名称必须唯一),然后调用invokeMethod()方法。invokeMethod()有2个参数:
(1)方法名,不能为空;
(2)调用方法的参数,该参数必须可JSON序列化,可以为空。

3.2.2 EventChannel:Native调用Flutter App

  /**
   * (2)EventChannel:Native调用Flutter App
   */
  static const EventChannel _eventChannel = const EventChannel('samples.flutter.io/charging');

  void listenNativeEvent() {
    _eventChannel.receiveBroadcastStream().listen(_onEvent, onError:_onError);
  }

  void _onEvent(Object event) {
    print("Battery status: ${event == 'charging' ? '' : 'dis'}charging.");
  }

  void _onError(Object error) {
    print('Battery status: unknown.');
  }

3.3 Plugin Android部分

3.3.1 Plugin 注册

import android.os.Bundle;
import io.flutter.app.FlutterActivity;
import io.flutter.plugins.GeneratedPluginRegistrant;

public class MainActivity extends FlutterActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        GeneratedPluginRegistrant.registerWith(this);
    }
}

在FlutterActivity的onCreate()方法中,注册Plugin。

    /**
     * Plugin 注册.
     */
    public static void registerWith(Registrar registrar) {
        /**
         * Channel名称:必须与Flutter App的Channel名称一致
         */
        private static final String METHOD_CHANNEL = "samples.flutter.io/battery";
        private static final String EVENT_CHANNEL = "samples.flutter.io/charging";

        // 实例Plugin,并绑定到Channel上
        FlutterPluginBatteryLevel plugin = new FlutterPluginBatteryLevel();

        final MethodChannel methodChannel = new MethodChannel(registrar.messenger(), METHOD_CHANNEL);
        methodChannel.setMethodCallHandler(plugin);

        final EventChannel eventChannel = new EventChannel(registrar.messenger(), EVENT_CHANNEL);
        eventChannel.setStreamHandler(plugin);
    }

(1)Channel名称:必须与Flutter App的Channel名称一致;
(2)MethodChannel和EventChannel初始化的时候都需要传递Registrar,即FlutterActivity;
(3)设置MethodChannel的Handler,即MethodCallHandler;
(4)设置EventChannel的Handler,即EventChannel.StreamHandler;

3.3.2 MethodCallHandler & EventChannel.StreamHandler

MethodCallHandler实现MethodChannel的Flutter App调用Native APIs;
EventChannel.StreamHandler实现EventChannel的Native调用Flutter App。

public class FlutterPluginBatteryLevel implements MethodCallHandler,EventChannel.StreamHandler {

    /**
     * MethodCallHandler
     */
    @Override
    public void onMethodCall(MethodCall call, Result result) {
        if (call.method.equals("getBatteryLevel")) {
            Random random = new Random();
            result.success(random.nextInt(100));
        } else {
            result.notImplemented();
        }
    }

    /**
     * EventChannel.StreamHandler
     */
    @Override
    public void onListen(Object obj, EventChannel.EventSink eventSink) {
        BroadcastReceiver chargingStateChangeReceiver = createChargingStateChangeReceiver(events);
    }

    @Override
    public void onCancel(Object obj) {
    }

    private BroadcastReceiver createChargingStateChangeReceiver(final EventSink events) {
        return new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1);

                if (status == BatteryManager.BATTERY_STATUS_UNKNOWN) {
                    events.error("UNAVAILABLE", "Charging status unavailable", null);
                } else {
                    boolean isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING ||
                            status == BatteryManager.BATTERY_STATUS_FULL;
                    events.success(isCharging ? "charging" : "discharging");
                }
            }
        };
    }
}

MethodCallHandler:
(1)public void onMethodCall(MethodCall call, Result result);

EventChannel.StreamHandler:
(1)public void onListen(Object obj, EventChannel.EventSink eventSink);
(2)public void onCancel(Object obj);

3.4 Plugin iOS部分

3.4.1 Plugin 注册

/**
 * Channel名称:必须与Flutter App的Channel名称一致
 */
#define METHOD_CHANNEL "samples.flutter.io/battery";
#define EVENT_CHANNEL "samples.flutter.io/charging";

@implementation AppDelegate

- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
    /**
      * 注册Plugin
      */
    [GeneratedPluginRegistrant registerWithRegistry:self];
    
    /**
      * FlutterViewController
      */
    FlutterViewController* controller = (FlutterViewController*)self.window.rootViewController;

    /**
      * FlutterMethodChannel & Handler
      */
    FlutterMethodChannel* batteryChannel = [FlutterMethodChannel methodChannelWithName:METHOD_CHANNEL binaryMessenger:controller];
    [batteryChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
        if ([@"getBatteryLevel" isEqualToString:call.method]) {
            int batteryLevel = [self getBatteryLevel];
            result(@(batteryLevel));
        } else {
            result(FlutterMethodNotImplemented);
        }
    }];
    
    /**
      * FlutterEventChannel & Handler
      */
    FlutterEventChannel* chargingChannel = [FlutterEventChannel eventChannelWithName:EVENT_CHANNEL binaryMessenger:controller];
    [chargingChannel setStreamHandler:self];
    
    return [super application:application didFinishLaunchingWithOptions:launchOptions];
}

@end

iOS的Plugin注册流程跟Android一致。只是需要注册到AppDelegate(FlutterAppDelegate)。

FlutterMethodChannel和FlutterEventChannel被绑定到FlutterViewController。

3.4.2 FlutterStreamHandler:

@interface AppDelegate () <FlutterStreamHandler>

@property (nonatomic, copy)   FlutterEventSink     eventSink;

@end

- (FlutterError*)onListenWithArguments:(id)arguments eventSink:(FlutterEventSink)eventSink {
    self.eventSink = eventSink;

    // 监听电池状态
    [[NSNotificationCenter defaultCenter] addObserver:self
                                           selector:@selector(onBatteryStateDidChange:)
                                               name:UIDeviceBatteryStateDidChangeNotification
                                             object:nil];
    return nil;
}

- (FlutterError*)onCancelWithArguments:(id)arguments {
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    self.eventSink = nil;
    return nil;
}

- (void)onBatteryStateDidChange:(NSNotification*)notification {
    if (self.eventSink == nil) return;
    UIDeviceBatteryState state = [[UIDevice currentDevice] batteryState];
    switch (state) {
            case UIDeviceBatteryStateFull:
            case UIDeviceBatteryStateCharging:
            self.eventSink(@"charging");
            break;
            case UIDeviceBatteryStateUnplugged:
            self.eventSink(@"discharging");
            break;
        default:
            self.eventSink([FlutterError errorWithCode:@"UNAVAILABLE"
                                           message:@"Charging status unavailable"
                                           details:nil]);
            break;
    }
}

4. 加载Plugin

现在我们已经有了Plugin,但是如何把它加载到Flutter App项目中呢?

It's Pub. Pub是Dart语言提供的Packages管理工具。

说到Package,它有2种类型:
(1) Dart Packages:只包含Dart代码,如“浏览大图Widget”。
(2) Plugin Packages:包含的Dart代码能够调用Android和iOS实现的Native APIs,如“获取剩余电量Plugin”。

4.1 将一个Package添加到Flutter App中

(1)通过编辑pubspec.yaml(在App根目录下)来管理依赖;
(2)运行flutter packages get,或者在IntelliJ里点击Packages Get
(3)import package,重新运行App。

管理依赖有3种方式:Hosted packages、Git packages、Path packages。

4.2 Hosted packages(来自pub.dartlang.org)

如果你希望自己的Pulgin给更多的人使用,你可以把它发布到 pub.dartlang.org。

发布Hosted packages:

$flutter packages pub publish --dry-run
$flutter packages pub publish

加载Hosted packages:

编辑pubspec.yaml:

dependencies:
 url_launcher: ^3.0.0

4.3 Git packages(远端)

如果你的代码不经常改动,或者不希望别人修改这部分代码,你可以用Git来管理你的代码。
我们先创建​一个Plugin(flutter_remote_package),并将它传到Git上,然后打个tag。

// cd 到 flutter_remote_package  
flutter_remote_package $:git init
flutter_remote_package $:git remote add origin git@gitlab.alibaba-inc.com:churui/flutter_remote_package.git
flutter_remote_package $:git add .
flutter_remote_package $:git commit
flutter_remote_package $:git commit -m"init"
flutter_remote_package $:git push -u origin master
flutter_remote_package $:git tag 0.0.1

加载Git packages:

编辑pubspec.yaml:

dependencies:
 flutter_remote_package:
  git:
   url: git@gitlab.alibaba-inc.com:churui/flutter_remote_package.git
   ref: 0.0.1

PS:ref可以指定某个commit、branch、或者tag。

4.4 Path packages(本地)

PS:如果你的代码没有特殊的场景需要, 可以直接把Package放到本地,这样开发和调试都很方便。

我们在Flutter App项目根目录下(flutter_app),创建文件夹(plugins),然后把插件(flutter_plugin_batterylevel)移动到plugins下。

07 目录结构.png

加载Path packages:

编辑pubspec.yaml:

dependencies:
  flutter_plugin_batterylevel:
    path: plugins/flutter_plugin_batterylevel

5. 踩过的坑

5.1 用XCode编辑Plugin

我们已经在pubspec.yaml里添加了依赖,但是打开iOS工程,却看不到Plugin?

这时需要执行pod install (或pod update)。

5.2 iOS编译没问题,但是运行时找不到Plugin

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  // Plugin注册方法
  [GeneratedPluginRegistrant registerWithRegistry:self]; 
  
  // 显示Window
  self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    [self.window setRootViewController:[[FlutterViewController alloc] initWithNibName:nil bundle:nil]]];
  [self.window setBackgroundColor:[UIColor whiteColor]];
  [self.window makeKeyAndVisible];  
    
  return [super application:application didFinishLaunchingWithOptions:launchOptions];
}

@end

[GeneratedPluginRegistrant registerWithRegistry:self]默认注册到self.window.rootViewController的。

所以需要先初始化rootViewController,再注册Plugin。

5.3 Native调用Flutter失败

Flutter App启动后,Native调用Flutter失败?

这是因为Plugin Channel的初始化大概要1.5秒,而且这是一个异步过程。虽然Flutter页面显示出来了,但是Plugin Channel还没初始化完,所以这时Native调用Flutter是没反应的。

5.4 iOS Plugin注册到指定的FlutterViewController

闲鱼首页是Native页面,所以Window的rootViewController不是FlutterViewController,直接注册Plugin会注册失败。我们需要将Plugin注册到指定的FlutterViewController。

FlutterAppDelegate.h

08 FlutterAppDelegate.png

- (NSObject<FlutterBinaryMessenger>*)binaryMessenger;
- (NSObject<FlutterTextureRegistry>*)textures;

我们需要在AppDelegate重写上面两个方法,方法内返回需要指定的FlutterViewController。

延展讨论

Flutter作为应用层的UI框架,底层能力还是依赖Native的,所以Flutter App调用Native APIs的应用场景还是挺多的。

在Plugin方法调用过程中,可能会遇到传递复杂参数的情况(有时需要传递对象),但是Plugin的参数是JSON序列化后的二进制数据,所以传参必须是可JSON序列化的。我觉得,应该有一层对象映射层,来支持传递对象。

说到Plugin传参,Plugin有个很牛逼的能力,就是传递textures(纹理)。闲鱼的Flutter视频播放,实际上是用的Native播放器,然后将textures(纹理)传递给Flutter App。

因为后面会有Flutter视频播放的专题文章,这里就不做延展了。
**简历投递:guicai.gxy@alibaba-inc.com
**

参考资料

https://www.dartlang.org/tools/pub/
https://pub.dartlang.org
https://flutter.io/platform-channels/

相关文章
|
4月前
|
开发框架 Dart 前端开发
【Flutter前端技术开发专栏】Flutter与React Native的对比与选择
【4月更文挑战第30天】对比 Flutter(Dart,强类型,Google支持,快速热重载,高性能渲染)与 React Native(JavaScript,庞大生态,热重载,依赖原生渲染),文章讨论了开发语言、生态系统、性能、开发体验、学习曲线、社区支持及项目选择因素。两者各有优势,选择取决于项目需求、团队技能和长期维护考虑。参考文献包括官方文档和性能比较文章。
196 0
【Flutter前端技术开发专栏】Flutter与React Native的对比与选择
|
4月前
|
前端开发 JavaScript Android开发
跨端技术栈综合考察:深入剖析 UniApp、Flutter、Taro 和 React Native 的优势与限制
跨端技术栈综合考察:深入剖析 UniApp、Flutter、Taro 和 React Native 的优势与限制
|
3月前
|
前端开发 自动驾驶 程序员
鸿蒙? 车载?Flutter? React Native? 为什么我劝你三思,说点不一样的
本文探讨了在信息技术快速发展的背景下,开发者如何选择学习路径。作者提倡使用终局思维来规划职业发展,考虑技术的长远影响。终局思维注重长远目标、系统分析、反向规划和动态调整。以车载开发为例,预测未来智能汽车可能由语音助手主导,而非依赖平板界面。此外,作者建议不要过分投入打工状态,应思考创建自己的产品,如App,以实现技能补充和额外收入。选择对未来发展和自主性有益的技术,如Kotlin,比盲目追求热点更为重要。做减法和有标准的选择,能帮助减轻焦虑,实现更高效的成长。关注公众号“AntDream”获取更多相关内容。
90 1
|
3月前
|
开发框架 前端开发 JavaScript
移动应用开发中的跨平台策略:Flutter与React Native的比较
在移动应用领域,跨平台解决方案已成为开发者追求高效、成本效益和广泛覆盖的关键。本文深入探讨了两种领先的跨平台框架——Flutter和React Native,从技术架构、性能、社区生态及实际应用案例四个维度进行全面对比分析。通过这一比较,旨在为移动应用开发者提供选择合适框架的参考依据,帮助他们根据项目需求做出明智的决策。
|
2月前
|
安全 Shell Android开发
Flutter和Native 通信 pigeon
Flutter和Native 通信 pigeon
|
2月前
|
Dart Android开发 Windows
Flutter和Native 通信 android端
Flutter和Native 通信 android端
|
3月前
|
Dart 前端开发 JavaScript
探索移动应用开发中的跨平台解决方案:Flutter与React Native的比较
在移动应用开发领域,选择合适的跨平台解决方案是关键。本文将深入分析Flutter和React Native这两大主流框架,从性能、开发效率、社区支持等方面进行比较,帮助开发者做出明智的选择。
51 0
|
4月前
|
开发框架 前端开发 JavaScript
【专栏】对比分析两种流行的跨平台开发框架——Flutter和React Native,探讨它们的优势、劣势以及适用场景
【4月更文挑战第27天】本文对比分析了Flutter和React Native两大跨平台移动开发框架。Flutter,由Google推出,以其接近原生的性能、快速启动和流畅滚动受青睐,适合高性能和高度定制的项目。React Native,Facebook维护,依赖JavaScript,虽性能受限,但热重载优势和丰富第三方库使其适合快速迭代的项目。两者都在拓展多平台应用,Flutter在桌面和Web,React Native在Windows。选择框架需考虑项目需求、团队技能和性能效率平衡。
321 1
|
4月前
|
Dart 前端开发 JavaScript
《跨平台移动应用开发探索:Flutter vs React Native》
在移动应用开发领域,跨平台技术日益成熟,Flutter和React Native作为两大主流框架备受关注。本文将对比Flutter和React Native在性能、开发体验、生态系统等方面的优劣,并探讨它们在不同场景下的适用性,以帮助开发者选择最适合自己项目的技术方案。
|
开发框架 Dart 前端开发
探索 React Native 和 Flutter
移动应用开发无需重复编写代码?创造令人惊叹的用户体验,同时适用于 iOS 和 Android 平台?这听起来像是不敢想象的未来科技,但事实上,React Native 和 Flutter 已经为我们实现了这些梦想。这两个备受瞩目的跨平台开发框架正改变着移动应用开发的游戏规则,提供了一种高效、便捷的开发方式。在这篇令人兴奋的博文中,我们将探索 React Native 和 Flutter 的技术细节,揭示它们的工作原理和独特之处。无论您是一位开发者还是一个对新技术充满好奇心的读者,本文将为您带来不可错过的前沿知识和启示。准备好迎接移动开发的新时代了吗?让我们一起踏上这段奇妙的旅程吧!
155 1