使用 QT 进行跨平台应用开发:iOS 设备上的状态转换
本文主要介绍如何在 iOS 设备上处理应用程序的状态转换。
引言
Qt 以开发跨平台应用程序而闻名,即 Windows、iOS、Android 和 Linux。编写一次代码,即可构建为不同的目标平台,这非常方便。此前,我一直认为使用网络通信的应用程序应该能够正常工作。
后来,我遇到一个问题。假设应用程序A正在运行,如果我们锁定设备或打开另一个应用程序,那么应用程序A的状态就会改变。
上述场景在Windows和Android上都可以正常工作,但在iOS上却不行。
本文将介绍在 iOS 上使用 Qt 使其正常工作的原因和解决方案。
iOS 中的应用程序状态转换
在 iOS 中,每个应用程序都是一个 UIApplication
对象,其职责是促进系统与其他对象之间的交互。
iOS 应用程序使用模型-视图-控制器架构;应用程序控制器包含以下组件:
UIApplication
- 事件循环
- View Controller (视图控制器)
AppDelegate
AppDelegate
与 UIApplication
协同工作,处理应用程序初始化、状态转换以及许多高级应用程序事件。
应用程序的执行状态
状态 |
描述 |
未运行 |
应用程序尚未启动,或已运行但被系统终止。 |
不活跃 |
应用程序在前台运行,但当前未接收事件。(但它可能正在执行其他代码。)应用程序通常仅在此状态下短暂停留,然后转换到另一个状态。 |
活动 |
应用程序在前台运行并接收事件。这是前台应用程序的正常模式。 |
背景 |
应用程序在后台运行并执行代码。大多数应用程序会短暂进入此状态,然后进入挂起状态。但是,请求额外执行时间的应用程序可能会在此状态下停留一段时间。此外,直接启动到后台的应用程序会进入此状态而不是不活跃状态。 |
Suspended |
应用程序在后台但未执行代码。系统会自动将应用程序移至此状态,并且不会提前通知。挂起期间,应用程序将保留在内存中,但不会执行任何代码。当出现低内存情况时,系统可能会在不发出通知的情况下清除挂起应用程序,以为前台应用程序腾出更多空间。 |
每个 iOS 应用程序始终处于这五种状态之一。下图显示了 iOS 应用程序的状态转换。

操作系统管理应用程序状态,并限制应用程序在后台可以执行的操作。如果应用程序需要在后台继续运行,则应妥善处理。
问题
当应用程序进入后台状态时(例如,为了优化电池使用和应用程序性能等),操作系统会限制应用程序在后台可以执行的功能。
例如,如果一个使用套接字通信的应用程序进入后台,则套接字将被操作系统关闭(只有 iOS 会这样做,Windows 和 Android 不会发生这种情况),当应用程序进入前台时,套接字通信才会恢复。但事实并非如此。
Qt 还提供了一个 enum
来指示应用程序的当前状态。以下是这些状态:
状态 |
描述 |
Qt::ApplicationSuspended (应用程序挂起) |
应用程序即将挂起。进入此状态时,应用程序应保存其状态,停止所有活动,并准备好代码执行停止。挂起期间,应用程序随时可能被终止,而不会收到进一步的警告(例如,低内存迫使操作系统清除挂起应用程序)。 |
Qt::ApplicationHidden (应用程序隐藏) |
应用程序被隐藏并在后台运行。对于需要执行后台处理(如播放音乐)的应用程序来说,这是正常状态,此时用户可能正在使用其他应用程序。应用程序在进入此状态时应释放所有图形资源。 |
Qt::ApplicationInactive (应用程序不活跃) |
应用程序可见,但未被选为前台。在桌面平台上,这通常意味着用户激活了另一个应用程序。在移动平台上,当操作系统通过来电或短信等中断用户时,更常见的情况是进入此状态。在此状态下,请考虑减少 CPU 密集型任务。 |
Qt::ApplicationActive (应用程序活跃) |
应用程序可见,并且被选为前台。 |
处理这些状态应该可以解决 iOS 平台上的问题。不幸的是,我在一个示例程序中尝试处理这些状态,但程序没有收到操作系统生成的信号,即应用程序在前台、后台等。
解决方案
为了在应用程序进入后台时保存其状态并在进入前台时重新加载状态,我不得不在我的 Qt 应用程序中实现特定于 iOS 的代码。我通过将 Objective-C 与 C++ 代码集成来实现这一点。下一节将简要介绍这一点;我对 iOS 开发知之甚少,但开发了一个简单的 POC 来展示 C++ 和 Objective-C 代码的集成。
创建一个基于 QtQuick 的项目,例如,AppDelegate
,然后按照以下步骤操作:
- 向项目中添加
IOSAppState
类 - 向项目中添加 AppDelegate.h 文件
- 向项目中添加 AppDelegate.mm 文件
IOSAppState
此类将用作主程序与特定于 Objective-C 的代码块之间的交互层。
IOSAppState.h
#ifndef IOSAPPSTATE_H
#define IOSAPPSTATE_H
#include <QObject>
void InitializeDelegate();
class IOSAppState : QObject
{
Q_OBJECT
explicit IOSAppState(QObject* parent=0);
~IOSAppState();
public:
static IOSAppState* getInstance();
void applicationDidEnterBackGround();
void applicationDidEnterForeGround();
void applicationDidBecomeActive();
static void destroyInstance();
private:
static IOSAppState* m_delegate;
};
#endif // IOSAPPSTATE_H
IOSAppState.cpp
#include "iosappstate.h"
#include <QDebug>
IOSAppState* IOSAppState::m_delegate = nullptr;
IOSAppState::IOSAppState(QObject* parent): QObject(parent)
{
}
IOSAppState::~IOSAppState()
{
}
IOSAppState* IOSAppState::getInstance()
{
if(nullptr == m_delegate)
{
m_delegate = new IOSAppState();
}
return m_delegate;
}
void IOSAppState::destroyInstance()
{
if(m_delegate)
{
delete m_delegate;
m_delegate = nullptr;
}
}
void IOSAppState::applicationDidBecomeActive()
{
qDebug()<<Q_FUNC_INFO;
}
void IOSAppState::applicationDidEnterBackGround()
{
qDebug()<<Q_FUNC_INFO;
}
void IOSAppState::applicationDidEnterForeGround()
{
qDebug()<<Q_FUNC_INFO;
}
IOSAppState
将是一个单例类,用于建立 iOS 特有事件与 Qt 应用程序之间的通信。
通过使用 IOSAppState
的单例实例,可以调用 applicationDidBecomeActive()
、applicationDidEnterBackGround
和 applicationDidEnterForeGround()
。可以从这些方法发出信号,并在负责的模块中处理相同的事件。例如,如果我们必须处理特定于网络的场景,那么可以有一个网络模块,该模块将拥有用于在这些信号上执行操作的槽。
我添加了调试消息,以便跟踪应用程序的流程。
AppDelegate
AppDelegate.h
#ifndef APPDELEGATE
#define APPDELEGATE
#import <UIKit/UIKit.h>
#import <iosappstate.h>
@interface AppDelegate : UIResponder <UIApplicationDelegate>
+(AppDelegate *)sharedAppDelegate;
@end
#endif // APPDELEGATE
它具有 AppDelegate
接口和 AppDelegate*
类型的属性,将用于实例化。
AppDelegate.mm
这是一个实现文件,其中将包含 AppDelegate
接口的 Objective-C 特定实现。
#import "appdelegate.h"
#import "iosappstate.h"
@implementation AppDelegate
static AppDelegate *appDelegate = nil;
+(AppDelegate *)sharedAppDelegate{
static dispatch_once_t pred;
static AppDelegate *shared = nil;
dispatch_once(&pred, ^{
shared = [[super alloc] init];
});
return shared;
}
void InitializeDelegate ()
{
//get app delegate.
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
[[UIApplication sharedApplication] setDelegate:[AppDelegate sharedAppDelegate]];
NSLog(@"Created a new appdelegate");
}
sharedAppDelegate()
的职责是检查 AppDelegate
实例是否已实例化。它将返回 AppDelegate*
类型的实例。
InitializeDelegate ()
方法的作用是触发 Objective-C 代码路径的实例化/执行。此方法将主要检索单例应用程序实例并设置委托。
- (void)applicationDidBecomeActive:(UIApplication *)application
{
// Restart any tasks that were paused (or not yet started) while the application was inactive.
// If the application was previously in the background, optionally refresh the user interface.
NSLog(@"In the Become Active");
IOSAppState::getInstance()->applicationDidBecomeActive();
}
- (void)applicationWillTerminate:(UIApplication *)application
{
// Called when the application is about to terminate.
// Save data if appropriate. See also applicationDidEnterBackground:.
}
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[application setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalMinimum];
// Override point for customization after application launch.
NSLog(@"DId this launch option happen");
return YES;
}
- (void)applicationWillResignActive:(UIApplication *)application
{
// Sent when the application is about to move from active to inactive state.
// This can occur for certain types of temporary interruptions
// (such as an incoming phone call or SMS message) or when the user quits
// the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down
// OpenGL ES frame rates. Games should use this method to pause the game.
}
- (void)applicationDidEnterBackground:(UIApplication *)application
{
// Use this method to release shared resources, save user data,
// invalidate timers, and store enough application state information
// to restore your application to its current state in case it is terminated later.
// If your application supports background execution,
// this method is called instead of applicationWillTerminate: when the user quits.
NSLog(@"In the background");
IOSAppState::getInstance()->applicationDidEnterBackGround();
}
- (void)applicationWillEnterForeground:(UIApplication *)application
{
// Called as part of the transition from the background to the inactive state;
// here you can undo many of the changes made on entering the background.
NSLog(@"In the foreground");
IOSAppState::getInstance()->applicationDidEnterForeGround();
}
@end
现在,每当应用程序进入后台或更改状态时,操作系统都会触发一个事件,即 applicationDidEnterBackground
、applicationWillEnterForeground
等。
从这些事件中,我们可以调用 IOSAppState
的成员方法来与基于 C++ 的模块进行通信。
上面提供的文件实现描述了基本的 Objective-C 代码以及如何将其与基于 .cpp 的模块进行通信。
main.cpp
#include <QApplication>
#include <QQmlApplicationEngine>
#include "iosappstate.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
InitializeDelegate();
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
突出显示的方法,即 InitializeDelegate()
,在 IOSAppState.h 中声明。调用此函数将导致 AppDelegate
实例的初始化。
这是一个简单的程序,仅用于展示 iOS 特定实现与 C++ 的集成。
结论
尽管 Qt 以其跨平台开发而闻名,但有时我们可能会遇到特定于操作系统的障碍。对于这种情况,没有直接的解决方案;我必须找到一种方法将两者集成到一个项目中,并根据操作系统启用执行路径。本文就是基于这种情况的结果。