likes
comments
collection
share

flutter 通信代码自动生成

作者站长头像
站长
· 阅读数 11

spi_flutter_package

还在用信鸽生成通信代码?快来试试这个吧,一个自动生成跨平台通信框架的工具,懒人必备

背景

该工具主要是为了解决 flutter 在跨平台通信时遇到的一些问题, 例如:

  1. 找不到 channel name
  2. 对应的方法未实现
  3. 性能监控(coming soon)

MethodChannel 官方为例, flutter 调用到原生平台的时候, channelName 和对应的方法名, 参数, 返回值, 都是直接硬编码的, 非常容易出错, 经常会出现找不到对应的 channel 或方法. 而且通信的协议多了, 就很难维护, 两个平台之间谁也不知道对应的方法实现了没有. 为了尽量减少这种情况的发生, 我们采用了 SPI(Service Provider Interface) 模式, 集中管理所有的通信协议, 统一标准, 让协议的定义和实现 解耦, 解决 platform channel 使用过程中的各种问题.

快速集成

  1. 首先请根据安装指引, 将当前 package 下载到项目中, 这里推荐直接加载 dev_dependencies 中, 因为它仅仅是一个工具, 无需与 APP 自身的源码一起导出发布:
dev_dependencies:
  spi_flutter_package:
    path: version

下载完成之后, 在 flutter 工程中, 新建一个目录 lib/channel , 这里用来存储通信协议的源码:

|____main.dart
|____channel //通信协议源目录
| |____flutter2native //flutter 调用原生平台源码
| | |____account.dart //自己自定义的通信协议
| |____native2flutter //原生平台调用 flutter 源码
| | |____flutter_fps.dart //自己自定义的通信协议

account.dart (文件名可自定义)文件中, 定义 flutter 调用原生平台的通信协议:

abstract class IAccount{
  Future<bool> login(String userName,String password);
  void logout();
  Future<String> getName();
  Future<int> getAge();
}

更多例子请看 example

  1. 在 flutter 工程的 test 目录下, 任意一个 dart 文件中写下:
import 'package:spi_flutter_package/spi_flutter_package.dart';
import 'package:spi_flutter_package/file_config.dart';

void main() async {
  await spiFlutterPackageStart([
    FlutterPlatformConfig()
      ..sourceCodePath = "./lib/channel" //flutter source code
      ..channelName = "com.siyehua.spiexample.channel" //channel name
    ,
    AndroidPlatformConfig()
      ..savePath = "./android/app/src/main/kotlin" //android save path
    ,
    IosPlatformConfig()
      ..iosProjectPrefix = "MQQFlutterGen_" //iOS pre
      ..savePath = "./ios/Classes" //iOS save path
    ,
  ], nullSafe: true);
}

点击左边的运行图标 ▶️, 运行当前的 main() 方法, 很快, 对应的目录下就会自动生成代码, 详情请看自动生成代码的存放路径和说明 代码生成完毕后, 在对应的平台实现实现该通信协议, 以 Android 为例:

public class AccountImpl implements IAccount {
    //....

    @Override
    public void logout() {
        Log.e("android", "logout method, nothing should call back");
    }

    @Override
    public void getName(ChannelManager.Result<String> callback) {
        Log.e("android", "getName method");

        callback.success("siyehua");
    }

    @Override
    public void getAge(ChannelManager.Result<Long> callback) {
        Log.e("android", "getAge method");
        callback.success(1L);
    }
}
  1. 实现完成之后, 就可以使用通信框架进行调用了 在 Android 端:
//1. 初始化(仅需一次)

```java
ChannelManager.init(flutterEngine.getDartExecutor(), new ChannelManager.JsonParse() {
    @Nullable
    @Override
    public String toJSONString(@Nullable Object object) {
        //your json parse
        return JSON.toJSONString(object);
    }

    @Nullable
    @Override
    public <T> T parseObject(@Nullable String text, @NonNull Class<T> clazz) {
        //your json parse
        return JSON.parseObject(text, clazz);
    }
});

//2. 将上面实现的类加入到 ChannelManager 中, 以便 flutter 能够调用到 ChannelManager.addChannelImpl(IAccount.class, new AccountImpl());


<br>在 flutter 端:

```dart
//1. 初始化(仅需一次)
  ChannelManager.init();

//2. 调用,即可看到打印的结果
  IAccount account = ChannelManager.getChannel(IAccount);
  var result = await account.login("userName", "password");
  print(result);
  account.logout();
  var name = await account.getName();
  print(name);
  var age = await account.getAge();
  print(age);

更多详情请看 example , 运行整个项目后, 即可看到调用结果

自动生成代码

在 flutter 工程中, 自定生成的代码, 会自动存放在之前定义的 flutterPath /generated 目录下:

|____main.dart
|____channel
| |____generated //自动生成的代码
| | |____channel
| | | |____impl
| | | | |____iaccount_impl.dart
| | | |____parse
| | | | |____object_parse.dart
| | | |____ChannelManager.dart
| |____flutter2native //flutter 调用原生平台源码
| | |____account.dart
| |____native2flutter //原生平台调用 flutter 源码
| | |____flutter_fps.dart

对比可以发现, 在自定义目录下, 多了一个自动生成的 generated, 该目录就是 package 自动生成的代码. 同样, 在 Android 项目中, 你之前填的 androidSavePath + packageName 目录下, 自定生成了对应的代码:

.
|____com
| |____siyehua
| | |____spiexample
| | | |____MainActivity2.java
| | | |____AccountImpl.java
| | | |____MainActivity.kt
| | | |____channel //整个目录都是自动生成的文件
| | | | |____flutter2native
| | | | | |____IAccount.java
| | | | |____native2flutter
| | | | | |____Fps2.java
| | | | | |____FpsImpl.java
| | | | | |____Fps2Impl.java
| | | | | |____Fps.java
| | | | |____ChannelManager.java

注意: 自动生成的代码不建议修改, 每次工具执行的时候, 都会覆盖掉修改

实现原理

spi_flutter_package 的实现原理比较简单,通过 IO 读取 dart 文件,解析 dart 文件的结构,然后通过反射,正则匹配等,获取当前 dart 文件包含的类,以及类的结构,然后存起来。接着根据解析好的类结构,根据 Java或 Object-C 语义,生成对应的 java 或 Object-C 代码。整个过程都采用 dart 语言完成,借助了 dart 的文件 IO,以及反射等 API 完成。

spi_flutter_package 没有重新定义通信,也没有提搞通信的效率。它只是把一些需要手动写的模板代码改成了自动生成,是一个懒人必备神器。

项目地址

pub.dev/packages/sp…

对比信鸽 pigeon

pub.flutter-io.cn/packages/pi…

spi_flutter_package 只所以出现,并不是为了重复造轮子,主要是为了解决 信鸽的一些痛点,两者的实现原理基本相同,都是通过先解析 dart 文件结构,然后根据根据解析后的文件结构,来描述Android 和 iOS 语言。

但是信鸽之前的版本不支持 List/Map,最新发的版本才开始支持, 其次是信鸽不支持复杂对象,也不支持异步 reply。 在设计层次上,信鸽和 spi_flutter_package 也不同, 信鸽是每个接口/协议都会生成一个通信渠道,每个渠道自行维护。 spi_flutter_package 如文章开头所说,采用的 SPI 集中管理模式,是将所有的接口集中统一管理,目前正在计划加入统一性能监控。

功能对比

通信基本类型List/Map空安全自定义复杂类型Object异步 Reply
信鸽支持部分支持(仅空安全版本)支持不支持不支持不支持
spi支持可任意版本支持支持支持(真实类型必须是基本类型)支持

混淆配置

注意:如果你开启了混淆,请在 proguard-rules.pro 文件中添加:

# in example, the package name is "com.siyehua.spiexample.channel"
-keep class {your android package name}.** {*;}

注意事项

支持的类型和属性, 详见: platforms_source_gen

所有的 dart 通信协议都必须返回 Future 或 void

因为 Flutter 的 Channel 本身返回必须是 Future ,异步的

后续计划

后续版本更新,将加入枚举支持,注释支持,以及性能监控,错误管理

其他

性能监控可以参考: github.com/siyehua/flu…