広告SDKモジュールを作るときによくやること(ライフサイクルを理解しよう)


はじめに

 おかげさまでTitaniumは今年も大きく発展し、また今後も @infosia さんの書いていたように興味深い発展を続けているようで何よりです。日本にはまだプロモーションを担当する主体がいないので、最近はあまり大きなイベントもなく、中にはオワコン呼ばわりする新し物好きさんもいるようですが、Let that shit die and find out the new goal.クリスマスだよ!みんなハッピーに仲良くしようよ!

 さて、Titanium向けにiOS/Android向けにモジュールを書くのは、実はそれほど難しい作業ではありません。その証拠に、これだけ多くのモジュールが世に出ているのは(1)Titaniumに機能が足りなさすぎるか(2)モジュールを作るのがそれほど難しくないのかのどちらかですが、答えは両方とかいってる人は悪い子なのでサンタさんが来てくれませんよ。

 モジュールの作成に限らず、まずはマニュアルをよく読む必要があります。Titaniumは「マイクロカーネル」を自称しているシステムなのですが、これは何もモLinuxのようなモノリシックなカーネルに対比するMachのようなものではなく、ようするにモジュラー化した小さな部品の集合が疎結合になるのを目指しているという程度の意味です。特徴的なのは、Titaniumにおいては元のSDKの機能とモジュールには特に上下の関係はなく、追加されたモジュールであろうがSDKだろうが等価に動作するのでパフォーマンスなどの劣化はありません(モジュールの呼び出し、特にネイティブなところとJavaScriptをブリッジしている箇所を往復するような呼び出しはモジュールだろうがSDKだろうがパフォーマンスを低下させます)。

 で、アドベントカレンダーなので短めに、この記事ではモジュールを作る際に必要ないくつかについて解説してみたいと思います。

ライフサイクル

 Titaniumでアプリを作るときはあまり意識しないのですが、スマートフォン向けアプリではアプリ自体のライフサイクルと呼ばれるものが大事になります。ライフサイクルというのは、アプリが起動して初期化処理を実行したり、一時停止や停止、リジュームや再起動といった動作をするタイミングとそこで実行される処理のことをいいます。

Androidのライフサイクル

 Androidにはアプリ全体のライフサイクルというのはあまり適切な考え方ではありません。普通はActivityにライフサイクルがあるものと考えます。

 AndroidのActivityには次のようなライフサイクルのイベントがあります:

* onCreate
* onStart
* onResume
* onPause
* onStop
* onDestroy
* onRestart

 これらをモジュールから扱うのはそれほど難しくはありません。モジュールはアプリの起動時にロードされるので、AndroidのrootとなるActivityに合わせて任意のタイミングでこれらのイベントにフックさせることができます。

 ただし、onCreateは実際にはモジュールの中では別の名前で呼び出されます。

@Kroll.onAppCreate
public static void onAppCreate(TiApplication app) {
  // アプリの起動時、requireでモジュールがロードされる前にここが実行されます。
  // モジュールの初期化処理はここでやってしまいましょう。
}

 このタイミングで実行できる処理はたくさんありますが、例えばtiapp.xmlから設定された値を取得できると便利ですよね。広告SDKのIDなんかを格納できれば使い回しの楽なモジュールが書けるようになります。

 まず、tiapp.xmlのandroidディレクティブの中にmetaを追加します。


<meta-data android:name="api_key" android:value="my_api_key" />

 次にonAppCreateの中で

//略
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Bundle;
//略

  //initialization
  @Kroll.onAppCreate
  public static void onAppCreate(TiApplication app) throws NameNotFoundException
  {
    ApplicationInfo ai;
    ai = app.getPackageManager().getApplicationInfo(app.getPackageName(), PackageManager.GET_META_DATA);
    Bundle bundle = ai.metaData;
    // AndroidManifest.xmlに「api_key」が指定されていないと例外を吐くよ
    String apiKey = bundle.getString("api_key"); //ここで取得
  }

Contextを扱う

 よく忘れてしまいがちなのが、広告に限らず各種SDKで初期化処理を要求するものは、たいていContextも寄越すように指定されています。ところが、検索してヒットするTitaniumのモジュールでContextを取得するサンプルがちょっと紛らわしいので、ここでよく間違えることがあります。

//よくあるContextの取得方法
Context context = app.getCurrentActivity().getApplicationContext();

まだアプリは起動していないのでCurrentActivityなんかないぞとNullPointerExceptionが発生します。いわゆる「ぬるぽ」ですね。

 こちらのリンク先「Android:引数はthisか?getApplicationContextか?ActivityとApplicationの違い」に詳しいのですが、ようするにContextにはApplication ContextとActivity Contextがあり、アプリの起動時にはWindowは作成されていない=Activityはまだ生成されていないのでここで渡すのはApplication Activityなんです。

 では、どうやってそのApplication Activityとやらを取得するのかというと、実は目の前にもう用意されています。

  @Kroll.onAppCreate
  public static void onAppCreate(TiApplication app){

 はい、この引数のappがそうなんですね。これでたいていの広告関連のSDKが要求する初期化処理には対応できると思います。

 もしダメなとき、最悪なケースでは、onStartというライフサイクルで実行するという手もありますが、あまりお勧めはできません。

 最後にひとつ、モジュールのライフサイクルの見本がこちらに公開されていますので、ぜひご覧いただければと思います。

iOSのライフサイクル

 一方、iOSにもライフサイクルのイベントはありますが、Androidとは異なりJSでもアプリ自体のresumeイベントなどを取得できるのでこちらも普段はあまり苦労はしないと思います。

 モジュールの場合、もう少しネイティブの知識が必要になります。例えばいろいろなiOS向けSDKで初期化処理を行う際などにライフサイクル関連のイベントを利用しなければいけないケースがあります。そして、こちらも結構簡単に実現できます。

 マニュアルにも記載がありますので目を通すことをお勧めします。

 例えばSDKがapplication:didFinishLaunchingWithOptionsのdelegateで処理を実行するよう指定している場合は、モジュールがロードされるタイミングでクラスメソッドを実行するよう依頼することができます。

+(void)initMySpecialSDK:(TiBindingRunLoop)runLoop
{
    // ここに実行したい処理を記述します
    //[[MySDK sharedInstance] initWithUrl: @"http://example.com/"];
}

+(void)load
{
    // ここで処理の実行を依頼します
    [self performSelectorDuringRunLoopStart:@selector(initMySpecialSDK:)];
}

 このように、実行したいメソッドを(先頭が+記号の)クラスメソッドとして用意しておいて、loadの中で実行を依頼すると動作します。

 他のタイミングでもいろいろ実行したいことがあるかもしれませんが、上述したようにiOSでは普通にJS側からイベントをフックできるので、モジュール側でネイティブコードで対応する必要は特にないでしょう。