Android: Start avd emulator via detached process

Address the TODO about the process leak.

Before, the process was potentially started in a separate thread.
In this case the done handler of the process could work only
when the thread was still executing, otherwise it couldn't be
invoked because of the missing event loop in a separate thread.
Thus, it was only serving for start process failures which are
raised synchronously.

After the successful start the process thread finished soon,
and then we were losing a handle to the running process.
Later, on shutdown, the process was still running (so a possible
assert from process laucher could have been triggered), and
the emulator process kept running after the Creator shutdown.

This patch executes the emulator process as a detached process.
This makes it possible to continue running the process after
the Creator shutdown without leaking a Process instance.
We also handle the detached process start failure and execute
a message box.

Change-Id: I855d280c257a0cfaca7722a3b1e14d1ead9021f7
Reviewed-by: Alessandro Portale <alessandro.portale@qt.io>
This commit is contained in:
Jarek Kobus
2024-06-26 08:36:01 +02:00
parent 6a2ad83477
commit b45e9bdf3a

View File

@@ -45,46 +45,32 @@ static bool is32BitUserSpace()
bool startAvdAsync(const QString &avdName) bool startAvdAsync(const QString &avdName)
{ {
const FilePath emulator = AndroidConfig::emulatorToolPath(); const FilePath emulatorPath = AndroidConfig::emulatorToolPath();
if (!emulator.exists()) { if (!emulatorPath.exists()) {
QMetaObject::invokeMethod(Core::ICore::mainWindow(), [emulator] { QMetaObject::invokeMethod(Core::ICore::mainWindow(), [emulatorPath] {
QMessageBox::critical(Core::ICore::dialogParent(), QMessageBox::critical(Core::ICore::dialogParent(),
Tr::tr("Emulator Tool Is Missing"), Tr::tr("Emulator Tool Is Missing"),
Tr::tr("Install the missing emulator tool (%1) to the" Tr::tr("Install the missing emulator tool (%1) to the"
" installed Android SDK.") " installed Android SDK.")
.arg(emulator.displayName())); .arg(emulatorPath.displayName()));
}); });
return false; return false;
} }
// TODO: Here we are potentially leaking Process instance in case when shutdown happens CommandLine cmd(emulatorPath);
// after the avdProcess has started and before it has finished. Giving a parent object here
// should solve the issue. However, AndroidAvdManager is not a QObject, so no clue what parent
// would be the most appropriate. Preferably some object taken form android plugin...
Process *avdProcess = new Process;
avdProcess->setProcessChannelMode(QProcess::MergedChannels);
QObject::connect(avdProcess, &Process::done, avdProcess, [avdProcess] {
if (avdProcess->exitCode()) {
const QString errorOutput = QString::fromLatin1(avdProcess->rawStdOut());
QMetaObject::invokeMethod(Core::ICore::mainWindow(), [errorOutput] {
const QString title = Tr::tr("AVD Start Error");
QMessageBox::critical(Core::ICore::dialogParent(), title, errorOutput);
});
}
avdProcess->deleteLater();
});
// start the emulator
CommandLine cmd(emulator);
if (is32BitUserSpace()) if (is32BitUserSpace())
cmd.addArg("-force-32bit"); cmd.addArg("-force-32bit");
cmd.addArgs(AndroidConfig::emulatorArgs(), CommandLine::Raw); cmd.addArgs(AndroidConfig::emulatorArgs(), CommandLine::Raw);
cmd.addArgs({"-avd", avdName}); cmd.addArgs({"-avd", avdName});
qCDebug(avdManagerLog).noquote() << "Running command (startAvdAsync):" << cmd.toUserOutput(); qCDebug(avdManagerLog).noquote() << "Running command (startAvdAsync):" << cmd.toUserOutput();
avdProcess->setCommand(cmd); if (Process::startDetached(cmd, {}, DetachedChannelMode::Discard))
avdProcess->start(); return true;
return avdProcess->waitForStarted(QDeadlineTimer::Forever);
QMetaObject::invokeMethod(Core::ICore::mainWindow(), [avdName] {
QMessageBox::critical(Core::ICore::dialogParent(), Tr::tr("AVD Start Error"),
Tr::tr("Failed to start AVD emulator for \"%1\" device.").arg(avdName));
});
return false;
} }
QString findAvd(const QString &avdName) QString findAvd(const QString &avdName)