原文:zh.annas-archive.org/md5/af60e363dff13d7591431ef9db482003

译者:飞龙

协议:CC BY-NC-SA 4.0

第八章:我在哪里?定位和定位

带有 GPS 芯片的设备无处不在。你甚至可以追踪你的猫或鸡!在本章中,你将学习如何使用 Qt 进行定位和定位服务。

Qt 定位包含来自各种来源的地理坐标,包括卫星、Wi-Fi 和日志文件。Qt 位置则是关于本地地点,例如服务,如餐厅或公共公园,以及路线信息。

在本章中,我们将涵盖以下主题:

  • 使用卫星进行定位

  • 映射位置

  • 兴趣点

使用卫星进行定位

一部手机通常内置了 GPS 调制解调器,但也拥有其他定位信息来源,因此我将使用 Android 作为示例。我们将关注的 Qt 主要类如下:

这里是 Qt 定位 API:

  • QGeoSatelliteInfo

  • QGeoLocation

  • QGeoPositionInfoSource

  • QGeoCoordinate

这里是 Qt 位置 API:

  • QPlaceSearchResult

  • QPlaceContent

  • QGeoRoute

首先,我们需要编辑.pro文件并添加QT += positioning

QGeoSatelliteInfoSource

你可以使用QGeoSatelliteInfoSource来向用户展示卫星信息,它有一个static方法来获取QGeoSatelliteInfoSource

源代码可以在 Git 仓库的Chapter08-1目录下的cp8分支中找到。

我们将首先调用QGeoSatelliteInfoSource::createDefaultSource

QGeoSatelliteInfoSource *source = QGeoSatelliteInfoSource::createDefaultSource(this);

在某些系统,如 iOS 上,卫星信息不会公开到公共 API,因此QGeoSatelliteInfoSource在该平台上将不起作用。

这将在系统上构建一个QGeoSatelliteInfoSource对象,该对象是最高优先级的插件,这大致等同于以下操作:

QStringList geoSources = QGeoSatelliteInfoSource::availableSources();
QGeoSatelliteInfoSource *source = QGeoSatelliteInfoSource::createSource(geoSources.at(0),this);

有两个特别感兴趣的信号:satellitesInUseUpdatedsatellitesInViewUpdated。此外,还有一个重载的error信号,因此我们需要使用特殊的QOverload语法:

connect(source, QOverload<QGeoSatelliteInfoSource::Error>::
    of(&QGeoSatelliteInfoSource::error), 
    this, &SomeClass::error);

当系统使用的卫星数量发生变化时,会发出satellitesInUseUpdated信号。当系统可以看到的卫星数量发生变化时,会发出satellitesInViewUpdated信号。我们将接收到一个QGeoSatelliteInfo对象列表。

QGeoSatelliteInfo

让我们连接satellitesInViewUpdated信号,以便我们可以检测到卫星被找到:

connect(source, SIGNAL(satellitesInViewUpdated(QList<QGeoSatelliteInfo>)),
    this, SLOT(satellitesInViewUpdated(QList<QGeoSatelliteInfo>)));

我们可以通过这种方式接收单个卫星的信息。包括卫星标识符、信号强度、仰角和方位角等信息:

void SomeClass::satellitesInViewUpdated(const QList<QGeoSatelliteInfo> &infos)
{
    if (infos.count() > 0)
        qWarning() << "Number of satellites in view:" << infos.count();

    foreach (const QGeoSatelliteInfo &info, infos) {
        qWarning() << "    "
            << "satelliteIdentifier" << info.satelliteIdentifier()
            << "signalStrength" << info.signalStrength()
            << (info.hasAttribute(QGeoSatelliteInfo::Elevation) ?  "Elevation "
+ QString::number(info.attribute(QGeoSatelliteInfo::Elevation)) : "")
            << (info.hasAttribute(QGeoSatelliteInfo::Elevation) ?  "Azimuth " +
QString::number(info.attribute(QGeoSatelliteInfo::Azimuth)) : "");
    }
}

在小屏幕上这里有很多东西可以看。每次更新都是新的一行,你可以看到它定位并添加不同的卫星,当它们进入视野时:

https://github.com/OpenDocCN/freelearn-c-cpp-zh/raw/master/docs/hsn-mobi-emb-dev-qt5/img/e67199f7-ab26-4d0e-84eb-0aa5d209cc9e.png

下一步是使用这些卫星来在全球上定位我们的位置。我们首先使用QGeoPositionInfoSource

QGeoPositionInfoSource

我们可以通过使用QGeoPositionInfoSource来获取设备的纬度和经度位置,它封装了位置数据。类似于QGeoSatelliteInfoSource,它有两个static方法来创建source对象:

源代码可以在 Git 仓库的Chapter08-2目录下的cp8分支中找到。

QGeoPositionInfoSource *geoSource = QGeoPositionInfoSource::createDefaultSource(this);

我们感兴趣的QGeoPositionInfoSource信号是positionUpdated(const QGeoPositionInfo &update)

connect(geoSource, &QGeoPositionInfoSource::positionUpdated,
    this, &MainWindow::positionUpdated);

要开始接收更新,请调用startUpdates();

 geoSource->startUpdates();

positionUpdated信号接收一个QGeoPositionInfo

QGeoPositionInfo

QGeoPositionInfo包含一个QGeoCoordinate,其中包含我们的纬度和经度坐标,以及位置数据的时间戳。

它还可以包含以下可选属性:

  • Direction

  • GroundSpeed

  • VerticalSpeed

  • MagneticVariation

  • HorizontalAccuracy

  • VerticalAccuracy

可以使用hasAttribute(QGeoPositionInfo::Attribute)检查属性,并使用attribute(QGeoPositionInfo::Attribute)函数检索:

if (positionInfo.hasAttribute(QGeoPositionInfo::MagneticVariation)
    qreal magneticVariation = positionInfo.attribute(QGeoPositionInfo::MagneticVariation);

要获取纬度和经度信息,请调用QGeoPositionInfo类中的coordinate()函数,它返回一个QGeoCoordinate

QGeoCoordinate

QGeoCoordinate包含纬度和经度坐标,可以通过调用相应的latitude()longitude()函数找到。它可以由不同类型的数据组成,可以通过调用type()函数来发现,该函数返回一个QGeoCoordinate::CoordinateTypeenum,它可以有以下值之一:

  • InvalidCoordinate:无效坐标

  • Coordinate2D:包含纬度和经度坐标

  • Coordinate3D:包含纬度、经度和高度坐标

我们可以通过调用QGeoPositionInfo对象的coordinate()函数从QGeoPositionInfo对象中获取QGeoCoordinate,该函数反过来具有latitudelongitude值:

QGeoCoordinate coords = positionInfo.coordinate();
QString("Latitude %1\n").arg(coords.latitude());
QString("Longitude %1\n").arg(coords.longitude());if (coords.type() == QGeoCoordinate::Coordinate3D)
    QString("Altitude %1\n").arg(coords.altitude())

让我们看看我们如何使用 Qt Quick 和 QML 来完成这个操作。

Qt Quick

有相应的 QML 元素可用于定位。

import语句将是import QtPositioning 5.12

让我们用 QML 做同样简单的事情,并显示我们的纬度和经度值。

这里是之前提到的类的 Qt Quick 项目等效:

  • PositionSourceQGeoPositionInfoSource

  • PositionQGeoPositionInfo

  • CoordinateQGeoCoordinate

Qt Quick 通常要简单得多,并且快速实现这些功能。

源代码可以在 Git 仓库的Chapter08-3目录下的cp8分支中找到。

我们使用 1,000 毫秒的updateInterval实现了PositionSource,这意味着设备的位置将每 1,000 毫秒更新一次。我们将其设置为active以开始更新:

    PositionSource {
        id: positionSource
        updateInterval: 1000
        active: true

此组件有一个名为onPositionChanged的信号,当位置改变时会被调用。我们接收改变后的坐标,然后可以使用它们:

        onPositionChanged: {
            var coord = positionSource.position.coordinate;
            console.log("Coordinate:", coord.longitude, coord.latitude);
            latitudeLabel.text = "Latitude: " + coord.latitude;
            longitudeLabel.text = "Longitude: " + coord.longitude;
            if (positionSource.position.altitudeValid)
                altitudeLabel.text = "Altitude: " + coord.altitude;
      }
 }

现在我们知道了我们的位置,我们可以使用这些位置细节来获取坐标周围的一些详细信息,比如地图和位置详情。

映射位置

我们现在需要一个某种地图来显示我们的位置发现。

QML 的 Map 组件是 Qt 提供的唯一地图功能,因此您必须使用 Qt Quick。

Map 组件可以由各种后端插件支持。实际上,您需要指定您正在使用哪个插件。Map 内置支持以下插件:

Provider Key Notes Url
Esri esri 需要订阅 www.esri.com
HERE here 需要访问令牌 developer.here.com/terms-and-conditions
Mapbox mapbox 需要访问令牌 www.mapbox.com/tos
Mapbox GL mapboxgl 需要访问令牌 www.mapbox.com/tos
Open Street Map (OSM) osm 免费访问 openstreetmap.org/

我将使用 OSM 和 HERE 提供商。

HERE 插件需要在 developer.here.com 上有一个账户。注册很容易,并且有一个免费版本。您需要应用程序 ID 和应用程序代码才能访问他们的地图和 API。

地图

要开始使用 Map 组件,在您选择的 .qml 文件中,在 import 行中添加 QtLocationQtPositioning

import QtLocation 5.12
import QtPositioning 5.12

源代码可以在 Git 仓库的 Chapter08-4 目录下的 cp8 分支中找到。

Map 组件需要一个 plugin 对象,其 name 属性是前面表格中的一个键。您可以通过设置 center 属性为一个坐标来设置地图的中心位置。

我正在使用 OSM 后端,并且它以澳大利亚的黄金海岸为中心:

    Map {
        anchors.fill: parent
        plugin: Plugin {
            name: "osm" 
        }
        center: QtPositioning.coordinate(-28.0, 153.4)
        zoomLevel: 10
    }

地图以 center 属性中我们指定的坐标为中心,该属性用于将地图定位到用户的当前位置。

我们将地图的 plugin 属性定义为 "osm" 插件,这是 Open Street Map 插件的标识符。

显示地图就这么简单。

MapCircle

您可以通过在 Map 中放置一个 MapCircle 来突出显示一个区域。再次以黄金海岸为中心。

MapCircle 有一个 center 属性,我们可以通过使用一个带有符号十进制值的 latitudelongitude 位置来定义它。

这里 radius 属性的单位是米,根据地图。所以在我们这个例子中,MapCircle 的半径将是 5,000 米。

        MapCircle {
             center {
                 latitude: -28.0
                 longitude: 153.4
             }
             radius: 5000.0
             border.color: 'red'
             border.width: 3
             opacity: 0.5
         }

每个地图后端都有自己的参数,可以使用 Map 组件中的 PluginParameter 组件来设置。

PluginParameter

默认情况下,OSM 后端下载的是低分辨率的瓦片。如果您想要高分辨率的地图,您可以指定 'osm.mapping.highdpi_tiles' 参数:

PluginParameter {
    name: 'osm.mapping.highdpi_tiles'
    value: true
}

每个 PluginParameter 元素只包含一个 name/value 参数对。如果您需要设置多个参数,您将需要一个 PluginParameter 元素来设置每个:

PluginParameter { name: "osm.useragent"; value: "Mobile and Embedded Development with Qt5"; }

您可以考虑的其他 PluginParameters 包括各种地图提供商的令牌和应用程序 ID,例如 HERE 地图。

这是我们的地图在 Android 上运行的样子:

https://github.com/OpenDocCN/freelearn-c-cpp-zh/raw/master/docs/hsn-mobi-emb-dev-qt5/img/49538139-169e-48a8-8e2c-0309ad29cccf.png

我们还可以使用地址在地图上使用其他 Qt Quick 元素。让我们看看路线规划。

RouteModel

要在地图上显示路线,您需要使用RouteModel,它是Map项的一个属性,RouteQuery用于添加航点,以及MapItemView用于显示它。

RouteModel需要一个插件,所以我们只是重用了Map项的插件。它还需要一个RouteQuery来设置其query属性:

        RouteQuery {
            id: routeQuery
        }
        RouteModel {
            id: routeModel
            plugin : map.plugin
            query: routeQuery
        }

MapItemView用于在地图上显示模型数据。它还需要一个MapRoute的代理。在我们的案例中,这是一条描述路线的线:

        MapItemView {
            id: mapView
            model: routeModel
            delegate: routeDelegate
        }
        Component {
            id: routeDelegate
            MapRoute {
                id: route
                route: routeData
                line.color: "#46a2da"
                line.width: 5
                smooth: true
                opacity: 0.8
            }
        }

现在我们需要的是一个起点、一个终点以及任何中间点。在这个例子中,我保持简单,只指定起点和终点。您可以通过使用QtPositioning.coordinate来指定 GPS 坐标,它接受纬度和经度值作为参数:

​property variant startCoordinate: QtPositioning.coordinate(-28.0, 153.4)
property variant endCoordinate: QtPositioning.coordinate(-27.579744, 153.100175)

起始点坐标位于澳大利亚黄金海岸的某个随机区域;终点是南半球最后一个 Trolltech 办公室的位置。RouteQuery travelModes属性决定了路线是如何计算的,是开车、步行还是公共交通。它可以有以下几种值:

  • CarTravel

  • PedestrianTravel

  • BicycleTravel

  • PublicTransit

  • TruckTravel

RouteQuery属性routeOptimzations限制了查询到以下不同的值:

  • ShortestRoute

  • FastestRoute

  • MostEconomicRoute

  • MostScenicRoute

在这个例子中,我在Component.onCompleted信号中触发了routeQuery。通常,这种操作会在用户配置查询后触发:

Component.onCompleted: {
    routeQuery.clearWaypoints();
    routeQuery.addWaypoint(startCoordinate)
    routeQuery.addWaypoint(endCoordinate)
    routeQuery.travelModes = RouteQuery.CarTravel
    routeQuery.routeOptimizations = RouteQuery.FastestRoute
    routeModel.update();
 }

这是路线的显示方式。这条由蓝色线条指示的路线从大红色圆圈开始:

https://github.com/OpenDocCN/freelearn-c-cpp-zh/raw/master/docs/hsn-mobi-emb-dev-qt5/img/189324f1-d058-4353-a244-4acaed6350f0.png

您可以添加更多Waypoints来建立不同的路线或通过将routeModel设置为ListView或类似的内容来获取逐段方向指示。

不仅 Qt Location 可以显示地图和路线,而且Places API 还支持显示兴趣点,如餐厅、加油站和国家公园。

兴趣点

在这一点上,我将切换到 HERE 地图插件。我尝试让 OpenStreetMaps 地点工作,但它找不到任何东西。

在我们地图构建的下一步中,我们使用PlaceSearchModel来搜索地点。与之前的RouteModel一样,MapItemView可以在我们的地图上显示此模型。

就像RouteModel一样,PlaceSearchModel需要一种显示数据的方式;我们可以选择ListView,这在某些用途上很有用,但让我们选择MapItemView以获得视觉效果。

我们需要使用searchAreasearchTerm来声明我们正在使用哪个插件:

PlaceSearchModel {
    id: searchModel
    plugin: mapPlugin
    searchTerm: "coffee"
    searchArea: QtPositioning.circle(startCoordinate)
    Component.onCompleted: update()
}

我们的MapItemViewdelegate代码如下。searchView代理将以图标的形式显示,其标题文本来自结果地点:

MapItemView {
    id: searchView
    model: searchModel
    delegate: MapQuickItem {
        coordinate: place.location.coordinate
        anchorPoint.x: image.width * 0.5
        anchorPoint.y: image.height
        sourceItem: Column {
            Image { id: image; source: "map-pin.png" }
            Text { text: title; font.bold: true; color: "red"}
        }
    }
}

如您所见,地点点有些难以阅读,并且叠加在周围的点上。这表明附近有太多地点,对于缩放级别来说太近了,地图在放置名称时遇到了困难。您可以通过使用不同的缩放级别或使用一些碰撞检测和布局算法来解决这个问题,这里我不会深入讨论。

https://github.com/OpenDocCN/freelearn-c-cpp-zh/raw/master/docs/hsn-mobi-emb-dev-qt5/img/90adbe26-04ad-42a1-a2a8-680274dde9d2.png

map-pin.png 图标来自 feathericons.com/,并采用开源 MIT 许可协议发布。

摘要

在本章中,我们使用 Qt Location 和 Qt Positioning 覆盖了映射的许多方面。我们可以使用 QGeoSatelliteInfo 获取卫星信息,并使用 QGeoPositionInfo 定位精确的当前位置坐标。我们学习了如何使用 Qt Quick Map 和不同的地图提供商来显示当前位置。我们介绍了如何使用 RouteModel 提供路线,使用 PlaceSearchModel 在附近搜索地点,并使用 MapItemView 显示它们。

在下一章中,我们将讨论使用 Qt Multimedia 的音频和视频。

第三部分:其他 API Qt SQL、Qt 多媒体和 Qt 购买

我们将从这个章节开始讨论一些适用于移动设备的实用 API,例如游戏中的音频和视频。然后,我们将转向设备中数据库带来的挑战,并学习如何通过网络远程利用数据库。我们还将讨论如何使用 Qt 购买功能启用应用内购买。

本节包含以下章节:

  • 第九章, 声音与视觉 - Qt 多媒体

  • 第十章, Qt SQL 的远程数据库

  • 第十一章, 使用 Qt 购买功能启用应用内购买

第九章:声音与视觉 - Qt Multimedia

需要播放声音或显示视频的应用程序通常是游戏,而其他则是完整的多媒体应用程序。Qt Multimedia 可以处理这两者。

Qt Multimedia 可以与 Qt Widgets 和 Qt Quick 一起使用,甚至在没有 GUI 界面的情况下使用。它具有 C++ 和 QML API,但 QML API 有一些特殊的特性和技巧。一个鲜为人知的事实是,Qt 还可以在 Qt Quick 中播放 3D 位置音频。你可以用三个维度来控制增益和音调。

本章我们将涵盖以下主题:

  • 声音振动 - 音频

  • 图像传感器 - 相机

  • 视觉媒体 - 播放视频

  • 调谐它 - FM 收音机调谐器

声音振动 - 音频

我与音频的关系可以追溯到很久以前——在计算机成为家庭用品之前,当 Mylar 磁带和磁铁统治着声音领域的时候。从那时起,事物已经进步了。现在,移动电话可以放入我们的口袋,灯泡可以播放音乐。

Qt 中的 3D 音频通过 OpenAL API 支持。如果你使用的是 Linux,Qt 公司提供的默认 Qt 二进制文件不包含所需的 Qt 音频引擎 API。你必须安装 OpenAL 开发包,然后自行编译 Qt Multimedia。OpenAL 在 Android 上不受支持,所以在这方面没有乐趣。幸运的是,它在 Apple Mac 和 iOS 上默认支持。所以,我将在下一个部分进行开发。让我们拿起最近的 MacBook,前往那里。

3D 音频是三维空间中的音频,就像 3D 图形一样——不仅仅是左右,还有上下、前后位置。术语 位置音频 可能能更好地解释这一点。

在 Qt 中,3D 音频仅通过 Qt Quick 支持。

本章的源代码可以在 Git 仓库的 Chapter09-3dAudio 目录下的 cp9 分支中找到。

要使用 Qt Multimedia,你需要编辑项目的 .pro 文件并添加以下行:

QT += multimedia

编辑你想要在 3D 音频中使用的 qml 文件,并添加 import 行:

import QtAudioEngine 1.0

3D 空间由三个轴组成,分别命名为 x、y 和 z,它们对应于三维空间中的水平/垂直和上下方向。

AudioEngine 和其他相关类使用 Qt.vector3d 值类型。理解这个元素对于使用 3D 音频至关重要。

Qt.vector3d

Qt.vector3d 是一个表示 x、y 和 z 轴的值数组——x 表示水平,y 表示垂直,z 表示向上或向下。每个值都是一个单精度 qreal

它可以使用如 Qt.vector3d(15, -5, 0)"15, -5, 0" 作为 String

音频的位置是通过使用 vector3d 属性值来控制的。

Qt.vector3d 用于在三维空间中定位音频。

在 QML 中使用 3D 音频的主要组件称为 AudioEngine。我们将使用的其他组件可以是此组件的子组件。

音频引擎

AudioEngine 是你将使用的其他 3D 音频项的中心容器。

我们可以轻松设置组件:

    AudioEngine {
        id: audioEngine
        dopplerFactor: 1
        speedOfSound: 343.33
}

dopplerFactor 属性创建多普勒频移效果。speedOfSound 值反映了计算多普勒效应时声音的速度。

您可以通过 listener 属性分配一个 listener。我们将在 AudioListener 部分中稍后讨论。

我们有一个想要加载并使用的音频样本,因此至少声明一个 AudioSample

AudioSample

AudioSample 可以定义为 AudioEngine 组件的子组件:

 AudioEngine {
        id: audioEngine
        dopplerFactor: 1
        speedOfSound: 343.33
        AudioSample {
            name:"plink"
            source: "thunder.wav"
            preloaded: true
        }
}

它也可以使用 AudioEngine.addAudioSample() 方法添加:

 AudioEngine {
        id: audioEngine
        dopplerFactor: 1
        speedOfSound: 343.33
        addAudioSample(plinkSound)
}
AudioSample {
    id: plinkSound
    name:"plink"
    source: "thunder.wav"
    preloaded: true
}

source 属性包含样本的文件名和一个用于引用它的名称。

现在,我们准备好使用 Sound 组件播放声音。

Sound

Sound 元素是一个容器,可以包含一个或多个样本,这些样本将以不同的参数和变化播放。换句话说,您可以定义一个 PlayVariation 项目,它定义了 Sound 如何播放 AudioSample,包括音调和增益的最大值和最小值。您还可以声明样本为 looping,这意味着它会反复播放:

Sound {
    name: "thunderengine"
    attenuationModel: "thunderModel"
    PlayVariation {
        looping: true
        sample: "plink"
        maxGain: 0.5
        minGain: 0.3
     }
}

attenuationModel 属性控制声音音量水平下降的方式,或者随时间淡出。它可以取以下值之一:

  • 线性是直线下降

  • 反向是一个更自然、非线性的曲线

您可以使用 startendrolloff 属性来控制这一点。

AudioListener

AudioListener 组件代表 listener 以及其在 3D 空间的位置。只有一个 listener。它可以构建为 AudioEngine 组件的 listener 属性,或者作为一个可定义的元素:

    AudioListener {
        engine: audioEngine
        position: Qt.vector3d(0, 0, 0)
    }

SoundInstanceSound 用于播放样本的组件。

SoundInstance

SoundInstance 有一些属性,您可以使用它们来调整声音:

  • direction

  • gain

  • pitch

  • position

这些属性接受一个 vector3d 值。

SoundInstance 元素的 sound 属性接受一个字符串,表示 Sound 组件的名称:

    SoundInstance {
        id: plinkSound
        engine: audioEngine
        sound: "thunderengine"
        position: Qt.vector3d(leftRightValue, forwardBacktValue,
upDownValue)
        Component.onCompleted: plinkSound.play()
    }

在这里,我在组件完成时开始播放声音。

现在,我们只需要一些机制来移动声音位置。如果我们设备上有加速度计,我们可以使用加速度计的值。我只会使用鼠标。记住,在触摸屏上,MouseArea 也包括触摸输入。

我们必须启用 hover 才能跟踪鼠标而不点击:

    MouseArea {
        anchors.fill: parent
        hoverEnabled: true
        propagateComposedEvents: true
        onPositionChanged: {
            leftRightValue = -((window.width / 2) - mouse.x)
            forwardBacktValue = (window.height / 2) - mouse.y
        }

当使用 MouseArea 时,为了将鼠标点击传播到按钮或其他项目,请将 MouseArea 放在文件顶部,因为 Qt Quick 会按照从文件顶部到底部的组件顺序设置 z 轴顺序。您也可以设置按钮的 z 属性,并将 MouseAreaz 属性设置为最低值。

我之前在 Window 组件中声明了三个值,用于音频的位置:

property real leftRightValue: 0;
property real forwardBacktValue: 0;
property real upDownValue: 0;

现在,当您移动鼠标时,音频将看起来在移动。

但手机上没有鼠标。有一个触摸点,但没有滚动。我可以使用加速度计,因为它有 z 轴,或者使用PinchArea来控制上下位置。

让我们看看处理音频的几种其他方法。

音频

Audio元素可能是播放音频最简单的方法。它只需要几行代码。它非常适合播放音效。

源代码也可以在本书的 Git 仓库中找到,位于Chapter09-1目录下的cp9分支。

我们将使用以下import语句:

import QtMultimedia 5.12

这是一个简单的段落,将播放名为sample.mp3.mp3文件:

Audio {
    id: audioPlayer
    source: "sample.mp3"
}

source属性是声明要播放哪个文件的地方。现在,你只需要调用play()方法来播放这个sample.wav文件:

Component.onCompleted: audioPlayer.play()

你也可以将autoPlay属性设置为true,而不是调用play,这样组件完成后就会播放文件。

设置音量就像声明volume属性并设置一个介于 0 和 1 之间的十进制值一样——1 表示全音量,0 表示静音:

volume: .75

从文件中获取元数据或 ID 标签并不明显,因为它们只有在metaDataChanged信号发出后才会可用。这个信号只由Audio元素的metaData对象发出。

有时,你可能需要显示文件的元数据,或者可以包含在音频文件头部的额外数据。Audio组件有一个metaData属性,可以像这样使用:

metaData {
    onMetaDataChanged: {
        titleLabel.text = "Title: " + metaData.title
        artistLabel.text = "Artist: " + metaData.contributingArtist
        albumLabel.text = "Album: " + metaData.albumTitle
    }
}

如果你需要访问麦克风并录制音频,你需要深入到 C++,让我们看看QAudioRecorder

QAudioRecorder

录制音频是我的一项爱好。录制音频,或者更具体地说使用麦克风,在某些平台上可能需要用户权限。

音频的录制,在我那个时代被称为录音,可以通过使用QAudioRecorder类来实现。录制属性由QAudioEncoderSettings类控制,你可以从中控制使用的编解码器、通道数、比特率和采样率。你可以显式设置比特率和采样率,或者使用更通用的setQuality函数。

源代码可以在本书的 Git 仓库中找到,位于Chapter09-2目录下的cp9分支。

你可能想查询输入设备并查看哪些设置可用。为此,你会使用QAudioDeviceInfo进行查询,遍历QAudioDeviceInfo::availableDevices(QAudio::AudioInput)


void MainWindow::listAudioDevices() 
{ 
    for (const QAudioDeviceInfo &deviceInfo : 
         QAudioDeviceInfo::availableDevices(QAudio::AudioInput)) { 
        ui->textEdit->insertPlainText( 
                    QString("Device name: %1\n") 
                    .arg(deviceInfo.deviceName())); 

        ui->textEdit->insertPlainText( 
                    "    Supported Codecs: " 
                    + deviceInfo.supportedCodecs() 
                    .join(", ") + "\n"); 
        ui->textEdit->insertPlainText( 
                    QString("    Supported channel count: %1\n") 
                    .arg(stringifyIntList(deviceInfo.supportedChannelCounts()))); 
        ui->textEdit->insertPlainText( 
                    QString("    Supported bit depth b/s: %1\n") 
                    .arg(stringifyIntList(deviceInfo.supportedSampleSizes()))); 
        ui->textEdit->insertPlainText( 
                    QString("    Supported sample rates Hz: %1\n") 
                    .arg(stringifyIntList(deviceInfo.supportedSampleRates()))); 
    }    
} 

Qt 多媒体使用“样本大小”这个术语来指代更常见的“位深度”。

如从我笔记本电脑上所见,我有几个不同的音频输入设备。笔记本电脑的内置音频芯片因电涌而损坏,这就是为什么它在这里没有显示:

https://github.com/OpenDocCN/freelearn-c-cpp-zh/raw/master/docs/hsn-mobi-emb-dev-qt5/img/a5acaf2c-000a-4c42-9ccb-ada79d0bf846.png

对于 iPhone,情况不同。它只有一个音频设备,名为default

https://github.com/OpenDocCN/freelearn-c-cpp-zh/raw/master/docs/hsn-mobi-emb-dev-qt5/img/4ef5a2e6-b9df-4ad1-ab1a-60cc43d0dd76.png

我的 Linux 桌面因为 ALSA 驱动程序报告了大量的音频输入设备,这里不包括在内。

我们需要设置录音编码设置,包括我们想要录制的音频文件的类型、通道数、编码、采样率和比特率:

QAudioEncoderSettings audioSettings;
audioSettings.setCodec("audio/pcm");
audioSettings.setChannelCount(2);
audioSettings.setBitRate(16);
audioSettings.setSampleRate(44100);

如果您想让系统决定各种设置,使用setQuality函数会更快捷,代码也更少,该函数可以接受以下值之一:

  • QMultimedia::VeryLowQuality

  • QMultimedia::LowQuality

  • QMultimedia::NormalQuality

  • QMultimedia::HighQuality

  • QMultimedia::VeryHighQuality

让我们选择NormalQuality,这将给出相同的结果:

audioSettings.setQuality(QMultimedia::NormalQuality);

QAudioRecorder类用于录音,所以让我们构建一个QAudioRecorder并设置编码设置:

QAudioRecorder *audioRecorder = new QAudioRecorder(this);
audioRecorder->setEncodingSettings(audioSettings);

您还可以指定要使用的音频输入,但首先您需要获取可用音频输入的列表:

QStringList inputs = audioRecorder->audioInputs();

如果您不想麻烦选择哪个音频设备,您可以使用defaultAudioInput()函数指定默认设备:

   audioRecorder->setAudioInput(audioRecorder->defaultAudioInput());

我们可以将它保存到文件,甚至网络位置,因为setOutputLocation函数接受一个QUrl。我们只需指定一个本地文件来保存:

audioRecorder->setOutputLocation(QUrl::fromLocalFile("record1.wav"));

如果文件是相对的,就像这里一样,一旦开始录音,您可以使用outputLocation()获取实际输出位置。

最后,我们可以开始录音过程:

audioRecorder->record();

还有一些方法来控制录音操作,比如stop()pause()

当然,您会想连接到错误信号,因为错误有时会发生。再次注意在错误报告信号中使用的QOverload语法:

connect(audioRecorder, QOverload<QMediaRecorder::Error>::of(&QMediaRecorder::error),
           ={ 
                ui->textEdit->insertPlainText("QAudioRecorder Error: " + audioRecorder->errorString()); 
               on_stopButton_clicked(); 
            }); 

因此,现在我们已经录制了一些音频,我们可能想听听它。这就是QMediaPlayer发挥作用的地方。

QMediaPlayer

QMediaPlayer相当简单。它可以播放音频和视频,但在这里我们只会播放音频。首先,我们需要通过调用setMedia来设置要播放的媒体。

我们可以使用QAudioRecorder获取输出文件并使用它来播放:

player = new QMediaPlayer(this);
player->setMedia(audioRecorder->outputLocation());

我们将不得不监控当前播放位置,因此我们将positionChanged信号连接到一个进度条:

connect(player, &QMediaPlayer::positionChanged,
         this, &MainWindow::positionChanged);

连接错误信号及其QOverload语法:

connect(player, QOverload<QMediaPlayer::Error>::of(&QMediaPlayer::error),
            ={ 
            ui->textEdit->insertPlainText("QMediaPlayer Error: " + player->errorString());
           on_stopButton_clicked();
   });

然后,只需在QMediaPlayer对象上调用play()即可:

player->play();

您甚至可以设置播放音量:

player->setVolume(75);

如果您需要访问媒体数据,比如说获取播放时的音量级别,您可能需要使用除了QMediaPlayer之外的其他方式来播放您的文件。

QAudioOutput

QAudioOutput提供了一种将音频发送到音频输出设备的方式:

QAudioOutput *audio;

使用QAudioOutput,您需要设置文件的精确格式。要获取文件的格式,您可以使用QMediaResource

抱歉,QMediaResource在 Qt 6.0 中被弃用,并且它并没有按照文档所说的那样工作,也没有像预期的那样工作。我们需要硬编码数据格式,因此我们将使用基本的优质立体声格式。QAudioFormat是这样做的方式:

    QAudioFormat format;
    format.setSampleRate(44100);
    format.setChannelCount(2);
    format.setSampleSize(16);
    format.setCodec("audio/pcm");
    format.setByteOrder(QAudioFormat::LittleEndian);
    format.setSampleType(QAudioFormat::UnSignedInt);

我们将遍历音频设备并检查QAudioDeviceInfo是否支持此格式:

    for (const QAudioDeviceInfo &deviceInfo : QAudioDeviceInfo::availableDevices(QAudio::AudioOutput)) {
        if (deviceInfo.isFormatSupported(format)) {
            audio = new QAudioOutput(deviceInfo, format, this);
            connect(audio, &QAudioOutput::stateChanged, [=] (QAudio::State
state) {
            qDebug() << Q_FUNC_INFO << "state" << state;
            if (state == QAudio::StoppedState) {
                if (audio->error() != QAudio::NoError) {
                    qDebug() << Q_FUNC_INFO << audio->error();
                }
            }
        });
 }

在这里,我连接到了stateChanged信号并测试了状态是否为StoppedState;我们知道可能存在错误,因此我们检查QAudioOutput对象的error()。否则,我们可以播放文件:

QFile sourceFile;
sourceFile.setFileName(file);
sourceFile.open(QIODevice::ReadOnly);
audio->start(&sourceFile);

现在,我们看到 Qt 多媒体有各种播放音频的方式。现在,让我们来看看相机和录制视频。

图像传感器 - 相机

首先,我们应该确定设备是否有任何相机。这有助于我们确定相机使用的具体细节以及其他相机规格,例如设备上的方向或位置。

对于此操作,我们将使用QCameraInfo

QCameraInfo

我们可以使用QCameraInfo::availableCameras()函数获取相机列表:

源代码可以在本书的 Git 仓库的Chapter09-4目录下的cp9分支中找到。

    QList<QCameraInfo> cameras = QCameraInfo::availableCameras();
    foreach (const QCameraInfo &cameraInfo, cameras)
        ui->textEdit->insertPlainText(cameraInfo.deviceName() + "\n");

在我的 Android 设备上,我看到了两个相机,分别命名为backfront。您也可以使用QCameraInfo::position()检查frontback相机,它将返回以下之一:

  • QCamera::UnspecifiedPosition

  • QCamera::BackFace

  • QCamera::FrontFace

FrontFace表示相机镜头与屏幕在同一侧。然后您可以使用QCameraInfo来构建QCamera对象:

QCamera *camera;
if (cameraInfo.position() == QCamera::BackFace) {
    camera = new QCamera(cameraInfo);
}

现在,检查相机支持的捕获模式,这可以是以下之一:

  • QCamera::CaptureViewfinder

  • QCamera::CaptureStillImage

  • QCamera::CaptureVideo

首先,让我们先进行一次快速静态图像拍摄。我们需要告诉相机使用QCamera::CaptureStillImage模式:

camera->setCaptureMode(QCamera::CaptureStillImage);

statusChanged信号用于监控状态,可以是以下值之一:

  • QCamera::UnavailableStatus

  • QCamera::UnloadedStatus

  • QCamera::UnloadingStatus

  • QCamera::LoadingStatus

  • QCamera::LoadedStatus

  • QCamera::StandbyStatus

  • QCamera::StartingStatus

  • QCamera::StoppingStatus

  • QCamera::ActiveStatus

让我们连接到statusChanged信号,这样我们就可以看到状态变化:

connect(camera, &QCamera::statusChanged, [=] (QCamera::Status status) {
    ui->textEdit->insertPlainText(QString("Status changed %1").arg(status) + "\n");
});

如果您需要调整任何相机设置,您必须在获取对QCameraImageProcessing对象的访问权限之前先load()它:

camera->load();
QCameraImageProcessing *imageProcessor = camera->imageProcessing();

使用QCameraImageProcessing类,您可以设置配置,例如亮度、对比度、饱和度和锐化。

在我们对相机调用start之前,我们需要为相机设置一个QMediaRecorder对象。由于QCamera是从QMediaObject继承的,我们可以将其馈送到QMediaRecorder对象。

Qt 多媒体小部件在 Android 上不受支持。

我在 Mac 和 iOS 上尝试了QCamera版本 5.12,但在尝试start()相机时它总是崩溃。在 Linux 桌面上我成功了。在 Android 上,由于多媒体小部件不受支持,相机取景器小部件无法工作,但我仍然可以从图像传感器捕获图像。

也许你在 QML 方面会有更好的运气。QML API 通常针对易于使用进行了优化。

Camera

是的,QML 的Camera实现起来要容易得多。实际上,您只需要两个组件来拍照:CameraVideoOutput

VideoOutput是用于取景器的元素。在您录制视频时也会使用它:

    Camera {
        id: camera
        position: Camera.BackFace
        onCameraStateChanged: console.log(cameraState)
        imageCapture {
            onImageCaptured: {
                console.log("Image captured")
            }
        }
    }

position属性控制使用哪个相机,尤其是在可能具有前置和后置相机的移动设备上。在这里,我不仅使用后置相机。您可以使用FrontFace位置来拍摄自拍。

imageCapturedCameraCapture子元素相关。我们可以处理onImageCaptured信号来预览图像或提醒用户已拍照。

Camera对象的其余属性可以通过它们相应的组件进行控制:

  • focus : CameraFocus

  • flash : CameraFlash

  • 曝光 : CameraExposure

  • imageProcessing : CameraImageProcessing

  • imageCapture : CameraCapture

  • videoRecorder: CameraRecorder

CameraRecorder是您用来控制饱和度、亮度、颜色滤镜、对比度和其他设置的元素。

CameraExposure控制诸如光圈、曝光补偿和快门速度等事项。

CameraFlash可以打开、关闭闪光灯或使用自动模式。它还可以设置红眼消除和视频(恒定)模式。

我们需要一个取景器来查看我们试图捕捉的是什么,让我们看看VideoOutput元素。

视频输出

VideoOutput是我们用来查看相机所感知内容的组件。

源代码可以在 Git 仓库的Chapter09-5目录下的cp9分支中找到。

要实现VideoOutput组件,您需要定义source属性。在这里,我们使用的是相机:

    VideoOutput {
        id: viewfinder
        source: camera
        autoOrientation: true
}

autoOrientation属性用于允许VideoOutput组件补偿图像传感器的设备方向。如果没有这个属性为真,图像可能会以错误的方向显示在取景器中,从而混淆用户,使得拍摄好照片或视频变得更加困难。

让我们通过添加一个MouseArea来使这个VideoOutput可点击,我将使用onClickedonPressAndHold信号来聚焦并实际捕捉图像:

MouseArea {
    anchors.fill: parent
    onPressAndHold: {
        captureMode: captureSwitch.position === 0 ?Camera.CaptureStillImage : Camera.CaptureVideo
        camera.imageCapture.capture()
    }
    onClicked: {
        if (camera.lockStatus == Camera.Unlocked)
            camera.unlock();
            camera.searchAndLock();
    }
 }

我还添加了来自 Qt Quick Controls 的Switch组件来控制用户是否想要记录静态照片或视频。

要聚焦相机,请调用searchAndLock()方法,它将启动聚焦、白平衡和曝光计算。

让我们添加录制视频的支持。我们将向Camera组件添加一个CameraRecorder容器:

VideoRecorder {
    audioEncodingMode: CameraRecorder.ConstantBitrateEncoding;
    audioBitRate: 128000
    mediaContainer: "mp4"
}

我们可以为视频设置某些方面,例如比特率、帧率、音频通道数以及要使用的容器。

我们还需要更改onPressAndHold信号的工作方式,以确保当用户通过开关指定时,我们记录视频。

onPressAndHold: {
    captureMode: captureSwitch.position === 0 ? Camera.CaptureStillImage : Camera.CaptureVideo
    if (captureSwitch.position === 0)
        camera.imageCapture.capture()
    else
        camera.videoRecorder.record()
}

我们需要一种停止录制的方法,所以让我们修改onClicked信号处理程序,以便在RecordingState时停止录制。

onClicked: {
    if (camera.videoRecorder.recorderState === CameraRecorder.RecordingState) {
        camera.videoRecorder.stop()
     } else {
         if (camera.lockStatus == Camera.Unlocked)
             camera.unlock();
         camera.searchAndLock();
     }
}

现在,我们需要真正看到我们刚刚录制的视频。让我们继续并看看如何播放视频。

视觉媒体 - 播放视频

使用 QML 播放视频与使用MediaPlayer播放音频类似,只是使用VideoOutput组件代替AudioOutput组件。

源代码可以在 Git 仓库的Chapter09-6目录下的cp9分支中找到。

我们首先实现一个MediaPlayer组件:

MediaPlayer {
    id: player

命名为autoPlay的属性将控制组件完成后自动开始视频。

在这里,source属性设置为我们的视频文件名:


    autoPlay: true
    source: "hellowindow.m4v"
    onStatusChanged: console.log("Status " + status)
    onError: console.log("Error: " + errorString)
}

然后我们创建一个VideoOutput组件,源为我们的MediaPlayer


VideoOutput {
    source: player
    anchors.fill : parent
 }

MouseArea {
    id: playArea
    anchors.fill: parent
    onPressed: player.play();
}

这里使用的MouseArea,即整个应用程序,用于在您点击应用程序的任何地方时开始播放视频。

使用 C++,您会使用QMediaPlayer类与QGraphicsVideoItemQVideoWidget或其他类似的东西。

由于QMultimediaWidgets在移动设备上的支持有限,我将把这留作读者的练习。

Qt 多媒体也支持 FM、AM 以及一些其他收音机,前提是您的设备中也有收音机。

调谐它 – FM 收音机调谐器

一些安卓手机有 FM 收音机接收器。我的手机就有!它需要插入有线耳机才能作为天线工作。

我们首先实现一个Radio组件:

Radio {
    id: radio

Radio元素有一个band属性,您可以使用它来配置收音机的频率波段使用。它们是以下之一:

  • Radio.AM : 520 - 1610 kHz

  • Radio.FM : 87.5 - 108 MHz,日本 76 - 90 MHz

  • Radio.SW : 1.711 到 30 MHz

  • Radio.LW : 148.5 到 283.5 kHz

  • Radio.FM2 : 范围未定义


    band: Radio.FM
    Component.onCompleted {
        if (radio.availability == Radio.Available)
            console.log("Good to go!")
        else 
           console.log("Sad face. No radio found. :(")
    }
}

availability属性可以返回以下不同的值:

  • Radio.Available

  • Radio.Busy

  • Radio.Unavailable

  • Radio.ResourceMissing

用户首先会用收音机搜索电台,这可以通过使用searchAllStations方法完成,该方法接受以下值之一:

  • Radio.SearchFast

  • Radio.SearchGetStationId: 与SearchFast类似,它发出stationFound信号

stationsFound信号为每个调谐的电台返回一个int类型的frequency和一个stationId字符串。您可以在基于模型的组件中收集这些信息,例如ListView,使用ListModelListView将使用ListModel作为其模型。

您可以通过调用cancelScan()方法取消扫描。scanUp()scanDown()方法与searchAllStations类似,但不会记住它找到的电台。tuneUptuneDown方法将根据frequencyStep调整频率上下一个步长。

这里有一些其他有趣的属性:

  • antennaConnected: 如果连接了天线则为 True

  • signalStrength: 信号强度,百分比%

  • frequency: 保存和设置收音机调谐到的频率

摘要

在本章中,我们讨论了 Qt 多媒体大 API 的不同方面。你现在应该能够以三维方式定位声音,用于三维游戏。我们学习了如何录制和播放音频和视频,以及如何控制和使用摄像头来拍摄自拍。我们还简要介绍了如何使用 QML 来收听电台。

在下一章中,我们将深入探讨如何使用 QSqlDatabase 访问数据库。

第十章:使用 Qt SQL 的远程数据库

Qt SQL 不依赖于任何特定的数据库驱动程序。相同的 API 可以与各种流行的数据库后端一起使用。数据库可以拥有巨大的存储空间,而移动和嵌入式设备存储量有限,嵌入式设备比手机更有限。您将学习如何使用 Qt 通过网络远程访问数据库。

我们将在本章中介绍以下主题:

  • 驱动程序

  • 连接到数据库

  • 创建数据库

  • 向数据库添加

技术要求

您可以在 cp10 分支中获取本章的源代码,地址为 git clone -b cp10 https://github.com/PacktPublishing/Hands-On-Mobile-and-Embedded-Development-with-Qt-5

您还应该为您的系统安装了 sqlite 或 mysql 软件包。

驱动程序是数据库后端

Qt 支持各种数据库驱动程序或后端,这些后端封装了各种系统数据库,并允许 Qt 拥有一个统一的 API 前端。Qt 支持以下数据库类型:

数据库类型 软件
QDB2 IBM Db2
QIBASE Borland InterBase
QMYSQL MySQL
QOCI Oracle Call Interface
QODBC ODBC
QPSQL PostgreSQL
QSQLITE SQLite 版本 3 或以上
QSQLITE2 SQLite 版本 2
QTDS Sybase Adaptive Server

我们将探讨 QMYSQL 类型,因为它支持远程访问。MySQL 可以安装在树莓派上。 QSQLITE3 可以在网络资源上共享并支持远程访问,iOS 和 Android 也支持 SQLite。

设置

MySQL 数据库需要配置以允许您远程访问它。让我们看看我们如何做到这一点:

  1. 您需要安装服务器和/或客户端。

  2. 然后,我们将创建数据库并在需要时使其可通过网络访问。这将通过命令行和终端应用程序来完成。

MySQL 服务器

我使用的是 Ubuntu,因此这些命令将主要针对基于 Debian 的 Linux。如果您使用的是不同的 Linux 发行版,只有安装命令会有所不同。您应根据您的发行版安装 MySQL 服务器和客户端。创建数据库的命令将是相同的。

这是我们将如何设置服务器的方式:

  1. 您需要安装 MySQL 服务器和客户端:

sudo apt-get install mysql-server mysql-client

  1. 运行 sudo mysql_secure_installation,这将允许您设置 root 账户。然后,登录到 mysql root 账户:

sudo mysql -u root -p

  1. 创建新的数据库 usernamepassword: GRANT ALL PRIVILEGES ON *.* TO 'username'@'localhost' IDENTIFIED BY 'password';。将 username 替换为您的数据库用户,将 password 替换为您想要用于访问此数据库的密码。

  2. 要使服务器可以从除了 localhost 之外的主机访问,请编辑 /etc/mysql/mysql.conf.d/mysqld.cnf

  3. bind-address = localhost行更改为bind-address = <your ip>,其中<your ip>是数据库所在机器的 IP 地址。然后,重启mysql服务器:

sudo service mysql restart

在你的 MySQL 控制台中,允许远程用户访问数据库:

GRANT ALL ON *.* TO 'username'@'<your ip>' IDENTIFIED BY 'password';

<your ip>更改为客户端设备的 IP 地址或主机名,username更改为你在 MySQL 服务器上使用的用户名,password更改为他们将要使用的密码。

SQLite

SQLite 是一个基于文件的数据库,因此没有服务器这样的东西。我们仍然可以通过网络文件系统远程连接到它,例如 Windows 文件共享/Samba、网络文件系统(NFS)或 Linux 上的安全外壳文件系统(SSHFS)。SSHFS 允许你像本地文件系统一样挂载和访问远程文件系统。

除非你需要,否则没有必要手动使用晦涩的命令来创建数据库,因为我们将会使用 Qt 来创建它!

在 Android 上,有 Samba 客户端,可以将 Windows 网络共享挂载,这样我们就可以使用它。如果你使用 Raspberry Pi 或其他开发板,你可能能够使用 SSHFS 通过 SSH 挂载远程目录。

连接到本地或远程数据库

一旦我们配置并启动了数据库,现在我们可以使用相同的函数连接到它,无论它是本地数据库还是远程数据库。现在,让我们看看如何编写代码来连接到数据库,无论是本地还是远程。

数据库要么是本地可用的,这通常意味着在同一台机器上,要么通过网络远程访问。使用 Qt 连接到这些不同的数据库基本上是相同的。并非所有数据库都支持远程访问。

首先,让我们使用本地数据库。

要使用sql模块,我们需要将sql添加到配置文件中:

QT += sql

要在 Qt 中连接到数据库,我们需要使用QSqlDatabase类。

QSqlDatabase

尽管名字叫QSqlDatabase,但它代表的是对数据库的连接,而不是数据库本身。

要创建数据库连接,你首先需要指定你正在使用的数据库类型。它以支持数据库的字符串表示形式引用。让我们首先选择 MySQL 的QMYSQL数据库。

源代码可以在 Git 仓库的Chapter10-1目录下的cp10分支中找到。

要使用QSqlDatabase,我们首先需要添加数据库以创建其实例。

静态的QSqlDatbase::addDatabase函数接受一个参数,即数据库类型,并将数据库实例添加到数据库连接列表中。

在这里,我们添加一个 MySQL 数据库,所以使用QMYSQL类型:

 QSqlDatabase db = QSqlDatabase::addDatabase("QMYSQL");

如果你正在连接到 SQLite 数据库,请使用MSQLITE数据库类型:

QSqlDatabase db = QSqlDatabase::addDatabase("MSQLITE");

大多数数据库都需要用户名和密码。要设置usernamepassword,请使用以下命令:

db.setUserName("username");
db.setPassword("password");

由于我们正在连接到远程 MySQL 数据库,因此我们需要指定主机名。它也可以是一个 IP 地址:

db.setHostName("10.0.0.243");

要开始连接,请调用 open() 函数:

bool ok = db.open()

open 返回一个 bool,如果成功则为 true,如果失败则为 false,在这种情况下我们可以检查错误:

if (!db.open()) {
    qWarning() << dq.lastError.text();
}

如果这成功打开,我们就连接到了数据库。

让我们实际上创建远程数据库,因为我们有需要的权限。

创建和打开数据库

对于 SQLite 数据库,一旦我们打开它,它就会在文件系统中创建数据库。对于 MySQL,我们必须发送 MySQL 命令来创建数据库。我们使用 QSqlQuery 构建 SQL 查询来完成这一点。QSqlQuery 将数据库对象作为参数:

QSqlQuery query(db);

要发送查询,我们在 QSqlQuery 对象上调用 exec() 函数。它需要一个 String 作为典型的 query 语法:

QString dbname = "MAEPQTdb";
if (!query.exec("CREATE DATABASE IF NOT EXISTS " + dbname)) {
    qWarning() << "Database query error" << query.lastError().text();
}

dbname 这里是任何我们希望数据库名称为 String;我正在使用 MAEPQT db

如果这个命令失败,我们发出警告消息。如果成功,我们就继续发出命令来 USE 它,所以我们调用另一个 query 命令:

query.exec("USE " + dbname);

从这里开始,我们需要创建一些表格。我会让它保持简单,并填充一些数据。

我们开始另一个查询,但使用空命令,并将 db 对象作为第二个参数,这将创建指定数据库上的 QSqlQuery 对象,但在我们准备好之前不会执行任何命令:

QSqlQuery q("", db);
q.exec("drop table Mobile");
q.exec("drop table Embedded");
q.exec("create table Mobile (id integer primary key, Device varchar,
Model varchar, Version number)");

q.exec("create table Embedded (id integer primary key, Device varchar,Model varchar, Version number)");

数据库已准备就绪,因此现在我们可以添加一些数据。

向数据库添加数据

Qt 文档指出,保留 QSqlDatabase 对象不是一个最佳实践。

这里有一些不同的方法我们可以这样做:

  1. 我们可以使用 QSqlDatabase::database 来获取已打开数据库的实例:
QSqlDatabase db = QSqlDatabase::database("MAEPQTdb");
QSqlQuery q("", db);
q.exec("insert into Mobile values (0, 'iPhone', '6SE', '12.1.2')");
q.exec("insert into Mobile values (1, 'Moto', 'XT1710-09', '2')");
q.exec("insert into Mobile values (1, 'rpi', '1', '1')");
q.exec("insert into Mobile values (1, 'rpi', '2', '2')");
q.exec("insert into Mobile values (1, 'rpi', '3', '3')");
  1. 我们还可以使用 QSqlQuery 的另一个函数,名为 prepare(),它使用代理变量准备查询字符串以执行。

然后,我们可以使用 bindValue 将值绑定到其标识符:

q.prepare("insert into Mobile values (id,  device, model, version)"
          "values ( :id, :device, :model, :version)");

q.bindValue(":id", 0);
q.bindValue(":device", "iPhone");
q.bindValue(":model", "6SE");
q.bindValue(":version", "12.1.2");
q.exec();
  1. 作为一种替代方案,你可以使用 bindValue 函数,并将第一个参数设置为标识符的位置索引,从数字 0 开始,向上通过值进行操作:
q.bindValue(1, "iPhone");
q.bindValue(3, "12.1.2");
q.bindValue(2, "6SE");
  1. 你也可以按值的顺序使用 bindValue
q.bindValue(0);
q.bindValue("iPhone");
q.bindValue("6SE");
q.bindValue("12.1.2");

接下来,让我们看看如何从数据库中检索数据。

执行查询

到目前为止,我们一直在运行查询,但没有返回任何数据。数据库的一个要点是查询数据,而不仅仅是输入数据。如果我们只能输入数据,那会有什么乐趣呢?Qt API 有一种方法来适应不同的语法和查询的数百万种方式。大多数情况下,它特定于需要返回的数据类型,但也特定于数据库数据本身。幸运的是,QSqlQuery 足够通用,查询参数是一个字符串。

QSqlQuery

要检索数据,请使用 QsqlQuery 执行查询,然后使用以下函数对记录进行操作:

  • first()

  • last()

  • next()

  • previous()

  • seek(int)

first()last() 函数分别用于检索第一条和最后一条记录。要反向遍历记录,请使用 previous()seek (int) 函数接受一个整数作为参数,以确定要检索的记录。

我们将使用 next(),它将遍历查询中找到的记录:

    QSqlDatabase db = QSqlDatabase::database("MAEPQTdb");
    QSqlQuery query("SELECT * FROM Mobile", db);
    int rowCount = 0;
    while (query.next()) {
        QString id = query.value(0).toString();
        QString device = query.value(1).toString();
        QString model = query.value(2).toString();
        QString version = query.value(3).toString();
        ui->tableWidget->setRowCount(rowCount + 1);
        ui->tableWidget->setItem(rowCount, 0, new QTableWidgetItem(id));
        ui->tableWidget->setItem(rowCount, 1, new
QTableWidgetItem(device));
        ui->tableWidget->setItem(rowCount, 2, new
QTableWidgetItem(model));
        ui->tableWidget->setItem(rowCount, 3, new
QTableWidgetItem(version));
        rowCount++;
    }

我们还使用 value 来检索每个字段的数据,它需要一个 int,表示从 0 开始的记录位置。

您还可以使用 QSqlRecordQSqlField 来做同样的事情,但可以更清晰地了解实际发生的情况:

QSqlField idField = record.field("id");
QSqlField deviceField = record.field("device");
QSqlField modelField = record.field("model");
QSqlField versionField = record.field("version");
qDebug() << Q_FUNC_INFO
         << modelField.name()
         << modelField.tableName()
         << modelField.value();

要获取记录数据,请使用 value(),它将返回一个 QVariant,表示该记录字段的值。

我们本可以使用基于模型的控件,然后使用 QsqlQueryModel 来执行查询。

QSqlQueryModel

QSqlQueryModel 继承自 QSqlQuery,并返回一个可以用于基于模型的控件和其他类的模型对象。如果我们将我们的 QTableWidget 改为 QTableView,我们可以使用 QSqlQueryModel 作为其数据模型:

QSqlQueryModel *model = new QSqlQueryModel;
model->setQuery("SELECT * FROM Mobile", db);
tableView->setModel(model);

这里是我的运行数据库示例的 Raspberry Pi(带有乐高支架!)使用 MySQL 插件远程运行:

https://github.com/OpenDocCN/freelearn-c-cpp-zh/raw/master/docs/hsn-mobi-emb-dev-qt5/img/0e6010a7-476f-4740-bd4c-a405253bbfda.jpg

摘要

在本章中,我们了解到 QSqlDatabase 代表了对任何受支持的数据库的连接。您可以使用它来登录远程 MySQL 数据库或网络共享上的 SQLite 数据库。要执行数据检索,请使用 QSqlQuery。您使用相同的类来设置数据、表和其他数据库命令。如果您正在使用模型-视图应用程序,则可以使用 QSqlDatabaseModel

在下一章中,我们将探讨使用 Qt Purchasing 模块进行应用内购买。

第十一章:使用 Qt 购买启用应用内购买

在手机上应用内购买对于生成更多收入至关重要。Qt 利用系统 API 将应用内购买引入 Qt 应用。Android 和 iOS 都有自己的应用商店,每个商店都有自己的产品注册方法。这就是 Qt 购买发挥作用的地方!

在本章中,我们将涵盖以下主题:

  • 在 Android 和 iOS 商店注册

  • 创建应用内产品

  • 恢复购买

在 Android Google Play 上注册

销售移动应用程序往往是不稳定的;只有少数可供购买的应用程序实际上能赚钱。目前赚钱的最好方法之一是让您的应用程序免费下载,但包含应用内购买。这样,人们可以尝试您的应用程序,同时也有机会在需要增强体验时进行购买。本节假设您已经将应用程序注册到相关的移动商店。要激活应用内购买,您首先需要注册您打算出售的商品。这有其自身的优势,因为它允许测试人员 购买 并安装您打算出售的商品。

首先,让我们看看如何在 Android 设备上完成这项操作:

  1. 您首先需要注册一个 Google 开发者账户,以便创建可在 Google Play 商店中使用的应用程序,developer.android.com

  2. 您还需要添加和编辑 AndroidManifest.xml 文件。

在 Qt Creator 中,导航到:

项目 | 构建 | 构建设置 | 构建 Android APK | 创建模板。在这里,您需要编辑包名,理想情况下使用约定,com.<公司>.<应用程序名称>。当然,还有其他命名约定可供选择,您可以将其命名为任何您想要的名称。

  1. 当您在 Google Play 商店更新您的包时,版本号必须递增。最简单的方法是勾选名为“包含 Qt 模块默认权限”的复选框。如果不勾选,您需要确保添加 uses-permission android:name="com.android.vending.BILLING" 权限。

  2. 您还需要使用您的证书签名此包,因此如果您还没有这样做,请创建一个密钥库。

  3. 在 Google 的应用内购买被称为 Google Play Billing,而 Google Play Console 是您发布应用到 Google Play 商店时访问的网站名称。您需要注册为开发者并支付注册费。(对我来说,是 25 澳元。)一旦支付了费用,您就可以设置一个商户账户。

  4. 之后,是时候提供有关您应用程序的信息并上传商店图形,如截图和宣传视频。这也是您需要提供客户联系详情的地方。

  5. 您可以通过仅向组织内的开发者提供内部测试来开发应用内购买。一旦解决了问题,并且您的应用进入 alpha 阶段,您可以扩大测试并执行封闭测试。之后,在 beta 开发期间,您可以进行公开测试。

  6. 在 Google Play Console 网站上,点击您的应用并导航到商店展示 | 应用内产品 | 管理产品。

  7. 然后,点击蓝色按钮创建管理产品,如下截图所示:

https://github.com/OpenDocCN/freelearn-c-cpp-zh/raw/master/docs/hsn-mobi-emb-dev-qt5/img/198f86cb-a3ed-407a-9aef-a3186c37c014.png

这将打开一个名为新管理产品的新表单,如下截图所示:

https://github.com/OpenDocCN/freelearn-c-cpp-zh/raw/master/docs/hsn-mobi-emb-dev-qt5/img/e0331322-e604-4c12-9950-800fa479731e.png

  1. 在此表单中,完成以下字段:
  • 产品 ID:这将用于 Qt 应用程序标识符

  • 标题

  • 描述

  • 状态:活动或非活动

  • 价格:此价格限制在$0.99 和$550.00 之间

  1. 然后,点击保存。您将在 Google Play 上注册。

如果您为 Android 和 iOS 使用相同的产品 ID,将使开发应用内购买的过程更容易。

注册 iOS 应用商店

您应该已经注册在 Apple 开发者计划中,并已接受所有与税务、银行和其他数据相关的必要协议。

本节假设您已经注册了应用 ID,已签署相关协议等。在 iOS 上注册应用内购买相对简单:

  1. 导航到您的 Apple App Store Connect 账户并登录。点击应用,因为我们将要注册应用的内置产品。

  2. 点击您的应用,然后选择功能。在页面顶部,点击包含加号标记的蓝色圆圈,标记为应用内购买(0),如下截图所示:

https://github.com/OpenDocCN/freelearn-c-cpp-zh/raw/master/docs/hsn-mobi-emb-dev-qt5/img/8915d530-a536-4bf3-a013-f9364bdeaffd.png

您可以从以下选项中选择:

可消耗 应用一次使用后需要重新购买的项目
非消耗性 一次性购买但不失效的项目
自动续订订阅 自动续订的订阅内容
非续订订阅 不再续订的订阅内容
  1. 您将需要填写此过程的部分表格,因此事先决定以下标记项的值:
  • 参考名称

  • 产品 ID

  • 价格

  • 分级价格(起价$1.49)

  • 开始日期

  • 结束日期

  • 显示名称

  • 描述

  • 截图

  • 审查备注

一旦您有了产品 ID,请记住此信息以备后用。您在 Qt Creator 创建应用内购买产品时将需要它。

创建应用内产品

现在,真正的乐趣开始了!假设您设计了一个寻宝游戏,用户在地图上移动并寻找宝藏。在这种情况下,您可能希望提供加速游戏玩法,用户可以购买提示来帮助他们找到游戏的隐藏宝藏。

在我们的例子中,我们将出售颜色。颜色真的很好,因为它们是可收集的,并且用户可以相互之间出售和交易。

当你根据上一节中提到的在iOS App StoreAndroid Google Play上注册的说明开发并注册了你的应用后,你现在可以开发并测试 Qt 购买。我们将从使用 QML 开始。

在你的 QML 应用中使用 Qt 购买时的导入行如下所示:

import QtPurchasing 1.0

将以下行添加到配置文件中:

QT += purchasing

现在,决定你的应用内购买将是什么。请注意,Qt 购买有以下两种产品类型:

  • 消耗品

  • 可解锁

消耗品购买是一些一次性使用且可以多次购买的东西,例如游戏代币。一个例子是游戏货币。

可解锁购买是诸如额外角色、广告移除和关卡解锁等特性,这些特性可以被重新下载、恢复,甚至转移。

我们的颜色产品是一种消耗品购买,使用户能够购买他们想要的任意数量的颜色。

在 QtPurchasing 中,有以下三个 QML 组件:

  • 产品

  • 存储

  • 交易

存储

存储组件代表平台默认的市场;在 Android 上,它是 Google Play 商店,而在 iOS 上则是 Apple App Store。一个存储元素有一个方法,restorePurchases(),当用户想要恢复他们的购买时使用。

你可以将产品作为存储的子组件,或者作为独立组件,其中存储对象由 ID 指定。

产品

产品组件代表应用内购买产品。identifier属性对应于你在相关商店注册应用内购买产品时使用的产品 ID。

关于产品组件,有以下几点需要注意:

  • 产品可以是存储的子组件,或者可以使用存储id属性来引用

  • 产品可以有两种类型之一:Product.ConsumableProduct.Unlockable

  • 一个Product.Consumable产品可以购买多次,前提是购买已经完成

  • 一个Product.Unlockable产品一旦购买就可以恢复

以下代码演示了一个具有Product.Consumable类型的产品组件:

Store {
    id: marketPlace
}

Product {
    id: colorProduct
    store:marketPlace
    identifier: "some.store.id"
    type: Product.Consumable
}

Button {
    text: "Buy this color"
    onClicked: {
        colorProduct.purchase()
   }
}

现在,是时候继续我们的购买选项了。看看下面的截图:

https://github.com/OpenDocCN/freelearn-c-cpp-zh/raw/master/docs/hsn-mobi-emb-dev-qt5/img/2279ba26-a26f-4ae8-8c84-99ec66b8908b.png

要开始购买流程,请使用purchase()方法,OK 按钮调用该方法以从 Google Play 商店弹出以下对话框:

https://github.com/OpenDocCN/freelearn-c-cpp-zh/raw/master/docs/hsn-mobi-emb-dev-qt5/img/721ccae4-182d-470a-b472-e595b6359b94.png

注意到前面截图中的付款不是真实的,而是使用 Google 测试卡进行的。没有进行货币交换。

你现在将想要处理onPurchaseSucceededonPurchaseFailed信号。如果你有可恢复的产品,请在onPurchaseRestored信号中恢复,如下所示:

onPurchaseSucceeded: {
    console.log("sale succeeeded " +  transaction.orderId)
// do something like fill a model view

    transaction.finalize()
}

您还应该保存交易。当应用启动时,它会查询商店中的任何购买。如果用户已购买产品,onPurchaseSucceeded 信号将被调用,并带有每个购买的过渡 ID,这样应用就知道已经完成了哪些购买,并可以相应地采取行动。

以下截图说明了 Google Play 商店上的成功购买:

https://github.com/OpenDocCN/freelearn-c-cpp-zh/raw/master/docs/hsn-mobi-emb-dev-qt5/img/91ef0de1-ad7d-4f0a-9609-aab7f3ca4925.png

如果购买因任何原因失败,将调用 onPurchaseFailed,如下所示:

onPurchaseFailed: {
    console.log("product purchase failed " + transaction.errorString)
    transaction.finalize()
}

您可能希望为这里查看的任何事件提供用户通知,仅为了提供清晰并避免用户混淆。

交易

Transaction 代表市场商店中购买的产品,并包含有关购买的信息,包括 statusorderId 以及描述可能发生的任何错误的字符串。以下表格解释了这些属性:

errorString 描述错误的特定平台字符串
failureReason 可以是 NoFailureCanceledByUserErrorOccurred
orderId 由平台商店发出的唯一 ID
product product 对象
status 可以是 PurchaseApprovedPurchaseFailedPurchaseRestored
timestamp 交易发生的时间

Transaction 有一个方法:finalize()。所有交易无论成功与否都需要最终化。

一旦用户成功购买了一种颜色,他们应该会看到如下截图所示的内容:

https://github.com/OpenDocCN/freelearn-c-cpp-zh/raw/master/docs/hsn-mobi-emb-dev-qt5/img/c61ef7ce-92ee-45ba-be11-9b64e00b26b3.png

注意可解锁产品可以恢复。让我们继续前进,看看如何处理这一点。

恢复购买

用户可能出于多种原因想要恢复购买。也许他们重新安装了应用,切换到了新手机,或者甚至重置了现有的手机。

只有可解锁产品才能恢复。

恢复购买通过 restorePurchases() 方法初始化,然后将为每个恢复的购买调用 onPurchaseRestored 信号,如下所示:

Button {
    text: "Restore Purchases"
    onClicked: {
        colorProduct.restorePurchases()
    }
}

product 组件中,它看起来如下所示:

onPurchaseRestored: {
    // handle product
    console.log("Product restored "+ transaction.orderId)

}

如您所见,QML 使得添加内购以及必要时恢复它们变得非常简单。

摘要

Qt 使得实现内购相当简单。大部分工作将涉及整理您的应用,并在您平台的应用商店中注册内购产品。

您现在应该能够将内购产品注册到相关的应用商店。您还应该知道如何使用 QML 实现内购产品并执行商店转换。我们还探讨了如何恢复任何可解锁产品的购买。

这章全部关于手机应用和购买。在下一章中,我们将探讨各种交叉编译方法以及如何使用嵌入式设备进行远程调试。

第四部分:移动部署和设备创建

为移动和嵌入式设备编译以及部署可能会很具挑战性。在嵌入式设备的情况下,读者可能需要部署到一个裸机——包括操作系统在内。本节将涵盖将跨平台应用程序部署到移动和嵌入式设备的不同方法,以及如何使用 Raspberry Pi 为设备创建一个启动到 Qt 的过程。

本节包括以下章节:

  • 第十二章,交叉编译和远程调试

  • 第十三章,部署到移动和嵌入式

  • 第十四章,移动和嵌入式设备的通用平台

  • 第十五章,构建 Linux 系统

第十二章:交叉编译和远程调试

由于在嵌入式设备上使用 Linux 系统的可能性很大,我们将介绍在 Linux 上设置交叉编译器的步骤。手机平台有自己的开发方式,这也会被讨论。你将学习如何为不同设备编译跨平台应用,并通过网络或 USB 连接进行远程调试。我们将探讨各种移动平台。

在本节中,我们将涵盖以下主题:

  • 交叉编译

  • 连接到远程设备

  • 远程调试

交叉编译

交叉编译是一种在宿主机上为不同于宿主机运行架构的应用程序和库构建的方法。当你使用 Android 或 iOS SDKs 为手机构建时,你正在进行交叉编译。

做这件事的一个简单方法就是使用 Qt for Device Creation,或者 Qt 的 Boot to Qt 商业工具。它可用于评估或购买。

你不必自己构建任何工具和设备系统镜像。我为我使用的树莓派使用了 Boot to Qt。这使得设置变得更快、更简单。还有更多传统的为不同设备构建的方法,除了目标机器之外,它们大致相同。

如果你使用的是 Windows,交叉编译可能会有些棘手。你可以安装 MinGW 或 Cygwin 来构建自己的交叉编译器,安装 Windows Subsystem for Linux,或者安装预构建的交叉toolchain,例如来自 Sysprogs。

传统交叉工具

获取交叉编译器有许多方法。设备制造商可以与其软件栈一起发布交叉toolchain。当然,如果你正在构建自己的硬件,或者只是想创建自己的交叉toolchain,还有其他选择。你可以为你的设备架构下载预构建的交叉toolchain,或者自己构建它。如果你最终决定编译toolchain,你需要一台快速且健壮的机器,并且有大量的磁盘空间,因为它将花费相当长的时间来完成,并且会使用大量的文件系统——如果你构建整个系统,可能需要 50 GB。

DIY 工具链

对于某些项目,你可能需要或者必须(如果没有提供toolchain)自己构建自己的toolchain。以下是一些较为知名的交叉工具:

BitBake 被 OpenEmbedded、Yocto 和 Ångström(以及 Boot to Qt)使用,因此从其中之一开始可能最容易。您可以说它是 Buildroot 2.0,因为它是原始 Buildroot 的第二次版本。尽管如此,它是一个完全不同的构建。Buildroot 更简单,没有包的概念,因此升级系统可能更困难。

我将在第十五章 构建 Linux 系统中描述使用 BitBake 构建 toolchain,本质上它与构建系统镜像非常相似;事实上,它必须在构建系统镜像之前构建 toolchain

Buildroot

Buildroot 是一个帮助构建完整系统的工具。它可以构建交叉 toolchain 或使用外部的一个。它传统上使用 ncurses 接口进行配置,就像 Linux 内核一样。它还有一个新的 ncurses 配置器,但还有一个基于 Qt 的配置器。让我们使用那个吧!

在您解压缩 Buildroot 的目录中,运行以下命令:

make xconfig

哎!它使用 Qt 4。如果您不想安装 Qt 4,您始终可以使用 make menuconfigmake nconfig

这是 Qt 接口的外观:

https://github.com/OpenDocCN/freelearn-c-cpp-zh/raw/master/docs/hsn-mobi-emb-dev-qt5/img/7a3b33a4-4924-4da7-abb3-96a917070e28.png

默认情况下,Buildroot 将创建一个基于 BusyBox 的系统,而不是 glibc。

一旦您已配置好系统,保存配置并关闭配置器。然后运行 make,坐下来,让它构建。它将文件放入一个名为 output/ 的目录中,其中您的系统镜像位于一个名为 image 的目录中。

Crosstool-NG

Crosstool-NG 是用于构建工具链的,而不是系统镜像。您可以使用用 crosstools 构建的 toolchain 来构建系统,尽管您可能需要手动完成。

Crosstool-NG 与 Buildroot 类似,因为它使用 ncurses 配置要构建的 toolchain。一旦您解压缩它,您需要运行以下 bootstrap 脚本:

./bootstrap

要安装它,您需要使用以下 --prefix 参数调用配置器:

./configure --prefix=/path/to/output

您也可以按照以下方式本地运行它:

./configure --enable-local

它将告诉您需要安装的任何缺失的包。在我的 Ubuntu Linux 上,我必须安装 flexlziphelp2manlibtool-binncurses-dev

然后运行 makemake install,如果您配置了前缀。

您需要将 /path/to/output/bin 添加到您的 $PATH 中。

export PATH=$PATH:/path/to/output/bin

现在您可以运行以下配置:

./ct-ng menuconfig

https://github.com/OpenDocCN/freelearn-c-cpp-zh/raw/master/docs/hsn-mobi-emb-dev-qt5/img/09fb9dce-d527-44f9-a869-f34254b3f006.png

然后运行 make,这将构建交叉 toolchain

预构建工具

有公司为各种设备和架构提供了以下预构建的交叉工具链:

这些是一些较好的选择。我大多数都体验过,并且一度使用过。每个都附带安装和使用说明。Linaro、Debian 和 Fedora 都制作 ARM 交叉编译器。这是一本关于 Qt 开发的书籍,所以我将描述 Qt 公司的产品——Boot to Qt。

Boot to Qt

Qt 公司的 Boot to Qt 产品包含开发工具和预构建的操作系统镜像,你可以将其写入微 SD 卡或烧录到设备上运行。除了 Raspberry Pi 之外,他们还支持以下其他设备:

  • Boundary Devices i.MX6 Boards

  • Intel NUC

  • NVIDIA Jetson TX2

  • NXP i.MX 8QMax LPDDR4

  • Raspberry Pi 3

  • Toradex Apalis iMX6 和 iMX8

  • Toradex Colibri iMX6, iMX6ULL, 和 iMX7

  • WaRP7

我选择了 RPI,因为我已经有一个带有触摸屏的 3 型模型在身边。

当你运行系统镜像时,你会启动一个 Qt 应用程序,该应用程序充当示例应用程序的启动器。它还设置 Qt Creator 以便能够在设备上运行交叉编译的应用程序。你可以在 Qt Creator 中点击运行按钮来在设备上运行它。

Boot to Qt 是一种快速且简单的方法,可以在相对较小的系统上快速将原型运行在触摸屏上。Qt 公司目前正在努力让 Qt 在更小的设备上运行良好,例如微控制器。

你可以直接运行 Boot to Qt 的toolchain;你只需要源环境文件。在 Raspberry Pi 和 Boot to Qt 的情况下,它被称为environment-setup-cortexa7hf-neon-vfpv4-poky-linux-gnueabi。你也可以直接调用toolchain的 qmake 并在你的配置文件/path/to/x86_64-pokysdk-linux/usr/bin/qmake myApp.pro上运行它。

这里还有一个选择是直接使用 Qt Creator 并选择 Raspberry Pi 作为目标。

如果你使用 Windows,有几个选项你可以使用来获取交叉编译toolchain

Windows 上的交叉工具链

你可以在 Windows 上以几种方式交叉编译,我们可以简要地介绍一下。它们如下,但无疑还有其他未涵盖的:

  • Sysrogs 为 Windows 提供了预构建的交叉toolchain

  • Windows Subsystem for Linux.

Sysprogs

Sysprogs 是一家为在 Windows 上运行针对 Linux 设备的交叉工具链的公司。他们的toolchain可以从gnutoolchains.com/下载

  1. 安装完成后,启动一个 Qt 5.12.1(MinGW 7.3.0 64 位)控制台终端

  2. 你需要按照以下方式将toolchain添加到你的路径中:

set PATH=C:\SysGCC\raspberry\bin;%PATH%
  1. 按照以下方式将PATH添加到 Qt 的mingw中:
set PATH=C:\Qt\Tools\mingw730_64\bin;%PATH%

你还必须构建 OpenGL 和其他 Qt 的要求。

按照以下方式配置 Qt 以交叉编译:

..\qtbase/configure -opengl es2 -device linux-rasp-pi-g++ -device-option CROSS_COMPILE=C:\SysGCC\raspberry\bin\arm-linux-gnueabihf- -sysroot C:\SysGCC\raspberry\arm-linux-gnueabihf\sysroot -prefix /usr/local/qt5pi -opensource -confirm-license -nomake examples -make libs -v -platform win32-g++

Windows Subsystem for Linux

您可以安装 Windows Subsystem for Linux 来安装交叉编译器,您可以从 docs.microsoft.com/en-us/windows/wsl/install-win10 下载。

然后,您可以选择所需的 Linux 发行版——Ubuntu、OpenSUSE 或 Debian。一旦安装完成,您就可以使用内置的包管理器来安装 Linux 的 toolchain

移动平台特定工具

iOS 和 Android 都提供了预构建的交叉工具和 SDK,可供下载。如果您打算在移动平台上使用 Qt,则需要其中之一,因为 Qt Creator 依赖于原生平台构建工具。

iOS

Xcode 是您想要下载的 IDE 巨兽,它只能在 macOS X 上运行。如果您还没有,可以从桌面上的 App Store 下载它。您需要注册为 iOS 开发者。从那里,您可以选择要下载和设置的 iOS 构建工具。一旦开始下载,这个过程就相当自动化了。

您还可以从命令行使用这些工具,但您需要从 Xcode 内安装命令行工具。对于 Sierra,您只需在终端中输入 gcc 命令即可。在这种情况下,系统将打开一个对话框询问您是否想要安装命令行工具。或者,您可以通过运行 xcode-select --install 来安装它。

我不知道有任何嵌入式系统工具可以与 Xcode 一起使用,除非您将 iWatch 或 iTV SDKs 计算在内。这两个 SDK 您都可以通过 Xcode 下载。

您当然可以使用 Darwin,因为它开源且基于 伯克利软件发行版BSD)。您也可以使用 BSD。这远远达不到在任意嵌入式硬件上运行苹果操作系统的能力,因此您的选择有限。

Android

Android 为其 IDE 开发包提供了 Android Studio,并且适用于 macOS X、Windows 和 Linux 系统。

与 Xcode 一样,Android Studio 也提供了命令行工具,您可以通过 SDK 管理器或 sdkmanager 命令进行安装。

~/Android/Sdk/tools/bin/sdkmanager --list 将列出所有可用的包。如果您想下载 adbfastboot 命令,可以执行以下操作:

~/Android/Sdk/tools/bin/sdkmanager install "platform-tools"

Android 为其不同版本提供了吸引人的代码名称,这与它们的 API 级别完全不同。在安装 Android SDKs 时,您应该坚持使用 API 级别。我有一部运行 Android 版本 8.0.0 的手机,其代码名称为 Oreo。我需要安装 API 级别 26 或 27 的 SDK。如果我想安装 SDK,我可能会执行以下操作:

~/Android/Sdk/tools/bin/sdkmanager install "platforms;android-26"

在使用 Qt 进行开发时,您还需要安装 Android NDK。我使用的是 NDK 版本 10.4.0,或者称为 r10e,Qt Creator 与之配合工作得很好。我在运行较新版本的 NDK 时遇到了问题。正如他们所说,您的体验可能会有所不同。

QNX

QNX 是一个商业化的类 UNIX 操作系统,目前由 Blackberry 拥有。它不是开源的,但我认为在这里提一下是合适的,因为 Qt 在 QNX 上运行,并且在市场上被商业使用。

连接到远程设备

这是一本关于 Qt 开发的书,我将坚持使用 Qt Creator。连接到任何设备的方法几乎相同,只有一些细微的差别。你也可以通过终端使用 Secure Shell (SSH) 和其他相关工具进行连接。我经常使用这两种方法,因为每种方法都有其自身的优缺点。

Qt Creator

我记得当现在被称为 Qt Creator 的版本首次在诺基亚内部进行测试时,它被称为 Workbench。当时,它基本上是一个好的文本编辑器。从那时起,它获得了大量的优秀功能,并且它是我 Qt 项目的首选 IDE。

Qt Creator 是一个多平台 IDE,它可以在 macOS X、Windows 和 Linux 上运行。它可以连接到 Android、iOS 或通用的 Linux 设备。你甚至可以获得 UBports(开源 Ubuntu 手机)或 Jolla 手机等设备的 SDK。

要配置您的设备,在 Qt Creator 中导航到 工具 | 选项… | 设备 | 设备。

通用的 Linux

一个通用的 Linux 设备可能是一个定制的嵌入式 Linux 设备,甚至是一个树莓派。它应该运行一个 SSH 服务器。由于我使用了一个 RPI,我将使用它进行演示。

以下是在设备选项卡中显示的连接细节,用于树莓派:

https://github.com/OpenDocCN/freelearn-c-cpp-zh/raw/master/docs/hsn-mobi-emb-dev-qt5/img/acc696a5-6f66-4d2e-baa6-fff925e52696.png

如您所见,最重要的可能就是主机名。请确保主机名配置中的 IP 地址与设备的实际 IP 地址相匹配。其他设备可能使用常规网络而不是直接 USB 连接。

Android

您需要安装 Android SDK 和 NDK。

Android 是一个使用直接 USB 连接的设备,因此当运行应用程序时,复制应用程序二进制文件将更容易:

https://github.com/OpenDocCN/freelearn-c-cpp-zh/raw/master/docs/hsn-mobi-emb-dev-qt5/img/0b655d7d-223b-4a0c-86e3-243ac6d4bd8d.png

Qt Creator 大概会自动配置这个连接。

iOS

确保您的设备首先被 Xcode 发现,然后 Qt Creator 将自动识别并使用它。

它看起来可能像这张图片:

https://github.com/OpenDocCN/freelearn-c-cpp-zh/raw/master/docs/hsn-mobi-emb-dev-qt5/img/89ed76f8-f521-4bce-8035-e363aedea0f5.png

注意到那个类似绿色 LED 的图标吗?是的,一切正常!

硬件裸机

如果您的设备没有运行 SSH 服务器,您可以使用 gdb/gdbserver 或硬件服务器通过它进行连接。您首先需要启用插件。在 Qt Creator 中,导航到 帮助 | 关于插件 | 设备支持,然后选择裸机。裸机连接使用您可以从 http://openocd.org 获取的 OpenOCD。OpenOCD 不是一个新的焦虑症,而是一个通过 JTAG 接口运行的片上调试器。Qt Creator 还支持 ST-LINK 调试器。两者都使用 JTAG 接口。有 USB JTAG 接口以及传统的 JTAG 接口,它们不需要任何设备驱动程序即可连接。

写这部分内容让我想起了当 Trolltech 推出 Trolltech Greenphone 并使其运行,以及在其他设备上工作,比如 OpenMoko 手机时的情景。美好的时光!

现在我们已经连接了设备,我们可以开始调试。

远程调试

开发软件很困难。所有软件都有 bug。有些 bug 比其他 bug 更痛苦。最糟糕的情况可能是当你遇到一个随机崩溃,需要特定的触发事件序列,这些事件序列位于一个只读文件系统的远程设备上,而这个设备是在发布模式下构建的。我经历过。做过。甚至得到了一件 T 恤。(我还有许多来自过去的 Trolltech 和诺基亚 T 恤。)

传统上,远程调试涉及在设备上运行gdbserver命令。在非常小的机器上,由于没有足够的 RAM 直接运行 gdb,所以在远程设备上运行gdbserver可能是使用 gdb 的唯一方法。让我们播放一些 groove salad,开始工作吧!

gdbserver

你可能想体验没有 UI 的远程调试,或者类似的东西。这将帮助你开始。gdbserver命令需要在远程设备上运行,并且需要有一个串行或 TCP 连接。

远程设备上,运行以下命令:

gdbserver host:1234 <target> <app args>

使用host参数将在端口1234上启动gdbserver。你也可以通过运行以下命令将调试器附加到正在运行的应用程序:

gdbserver host:1234 --attach <pid>

pid是你试图调试的已运行应用程序的进程 ID,你可以通过运行ps、top 或类似的命令来获取。

主机设备上,运行以下命令:

gdb target remote <host ip/name>:1234

然后,你将通过运行gdb的控制台在主机设备上issue命令。

如果你遇到崩溃,崩溃发生后,你可以输入bt来获取一个回溯列表。如果你在远程设备上有崩溃内存转储,或者称为核心转储,gdbserver不支持远程调试核心内存转储。你必须远程运行gdb本身才能完成这项工作。

通过命令行使用gdb可能对某些人来说很有趣,但我更喜欢 UI,因为它更容易记住要完成的事情。拥有一个可以进行远程调试的 GUI 可以帮助你,如果你不太熟悉运行gdb命令,因为这可能是一项艰巨的任务。Qt Creator 可以进行远程调试,所以让我们继续使用 Qt Creator 进行调试。

Qt Creator

Qt Creator 在设备上使用gdbserver,所以它本质上只是一个 UI 界面。你需要为设备上的gdbserver提供 Python 脚本支持;否则,你会看到一条消息“Selected build of GDB does not support Python scripting”,并且它将无法工作。

对于大多数情况,使用 Qt Creator 进行调试对于 Android、iOS 和任何支持的 Boot to Qt 设备来说都是即插即用的。

在 Qt Creator 中加载任何项目,它都可以处理 C++ 调试,以及调试 Qt Quick 项目。请确保在 Qt Creator 的运行设置页面中正确配置了设置,在下面的调试器设置中,以启用所需的 qml 调试和/或 C++ 调试。

将以下内容添加到您的项目中并重新构建:

CONFIG+=debug qml_debug

将以下内容添加到应用程序启动参数 -qmljsdebugger=port:<port>, host:<ip>

要中断应用程序的执行,请单击工具栏上提示为 ‘中断 GDB for “yourapp”’ 的图标。然后您可以检查变量的值并逐行执行代码。

在某处设置一个断点——在相关的行上右键单击并选择在行上设置断点。

F5 开始应用程序构建(如果需要)。一旦成功构建,它将被传输并在设备上执行,同时远程调试服务启动。当然,如果设置了断点,它将在断点处停止执行。要继续正常执行,请按 F5 直到遇到那个痛苦的崩溃,然后您可以检查那个美妙的回溯!从这里,您可能希望收集足够的线索来修复它。

Qt Creator 默认支持的其他键命令如下:

  • F5:开始/继续执行

  • F9:切换断点

  • F10:跳过

  • Ctrl + F10:运行到当前行

  • F11:进入

  • Shift + F11:跳出

让我们试试。加载本章的源代码。

要在 Qt Creator 编辑器中的当前行切换断点,请按 Linux 和 Windows 上的 F9,或在 macOS 上按 F8,如下所示:

https://github.com/OpenDocCN/freelearn-c-cpp-zh/raw/master/docs/hsn-mobi-emb-dev-qt5/img/84ec6236-d9de-44d0-925a-7cd43c4d946a.png

现在通过按 F5 运行调试器中的应用程序来启动调试器。它将在我们的行上停止执行,如下所示:

https://github.com/OpenDocCN/freelearn-c-cpp-zh/raw/master/docs/hsn-mobi-emb-dev-qt5/img/3cda3273-f7a4-434a-ade2-c1f34afe8813.png

看到那个小黄色箭头吗?它告诉我们执行在执行语句之前停止了。

您将能够看到变量的以下值:

https://github.com/OpenDocCN/freelearn-c-cpp-zh/raw/master/docs/hsn-mobi-emb-dev-qt5/img/37d3bd2f-15bc-497d-912b-d62bcbfe5e15.png

如您所见,断点在 QString b 被初始化之前就停止了执行,所以其值是 ""。如果您按 F10 或跳过,QString b 将被初始化,您可以看到新的值如下:

https://github.com/OpenDocCN/freelearn-c-cpp-zh/raw/master/docs/hsn-mobi-emb-dev-qt5/img/9521bc99-abd2-4ab8-8367-8bac3e2ec6d1.png

您可以从下面的屏幕截图注意到,执行行也会移动到下一行:

https://github.com/OpenDocCN/freelearn-c-cpp-zh/raw/master/docs/hsn-mobi-emb-dev-qt5/img/681dd2a8-e4c7-4156-8b00-f1b79aaa78f9.png

您还可以通过在编辑器中右键单击断点并选择编辑断点来编辑断点。让我们在 for 循环的第 20 行设置一个断点,如下所示:

https://github.com/OpenDocCN/freelearn-c-cpp-zh/raw/master/docs/hsn-mobi-emb-dev-qt5/img/a0aba595-2164-4246-897d-b69b5d1ab132.png

右键单击并选择编辑断点以打开以下编辑断点属性对话框:

https://github.com/OpenDocCN/freelearn-c-cpp-zh/raw/master/docs/hsn-mobi-emb-dev-qt5/img/ce1db2e2-1e79-4137-aaf8-f78275a29dfd.png

编辑条件字段并添加 i == 15,然后点击确定:

https://github.com/OpenDocCN/freelearn-c-cpp-zh/raw/master/docs/hsn-mobi-emb-dev-qt5/img/edb54855-33c5-4510-a91c-1a0a26fe9d6c.png

通过点击 F5 运行调试器中的应用程序。点击字符串按钮。当它遇到断点时,你可以看到它停止时 i 包含的值是 15:

https://github.com/OpenDocCN/freelearn-c-cpp-zh/raw/master/docs/hsn-mobi-emb-dev-qt5/img/746ee8de-bda8-4c15-a82e-52ab03c6e52d.png

你可以接着进入或跳过。

让我们现在看看一个崩溃错误,当你点击崩溃按钮时,它是一个除以零的崩溃。

在第 31 行设置断点。运行调试器,它将在崩溃前停止。现在执行下一步。你应该会看到一个如下所示的对话框弹出:

https://github.com/OpenDocCN/freelearn-c-cpp-zh/raw/master/docs/hsn-mobi-emb-dev-qt5/img/64eb0602-9f5a-4c92-a5d0-32e74c5e48d7.png

哇,现在看起来真丑。

在以下屏幕截图所示的堆栈视图中,你可以看到程序崩溃的位置:

https://github.com/OpenDocCN/freelearn-c-cpp-zh/raw/master/docs/hsn-mobi-emb-dev-qt5/img/d12dc805-5795-4da9-83d0-15f05fd99896.png

是的,它就在我放的地方!在 C++ 中除以零会发生糟糕的事情。

摘要

调试是一个强大的过程,通常需要修复错误。你可以从命令行运行调试器,例如 gdb,或者你应该能够将 gdb 连接到在远程设备上运行的调试服务器。使用基于 GUI 的调试器会更有趣。你应该能够通过远程连接从 Qt Creator 调试运行在您的移动或嵌入式设备上的 Qt 应用程序。

下一步是部署你的应用程序。我们将探讨在几个不同的移动和嵌入式平台上部署你的应用程序的多种方法。

第十三章:部署到移动和嵌入式

手机、平板电脑和手表都有它们自己的平台方式来部署应用程序——通常是通过应用商店。部署插件和其他库需要特别注意。在本章中,我们将讨论其他操作系统,例如 Jolla 的 Sailfish OS,因为嵌入式设备有几种选择。我使用 Raspberry Pi 作为嵌入式 Linux 设备的示例。

对于主要的移动手机应用商店,您需要使用安全证书对您的包进行数字签名,系统使用该证书作为识别作者并足以信任应用程序的方式。

证书涉及一个公钥私钥对。私钥就是那个。您只需将其保密。公开证书可以公开分发。我不会深入讲解这里涉及的加密。Qt Creator 将这些证书称为密钥库,您可以使用 Qt Creator 生成这些自签名的证书。

我们将检查以下部署目标:

  • 对于 Android

  • 对于 iOS

  • 对于其他操作系统

  • 对于嵌入式 Linux

在备份您的数字证书时,因为如果您丢失它们,您将无法在商店中更新您的应用程序。

部署到 Android

Android 不需要 Google Play Store 来安装应用程序;这只是一个最方便的方式。还有其他市场可供选择,例如 Aptoid、Yandex、F-Droid 和 Amazon。

您还可以侧载应用程序。侧载是通过通过 USB、内存卡或通过互联网传输包来安装应用程序,而不使用官方商店。

从技术上讲,Qt Creator 可以侧载您正在工作的应用程序的包。它可以安装该包,或者在不安装的情况下简单地运行设备上的可执行文件。

实际上,您可以将包文件放在您的 web 服务器上,让人们将其下载到他们的手机或电脑上,并让他们手动安装。

您还可以通过官方发布使其在 Google Play Store 上可用。您需要能够使用从您的开发者账户获得的证书对其进行签名。这个 Android 证书不需要由证书颁发机构签名,但可以是自签名的。

在开发和测试您的应用程序之后,您需要制作一个包,以便人们可以安装它。制作包有几种方式,可以通过 Qt Creator 或通过命令行进行。

androiddeployqt

命令行工具 androiddeployqt 随 Qt Creator 一起提供,用于 Android SDK。这是一个命令行工具,用于帮助构建和签名 Android 包。要查看其帮助输出,请运行 /path/to/androiddeployqt --help。除了说明它可用之外,我不会深入讲解命令行部署。

Qt Creator

在 Qt Creator 中,数字证书在项目的构建设置页面上进行处理。现在,让我们构建:

  1. 项目 | 构建 将带您进入构建设置页面。如果您正在制作发布版本,请确保您的项目在此页面的顶部处于发布模式:

https://github.com/OpenDocCN/freelearn-c-cpp-zh/raw/master/docs/hsn-mobi-emb-dev-qt5/img/cc60086f-401b-4dae-bae2-f8d9b9ee95d7.png

  1. 导航到构建步骤 | 构建 Android APK:

https://github.com/OpenDocCN/freelearn-c-cpp-zh/raw/master/docs/hsn-mobi-emb-dev-qt5/img/4259b16a-acac-4ecd-878c-a2cda549bed9.png

  1. 您需要点击创建模板,这将创建商店所需的manifest.xml文件。两个主要条目是包名和应用程序图标。我使用 Android Studio 创建不同尺寸的图标,因为它在同时创建多个图标时效率最高。我从一个大 PNG 开始。务必选择正确的 SDK 版本,否则以后可能会出现问题:

https://github.com/OpenDocCN/freelearn-c-cpp-zh/raw/master/docs/hsn-mobi-emb-dev-qt5/img/b520da3d-9632-4120-b084-c8845de0a982.png

  1. 您还需要生成证书。在构建步骤页面中,找到签名包部分。密钥库条目最右侧的按钮,上面写着创建…,将弹出此对话框:

https://github.com/OpenDocCN/freelearn-c-cpp-zh/raw/master/docs/hsn-mobi-emb-dev-qt5/img/5229d052-7a5a-4fde-91e5-9478eb6286a7.png

  1. 您需要为密钥库和证书提供密码,它们是私钥和公钥。

  2. 您还需要提供您的姓名、组织、城市、州和两位字母的国家代码。

进行另一个构建,它将创建一个已签名的 Android 包,您可以安装或上传到商店。现在您已经准备好在封闭的、内部的或公开的测试轨道中测试您的应用了。Qt Creator 在那里不会帮到您。

测试轨道

您可以为您的组织中的测试者设置一个内部测试轨道。如果您是一家一站式商店,那么您就是测试者!

内部测试

您最多可以有 100 名内部测试者。要在 Play Console 上创建测试者列表,请导航到:

设置 | 管理测试者 | 创建列表

您需要提供以下信息:

  • 列表名称

  • 电子邮件地址(您也可以上传 csv 电子邮件列表!)

您可以添加测试者到应用(您可能之前已经添加或尚未添加)。现在选择您的应用,导航到应用发布,并选择以下选项之一:

  • 内部:一个内部封闭轨道

  • 首次发布:一个封闭轨道

  • 测试版:一个公开轨道

  • 生产:发布

点击管理(内部)| 创建发布

您需要确保您的账户已设置好,提供以下项目:

  • 商店列表—截图

    • 高分辨率图标(512 x 512 png)

    • 特色图形(1,024 x 500 jpg, png)

  • 内容评级

  • 定价和分发

在添加测试者之前,您需要上传一个应用包。一旦您上传了包应用,您将需要选择“审查”。

当您返回到应用发布页面时,您将被要求选择您的测试者列表。您应该会收到一个 URL,可以与您的测试者分享,以便他们可以下载并安装您内部测试的应用程序包。

页面顶部的那个红色图标意味着需要检查某些内容:

https://github.com/OpenDocCN/freelearn-c-cpp-zh/raw/master/docs/hsn-mobi-emb-dev-qt5/img/fe17f4e7-c342-4536-a563-5b8a16718b35.png

在我的情况下,我实际上还没有为这次发布选择任何测试者。

当您向测试者列表添加内容时,它应该看起来像这样:

https://github.com/OpenDocCN/freelearn-c-cpp-zh/raw/master/docs/hsn-mobi-emb-dev-qt5/img/441ba5b3-76aa-4d6b-9edb-528f92d0eef1.png

您的测试者将在 Google Play 商店看到以下内容:

https://github.com/OpenDocCN/freelearn-c-cpp-zh/raw/master/docs/hsn-mobi-emb-dev-qt5/img/34f25046-f7bd-4be8-bcc9-22349032d3ce.png

在 iOS 上的部署非常相似,所以让我们看看那个。

iOS 部署

iOS 商店可能是所有移动应用商店中最具限制性和复杂的,要提交应用。它也有更详尽的提交指南,例如:不应复制原生应用的功能。到达提交应用这一点的过程也更加复杂。

Qt Creator 支持创建和签名 iOS 包。与 Android 一样,您需要从您的开发者账户获取证书。这是您在 Apple 开发者账户登录时看到的:

https://github.com/OpenDocCN/freelearn-c-cpp-zh/raw/master/docs/hsn-mobi-emb-dev-qt5/img/8e06b7cc-dbe1-4bd2-a8bb-bdac7e502927.png

一旦进入您的开发者账户,点击标有“证书、标识符和配置文件”的图标以添加证书。注意左侧的证书列表:

https://github.com/OpenDocCN/freelearn-c-cpp-zh/raw/master/docs/hsn-mobi-emb-dev-qt5/img/1158d39c-545e-4369-bf22-811bb0495ec9.png

有两种类型的证书:开发证书和生产证书。生产证书用于发布分发。如果您没有生产证书,现在可以通过点击加号图标添加一个。

https://github.com/OpenDocCN/freelearn-c-cpp-zh/raw/master/docs/hsn-mobi-emb-dev-qt5/img/6676edda-60b5-4633-be35-477b482d7f44.png

这将打开以下对话框:

https://github.com/OpenDocCN/freelearn-c-cpp-zh/raw/master/docs/hsn-mobi-emb-dev-qt5/img/f614f553-fa2b-4d3d-8530-97cd0913a7b2.png

选择“App Store”和“Ad Hoc”。Ad Hoc 意味着您只能在少数测试设备上安装。

接下来,在“标识符”下,选择App IDs

https://github.com/OpenDocCN/freelearn-c-cpp-zh/raw/master/docs/hsn-mobi-emb-dev-qt5/img/32808075-68c2-4024-b4cf-66a13b2a9dd4.png

有两种类型的 App ID:

  • 通配符,如果应用不需要 iCloud 或应用内购买,则可用于多个应用。

  • 显式,用于每个应用的应用内购买。如果您有使用应用内购买的应用,您将需要其中之一。

您还需要配置文件:

https://github.com/OpenDocCN/freelearn-c-cpp-zh/raw/master/docs/hsn-mobi-emb-dev-qt5/img/e7736952-884c-44cb-9ab1-58abd4ae3da3.png

如您所见,这里有不同类型的配置文件。在“分发”下选择“App Store”,然后点击页面底部的“继续”按钮。选择您之前创建的 App ID。选择也之前创建的证书。您需要为此配置文件命名,并且它必须与您的包的 Bundle Identifier 匹配。下载此文件并将其保存在您记得的地方;您将需要此文件通过 XCode 对应用进行签名。

您还需要 App ID,因此从左侧选择 App IDs 并创建一个新的。

现在我们可以对发布模式的包进行签名。

Qt Creator

Qt Creator 本身无法创建 iOS 包。我们需要使用 Qt Creator 生成的 Xcode 项目文件来使用 Xcode 创建包。

选择发布构建,然后运行 qmake 以创建我们将使用的 Xcode 项目文件。

Xcode

从构建目录中,在 Xcode 中打开生成的 <target>.xcodeproj 文件。从左侧选择项目以打开项目设置。

点击“通用”,然后取消选择“自动管理签名”,以便能够手动选择包签名:

https://github.com/OpenDocCN/freelearn-c-cpp-zh/raw/master/docs/hsn-mobi-emb-dev-qt5/img/ce97988e-3681-465e-9e13-58faa7b578bc.png

您可以通过从标记为“配置文件”的下拉列表中选择“导入配置文件…”来导入配置文件,然后选择“导入配置文件…”。一旦文件对话框打开,您可以导航到您放置<profilename>.mobileprovision文件的位置:

https://github.com/OpenDocCN/freelearn-c-cpp-zh/raw/master/docs/hsn-mobi-emb-dev-qt5/img/2e63d252-9933-4248-b512-dcd53bc6fda8.png

您可以为签名(调试)和签名(发布)都这样做。

您的 Bundle 标识符必须与配置文件名称匹配。如果不匹配,它会提醒您:

https://github.com/OpenDocCN/freelearn-c-cpp-zh/raw/master/docs/hsn-mobi-emb-dev-qt5/img/9dd88fde-728d-4f92-85f5-af6d71652526.png

证书已经申请:

https://github.com/OpenDocCN/freelearn-c-cpp-zh/raw/master/docs/hsn-mobi-emb-dev-qt5/img/e32c019f-fc21-4c87-9185-d86dd740ed34.png

测试并构建包。在签名包时,它应该会要求您输入管理员密码。

要构建发布模式的包,请导航到产品 | 方案 | 编辑方案 | 信息 | 构建配置。

选择发布然后构建包。

从这里,您需要再次打开您的网络浏览器并导航到 App Store Connect,选择我的应用,然后点击+创建新应用:

https://github.com/OpenDocCN/freelearn-c-cpp-zh/raw/master/docs/hsn-mobi-emb-dev-qt5/img/275b8f25-d0f2-4a9d-96f9-3fc5f5e3f244.png

从这里,填写应用信息、定价和可用性。

接下来,为应用页面和 App Store 添加截图、图标、商店文本和其他项目。

要从 Xcode 上传您的应用,请选择产品 | 归档。

这将创建包并打开存档窗口。

在将包上传到 App Store 之前,您应该验证包,因此请选择“验证应用”,然后选择配置文件和证书:

https://github.com/OpenDocCN/freelearn-c-cpp-zh/raw/master/docs/hsn-mobi-emb-dev-qt5/img/718f21e1-6790-42ff-965b-ebe8a3854b06.png

修复您可能遇到的所有问题。这还包括为应用商店的店面添加截图、应用图标等。然后您可以点击“分发应用”按钮。

替代操作系统

有其他可用的移动和嵌入式操作系统,您可能听说过,也可能没有听说过,例如我最喜欢的替代移动操作系统:Jolla 的 Sailfish。另一个操作系统是 UBports,它是 Canonical 现在已停用的移动手机操作系统 Ubuntu Touch 的开源版本。

Sailfish OS

Sailfish OS 是诺基亚 MeeGo 的延续,而 MeeGo 又是 Maemo 的延续。

用户界面由 Jolla 开发,基础操作系统是开源的 Mer,由 Jolla 和社区开发。

Jolla 有一个名为 Harbour 的应用商店(harbour.jolla.com)。目前,您无法通过这个应用商店销售应用:

https://github.com/OpenDocCN/freelearn-c-cpp-zh/raw/master/docs/hsn-mobi-emb-dev-qt5/img/3b80ce25-022e-4522-a811-993aede9f94f.png

这就是我的开发者页面看起来像:

https://github.com/OpenDocCN/freelearn-c-cpp-zh/raw/master/docs/hsn-mobi-emb-dev-qt5/img/53a4b008-a2bc-48d5-afb7-d455b8db017d.png

是的,自从我上次更新它以来已经过去了五年。

您可以将 Jolla 安装到某些 Android 手机上——如果您足够幸运拥有实际的 Jolla 硬件,或者如果您有一部预装了 Jolla 的手机,您可以通过商店应用访问 Harbour。以下是顶级应用页面的视图:

https://github.com/OpenDocCN/freelearn-c-cpp-zh/raw/master/docs/hsn-mobi-emb-dev-qt5/img/90c6fa09-d95f-49d1-8a68-03a4de6ae1d8.png

在那个商店里,我有一个名为 Compass 的老应用,我需要将其更新到新的 Sailfish OS 版本 3:

https://github.com/OpenDocCN/freelearn-c-cpp-zh/raw/master/docs/hsn-mobi-emb-dev-qt5/img/bdb1a474-9b30-45d3-aba4-38eba4e03350.png

我需要从 releases.sailfishos.org/sdk/installers/1.24/ 下载应用程序 SDK。

我将其安装在以下 Linux 开发机上:

chmod +x SailfishOSSDK-Beta-1.24-Qt5-linux-64-offline.run
./SailfishOSSDK-Beta-1.24-Qt5-linux-64-offline.run 

Sailfish OS SDK 已安装!

https://github.com/OpenDocCN/freelearn-c-cpp-zh/raw/master/docs/hsn-mobi-emb-dev-qt5/img/30c4bfde-de9d-466f-8a16-e5dd16b3c58b.png

在 Qt Creator 打开并带有 Sailfish SDK 后,点击左侧的 Sailfish OS 图标:

https://github.com/OpenDocCN/freelearn-c-cpp-zh/raw/master/docs/hsn-mobi-emb-dev-qt5/img/3002229b-0519-4003-bb03-c529c33f21a0.png

您应该会看到一个消息说构建引擎没有运行,因此我们需要启动它。点击“启动构建引擎!”:

https://github.com/OpenDocCN/freelearn-c-cpp-zh/raw/master/docs/hsn-mobi-emb-dev-qt5/img/376220ef-6daf-42ff-9aa0-1ac9555d8342.png

完成上述操作后,您将进入 Sailfish 控制中心,在那里您可以向 SDK 添加组件,并在需要时应用更新。

如果您有设备,请设置您的设备,并导航到“工具”|“选项”|“设备”。

到达那里后,点击“工具”|“选项”|“套件”,并选择 armv7hl 套件。

在“设备选项”部分,请确保选择您之前设置的 Jolla 设备而不是模拟器,除非您想将应用运行在模拟器上。

Jolla SDK 构建引擎在虚拟机中运行,因此可以从任何平台使用。它使用的 IDE 是 Qt Creator。Jolla 的独特之处在于您不仅可以运行 Jolla OS 的原生应用,还可以运行 Android 应用。关于 Android 支持的注意事项是,没有 Google Play API。

现在,构建引擎(交叉编译器)正在运行,我们可以构建我们的应用,测试,然后制作一个包上传到 Harbour。

从“项目”|“运行设置”中,确保选择了“通过复制二进制文件部署”或“作为 RPM 包部署”作为方法。如果您在没有安装包的情况下运行它,请选择“复制方法”。以下是我在 Jolla 手机上运行的更新后的 Compass 应用程序:

https://github.com/OpenDocCN/freelearn-c-cpp-zh/raw/master/docs/hsn-mobi-emb-dev-qt5/img/5a2051d2-468b-4d36-9967-91fa018c8463.png

一旦构建了发布包,导航到 Sailfish OS | 发布,点击 RPM 验证器,并选择您的包文件进行验证。

Jolla 商店界面是这里移动应用商店中最简单的,部分原因是因为这里没有应用销售。

点击“添加应用”。您需要填写以下内容:

  • 标题

  • 详细信息:

    • 描述

    • 摘要

    • 近期更改

  • 分类

  • 兼容性

  • 视觉辅助(截图、图标)

  • 联系方式

  • 任何给 QA 的信息

一旦您将应用程序提交到 Jolla 的 Harbour 商店,您将看到类似以下图片的内容:

https://github.com/OpenDocCN/freelearn-c-cpp-zh/raw/master/docs/hsn-mobi-emb-dev-qt5/img/4e5fb11b-d6c6-49fa-9c70-04f850fd776c.png

QA 将检查包,要么拒绝要么接受它。

UBports

UBports 是基于 Canonical 现已停用的移动产品 Ubuntu Touch 的融合操作系统。融合意味着它被设计为可以在桌面和移动设备上运行。它运行在多种手机和平板电脑上,UI 基于 Qt 和 QML。我在 Nexus 4 上运行了我的系统。我不会详细介绍,但我想提一下。更多信息可以在:ubports.com/ 上找到。

ubuntu-touch.io/get-ut有一个简单的安装程序,可以将 UBports 安装到支持的设备上。

你可以在docs.ubports.com/en/latest/appdev/index.html获取 SDK。

可点击的

UBports SDK 可以从命令行生成 click 包。Ubuntu Touch SDK IDE 不再由 Canonical 或 UBports 支持。商店被称为 OpenStore。你需要一个账户,提交应用的 URL 是open-store.io/submit

嵌入式 Linux

嵌入式 Linux 设备有多种不同的尺寸和种类。一些可能有应用商店,但大多数没有。有各种方法将操作系统和应用程序安装到设备上。

操作系统部署

操作系统的部署将直接在你的设备上进行,因为一些嵌入式设备有非常特定的方法来部署操作系统到设备上。以树莓派为例,将镜像复制到 SD 卡并放入 RPI 中启动非常简单。

我有一个名为writeIso的脚本,我使用它;它由两行组成:

#!/bin/bash
sudo dd if=$1 of=$2 bs=4M status=progress

我运行它的方式如下:

./writeIso /path/to/deviceImage.img /dev/sdc

其他设备可能有一种flash方法,即镜像直接复制到设备上。这可能需要使用 JTAG 这样的低级方法,或者可能是使用 Android 的adb命令这样的高级方法。有时,你必须将镜像写入 SD 卡,将其放入设备中,然后通过一些按键或按钮的组合将镜像闪存到机器的 ROM 中。

应用部署

使用带有包管理器的发行版,如 Raspbian 或 Yocto,你可以轻松地分发你的应用程序,无论是直接在设备上安装还是添加到包仓库。在 Yocto 的情况下,你可以有一个本地仓库来分发。

要将包文件安装到设备上,你可以使用 Qt Creator 并设置一个通用的 Linux 设备。这需要在设备上运行一个 SSH 服务器,并建立某种类型的网络连接。

你还可以使用scp命令将包和/或二进制文件复制到设备上。这也需要一个 SSH 服务器。

摘要

操作系统和应用程序的部署有几种不同的方法。手机有应用商店,它们也有各种提交应用程序的方法。通常,这些操作是通过网页浏览器完成的。你应该能够将应用程序发布到 Android、iOS 和替代操作系统,如 Jolla 的 Sailfish 应用商店。

你也应该能够将你的应用程序分发到嵌入式设备上,例如树莓派。

在下一章中,我们将探讨 Qt for WebAssembly 这项全新的技术,它允许 Qt 应用程序在网页浏览器中运行。

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐