CTK Plugin Framework事件管理机制

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/wyy626562203/article/details/82782070

事件管理机制

基于sendEvent()/postEvent()

简述

事件管理是OSGi概要规范的一部分,实现了插件通信机制。该通信机制采用发布/订阅模式,并且可以使用同步或异步的方式执行。

发布/订阅通信的主要组件有:

  • 事件发布者(Event Publisher):发送与某个特定主题相关的事件或消息
  • 事件处理程序(Event HandlerSubscriber):表达对一个或多个主题的兴趣,并接收该主题的所有消息。

事件由两个属性组成:

  • 主题(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 之外,同时还要configadminlogmetatype 相关插件。

重新编译CTK,在 CTK-build/bin目录中便可以找到相关插件。

使用事件管理

在 CTK 中,Event Admin 提供了两种方式来进行事件管理:

  • sendEvent()/postEvent() + ctkEventHandlersendEvent() 以同步方式发送,而 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_TOPICQStringQStringList 属性,该属性描述了事件处理程序感兴趣的主题列表。

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

需要同时包含 CTKCoreCTKPluginFramework

事件发布者

使用 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 被注册为框架中的一个服务对象。

猜你喜欢

转载自blog.csdn.net/wyy626562203/article/details/82782070