HTML5とPhoneGapで作る、iPhone/Androidアプリ開発TIPS

第4回JavaScript読み込み位置による起動時間パフォーマンス差を計測する

WebアプリケーションではJavaScriptの読み込み位置をHTMLの最後に行うことで、ページが表示されるまでの体感速度を向上させる手法があります(プログレッシブ・レンダリング⁠⁠。PhoneGapアプリケーションでも<script>の読み込み位置によってアプリケーションの起動速度に影響が出てくるのでしょうか。いくつかパターンを用意して検証してみます。

JavaScript読み込み位置における起動時間パフォーマンス差

PhoneGapに付属しているターミナル/コマンドプロンプト用のcreateコマンドで作成したプロジェクトでは、Jasmineによるテスト用のコードが付属しています。また、サンプルアプリケーションではJavaScriptの読み込み位置が<head>要素内の末尾から、<body>要素内の末尾に移動しました。

外部JavaScriptファイルの読み込みをHTMLの最後に行うことで、ページのレンダリングを先に行った後に、JavaScriptのロードが行われることになります。PhoneGapアプリケーションではJavaScriptの読み込み位置や処理の順番が、起動時間にどのように影響してくるのでしょうか。いくつかのパターンを用意して検証してみます。

PhoneGapアプリケーションにおける、アプリケーション操作可能になるまでのイベントを把握してみましょう。

load
ドキュメントの読み込みがすべて完了した時点で発火(Window)
DOMContentLoaded
DOMの構築が完了した時点で発火(Document)
onDOMContentLoaded
Cordova内部のイベント。Webページの読み込み・パースが完了した時点で発火
onNativeReady
Cordova内部のイベント。Cordovaのネイティブ側の使用準備が整った段階で発火
onCordovaReady
Cordova内部のイベント。Cordovaで提供されるJavaScriptオブジェクトがすべて作成された時点で発火
onCordovaInfoReady
Cordova内部のイベント。Device APIでデバイス情報を取得できるようになった段階で発火
onCordovaConnectionReady
Cordova内部のイベント。Connection APIで回線情報を取得できるようになった段階で発火
onDeviceReady
Cordovaの機能が利用できるようになった段階で発火

Cordova内部で利用されているイベントは、CordovaのJavaScriptファイルを修正しない限りは捕捉することができません。今回は、load, DOMContentLoaded, onDeviceReadyの3種類のイベントと、JavaScriptの処理完了までの時間を計測してみました。

JavaScriptのロード位置を<head>内末尾にした場合
  • ループ処理をloadイベント後に行った場合
  • ループ処理をDOMContentLoadedイベント後に行った場合
  • ループ処理をdevicereadyイベント後に行った場合
  • リモートホストのJavaScriptファイルを<script>タグで読み込んだ場合
  • リモートホストのJavaScriptファイルをdevicereadyイベント後に読み込んだ場合
JavaScriptのロード位置を<body>内末尾にした場合
  • ループ処理をloadイベント後に行った場合
  • ループ処理をDOMContentLoadedイベント後に行った場合
  • ループ処理をdevicereadyイベント後に行った場合
  • リモートホストのJavaScriptファイルを<script>タグで読み込んだ場合
  • リモートホストのJavaScriptファイルをdevicereadyイベント後に読み込んだ場合

なお、計測環境は次のとおりです。

  • デバイス: iPad 2
  • Cordova: 2.1.0

計測に使用したファイル・計測結果

ベースとなるHTMLファイル/JavaScriptの読み込み/ループのコードは次のとおりです。

リスト1 HTMLファイル index.html
<script type="text/javascript">
var time = Number(new Date());
</script>
<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
        <meta name="format-detection" content="telephone=no" />
        <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height, target-densitydpi=device-dpi" />
        <title></title>
    </head>
    <body>
        <div class="app">
            <ul>
                <li id="onload"></li>
                <li id="domloaded"></li>
                <li id="deviceready"></li>
                <li id="heavyjsloaddone"></li>
                <li id="loopjsloaddone"></li>
            </ul>
        </div>
    </body>
</html>
リスト2 index.htmlからJavaScriptを読み込むコード。head要素内末尾、body要素内末尾に配置
<script type="text/javascript" src="cordova-2.1.0.js"></script>
<script type="text/javascript" src="js/index.js"></script>
<script type="text/javascript">
    app.initialize();
</script>
リスト3 JavaScriptファイル js/index.js
var app = {
    initialize: function() {
        this.bindEvents();
    },
    bindEvents: function() {
        window.addEventListener('load', this.onLoad, false);
        document.addEventListener('DOMContentLoaded', this.domContentLoaded, false);
        document.addEventListener('deviceready', this.onDeviceReady, false);
    },
    onLoad: function() {
        document.getElementById('onload').textContent = 'onload: ' + ( Number(new Date()) - time );
    },
    domContentLoaded: function() {
        document.getElementById('domloaded').textContent = 'domContentLoaded: ' + ( Number(new Date()) - time );
    },
    onDeviceReady: function() {
        document.getElementById('deviceready').textContent = 'deviceready: ' + ( Number(new Date()) - time );
    }
};
リスト4 ループ処理 - 各イベント後に関数を呼び出して実行
function loop()
{
    val = 0;
    for ( i = 1; i<1000000; i++) 
    {
        val++;
    }
    document.getElementById('loopjsloaddone').textContent = 'loopjsloaddone: ' + ( Number(new Date()) - time );
}

リモートホストに配置するJavaScriptファイルサイズは、約2Mとします。

/usr/local/www/nginx# ls -l | grep js 
-rw-r--r--  1 root  wheel  2060214 Oct 21 21:28 heavy-library.js

上記の条件をもとにそれぞれ5回試行し、平均値を採用しました。結果は次のとおりです。なお、数値の単位はミリ秒となっています。

表1 JavaScriptの読み込み位置を<head>要素末尾にした場合
実施内容loadDOMContentLoadeddevicereadyループ終了/JSロード完了
A. ループ処理をloadイベント後に行った場合4443728595
B. ループ処理をDOMContentLoadedイベント後に行った場合59744730596
C. ループ処理をdevicereadyイベント後に行った場合5344172724
D. リモートホストのJavaScriptファイルを<script>タグで読み込んだ場合1677167818161677
E. リモートホストのJavaScriptファイルをdevicereadyイベント後に読み込んだ場合43421711521
表2 JavaScriptの読み込み位置を<body>要素末尾にした場合
実施内容 loadDOMContentLoadeddevicereadyループ終了/JSロード完了
A'. ループ処理をloadイベント後に行った場合4443729595
B'. ループ処理をDOMContentLoadedイベント後に行った場合59844731597
C'. ループ処理をdevicereadyイベント後に行った場合4443173725
D'. リモートホストのJavaScriptファイルを<script>タグで読み込んだ場合1598159717321598
E'. リモートホストのJavaScriptファイルをdevicereadyイベント後に読み込んだ場合46451751658

計測結果からわかること

筆者が行った環境では、JavaScriptの読み込み位置(head要素、body要素)におけるパフォーマンス差に明確な違いは見受けられませんでした。

読み込み位置におけるパフォーマンス差を考慮しない場合、上記のパターンでアプリケーションが高速に起動したのは

  • ループ処理をdevicereadyイベント後に行った場合
  • リモートホストのJavaScriptファイルをdevicereadyイベント後に読み込んだ場合

の2つです。これは、アプリケーションの画面描画がdevicereadyイベント後以降に行われることに由来すると考えられます。

表4 ループ処理、JSファイルの読み込みとdevicereadyイベントの位置による違い
実施内容devicereadyループ終了/JSロード完了
A. ループ処理をloadイベント後に行った場合728595
B. ループ処理をDOMContentLoadedイベント後に行った場合730596
C. ループ処理をdevicereadyイベント後に行った場合172724
D. リモートホストのJavaScriptファイルを<script>タグで読み込んだ場合18161677
E. リモートホストのJavaScriptファイルをdevicereadyイベント後に読み込んだ場合1711521

これらの結果から、HTMLが単純な場合は<script>タグの位置で動作速度が大幅に変わることはなく、devicereadyイベントの発火前に時間のかかる処理が記述されていないことがポイントになってくることがわかります。

devicereadyイベントが発火する前に、JavaScriptで負荷の大きい処理や大きいサイズのファイルをロードしようとした場合、その間スプラッシュ画像が表示されることになります。結果、起動に時間のかかるアプリケーションができてしまうことになります。デバイスが古いモデルや低速通信環境下にいる場合、より顕著になります。

検証の結果から、PhoneGapを利用したアプリケーションを作成する場合に注意したいことをまとめます。

  • devicereadyイベント前に負荷の大きい処理や、リモートホスト上のサイズの大きいファイルをロードさせない。これらはすべて、devicereadyイベント発火後に行わせるように
  • 可能な限り、ファイルはすべてローカルに配置する
  • リモートホスト上のファイルを使用する場合、リソースの圧縮やWebサーバのgzip圧縮転送を利用して転送量を少なくする

外部WebサービスのAPIなどを利用している場合、<script>タグを使わずに次のようなJavaScriptコードを用いることで、任意のタイミングでJavaScriptファイルを読み込むことができます。

var script = document.createElement('script');
script.src = '(読み込みたいJavaScriptファイルのURI)';
script.type = 'text/javascript';
document.getElementsByTagName('head').item(0).appendChild(script);

規模の大きいPhoneGapアプリケーションを開発していて起動速度が当初に比べて遅くなってきていると感じた場合、これらの処理の順番を最適化することで改善する可能性があります。一度チェックしてみましょう。

おすすめ記事

記事・ニュース一覧