用 SWIG 生成 Java 代码(IM SDK)

原创 一乐 美信拓扑 2020-05-26 18:23

美信拓扑技术分享系列 0x02:Floo-android 开源和高级 API 介绍

代码开源在 Github,你可以参照生成其他语言版本的 IM SDK。阅读本文你可以了解:

  1. SWIG,一个联结 C/C++ 软件与其它各种高级编程语言的开发工具;
  2. 一个在生产环境实际使用的 SWIG 案例;
  3. 一个跨平台云服务 IM SDK的典型设计;
  4. 即时通讯 IM 业务 与 API 设计;

关注「美信拓扑」微信公众号,第一时间阅读本系列后续文章,了解美信拓扑IM的协议、架构和源码。

作者 | 一乐

前几天在直播里介绍过的内部创新项目,代码正式开源了[1],喜欢玩的同学可以去 Github 看看(star)啦。

为了更好的让大家理解项目,本文会介绍 SWIG 和本工程代码,同时也会介绍项目背景,也即美信拓扑跨平台 IM SDK 的设计与实践。

SWIG 是什么

Photo by Marco Djallo on @unsplash
Photo by Marco Djallo on @unsplash

SWIG 全称是 Simplified Wrapper and Interface Generator [2]。这个名字相当直白,翻译过来就是简化的包装和接口生成器。官方介绍是一个联结 C/C++ 软件与其它各种高级编程语言的开发工具。其实主要做了一件事,就是把 C/C++ 写的软件库封装成其他高级编程语言可以调用的本地库。

现在它支持的高级编程语言不仅包括 Javascript,Perl,PHP,Python,Tcl 以及 Ruby 这样的脚本语言,也支持非脚本语言如 C#,D,Golang,Java(包括Android),Lua,OCaml,Octave,Scilab 以及 R 语言。

这正是美信拓扑的 IM SDK 需要的,我们底层通讯层使用 C++ 实现,然后再在上层进行本地化封装(为什么这么做,后面讲)。

开源这个库的目的,一方面是为了介绍 SWIG 框架,因为自动生成代码,虽然大多数人并不陌生,RPC 框架如 Thrift 可以自动生成客户端服务器代码,序列化库如 protobuf 可以自动生成序列化反序列化的类,进行面向对象编程的可以借助 UML 生成类框架,数据库工具可以自动生成建库 SQL 脚本等等。但到了相对复杂的业务系统,用的就少多了,毕竟手写出来的最踏实(即使会 Bug 很多)。

另一方面,我们用 SWIG 生成了供安卓使用的本地 Java 库,但是难免遇到有开发者需要其他语言的时候,因此我们将工程模板也开源出来,如果有朋友想玩的话,可以直接改造 SWIG 脚本生成需要的其他语言库。

如果你做了,请第一时间告诉我们,不管是任何时候。有神秘礼物等着你哦 :)

看代码前,我们先介绍下美信拓扑这个跨平台的 IM SDK。

跨平台 IM SDK 架构

如前所述,美信拓扑IM SDK(研发代号 Floo )底层使用 C++ 实现,各平台(Android、iOS、Linux等)在此基础上再行封装, 完成本地库的开发,以达到多平台复用的目的,并保持跨平台协议实现的一致性。如下图所示:

左边蓝色是本地库,包括Android、iOS、Linux等,右边黄色是 Web,包括浏览器、小程序、H5等。所有库的分层基本一致,从下往上依次是

  1. 系统层,包括操作系统和系统库;
  2. 协议层,实现了 XSync 协议,美信拓扑自定义的基于同步的IM协议;
  3. 转接层,将 C++ 库转接适配成各平台可以调用的原生应用库,本次开源的 floo-android 即属于此层;
  4. UI层,提取 UI Kit 等需要;
  5. 应用层,实现 App 业务功能;

这样分层的好处在于协议之需要实现一次,即减少了工作量,又减少了潜在的Bug。在一个优秀的 IM 系统实现里,可靠高效的协议设计,与灵活柔性的客户端 SDK和高性能的服务端,是同等重要的。

有朋友可能好奇 Floo 是什么意思?研发代号 Floo 来源于哈利波特里的飞路粉[3],根据介绍它是

一种亮晶晶的银色粉末,在男女巫师用飞路网旅行时使用。飞路网与绝大多数巫师家庭和魔法建筑相连接。

使用时人们将一把粉末撒入已连接飞路网的壁炉,走进火焰,并口齿清晰、大声地说出目的地,则能确保顺利到达目的地。

https://harrypotter.fandom.com/zh/wiki/飞路粉

这也是我们服务端叫 Fireplace 的原因,如果你前几天跟我们一起玩过树莓派[4][5],在安装后的服务里会发现它。

如何掌握 IM API

Photo by @JamieDotSt on @unsplash
Photo by @JamieDotSt on @unsplash

在我们说起一个 IM SDK 的时候,经常有一种声音出现,他们说,IM嘛,不就一个聊天,能发消息就行呗。

大多时候我只是笑笑,心情好的时候还会说你说的对。

但如果是好朋友,还会告诉他们,一个典型的 IM SDK,会有 40 余类几百个方法[6],因为要同时兼顾分层设计和灵活性,保证协议实现的质量,又要支持事件通知和自定义协议等确保业务逻辑的完整。

我会告诉他们,在移动网络这种长延迟不稳定链路下,要保证消息的有序投递,又要保证软实时且可靠,XMPP 协议有太多的限制,你需要全新设计和优化的 IM 协议。这也是现在真正高质量 IM 的基础。

在这些的基础上,才是跨平台实现要考虑的问题,独立的协议解析层,插件式的底层系统库和上层应用库实现。底层系统库容易理解,就是网络调用、文件读写以及数据库操作;上层应用库主要是 HTTP 库,各平台并不一样,因此协议库的 HTTP 实现,需要通过与各端上层协调来实现。

当然这些是设计实现一个好的云服务 IM SDK 才需考虑,如果只是使用,自然可以找简单的方式。

从业务上看,IM 有三条主线,消息,用户和关系。

消息的核心是聊天,这包括单聊与群聊,所有聊天都会有发送与接收,发送涉及同步异步以及失败重试,接收涉及回调监听以及未读数计算。内容上,除了文本之外,还会支持图片、语音、附件等富媒体,这些会进一步转化为与存储系统的交互,上传和下载。

用户方面,首先是个人身份,用户 ID 设计,登陆鉴权;然后是信息,头像、昵称以及第三方账号身份打通。

关系有三种,好友、群组和黑名单。好友需要考虑的是好友列表、单向/双向关系以及陌生人的消息控制;群组则是进群、退群、成员管理,以及群组设置;然后在此之上,有好友黑名单,群组黑名单。

沿着这三条主线,各种功能在相关类中依次展开,或作为方法,或作为变量,或作为组合(先调用后监听)分布在类库的各个角落。

掌握了这三条主线,再多的类也不怕啦。

也因此,介绍类库时,我们首先会提四个 Service 类,他们是 BMXUserService、BMXChatService、BMXRosterService 和 BMXGroupService。

不过在完整介绍类库之前,我们还有最后一个问题要说明,那就是同步和异步。

同步 vs 异步

如前所述,用 SWIG 生成的 Java 代码,通过 JNI 方式调用底层 C++ 类库,因此大部分接口均为同步,这便是 Floo-android 低级 API 的主体。 代码生成和转换的过程中,相关数据结构得以直接映射到底层类库,减少了内存拷贝,因此其性能接近于直接调用底层库。

同时,又考虑到开发者集成方便,我们也基于此类重新封装了高级 API,使用了更为友好的数据结构,并完成了异步机制封装。

也因此,整个类库(floo-android)分为三部分:

                                                 +---> BMXUserService
                                                 |
                              +---------------+  +---> BMXChatService
                              |               |  |
                          +---+ 低级 API: L/S  +------> BMXRosterService
                          |   |               |  |
                          |   +---------------+  +---> BMXGroupService
                          |
                          |                         +---> BMXUserManager
                          |   +---------------+     |
+----------------------+  |   |               |     +---> BMXChatManager
|                      |  +---+ 高级 API: H/A +-----+
| 美信拓扑 IM SDK: Floo +--+   |               |     +---> BMXRosterManager
|                      |  |   +---------------+     |
+----------------------+  |                         +---> BMXGroupManager
                          |
                          |                       +--->  BMXClient
                          |   +----------------+  |
                          |   |                |  +--->  BMXSDKConfig
                          +---+ Utility:工具类 +--+
                              |                |  +--->  BMXMessage
                              +----------------+  |
                                                  +--->  BMXConversation
                                                  |
                                                  +--->  BMXUserProfile
                                                  |
                                                  +--->  BMXGroup
                                                  |
                                                  +--->  BMXDevice
  1. 低级 API (low-level)

同步调用接口,类名以Service结尾,为方便理解,下文说明中用 L/S 表示,其中 L 表示 Low-level, S 表示 Sync。

主要有 BMXUserService、BMXChatService、BMXRosterService、BMXGroupService。

  1. 高级 API (high-level)

异步调用接口,类名以Manager结尾,为方便理解,下文用 H/A 表示,其中 H 表示 High-level, A 表示 Async。

主要有 BMXUserManager、BMXChatManager、BMXRosterManager、BMXGroupManager。

简单来讲,相关调用会在子线程执行具体操作(例如:搜索好友),当前线程会直接返回而不阻塞。具体操作的结果则通过回调函数通知调用方,后者可以在其中处理 UI 刷新等业务逻辑。

  1. 工具类 Utility

包括客户端实例化类 BMXClient 、配置类 BMXSDKConfig、消息类 BMXMessage、会话类 BMXConversation、用户身份类 BMXUserProfile、群类 BMXGroup、设备类 BMXDevice等。

我们这里不再赘述,如果需要参照可以阅读美信拓扑快速集成指南安卓版[6],或者查看详细类库文档[7],当然后者也是此次开源仓库自动生成的。

最后,让我们看看代码吧。

TIPS:如果你已阅读到这里,可以给自己点个赞啦。

代码

整个工程代码结构比较简单,IM API 相关的头文件和so文件已经放在工程里,而运行命令也只有关键的一行:

/usr/local/bin/swig -debug-classes -debug-module 4 -debug-typemap -c++ -java -package im.floo.floolib -outdir src/main/java/im/floo/floolib/ -o src/main/cpp/floo_wrap.cxx -Ifloo/include -Ifloo/src swig/floo.i

指定输出 Java 代码的包名是 im.floo.floolib,指定 floo 头文件的地址,剩下的就是 SWIG 定义文件了,他们都放在 ./swig/floo.i [7] 里,下面是 49-126 行:

%include "std_shared_ptr.i"
%include "std_vector.i"
%include "std_string.i"
%shared_ptr(floo::BMXMessageConfig)
%shared_ptr(floo::BMXMessage)
%template(BMXMessageList) std::vector<std::shared_ptr<floo::BMXMessage>>;

typedef floo::BMXConversation::Type BMXConversationType;

%shared_ptr(floo::BMXConversation)
%template(BMXConversationList) std::vector<std::shared_ptr<floo::BMXConversation>>;

%shared_ptr(floo::BMXRosterItem)
%template(BMXRosterItemList) std::vector<std::shared_ptr<floo::BMXRosterItem>>;
%shared_ptr(floo::BMXDevice)
%template(BMXDeviceList) std::vector<std::shared_ptr<floo::BMXDevice>>;

%shared_ptr(floo::BMXImageAttachment)
%shared_ptr(floo::BMXLocationAttachment)
%shared_ptr(floo::BMXMessageAttachment)
%shared_ptr(floo::BMXNetworkListener)
%shared_ptr(floo::BMXClient)
%shared_ptr(floo::BMXBaseObject)
%shared_ptr(floo::BMXSDKConfig)
%shared_ptr(floo::BMXFileAttachment)
%shared_ptr(floo::BMXGroup)
%shared_ptr(floo::BMXGroup::Member)
%shared_ptr(floo::BMXGroup::BannedMember)
%shared_ptr(floo::BMXGroup::Announcement)
%shared_ptr(floo::BMXGroup::SharedFile)
%shared_ptr(floo::BMXRosterService::Application)
%shared_ptr(floo::BMXGroup::Application)
%shared_ptr(floo::BMXGroup::Invitation)
%shared_ptr(floo::BMXUserProfile)
%shared_ptr(floo::UserProfileImpl)
%shared_ptr(floo::BMXVoiceAttachment)
%shared_ptr(floo::BMXVideoAttachment)
%shared_ptr(floo::BMXResultPage<floo::BMXMessage>)

%template(BMXGroupList) std::vector<std::shared_ptr<floo::BMXGroup>>;
%template(BMXGroupMemberList) std::vector<std::shared_ptr<floo::BMXGroup::Member>>;
%template(BMXGroupBannedMemberList) std::vector<std::shared_ptr<floo::BMXGroup::BannedMember>>;
%template(BMXGroupSharedFileList) std::vector<std::shared_ptr<floo::BMXGroup::SharedFile>>;
%template(BMXGroupAnnouncementList) std::vector<std::shared_ptr<floo::BMXGroup::Announcement>>;
%template(BMXRosterServiceApplicationList) std::vector<std::shared_ptr<floo::BMXRosterService::Application>>;
%template(BMXGroupApplicationList) std::vector<std::shared_ptr<floo::BMXGroup::Application>>;
%template(BMXGroupInvitationList) std::vector<std::shared_ptr<floo::BMXGroup::Invitation>>;

%template(ListOfLongLong) std::vector<long long>;

%include "bmx_error.h"
%include "bmx_defines.h"
%include "bmx_device.h"
%include "bmx_base_object.h"
%include "bmx_message_attachment.h"
%include "bmx_message_config.h"
%include "bmx_message.h"
%include "bmx_conversation.h"
%include "bmx_sdk_config.h"
%include "bmx_network_listener.h"
%include "bmx_chat_service.h"
%include "bmx_chat_service_listener.h"
%include "bmx_client.h"
%include "bmx_file_attachment.h"
%exception floo::BMXFileAttachment::dynamic_cast(floo::BMXMessageAttachment *attachment) {
  $action
    if (!result) {
      jclass excep = jenv->FindClass("java/lang/ClassCastException");
      if (excep) {
        jenv->ThrowNew(excep, "dynamic_cast exception");
      }
    }
}
%extend floo::BMXFileAttachment {
  static floo::BMXFileAttachment *dynamic_cast(floo::BMXMessageAttachment *attachment) {
    return dynamic_cast<floo::BMXFileAttachment *>(attachment);
  }
};

详细代码可以去仓库里查看,这里只提几点,希望对你后续使用有所帮助:

  1. 标准库有专门头文件,如果你用了它们,也需要首先关联它们的定义文件
%include "std_shared_ptr.i"
  1. 继承可以用 % entend 关键字
%extend floo::BMXFileAttachment {
  static floo::BMXFileAttachment *dynamic_cast(floo::BMXMessageAttachment *attachment) {
    return dynamic_cast<floo::BMXFileAttachment *>(attachment);
  }
};
  1. 最后一点,也是最重要的一点是:顺序很重要,顺序很重要,顺序很重要!

因为代码生成的过程是单次遍历,所以在生成当前代码的时候,如果用到的类没有被定义,就会重新级联生成一个新的辅助类,这样你会得到很多命名超长的类,很难看很难用。

总的来说,虽然要学习一个新的框架,还要定义一些配置,但问题都搞定后,还真挺香的,哈哈。

今天的分享就到这里,欢迎试玩,也欢迎继续关注「美信拓扑」微信公众号,第一时间阅读本系列后续文章,了解美信拓扑IM的协议、架构和源码。

美信拓扑是一家很酷的技术公司。美信拓扑 IM 是第一个多云架构的即时通讯云服务,具有超强的伸缩能力,可以支撑从亿级用户千万并发的公有云服务,到一台主机十分钟安装完成的私有云。目前各种组件正在陆续开源。

引用

  1. 美信拓扑IM SDK 安卓版(Floo-Android)Github地址
  2. SWIG:简化的包装和接口生成器
  3. 飞路粉(floo powder)
  4. 十分钟安装一套即时通讯 IM 私有云
  5. 树莓派中的 IM 私有云支持多少并发?
  6. Floo-android reference 类库文档
  7. Floo-android Github 仓库里的 floo.i
  8. 十分钟安装的即时通讯私有云 美信拓扑

特别提示

美信拓扑海外可用区「印度区」正在内测,如果有印度出海的公司或业务可以官网直接申请。

美信拓扑已经是阿里云和AWS的技术合作伙伴,借助于先进的多云架构,美信拓扑IM的「专有云服务」可以在你业务需要的任何地区进行部署实施,为您和您的用户提供更好的聊天体验。

海外专有云定价与国内专有云定价方式一致,差价仅来源于服务资源差异,且可以与客户共管阿里云或AWS账号。

© 2019-2022 美信拓扑 | 官网 该文件修订时间: 2022-09-21 11:43:07

results matching ""

    No results matching ""