文章目录
事件管理机制
基于sendEvent()/postEvent()
简述
事件管理是OSGi概要规范的一部分,实现了插件通信机制。该通信机制采用发布/订阅模式,并且可以使用同步或异步的方式执行。
发布/订阅通信的主要组件有:
- 事件发布者(
Event Publisher
):发送与某个特定主题相关的事件或消息 - 事件处理程序(
Event Handler
或Subscriber
):表达对一个或多个主题的兴趣,并接收该主题的所有消息。
事件由两个属性组成:
- 主题(
topic
):用于定义事件性质,主题名通常被放在一个层次化的命名空间中,其中斜杠用于分隔级别(例如:`org/commontk/PluginFrameworkEvent/TEST)。 - 属性(
properties
):描述事件的一组属性
事件管理
org.commontk.eventadmin
中的 CommonTK Event Admin
是基于 Apache Felix
项目中的 Event Admin
规范来实现的,并提供了进程内通信。它展示了与 Apache Felix
实现相似的一组特性:
- 尽可能快地交付事件
- 从不同线程发送的事件是并行发送的
- 来自同一线程的事件按照收到的顺序发送(根据规范)
- 可以配置一个用于事件处理程序的超时。如果事件处理程序花费的时间比配置的超时时间长,那么就会被列入黑名单。一旦处理程序被列入黑名单,它就不会再发送任何事件。
编译事件管理插件
使用事件管理机制需要 Event Admin
。但默认情况下,在编译CTK时,并不会生成相关插件,需要自行配置。
打开 CTK-master/CMakeLists.txt
,将 eventadmin
的相关配置项设置为 ON。
注意: 这里设置的是 plugin_list,所以除了编译 eventadmin
之外,同时还要configadmin
、log
、metatype
相关插件。
重新编译CTK,在 CTK-build/bin
目录中便可以找到相关插件。
使用事件管理
在 CTK 中,Event Admin
提供了两种方式来进行事件管理:
sendEvent()/postEvent() + ctkEventHandler
:sendEvent()
以同步方式发送,而postEvent()
则以异步方式发送。signal/slot
:同步或异步发送,取决于发射信号时使用的Qt:ConnectionType
。
下面,来演示 sendEvent()/postEvent() + ctkEventHandler
方式:
工程文件
创建一个 Qt 控制台应用程序,.pro
内容如下:
QT += core
QT -= gui
TARGET = SendEvent
CONFIG += console
TEMPLATE = app
LIBS += -L$$PWD/Libs -lCTKCore -lCTKPluginFramework
INCLUDEPATH += \
$$PWD/../../../CTK-master/Libs/Core \
$$PWD/../../../CTK-master/Libs/PluginFramework
HEADERS += \
publisher.h \
subscriber.h
SOURCES += \
main.cpp \
publisher.cpp \
subscriber.cpp
事件发布者
事件发布者可以是一个简单的 C++ 类,用于创建事件并使用 ctkEventAdmin
服务接口来发送。
publisher.h
内容如下:
#ifndef PUBLISHER_H
#define PUBLISHER_H
#include <ctkPluginContext.h>
// 事件发布者
class Publisher {
public:
Publisher(ctkPluginContext* context);
// 发布事件
void publish();
private:
ctkPluginContext* pc;
};
#endif // PUBLISHER_H
这里,以 ctkEventAdmin::sendEvent()
为例,来发送 ctkEvent
对象。
publisher.cpp
内容如下:
#include "publisher.h"
#include <service/event/ctkEventAdmin.h>
Publisher::Publisher(ctkPluginContext* context)
: pc(context)
{
}
// 发布事件
void Publisher::publish()
{
ctkServiceReference ref = pc->getServiceReference<ctkEventAdmin>();
if (ref) {
ctkEventAdmin* eventAdmin = pc->getService<ctkEventAdmin>(ref);
ctkDictionary props;
props.insert("name", "Waleon");
props.insert("age", 18);
ctkEvent event("org/commontk/login", props);
eventAdmin->sendEvent(event);
}
}
同步事件交付比异步交付要昂贵得多。即使对于同步交付,事件通知也可以在一个单独的线程中处理(取决于 EventAdmin
的实现)。这意味着 sendEvent()
的调用者在调用此方法时,一般不应该持有任何锁。异步交付应优于同步交付。
事件处理程序
通过实现 ctkEventHandler
接口,来创建一个事件处理程序。
subscriber.h
内容如下:
#ifndef SUBSCRIBER_H
#define SUBSCRIBER_H
#include <QObject>
#include <service/event/ctkEventHandler.h>
#include <ctkPluginContext.h>
// 事件处理程序(或订阅者)
class Subscriber : public QObject, public ctkEventHandler
{
Q_OBJECT
Q_INTERFACES(ctkEventHandler)
public:
Subscriber(ctkPluginContext* context);
// 将事件处理程序注册为服务
void registerService();
// 处理事件
void handleEvent(const ctkEvent& event) Q_DECL_OVERRIDE;
private:
ctkPluginContext* pc;
};
#endif // SUBSCRIBER_H
要接收事件通知,事件处理程序必须被注册为服务。在注册服务时,必须指定一个名为 EVENT_TOPIC
的 QString
或 QStringList
属性,该属性描述了事件处理程序感兴趣的主题列表。
subscriber.cpp
内容如下:
#include "subscriber.h"
#include <QDebug>
#include <service/event/ctkEventConstants.h>
Subscriber::Subscriber(ctkPluginContext* context)
: pc(context)
{
}
// 将事件处理程序注册为服务
void Subscriber::registerService()
{
ctkDictionary props;
props.insert(ctkEventConstants::EVENT_TOPIC, "org/commontk/login");
pc->registerService<ctkEventHandler>(this, props);
}
// 处理事件
void Subscriber::handleEvent(const ctkEvent& event)
{
QString name = event.getProperty("name").toString();
int age = event.getProperty("age").toInt();
qDebug() << QString("name:%1 age:%2").arg(name).arg(age);
}
主函数
有了发布者和订阅者,来看看 main.cpp
:
#include <QCoreApplication>
#include <ctkPluginFrameworkLauncher.h>
#include <ctkPluginContext.h>
#include "publisher.h"
#include "subscriber.h"
// liborg_commontk_eventadmin.dll 所在路径
const QString c_strSearchPath = "E:/CTK-Examples/EventAdmin/SendEvent/Plugins";
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
// 在插件的搜索路径列表中添加一条路径
ctkPluginFrameworkLauncher::addSearchPath(c_strSearchPath);
// 设置并启动 CTK 插件框架
ctkPluginFrameworkLauncher::start("org.commontk.eventadmin");
// 获取插件上下文
ctkPluginContext* pluginContext = ctkPluginFrameworkLauncher::getPluginContext();
// 注册服务,用于接收事件通知
Subscriber sub(pluginContext);
sub.registerService();
// 发布事件
Publisher pub(pluginContext);
pub.publish();
// 停止插件
ctkPluginFrameworkLauncher::stop();
return app.exec();
}
这里,我们使用了一个很方便的类 - ctkPluginFrameworkLauncher
,用来启动 CTK 插件框架,并安装和启动插件。但在 start()
之前,需要调用 addSearchPath()
将 liborg_commontk_eventadmin.dll
所在路径添加至插件的搜索路径列表中,这样以来,就能加载该插件,以使用 CTK 事件管理机制。
在插件启动成功之后,可以使用 ctkPluginFrameworkLauncher::getPluginContext()
获取插件上下文。
然后,订阅者注册服务 -> 发布者发布事件,一旦事件被发布,订阅者便可以通过 handleEvent()
来对事件进行相应的处理。
最后,当使用完成,通过 ctkPluginFrameworkLauncher::stop()
来停止插件。
基于signal/slot
简述
在 CTK 中,插件之间的通信,可以通过 Event Admin
来完成。Event Admin
是一种基于发布/订阅的方式,一个插件订阅某一主题之后,另一个插件发布一个与该主题相关的事件,从而达到通信的目的。
除了sendEvent()/postEvent() + ctkEventHandler
方式之外,Event Admin
还提供了另一种方式 - signal/slot
,可以达到相同的效果。
使用事件管理机制
下面,来演示 signal/slot
方式
工程文件
创建一个 Qt 控制台应用程序,.pro
内容如下:
QT += core
QT -= gui
TARGET = PublishSignal
CONFIG += console
TEMPLATE = app
LIBS += -L$$PWD/Libs -lCTKCore -lCTKPluginFramework
INCLUDEPATH += \
$$PWD/../../../CTK-master/Libs/Core \
$$PWD/../../../CTK-master/Libs/PluginFramework
HEADERS += \
publisher.h \
subscriber.h
SOURCES += \
main.cpp \
publisher.cpp \
subscriber.cpp
需要同时包含 CTKCore
和 CTKPluginFramework
。
事件发布者
使用 Qt 信号来发布事件需要声明一个 signal
,并使用 Event Admin
注册(发布)它。
publisher.h
内容如下:
#ifndef PUBLISHER_H
#define PUBLISHER_H
#include <QObject>
#include <ctkPluginContext.h>
#include <service/event/ctkEventAdmin.h>
// 事件发布者
class Publisher : public QObject {
Q_OBJECT
public:
Publisher(ctkPluginContext* context);
// 使用一个特定的主题来注册信号
void publishSignal();
// 发布事件
void publish();
signals:
void finished(const ctkDictionary&);
private:
ctkPluginContext* pc;
};
#endif // PUBLISHER_H
使用一个特定的主题来注册信号(发射信号将始终发送 ctkEvent
对象,该对象将此主题作为 EVENT_TOPIC
属性)。
publisher.cpp
内容如下:
#include "publisher.h"
Publisher::Publisher(ctkPluginContext *context)
: pc(context)
{
}
// 使用一个特定的主题来注册信号
void Publisher::publishSignal()
{
ctkServiceReference ref = pc->getServiceReference<ctkEventAdmin>();
if (ref) {
ctkEventAdmin* eventAdmin = pc->getService<ctkEventAdmin>(ref);
eventAdmin->publishSignal(this, SIGNAL(finished(ctkDictionary)), "org/commontk/login", Qt::DirectConnection);
}
}
// 发布事件
void Publisher::publish()
{
ctkDictionary props;
props.insert("name", "Waleon");
props.insert("age", 18);
emit finished(props);
}
发射信号将自动创建一个 ctkEvent
对象,同步或异步发送,取决于发射信号时使用的 Qt:ConnectionType
。
事件处理程序
以 ctkEvent
对象作为参数的每个 Qt 槽,都可以被订阅来接收事件通知。
subscriber.h
内容如下:
#ifndef SUBSCRIBER_H
#define SUBSCRIBER_H
#include <QObject>
#include <ctkPluginContext.h>
#include <service/event/ctkEvent.h>
// 事件处理程序(或订阅者)
class Subscriber : public QObject
{
Q_OBJECT
public:
Subscriber(ctkPluginContext* context);
// 可以被订阅,来接收事件通知
void subscribeSlot();
private slots:
// 处理事件
void onFinished(const ctkEvent& event);
private:
ctkPluginContext* pc;
};
#endif // SUBSCRIBER_H
subscriber.cpp
内容如下:
#include "subscriber.h"
#include <QDebug>
#include <service/event/ctkEventConstants.h>
#include <service/event/ctkEventAdmin.h>
Subscriber::Subscriber(ctkPluginContext* context)
: QObject(),
pc(context)
{
}
// 可以被订阅,来接收事件通知
void Subscriber::subscribeSlot()
{
ctkDictionary props;
props[ctkEventConstants::EVENT_TOPIC] = "org/commontk/login";
ctkServiceReference ref = pc->getServiceReference<ctkEventAdmin>();
if (ref) {
ctkEventAdmin* eventAdmin = pc->getService<ctkEventAdmin>(ref);
eventAdmin->subscribeSlot(this, SLOT(onFinished(ctkEvent)), props);
}
}
// 处理事件
void Subscriber::onFinished(const ctkEvent& event)
{
QString name = event.getProperty("name").toString();
int age = event.getProperty("age").toInt();
qDebug() << QString("name:%1 age:%2").arg(name).arg(age);
}
使用 Qt 槽作为事件处理程序,可以很容易地确保事件处理代码在接收者的线程中执行(默认的连接类型是 Qt::AutoConnection
)。
主程序
main.cpp
:
#include <QCoreApplication>
#include <ctkPluginFrameworkLauncher.h>
#include <ctkPluginContext.h>
#include "publisher.h"
#include "subscriber.h"
// liborg_commontk_eventadmin.dll 所在路径
const QString c_strSearchPath = "E:/CTK-Examples/EventAdmin/PublishSignal/Plugins";
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
// 在插件的搜索路径列表中添加一条路径
ctkPluginFrameworkLauncher::addSearchPath(c_strSearchPath);
// 设置并启动CTK插件框架
ctkPluginFrameworkLauncher::start("org.commontk.eventadmin");
// 获取插件上下文
ctkPluginContext* pluginContext = ctkPluginFrameworkLauncher::getPluginContext();
// 订阅接收事件
Subscriber sub(pluginContext);
sub.subscribeSlot();
Publisher pub(pluginContext);
// 使用一个特定的主题来注册信号
pub.publishSignal();
// 发布事件
pub.publish();
// 停止插件
ctkPluginFrameworkLauncher::stop();
return app.exec();
}
好了,此过程在前面已经讲过,这里就不再赘述。
比较
回过头来,比较下这两种方式:
sendEvent()/postEvent() + ctkEventHandler
signal/slot
使用 Qt 信号,可以简化发送事件的行为。然而,由于信号发射需要经过 Qt 元对象系统,所以其性能较差。此外,信号被绑定到一个特定的事件主题上。
使用 ctkEventHandler
接口或 Qt 槽来注册一个事件处理程序,涉及的代码量大致相同。然而,使用槽会降低性能(可能会被忽略不计),但是代码将自动与接收者线程同步。
此外,订阅槽意味着需要一个已注册的事件管理服务实现。而 ctkEventHandler
方式不需要了解 Event Admin
的任何信息,因为 handler 被注册为框架中的一个服务对象。