目录
1.概述
在CTK Plugin Framework中,插件间的通信主要通过EventAdmin服务来完成。EventAdmin是一种基于发布/订阅的通信方式,一个插件可以订阅某一主题的事件,而另一个插件则可以发布与该主题相关的事件,从而实现通信。
CTK框架中的事件监听,其实就是观察者模式,流程大概如下:
1)接收者注册监听事件(接收方想监听xxx信息)
2)发送者发送事件(发送方发送xxx信息)
3)接收者接收到事件并响应(接收方收到xxx事件后的动作)
相比调用插件接口,监听事件插件间依赖关系更弱,不用指定事件的接收方和发送方是谁。
2.主要接口和方法
1.通信主要用到了ctkEventAdmin结构体,主要定义了如下接口:
postEvent
:以异步方式发送事件。sendEvent
:以同步方式发送事件。publishSignal
:通过信号与槽的方式发送事件。unpublishSignal
:取消发送事件。subscribeSlot
:通过信号与槽的方式订阅事件,并返回订阅的ID。unsubscribeSlot
:取消订阅事件。updateProperties
:更新某个订阅ID的主题。
2.通信是数据:ctkDictionary
CTK插件间通信的数据主要通过ctkDictionary
传递,它实际上是一个hash表(在Qt中通常使用QHash<QString, QVariant>
实现),用于存储事件的属性。
3.通信方式
1. 类通信
类通信的原理是直接将信息使用CTK的eventAdmin
接口send
或post
出去。发送插件需要创建事件对象,并设置事件的属性(如主题、数据等),然后通过EventAdmin
服务发送事件。接收插件则通过订阅相应主题的事件来接收信息,并在接收到事件时执行相应的处理函数。
2. 信号与槽通信
除了类通信外,CTK还支持通过Qt的信号与槽机制进行插件间通信。发送插件可以发射一个信号,该信号携带需要传递的信息;接收插件则连接该信号到一个槽函数上,当信号被发射时,槽函数会被调用,从而实现信息的传递和处理。
4.实现步骤
以发布博客为例,实现CTK插件间通信的大致步骤如下:
- 编译EventAdmin库:确保
eventadmin.dll
(或其他平台对应的库文件)已经编译成功。 - 创建发送插件:
- 新建一个插件项目,编写发送类(如
BlogManager
),在该类中实现发布事件的方法(如publishBlog
)。 - 在发送类的构造函数中,通过
ctkPluginContext
获取EventAdmin
服务的引用,并保存为成员变量。 - 在发布事件的方法中,创建
ctkEvent
对象,设置事件的属性和数据,然后通过EventAdmin
服务的sendEvent
或postEvent
方法发送事件。
- 新建一个插件项目,编写发送类(如
- 创建接收插件:
- 新建一个插件项目,编写接收类(如
BlogEventHandler
),实现ctkEventHandler
接口中的handleEvent
方法。 - 在接收类的激活类中,通过
ctkPluginContext
注册服务,并订阅相应主题的事件。 - 在
handleEvent
方法中编写处理事件的逻辑。
- 新建一个插件项目,编写接收类(如
- 启用插件:
- 在主程序中加载并启动发送插件和接收插件。
- 发送插件发布事件后,接收插件将接收到该事件,并执行相应的处理逻辑。
5.实现示例
5.1.类通信
代码结构:
插件结构说明:
cn.qtio.publisher:发送【发布者】
cn.qtio.subscriber:接收【订阅类】
发送【发布者】
publisheractivator.h
#ifndef TESTPLUGINACTIVATOR_H
#define TESTPLUGINACTIVATOR_H
#include <QObject>
#include <ctkPluginActivator.h>
class PublisherActivator : public QObject, public ctkPluginActivator
{
Q_OBJECT
Q_INTERFACES(ctkPluginActivator)
Q_PLUGIN_METADATA(IID "Publisher")
public:
void start(ctkPluginContext* context) Q_DECL_OVERRIDE;
void stop(ctkPluginContext* context) Q_DECL_OVERRIDE;
};
#endif // TESTPLUGINACTIVATOR_H
publisheractivator.cpp
#include "publisheractivator.h"
#include <QDebug>
#include <QThread>
#include <service/event/ctkEventAdmin.h>
void PublisherActivator::start(ctkPluginContext *context)
{
ctkServiceReference ref = context->getServiceReference<ctkEventAdmin>();
ctkEventAdmin *eventAdmin = qobject_cast<ctkEventAdmin*>(context->getService(ref));
ctkProperties props;
props.insert("type", "消息类型");
ctkEvent event("cn/qtio/eventAdmin/subscriber/handleEvent", props);
if(eventAdmin){
//sendEvent:同步通信
eventAdmin->sendEvent(event);
}
}
void PublisherActivator::stop(ctkPluginContext *context)
{
Q_UNUSED(context)
}
#if (QT_VERSION < QT_VERSION_CHECK(5,0,0))
Q_EXPORT_PLUGIN2(Publisher, PublisherActivator)
#endif
接收【订阅类】
subscriberactivator.h
#ifndef SUBSCRIBERACTIVATOR_H
#define SUBSCRIBERACTIVATOR_H
#include <QObject>
#include <ctkPluginActivator.h>
#include <service/event/ctkEventAdmin.h>
#include <service/event/ctkEventHandler.h>
class SubscriberActivator :
public QObject,
public ctkPluginActivator,
public ctkEventHandler
{
Q_OBJECT
Q_INTERFACES(ctkPluginActivator)
Q_INTERFACES(ctkEventHandler)
Q_PLUGIN_METADATA(IID "Subscriber")
public:
void start(ctkPluginContext* context) Q_DECL_OVERRIDE;
void stop(ctkPluginContext* context) Q_DECL_OVERRIDE;
private:
void handleEvent(const ctkEvent &event) Q_DECL_OVERRIDE;
private:
ctkEventAdmin *m_eventAdmin;
};
#endif // SUBSCRIBERACTIVATOR_H
subscriberactivator.cpp
#include "subscriberactivator.h"
#include <QDebug>
#include <service/event/ctkEventConstants.h>
void SubscriberActivator::start(ctkPluginContext* context)
{
//事件管理服务
ctkServiceReference eventAdminRef = context->getServiceReference<ctkEventAdmin>();
m_eventAdmin = qobject_cast<ctkEventAdmin*>(context->getService(eventAdminRef));
if (!m_eventAdmin) {
qDebug() << "事件管理服务获取失败!";
}
//消息订阅
ctkDictionary props;
props.insert(ctkEventConstants::EVENT_TOPIC, "cn/qtio/eventAdmin/subscriber/handleEvent");
context->registerService<ctkEventHandler>(this, props);
}
void SubscriberActivator::stop(ctkPluginContext* context)
{
Q_UNUSED(context)
}
void SubscriberActivator::handleEvent(const ctkEvent& event)
{
//查看消息包所有信息
foreach(QString propertyName, event.getPropertyNames()) {
qDebug() << "SubscriberActivator key:"
<< propertyName
<< "value:"
<< event.getProperty(propertyName);
}
}
#if (QT_VERSION < QT_VERSION_CHECK(5,0,0))
Q_EXPORT_PLUGIN2(Subscriber, SubscriberActivator)
#endif
5.2.信号槽通信
信号槽通信和Qt的信号槽绑定类似,就不在这里赘述了。
6.类通信和信号槽通信的区别
1) 通过event事件通信,是直接调用CTK的接口,把数据发送到CTK框架;通过信号槽方式,会先在Qt的信号槽机制中转一次,再发送到CTK框架。故效率上来讲,event方式性能高于信号槽方式。
2) 两种方式发送数据到CTK框架,这个数据包含:主题+属性。主题就是topic,属性就是ctkDictionary。 一定要注意signal方式的信号定义,参数不能是自定义的,一定要是ctkDictionary,不然会报信号槽参数异常错误。
3) 两种方式可以混用,如发送event事件,再通过槽去接收;发送signal事件,再通过event是接收。
4) 同步:sendEvent、Qt::DirectConnection;异步:postEvent、Qt::QueuedConnection
这里的同步是指:发送事件之后,订阅了这个主题的数据便会处理数据【handleEvent、slot】,处理的过程是在发送者的线程完成的。可以理解为在发送了某个事件之后,会立即执行所有订阅此事件的回调函数。
异步:发送事件之后,发送者便会返回不管,订阅了此事件的所有插件会根据自己的消息循环,轮到了处理事件后才会去处理。不过如果长时间没处理,CTK也有自己的超时机制。如果事件处理程序花费的时间比配置的超时时间长,那么就会被列入黑名单。一旦处理程序被列入黑名单,它就不会再被发送任何事件。
5) handleEvent里是线程池里运行的,可以通过打印线程id测试出。
7.注意事项
- 在进行插件间通信时,需要确保所有相关的库文件都已经正确编译并链接到项目中。
- 订阅事件时,可以指定事件过滤器来过滤不需要处理的事件。
- 发送事件时,需要确保事件的主题和数据已经正确设置,以便接收插件能够正确解析和处理。