Qt Quickでスプラッシュスクリーンを表示

こんにちは。こんばんわ?
Qt Advent Calendar 2017の24日目を担当する@IoriAYANEです。

今回は、Qt Quickでスプラッシュスクリーンを表示しつつ、その必要性について語りたいと思います。
他のカレンダーの記事と比べるととってもゆるふわですが、ご了承ください。

/// まずは導入してみる ///
 導入してみます。
 今回のサンプルの主なファイルは以下の通りです。

  ・main.qml
  ・MainWindow.qml

 main.qmlの方でスプラッシュスクリーンを表示する部分を作り込みます。
 MainWindow.qmlは今回のサンプルではどんな内容でも良いので省略します。

 サンプルの仕様を簡単にまとめると以下の通りです。

  ・起動後、画像だけのウインドウを表示
  ・上記ウインドウが表示できしだいメインウインドウのロード開始
  ・メインウインドウのロードが完了しても最低2秒は、上記ウインドウを表示しつづける

 です。

import QtQuick 2.9
import QtQuick.Window 2.3

Item {
    property double startTime: 0

    //スプラッシュスクリーンに相当するウインドウ
    Window {
        id: splash
        visible: true
        width: 480
        height: 300
        //ウインドウ枠をなし・閉じるボタンなどをなしにする
        flags: Qt.SplashScreen
        Component.onCompleted: {
            //スプラッシュスクリーンができたらアプリ本体である
            //MainWindow.qmlを読み込み開始
            startTime = new Date().getTime()
            loader.source = "MainWindow.qml"
        }
        Image {
            //かっこいい絵を用意しよう
            anchors.centerIn: parent
            source: "images/splash_screen.png"
        }
        //ロードが終わっても一定時間は表示するために
        //時間を管理するためのタイマー
        Timer {
            id: timer
            repeat: false
            interval: 1000
            onTriggered: {
                splash.close()
                loader.item.visible = true
            }
        }
    }

    Loader {
        id: loader
        asynchronous: true
        onLoaded: {
            var elapseTime = new Date().getTime() - startTime
            if(elapseTime > 2000){
                //スプラッシュスクリーンが一定時間以上表示されているので
                //すぐにウインドウを切り替える
                splash.close()
                loader.item.visible = true
            }else{
                //ロードは終わったけど一定時間たってないのでちょっと待つ
                timer.interval = 2000 - elapseTime
                timer.start()
            }
        }
    }
}

 対して難しくないですね。
 
 QMLの使い方というかエレメントの組み方でポイントになっているのはアプリケーションのルートにItemエレメントを使用していることです。
 むかーしは、どんなエレメントを使用してもウインドウが生成されていたのですが、現状はApplicationWindowやWindowエレメントのようなウインドウ系のエレメントをあえて使用しないとウインドウは生成されません。
 複数のウインドウを持つアプリケーションを作成しやすくするための措置だと思うのですが、C++(main.cppなど)からQMLを利用するときに使用するクラスにQQmlApplicationEngineクラスを使用するとウインドウが表示されません。そのQQmlApplicationEngineクラスがQt Creatorがプロジェクトを新規作成したときに使われているため、今回のサンプルのような構成でウインドウを二つ並列で用意するといったことが可能になります。ちなみに、QQuickViewクラスを使用するとウインドウが作成されます。

 Windowエレメントのflagsプロパティでウインドウの形態(ウインドウ枠をダイアログ形式や×ボタンを無くすとか)を設定できます。しかも、スプラッシュスクリーン用も用意されいて準備がとても良いです。
 どんな値が設定できるかは「enum Qt::WindowType」を参照してください。

 Loaderエレメントのasynchronousプロパティをtrueにするとメインウインドウをロード中にスプラッシュスクリーンをブロックしないです。今回の作りではたいして意味はないですが......。何らかの状況表示を更新する場合には必要です。

 やっぱり、簡単ですね。


/// で、なんでいるんだっけ? ///
 Qt Quickのアプリケーション(というよりQt QuickでGUIを作ったアプリケーション)は、実行時にテキストのQMLファイルを解釈している関係で起動が遅いです。しかも、QMLファイルの解釈が終わるまでウインドウなどは表示されず、ユーザーにはダブルクリックのミスを疑わせたりと非常にわかりづらい状況です。
 そんな中でQt 5.9からQt Quick Compilerの恩恵を一部受けられるようになりました。QMLのキャッシュファイル(*.qmlc, *.jsc)を用意して近い状況にしてくれるようになりました。デフォルトでは初回起動時に作成します。そのため、起動速度が2回目以降で改善されました。
 
 そうは言っても大規模なアプリケーションで読み込むQMLファイルが多いと起動に時間がかかるのは避けられません。
 そこで、スプラッシュスクリーンです。
 可能な限り軽量に作成したスプラッシュスクリーンを表示し、その後、メインのGUIを含むQMLファイルを読み込む流れにします。
 もちろん、アプリ本体の扉になる画面が軽量ならそこから必要に応じて逐次ロードもありですね。それを極端にしたのがスプラッシュスクリーンなので。

 以前、Qtデバイスの高速起動】Part1: 車載インストルメントクラスタという公式のブログ記事でも紹介されていたように、全くノーリアクションは良くない、なにかユーザーの目に映るモノを用意して裏で頑張るという手法をとるわけです。


/// 状況確認 ///
 QMLの解釈で時間がかかるとはいったものの実際どんなもんだろうってところだと思います。
 なので、参考情報です。
 QMLプロファイラーが用意されているため、それを使用して計測しました。

 詳細:Profiling QML Applications

 題材として僕が開発している電子書籍(epub)を作るソフト「LeME」を使用しました。手元に使えそうなのがなかったので^^;
 結果としてはすごくざっくり数値だと思って以下をみてください。

 
ファイルキャッシュコンパイル(ms)生成(ms)合計(ms)
スプラッシュなし40237277
あり15222237
本体なし253493746
あり156539695
 
 実行するたびに誤差で値が変化していると思いますが、キャッシュがあるとプロファイラでいうところのコンパイル時間が減ります。
 今回題材にしたLeMEがQMLファイル20個程度の規模ですが、スプラッシュスクリーンの方が体感できるレベルで早く表示が可能です(と言っても1秒以下ですが)。
 ただ、Qtの大きなライブラリがディスクキャッシュに入っているかも大きく影響しそうで、そっちの方が実は影響が大きいのではなかろうかという説もあり、無駄なあがき感もありそうですけどね......。ディスクキャッシュが効いているだろうなって状況でもスプラッシュスクリーンのような軽量ウインドウならワンテンポ速く表示できるので微妙な待ちがなくなって良いと思います。

/// おまけ ///
 キャッシュファイルは基本的にQMLファイルと同じフォルダに作成されますが、書き込みできない場合(リソースに含まれるときなど)は、QStandardPaths::CacheLocationのサブフォルダに保存されます。


/// 最後に ///
 本当に意味があるのか怪しい感をにおわせてしまいましたが、キャッシュが効けば早くなるのは間違いないですし、複数ウインドウのアプリケーションも作れることは理解していただけたと思います。
 
 Qt Advent Calendar 2017も残すところ、最終日のみ。
 期待して待ちたいと思います。ではでは。