第五章 插件:插件开发指南
目录
-
概述 -
SDK核心组件 -
插件开发步骤 -
关键实现要点 -
资源管理 -
插件加载与测试 -
代码优化建议 -
总结
1. 概述
ScreenCast支持通过插件系统扩展对不同设备类型的支持。本文档详细介绍了如何利用SDK为目标设备平台开发对应的插件,使ScreenCast能够与新的设备类型进行交互。
插件系统基于Qt的插件框架实现,每个插件都是一个动态链接库(DLL/SO/dylib),实现特定的接口以支持新设备类型。
2. SDK核心组件
2.1 插件接口 (IDevicePlugin)
IDevicePlugin是所有设备插件必须实现的核心接口,定义了插件的基本信息和行为:
classIDevicePlugin
{
public:
virtual ~IDevicePlugin() = default;
// 插件基本信息
virtual QString pluginName()const= 0;
virtual QString pluginVersion()const= 0;
virtual QIcon pluginIcon()const= 0;
virtual QString description()const= 0;
virtual QString author()const= 0;
// 设备代理创建
virtual DeviceProxy* createDeviceProxy(QObject* parent = nullptr)= 0;
virtual DeviceType deviceType()const= 0;
// 驱动相关
virtualboolcheckDriverAvailable()const= 0;
virtual QString getDriverName()const= 0;
virtual QString getDriverPath()const= 0;
};
2.2 设备代理接口 (DeviceProxy)
DeviceProxy是设备代理的抽象接口,定义了与设备交互的所有方法:
classDeviceProxy :public QObject
{
Q_OBJECT
public:
// 设备列表和信息查询
virtual QVector<DeviceInfo> listDevices()= 0;
virtual QString deviceModel(const QString& serial)= 0;
virtual QString deviceName(const QString& serial)= 0;
virtual QSize deviceResolution(const QString& serial)= 0;
virtual DeviceType deviceType()const= 0;
virtualboolqueryDeviceInfo(const QString& serial, DeviceInfo& info)= 0;
// 镜像服务器相关
virtualboolsetupMirrorServer(const QString& serial, int forwardPort)= 0;
virtualboolstartMirrorServer(const QString& serial)= 0;
virtualboolstopMirrorServer(const QString& serial)= 0;
virtual QString getMirrorServerIp(const QString& serial)= 0;
// 设备控制
virtualboolisScreenOn(const QString& serial)= 0;
virtualintdeviceRotation(const QString& serial)= 0;
virtualboolsendEvent(const DeviceInfo& dev, DeviceEvent eventType)= 0;
virtualboolsendTouchEvent(const DeviceInfo& dev, QPoint pos)= 0;
virtualboolsendTextEvent(const DeviceInfo& dev, const QString& text)= 0;
virtualboolsendSwipeEvent(const DeviceInfo& dev, QPoint start, QPoint end, int duration = 300)= 0;
virtualboolscreenshot(const QString& serial, QByteArray& imageData)= 0;
virtualboolsupportEvent(DeviceEvent eventType)const= 0;
// 信号
signals:
voidserverStarted(const QString& serial);
voidserverStopped(const QString& serial);
};
2.3 设备信息结构 (DeviceInfo)
DeviceInfo结构体包含设备的基本信息:
enumclassDeviceType
{
Unknown,
Android,
OHOS,
iOS,
Windows,
Linux,
MacOS,
};
enumclassDeviceEvent
{
INVALID = 0,
BACK, HOME, MENU, WAKEUP, SLEEP, ROTATE, ROTATE_LOCK,
UNLOCK, SHUTDOWN, REBOOT, TOUCH, KEY, TEXT,
VOLUME_UP, VOLUME_DOWN, MUTE, POWER, VOLUME_MUTE, SWIPE
};
structDeviceInfo
{
QString serial; // 设备序列号
QString model; // 设备型号
QString name; // 设备名称
DeviceType type; // 设备类型
int forwardPort; // 端口转发端口
int width; // 屏幕宽度
int height; // 屏幕高度
int rotation; // 设备旋转角度
};
3. 插件开发步骤
3.1 创建插件项目
-
创建新的Qt库项目:
-
使用Qt Creator创建一个新的库项目(选择”Library”类型) -
选择”Qt Plugin”作为库类型 -
设置项目名称(例如 NewDevicePlugin) -
配置项目文件:
QT += core widgets gui
TARGET = $$qtLibraryTarget(newdeviceplugin)
TEMPLATE = lib
CONFIG += plugin
# 指定插件输出目录
DESTDIR = $$PWD/../output/plugins
# 添加SDK头文件路径
INCLUDEPATH += $$PWD/../../sdk/include
# 添加源文件
SOURCES += \
NewDevicePlugin.cpp \
NewDevice.cpp
HEADERS += \
NewDevicePlugin.h \
NewDevice.h
# 添加资源文件
RESOURCES += newdevice.qrc
3.2 实现插件接口
-
创建插件类:
#pragma once
#include"NewDevice.h"
#include"plugin/IDevicePlugin.h"
classNewDevicePlugin :public QObject, public IDevicePlugin
{
Q_OBJECT
Q_INTERFACES(IDevicePlugin)
Q_PLUGIN_METADATA(IID "IDevicePlugin")
public:
NewDevicePlugin(QObject* parent = nullptr);
~NewDevicePlugin() override = default;
QString pluginName()constoverride;
QString pluginVersion()constoverride;
QIcon pluginIcon()constoverride;
DeviceProxy* createDeviceProxy(QObject* parent = nullptr)override;
DeviceType deviceType()constoverride;
boolcheckDriverAvailable()constoverride;
QString description()constoverride;
QString author()constoverride;
QString getDriverName()constoverride;
QString getDriverPath()constoverride;
};
-
实现插件方法:
#include"NewDevicePlugin.h"
#include<QCoreApplication>
#include<QDir>
#include<QProcessEnvironment>
#include<QStandardPaths>
NewDevicePlugin::NewDevicePlugin(QObject* parent) : QObject(parent) {}
QString NewDevicePlugin::pluginName()const
{
return"New Device Type";
}
QString NewDevicePlugin::pluginVersion()const
{
return"1.0.0";
}
QIcon NewDevicePlugin::pluginIcon()const
{
return QIcon(":/newdevice/icon");
}
DeviceProxy* NewDevicePlugin::createDeviceProxy(QObject* parent)
{
returnnew NewDevice(parent);
}
DeviceType NewDevicePlugin::deviceType()const
{
return DeviceType::NewDevice; // 假设已在DeviceType中添加
}
boolNewDevicePlugin::checkDriverAvailable()const
{
QString driverPath = getDriverPath();
return !driverPath.isEmpty();
}
QString NewDevicePlugin::description()const
{
return"New device type support plugin";
}
QString NewDevicePlugin::author()const
{
return"Developer Name";
}
QString NewDevicePlugin::getDriverName()const
{
return"newdevdriver";
}
QString NewDevicePlugin::getDriverPath()const
{
// 1. 检查环境变量
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
QString path = env.value("NEWDEV_HOME");
if (!path.isEmpty())
{
QString driverPath = QDir(path).filePath("bin/newdevdriver");
if (QFile::exists(driverPath))
{
return driverPath;
}
}
// 2. 检查PATH环境变量中的驱动
QString driverPath = QStandardPaths::findExecutable("newdevdriver");
if (!driverPath.isEmpty())
{
return driverPath;
}
// 3. 检查应用程序目录
QString appDir = QCoreApplication::applicationDirPath();
driverPath = QDir(appDir).filePath("newdevdriver");
if (QFile::exists(driverPath))
{
return driverPath;
}
return QString();
}
3.3 实现设备代理
-
创建设备代理类:
#pragma once
#include<QMutex>
#include<device/DeviceInfo.h>
#include<device/DeviceProxy.h>
classNewDevice :public DeviceProxy
{
Q_OBJECT
public:
explicitNewDevice(QObject* parent = nullptr);
// DeviceProxy接口实现
QVector<DeviceInfo> listDevices()override;
QString deviceModel(const QString& serial)override;
QString deviceName(const QString& serial)override;
QSize deviceResolution(const QString& serial)override;
DeviceType deviceType()constoverride;
boolqueryDeviceInfo(const QString& serial, DeviceInfo& info)override;
// 镜像服务器相关
boolsetupMirrorServer(const QString& serial, int forwardPort)override;
boolstartMirrorServer(const QString& serial)override;
boolstopMirrorServer(const QString& serial)override;
QString getMirrorServerIp(const QString& serial)override;
// 屏幕相关
boolisScreenOn(const QString& serial)override;
intdeviceRotation(const QString& serial)override;
// 事件发送相关
boolsendEvent(const DeviceInfo& dev, DeviceEvent eventType)override;
boolsendTouchEvent(const DeviceInfo& dev, QPoint pos)override;
boolsendTextEvent(const DeviceInfo& dev, const QString& text)override;
boolsendSwipeEvent(const DeviceInfo& dev, QPoint start, QPoint end, int duration = 300)override;
// 截图相关
boolscreenshot(const QString& serial, QByteArray& imageData)override;
// 功能支持检查
boolsupportEvent(DeviceEvent eventType)constoverride;
private:
// 私有辅助方法
boolpushResourceToDevice(const QString& serial, const QString& sourcePath, const QString& destPath);
private:
QMutex m_mutex; // 线程安全锁
// 其他私有成员变量
};
-
实现设备代理方法: 设备代理的实现需要根据具体设备的通信协议来完成。以下是一些关键方法的实现示例:
#include"NewDevice.h"
#include"utils/Shell.h"
#include<QDebug>
#include<QRegularExpression>
NewDevice::NewDevice(QObject* parent) : DeviceProxy(parent)
{
qDebug() << "NewDevice::NewDevice";
}
QVector<DeviceInfo> NewDevice::listDevices()
{
QVector<DeviceInfo> devices;
QString output;
// 使用设备专用命令列出设备
if (!Shell::executeCommandQuiet("newdevdriver", {"list"}, &output))
{
return devices;
}
// 解析输出并构建设备信息列表
QStringList lines = output.split('\n', Qt::SkipEmptyParts);
for (const QString& line : lines)
{
// 根据设备命令输出格式解析
QString serial = parseSerialFromOutput(line); // 自定义解析方法
if (!serial.isEmpty())
{
DeviceInfo info;
info.type = DeviceType::NewDevice;
info.serial = serial;
devices.append(info);
}
}
return devices;
}
boolNewDevice::sendTouchEvent(const DeviceInfo& dev, QPoint pos)
{
// 实现触摸事件发送
QString command = QString("tap %1 %2").arg(pos.x()).arg(pos.y());
return Shell::executeCommandQuiet("newdevdriver", {"-s", dev.serial, "input", command});
}
boolNewDevice::supportEvent(DeviceEvent eventType)const
{
// 根据设备支持的事件类型返回
switch (eventType)
{
case DeviceEvent::TOUCH:
case DeviceEvent::HOME:
case DeviceEvent::BACK:
returntrue;
// 其他事件类型...
default:
returnfalse;
}
}
4. 关键实现要点
4.1 设备通信
设备插件通常需要通过特定的命令行工具或API与设备通信。ScreenCast SDK提供了Shell类来简化命令执行:
// 使用Shell类执行命令
QString output;
bool success = Shell::executeCommandQuiet("driver_cmd", {"arg1", "arg2"}, &output);
4.2 镜像服务器部署
为了实现屏幕镜像功能,插件需要:
-
将镜像服务器程序推送到设备 -
设置端口转发 -
启动/停止镜像服务器
boolNewDevice::setupMirrorServer(const QString& serial, int forwardPort)
{
// 将服务器程序推送到设备
if (!pushResourceToDevice(serial, ":/newdevice/server", "/data/local/tmp/mirror_server"))
{
qWarning() << "Failed to push mirror server to device";
returnfalse;
}
// 设置端口转发
return Shell::executeCommandQuiet("newdevdriver", {"-s", serial, "forward", ":" + QString::number(forwardPort), "tcp:7890"});
}
boolNewDevice::startMirrorServer(const QString& serial)
{
// 启动镜像服务器
bool success = Shell::executeCommandQuiet("newdevdriver", {"-s", serial, "shell", "/data/local/tmp/mirror_server start"});
if (success)
{
emit serverStarted(serial);
}
return success;
}
4.3 线程安全
由于设备操作可能在多线程环境中执行,需要确保线程安全:
boolNewDevice::listDevices()
{
QMutexLocker locker(&m_mutex); // 加锁保护
// 实现设备列表获取
// ...
}
5. 资源管理
5.1 图标资源
为插件添加图标资源:
-
创建 newdevice.qrc文件:
<!DOCTYPE RCC>
<RCCversion="1.0">
<qresource>
<filealias="icon">res/icon.png</file>
<filealias="server">res/mirror_server</file>
</qresource>
</RCC>
-
在插件中使用图标:
QIcon NewDevicePlugin::pluginIcon()const
{
return QIcon(":/newdevice/icon");
}
5.2 镜像服务器资源
镜像服务器程序需要作为资源文件包含在插件中,并在运行时推送到设备:
boolNewDevice::pushResourceToDevice(const QString& serial, const QString& sourcePath, const QString& destPath)
{
// 实现将资源推送到设备的逻辑
// 可以使用临时文件中转
QTemporaryFile tempFile;
if (tempFile.open())
{
QFile resourceFile(sourcePath);
if (resourceFile.open(QIODevice::ReadOnly))
{
tempFile.write(resourceFile.readAll());
tempFile.close();
// 推送临时文件到设备
return Shell::executeCommandQuiet("newdevdriver", {"-s", serial, "push", tempFile.fileName(), destPath});
}
}
returnfalse;
}
6. 插件加载与测试
6.1 构建和部署插件
-
构建插件生成动态库文件 -
将生成的插件文件复制到ScreenCast应用的plugins目录 -
启动ScreenCast应用,系统会自动加载插件
6.2 调试技巧
-
设置环境变量:
QT_DEBUG_PLUGINS=1
这将输出插件加载的详细信息,帮助排查加载问题。
-
检查依赖:确保插件依赖的所有库都可用。
-
日志输出:在关键位置添加调试日志,帮助跟踪问题。
7. 代码优化建议
7.1 错误处理
boolNewDevice::sendEvent(const DeviceInfo& dev, DeviceEvent eventType)
{
// 增强的错误处理
if (!supportEvent(eventType))
{
qWarning() << "Event type not supported:" << static_cast<int>(eventType);
returnfalse;
}
// 执行命令并捕获错误
QString command = eventToString(eventType);
QString output;
QString error;
if (!Shell::executeCommand("newdevdriver", {"-s", dev.serial, "input", command}, &output, &error))
{
qWarning() << "Failed to send event:" << error;
returnfalse;
}
returntrue;
}
7.2 超时控制
为设备操作添加超时控制,避免长时间阻塞:
boolNewDevice::executeWithTimeout(const QStringList& arguments, QString* output, int timeoutMs = 5000)
{
// 实现带超时的命令执行
QProcess process;
process.start("newdevdriver", arguments);
if (!process.waitForStarted())
{
returnfalse;
}
if (!process.waitForFinished(timeoutMs))
{
process.kill();
process.waitForFinished();
qWarning() << "Command timed out";
returnfalse;
}
if (output)
{
*output = process.readAllStandardOutput();
}
return process.exitCode() == 0;
}
7.3 缓存机制
对频繁查询的设备信息实施缓存,提高性能:
QString NewDevice::deviceName(const QString& serial)
{
// 使用缓存避免重复查询
static QHash<QString, QString> nameCache;
if (nameCache.contains(serial))
{
return nameCache[serial];
}
// 执行实际查询
QString name = actualDeviceNameQuery(serial);
nameCache[serial] = name;
return name;
}
8. 总结
通过本文档,我们详细介绍了如何使用ScreenCast SDK开发设备插件。插件开发的核心步骤包括:
-
创建Qt插件项目 -
实现IDevicePlugin接口 -
实现DeviceProxy接口 -
处理设备通信和镜像服务器部署 -
添加资源文件和图标
开发插件时,需要特别注意线程安全、错误处理和资源管理。通过遵循本文档中的指南和最佳实践,开发者可以高效地为ScreenCast添加新设备类型的支持,扩展应用的兼容性和功能。
夜雨聆风
