リリースしたアプリの起動がある日突然遅くなった話

昨日 11/2 お昼前、いっぷくしようと思い、自作アプリ Smoking Note を立ち上げようとしたときのことです。
… アプリの起動が妙に遅い
スプラッシュスクリーンが消えた後も、時刻などの動的に描画する要素がなかなか表示されない。ちょっと許容できない遅さで、たまたまかなと思い何度か立ち上げなおしても状況は変わらず。アプリの起動後の動作には問題がなく、広告自体もちゃんと表示されるのですが、ただただ遅い。

アプリのキャッシュをクリアしても、スマホを再起動してもだめ。
「いやいや、今日ついさっきまで普通に起動してたのに、冗談でしょう?」と軽く現実逃避しながら昼食をとり、気を取り直して再度アプリを立ち上げてみても、やっぱり起動が異常に遅い。

このとき、めっちゃ焦りました。さーっと血の気が引く感じ。

突然動作が変わる原因として、広告配信かファイル関連くらいしか思いつかなくて、私が使用している広告 admob について現在なにか起こっていないか、ネットでずっと調べてました。admob の表示には cordova プライグインの cordova-admob-pro を使っているので、これについても調べたのですが、それらしい情報はどこにもなく、admob への問い合わせ先も見つからない。

で、デバッグ開始です。
実機での起動時の動作確認のため、ちょっとめんどいけど仕方なし。
まず、ソースを変更せず(アプリ名だけ変更)デバッグビルドで構築して実機にインストールし、現象が再現するかを確認。
うん、再現します。間違いなく。
この時点でファイルなしでも発生することが確認できました。
また、広告をテスト広告に変更しても同様に発生しました。
次に、ブレークポイントは貼れないのでコンソール文とアラート関数を埋め込み、ビルド・インストールし動作確認。
これを繰り返してやっとのことで発生個所を特定しました。

/*** test 2016/11/02 ***/
window.alert("init_ad in");
  if(window.AdMob) AdMob.prepareInterstitial({
                     adId: admobid.interstitial,
//                     isTesting: true,
                     autoShow: false
                   });
window.alert("init_ad out");  // 上記の alert 表示後の
                              // この alert 表示が異常に遅い!

上記のインタースティシャル広告作成関数でプログラムがしばらく止まるんです
この関数自体は非同期関数のようにすぐに返ってくるはずなんですが、この日このとき、なぜかすぐに返ってこない。

で、特定はできたけどこれからどうしようと考えながらいっぷくしようと Smoking Note を立ち上げたら …
なおってる!
なんかしらないけど、普通に起動しました!
このとき、午後 18:16 。
まるで夢のような出来事でした。
こうゆうケースが現実にありましたということで、ご参考まで。

クラス操作関数

以前に正規表現を使用したことを思い出し調べていたときにでてきたソースです。実際、正規表現は一か所でしか使用していないのですが、こうゆう使い方もあるということで、クラス操作関数とあわせて備忘録として残しておこうと思います。
クラス操作は classList のメソッドで容易に行うことができますが、Can I use で調べてみると Android 4.4 からの対応になっています。それ以前のバージョンに対応する場合は classList の代用が必要になりますが、そのようなときに使えるかと思います。(indexOf()を使用しているのでクラス名の重複部分には注意が必要になります)

/***
 * クラス追加
 */
function add_class(elem, cls) {
    if (!elem || !cls) return;
    elem.className = add_str(elem.className, cls);
}
/***
 * クラス削除
 */
function remove_class(elem, cls) {
    if (!elem || !cls) return;
    elem.className = remove_str(elem.className, cls);
}
/***
 * クラス確認
 */
function has_class(elem, cls) {
    if (!elem || !cls) return;
    return has_str(elem.className, cls);
}
/***
 * クラス追加・削除
 */
function toggle_class(elem, cls) {
    if (!elem || !cls) return;
    elem.className = toggle_str(elem.className, cls);
}

// 文字列 strings に文字列 str を追加する
function add_str(strings, str) {
    var i = strings.indexOf(str);
    if (i === -1) {
        var len = strings.length;
        if (len !== 0 && strings.charAt(len -1) !== " ") {
            strings += " ";
        }
        strings += str;
    }
    return strings;
}

// 文字列 strings から文字列 str を削除する
function remove_str(strings, str) {
    var i = strings.indexOf(str);
    if (i !== -1) {
        strings = strings.substr(0, i)
                + strings.substr(i + str.length + 1);
    }
    return strings;
}

// 文字列 strings に文字列 str が含まれているか検査する
function has_str(strings, str) {
    var regex = new RegExp("(^|\\s)" + str + "(\\s|$)");
    if (regex.test(strings)) return true;
    else return false;
}

// 文字列 strings に文字列 str があれば str を削除し、
// str がなければ追加する
function toggle_str(strings, str) {
    if (has_str(strings, str)) {
        return remove_str(strings, str);
    } else {
        return add_str(strings, str);
    }
}

Meditation Noteアップデート作業中

現在、瞑想アプリ「Meditation Note」のアップデート作業を行っています。このアプリは私が初めて作成したアプリで、これまでにほとんどダウンロードされていないのですが、思い入れがとても強いアプリです。瞑想関連のアプリはすでにたくさんありますし、現在も新しいタイプのアプリがリリースされ続けているので、PLAYストアで「瞑想」で検索しても表示すらされない状態です。それでも、もともと「自分で使いたい」という想いで作った記念すべき初アプリなので、少しでもいいものにしたいと思いアップデートすることにしました。

まず、今回グラフを追加しようと思い、ソースを調べました。2作目、3作目のアプリでグラフを使ったことがあるので今回は楽かなと思っていたのですが、ソースを見て固まってしまいました。

… グラフに必要なデータである日付と時間が一つの文字列になってる …

// 日本語版
record: {
  date: "2016/10/31(月) 09:00",
  ...
}

// 英語版
record: {
  date: "Mon. Oct.27, 2016, 09:00 am",
  ...
}

作成当初グラフ化なんて考えてもいなかった(そんな余裕がなかった)からか「日付は表示するだけだから」と文字列にしてしまいました。グラフは、横軸を日付、縦軸を累積時間や回数にしようと思っているので、これを、

record: {
  date: {
    y: 2016,
    m: 9,
    d: 31,
    w: 0,
    s: {
      h: 9,
      m: 0
    }
  },
  ...
}

のような構造にして、同じ月にあたるレコードの時間と個数を集計できる形にしないと。

元の文字列から値を取り出す方法としてまず思いつくのが slice()などを使う方法。文字列の先頭から年にあたる4文字を取り出し、そこから一つ飛ばして、月にあたる2文字を取り出し、さらに、、、って、できないことはないんですけど、なんかイヤな感じが。文字列のインデックスを指定しなくちゃいけないので、ソースコードがごちゃごちゃしちゃうなと。で、悩んだ挙句、正規表現を使うことにしました。前に使ったことはあったのですが、ものの見事にすっかり忘れていて、本読みました。
ソースはこんな感じになりました。

result = date.match(/[^/() :.,]+/g);
if (mdt.lang_jp) {
  // "2016/10/27(木) 9:00"
  // [0]/[1]/[2]([3]) [4]:[5]
  record.date.y = Number(result[0]);
  record.date.m = Number(result[1]);
  record.date.d = Number(result[2]);
  record.date.w = Number(WEEK[result[3]]);
  record.date.s.h = Number(result[4]);
  record.date.s.m = Number(result[5]);
} else {
  // "Thu. Oct.27, 2016, 9:00 am"
  // [0]. [1].[2], [3], [4]:[5] [6]
  record.date.y = Number(result[3]);
  record.date.m = Number(MONTH[result[1]]);
  record.date.d = Number(result[2]);
  record.date.w = Number(WEEK[result[0]]);
  record.date.s.h = Number(result[4]) + ((result[6] === "pm") ? 12 : 0);
  record.date.s.m = Number(result[5]);
}

正規表現で「/」、「(」、「)」、「 」、「:」、「.」、「,」以外にマッチするパターンを見つけて配列に格納します。この抽出処理がたった一行で済むので、かなり便利です。
正規表現を使うことで複雑な処理を簡潔に記述するということは、些細なことのようでけっこう重要な気がします。なんとか使いこなせるようになりたいです。