TreasureData (Android|iOS|Unity) SDKについて

こんにちは。TreasureDataの駒津です。初Qiita投稿となりますが、今回は [Treasure Data Advent Calendar 2014](http://qiita.com/advent-calendar/2014/td) の22日目の記事として、TreasureData (Android|iOS|Unity) SDKについて紹介したいと思います。

# TreasureDataが提供しているSmartphone向けSDK

[Treasure Dataのデータインポート方法一覧](http://qiita.com/repeatedly/items/d00beb89e30edcb533a6) でも紹介されている通り、現在TreasureDataにデータを集める手段は数種類あります。その中でSmartphone向けでは以下のSDKが提供されています。

- Android SDK
- iOS SDK
- Unity SDK

いずれも各SDKからTreasureDataに直接データをインポートするため、ユーザー側でサーバーを準備する手間が不要となります。

## Smartphone向けSDKの機能

Smartphone向けSDKの機能としては以下のものがあります。

- アプリ内で発生したイベントを登録（クライアント側でバッファリング） (`addEvent`/`addEventWithCallback`)
 - バッファリングされたデータは任意の共通鍵にて自動的に暗号・復号化が可能 (`initializeEncryptionKey`, default:disable)
- バッファリングされたイベント群をTDにアップロード（アップロード成功時のみバッファをクリアするためデータロストを防止） (`uploadEvents`/`uploadEventsWithCallback`)
 - アップロード失敗時には一定回数のリトライ（Exponential backoff）を実施 (`enableRetryUploading`/`disableRetryUploading`, default:enable)
 - アップロードデータは自動的に圧縮され転送データ量を1/8程度に低減 (`enableEventCompression`/`diableEventCompression`, default:enable)
- イベント登録・アップロード処理の成否に応じて呼ばれるコールバック処理を登録可能 (`addEventWithCallback`/`uploadEventsWithCallback`)
- SDK内部にてデバイスごとに一意となるIDを生成。当該IDを各イベントに自動付与できるため、デバイスごとの集計が容易に (`enableAutoAppendUniqId`/`disableAutoAppendUniqId`, default:disable)
- OSのバージョンやデバイスの種類を各イベントに自動付与可能 (`enableAutoAppendModelInformation`/`disableAutoAppendModelInformation`, default:disable)
- セッションの開始、終了ごとにセッションIDが生成可能。各イベントに自動付与できるため、アプリで発生する一連のイベント群ごとに集計できる (`startSession`/`endSession`)
- SDKが初回起動済みかどうかの状態を管理しており、アプリの初回起動判定に利用することが可能 (`isFirstRun`/`clearFirstRun`)
- サーバー側でイベントの重複排除を行っているためアップロードが複数回行われてもデータの重複を防止（現在、1日間の重複排除を実施）

各機能説明の末尾に記載してある文字列は、対応するSDKのAPI名（具体的には `TreasureData` クラスのメソッド名）です。

また、Smartphone向けSDKでは、登録するイベントについてデータベースとテーブルを指定するようになっています。これらはインポート先となるTreasureDataサービス上のデータベースとテーブルと対応しています。

一例として、`enableAutoAppendUniqId`, `enableAutoAppendModelInformation` を有効にし、 `startSession`, `endSession` を利用した際のテストデータの一部をTD上で確認すると以下のようになります。

![Screen Shot 2014-12-22 at 1.04.24 AM.png](https://qiita-image-store.s3.amazonaws.com/0/4148/28d17f93-049a-3837-d2ef-c8e25cbec90d.png)


## 書き込み専用ユーザーおよびAPI keyの利用

TreasureDataではデータのインポートやクエリの発行時の認証のためにAPI keyを用いますが、Smartphone向けSDK（JS SDKも）を利用する場合は、以下の手順を推奨します。

- Smartphone向けSDKからのインポート用にTDにデータベースを作成
- 上記で作成したデータベースにのみアクセス可能なユーザーをTDのアカウントに追加
 - ユーザーの追加は https://console.treasuredata.com/users から `Add Team Member` をご利用ください
- 上記で追加したユーザーの書き込み専用API keyを作成
- 上記で作成した書き込み専用API keyをSmartphone向けSDKにて利用

なお、書き込み専用API keyの発行は https://console.treasuredata.com/ 画面の右上の `My Profile` を押下した後、`API Keys` メニューにパスワードを入れることで発行可能になります（下記画像）。

![example_write_apikey.png](https://qiita-image-store.s3.amazonaws.com/0/4148/bed7547b-a966-340b-7ac9-2b4ce2262bf8.png)

# Android SDKの概要とインストール方法

現在の最新バージョンは0.1.6です。0.1.5に比べるとバグフィックスや新機能（セッション、デバイス固有のID、OSバージョン等の付与設定）が追加されています。古いバージョンをご利用の方は是非最新バージョンをご利用ください。

Mavenをご利用の場合は以下のdirectiveをpom.xmlに追加してください。

```
  <dependency>
    <groupId>com.treasuredata</groupId>
    <artifactId>td-android-sdk</artifactId>
    <version>0.1.6</version>
  </dependency>
```

直接jarを利用したい場合は以下からダウンロードしてください。

- http://search.maven.org/remotecontent?filepath=com/treasuredata/td-android-sdk/0.1.6/td-android-sdk-0.1.6-shaded.jar

また、TDへのアップロード時にはインターネットへの接続が必須となりますので以下のpermissionの追加が必要となります。

```
<uses-permission android:name="android.permission.INTERNET"/>
```

Android SDKについては [TreasureData + Android SDK] (http://qiita.com/akito1986/items/b919ac4fe89ce6118311) の記事で非常にわかりやすく説明されています。

# iOS SDKの概要とインストール方法

現在の最新バージョンは0.1.6です。Android SDKと同様、0.1.5に比べるとバグフィックスや新機能（セッション、デバイス固有のID、OSバージョン等の付与設定）が追加されています。古いバージョンをご利用の方は是非最新バージョンをご利用ください。

[CocoaPods](http://cocoapods.org/) でのインストールとなりますので、CocoaPodsインストール未済の場合は以下のようにインストールが必要となります。

```
$ gem install cocoapods
```

iOSアプリプロジェクト直下のPodfileに以下を追記（ファイルが無ければ作成）。

```
pod 'TreasureData-iOS-SDK', '= 0.1.6'
```

その後、`pod install` コマンドを実行し、更新された (ProjectName).xcworkspace を開いてください。

```
$ pod install
```

## アプリ終了時にアップロードする際の注意点

アプリの終了のタイミングでTDへのアップロードを行いたい場合、`UIApplicationDelegate` の `applicationDidEnterBackground` 内で、`uploadEvents` 系のメソッド（詳細は後述）を呼び出すケースがあるかと思いますが、その場合は以下のように `UIApplication` の `beginBackgroundTaskWithExpirationHandler` を呼んで long-running background taskを開始しておいてください。

```
    __block UIBackgroundTaskIdentifier bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
        [application endBackgroundTask:bgTask];
        bgTask = UIBackgroundTaskInvalid;
    }];
    
    [[TreasureData sharedInstance] uploadEventsWithCallback:^() {
            [application endBackgroundTask:bgTask];
            bgTask = UIBackgroundTaskInvalid;
        }
        onError:^(NSString *code, NSString *msg) {
            [application endBackgroundTask:bgTask];
            bgTask = UIBackgroundTaskInvalid;
        }];
```

以前、https://github.com/treasure-data/td-ios-sdk および http://docs.treasuredata.com/articles/ios-sdk では `beginBackgroundTaskWithExpirationHandler` を使わないコード例が記載されていた時期がありました。そのコード例をそのまま使うとアプリ終了時のアップロードに失敗する場合があるため、是非最新のコード例を参照ください。

# Unity SDKの概要とインストール方法

Unity SDKは、内部的には前述のAndroidおよびiOS SDKをNative pluginとして組み込み、Unity側ではC#で橋渡しをする処理を実装し各SDKを利用しています。

Unity SDKは
https://github.com/treasure-data/td-unity-sdk-package にてunitypackageとしてリリースしています。~~現在の最新版は0.1.2ですが、これはまだ
Android/iOS SDKともに最新版を組み込んでいないため、今週リリース予定（...!）である次の0.1.3を待ったほうが良いかと思います。~~

0.1.3がリリースされました。https://github.com/treasure-data/td-unity-sdk-package/blob/master/TD-Unity-SDK-0.1.3.unitypackage 前述のAndroid/iOS SDK 0.1.6 を組み込んでいますので、古いバージョンをお使いの方は是非最新版をお試しください。

UnityプロジェクトにSDKをインストールする際は、以下の手順が必要となります。

- https://github.com/treasure-data/td-unity-sdk-package から https://github.com/treasure-data/td-unity-sdk-package/blob/master/TD-Unity-SDK-x.x.x.unitypackage をダウンロード
- Unityプロジェクト上で `Assets -> Import Package -> Custom Package` を選択し、当該unitypackageをインポート


# 各SDKに関する詳細

ここまで、Smartphone向けSDKに関する概要を説明しましたが、より詳細な情報については以下のリンクが有用かと思います。

- Android SDK
 - [詳細情報](https://github.com/treasure-data/td-android-sdk/blob/master/README.md)
 - [サンプルプロジェクト](https://github.com/treasure-data/td-android-sdk/tree/master/example/td-android-sdk-demo)
- iOS SDK
 - [詳細情報](https://github.com/treasure-data/td-ios-sdk/blob/master/README.md)
 - [サンプルプロジェクト](https://github.com/treasure-data/td-ios-sdk/tree/master/TreasureDataExample)
- Unity SDK
 - [詳細情報](https://github.com/treasure-data/td-unity-sdk-package/blob/master/README.md)
 - [サンプルプロジェクト](https://github.com/treasure-data/td-unity-sdk-package/tree/master/Example/Assets)

# サンプルコードを例にした説明

最後にAndroid SDKを利用したサンプルコードを紹介したいと思います。細かめなコメントを含んでいるので参考になれば幸いです。

```
package com.treasuredata.android.exampleapp;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.util.Pair;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import com.treasuredata.android.TDCallback;
import com.treasuredata.android.TreasureData;


public class MainActivity extends Activity {
    private static final String TAG = MainActivity.class.getSimpleName();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // このAPIにてインポート先のエンドポイントを変更できます.
        // この例は "Yahoo!ビッグデータインサイト" を利用する際の例です.
        // TreasureData.initializeApiEndpoint("https://mobile-ybi.jp-east.idcfcloud.com");

        // デフォルトのAPIキーの登録が可能です
        // TreasureData.initializeDefaultApiKey("your_default_api_key");

        // デバッグのためLoggingを有効に
        TreasureData.enableLogging();

        // バッファリング中の暗号化を有効にし、共通鍵の元となるフレーズを登録
        TreasureData.initializeEncryptionKey("hello world");

        // データ転送時の圧縮を無効にできます（初期状態は有効）
        // TreasureData.disableEventCompression();

        // アプリケーション内で共有するインスタンスを初期化
        // API keyは各インスタンスを生成するタイミングでも指定できます
        TreasureData.initializeSharedInstance(this, "your_default_api_key");

        // 上記で初期化した共有インスタンスに対し各種設定を行います
        // - デバッグモードを有効にしエラー発生時に例外を発生させるように変更
        // TreasureData.sharedInstance().setDebugMode(true);

        // - SDK内部においてデバイスごとに一意なIDを、各イベントに自動的に付与します
        TreasureData.sharedInstance().enableAutoAppendUniqId();

        // - OSのバージョンやデバイス名などを、各イベントに自動的に付与します
        TreasureData.sharedInstance().enableAutoAppendModelInformation();

        // - デフォルトのデータベースを指定することで以降はテーブル名のみの指定が可能となります
        TreasureData.sharedInstance().setDefaultDatabase("testdb");

        // - セッションを開始します
        //   このタイミングで "td_session_event":"start" というイベントが追加されます
        TreasureData.sharedInstance().startSession("demotbl");

        // - アップロード時のリトライ機能を無効にします (初期状態は有効)
        // TreasureData.sharedInstance().disableAutoRetryUploading();

        // SDKが初回起動状態かどうかを判定します
        if (TreasureData.sharedInstance().isFirstRun(this)) {

            // イベントを登録します. 処理成否に応じたコールバックが呼ばれます
            TreasureData.sharedInstance().addEventWithCallback("demotbl", "first_run", true, new TDCallback() {
                @Override
                public void onSuccess() {
                    // 登録処理が成功した場合には、SDK初回起動フラグをクリアします (以降の isFirstRun() は false を返します)
                    TreasureData.sharedInstance().clearFirstRun(MainActivity.this);
                }

                @Override
                public void onError(String errorCode, Exception e) {
                    Log.w(TAG, "TreasureData.addEvent:onError errorCode=" + errorCode + ", ex=" + e);
                }
            });
        }

        findViewById(R.id.image).setOnTouchListener(new OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent ev) {
                // イベントの登録を行います
                // ここでは R.id.image という View をタッチした際のイベントを
                // 簡単のため文字列化して登録していますが、実際は Map<String, Object> に変換して
                // addEvent() にて登録したほうが、後のTD上での集計がやりやすいと思います
                TreasureData.sharedInstance().addEvent("demotbl", "image", ev.toString());
                return false;
            }
        });

        findViewById(R.id.upload).setOnTouchListener(new OnTouchListener() {
            @Override
            public boolean onTouch(View view, MotionEvent motionEvent) {
                // addEvent()にてバッファリングされたイベント群をアップロードします (処理成否のコールバック付き)
                // ここでは R.id.upload という View がタッチされたタイミングで処理を実行しています
                TreasureData.sharedInstance().uploadEventsWithCallback(new TDCallback() {
                    @Override
                    public void onSuccess() {
                        Log.d(TAG, "TreasureData.uploadEvents:onSuccess");
                    }

                    @Override
                    public void onError(String errorCode, Exception e) {
                        Log.w(TAG, "TreasureData.uploadEvents:onError errorCode=" + errorCode + ", ex=" + e);
                    }
                });
                return false;
            }
        });
    }

    @Override
    protected void onDestroy() {
        // セッションを終了します
        // このタイミングで "td_session_event":"end" というイベントが追加されます
        TreasureData.sharedInstance().endSession("demotbl");

        // バッファリングされたイベント群をアップロードします
        // ここでは MainActivity の終了のタイミングで処理を実行しています
        TreasureData.sharedInstance().uploadEvents();
        super.onDestroy();
    }
}
```

Android/iOS SDKのv0.1.6のリリースがこの記事に間に合って少しほっとしています。
なお、Unity SDK v0.1.3のリリースのタイミングで本記事を更新する予定です。