検索
連載

もはや無料BaaS。ゲーム以外でも使いたくなるGoogle Play Game Servicesのデータ管理機能Androidで動く携帯Javaアプリ作成入門(46)(2/3 ページ)

グーグルの提供するライブラリ「Google Play Game Services」はバイト配列でクラウドにデータを保存・参照・削除できるので、無料でBaaSのデータ同期機能が簡単に使えるようなものです。Androidアプリに組み込む方法を解説します。

PC用表示 関連情報
Share
Tweet
LINE
Hatena

AppStateClient関連のリスナー実装

 今回のサンプルでは、ステータス関連のリスナーとして「OnStateLoadedListener」「OnStateDeletedListener」を実装します。

 OnStateLoadedListenerは、クラウドから状態を読み込んだ際、または読み込んだ状態がコンフリクト(競合)した際に呼び出されるコールバックが定義されています。

 OnStateDeletedListenerは、クラウドから状態を削除した際に呼び出されるコールバックが定義されています。

OnStateLoadedListener

 まずはOnStateLoadedListenerの実装から見てみます。実装するメソッドは2つです。

@Override
public void onStateLoaded(int statusCode, int stateKey, byte[] data) {
    Log.d(TAG, "onStateLoaded");
    if (statusCode == AppStateClient.STATUS_OK) {
        // successfully loaded data
        displayMessage("Loaded: " + toLong(data));
    } else if (statusCode == AppStateClient.STATUS_NETWORK_ERROR_STALE_DATA) {
        // could not connect to get fresh data,
        // but loaded (possibly out-of-sync) cached data instead
        displayMessage("Stale data: " + toLong(data));
    } else {
        // handle error
        displayMessage("Data load error: " + statusCode);
    }
}

 onStateLoaded()は状態が読み込まれた際に呼び出されます。statusCodeは読み込みに成功したかどうかを値として持ち、stateKeyはどの状態が読み込まれたかを判別するのに使用します。状態はバイト配列として読み込まれます。サンプルアプリでは、簡単な判定を行い、TextViewに内容を表示しています。

 判定内で「STATUS_NETWORK_ERROR_STALE_DATA」を特別扱いしているのは、状態がクラウドから取得はできたものの、それが同期が取られた最新のデータではなく、キャッシュから取得したデータである場合を考慮するためです。クラウドへの保存と取得の間隔が短い場合や、それが高頻度で発生する場合に起こりやすいようです。

 サンプルでは、上記のように簡単に済ませていますが、間に通信が挟まり、サービスが自身の管理下にないため、エラーハンドリングは発生する可能性のあるものごとに、きめ細かく行うべきです。ぜひ、Javadocでメソッドの詳細を確認してみてください。

 “状態”はバイト配列なので、アプリの状態に限らず、何でもクラウドに保存可能です。

 ただハイスコアのような、ゲームに関するデータは、GamesClientでそれ専用のメソッドが提供されているので、ここでは、最終更新日時のようなものを保存することにします。toLong(byte[])がバイト配列をlongに変換するメソッドです。

 もう1つのonStateConflict()は、状態がコンフリクトした場合に呼び出されます。

@Override
public void onStateConflict(int stateKey, String resolvedVersion, byte[] localData, byte[] serverData) {
    Log.d(TAG, "onStateConflict");
    long localVal = toLong(localData);
    long serverVal = toLong(serverData);
    long resolvedVal = Math.max(localVal, serverVal);
    byte[] resolvedData = toBytes(resolvedVal);
    getAppStateClient().resolveState(this, STATE_LAST_UPDATE, resolvedVersion, resolvedData);
    displayMessage("Conflicted: " + resolvedVal);
}

 このメソッド内では、何らかの方法でコンフリクトを解決しなければなりません。サンプルでは、より大きな値、つまり常に新しいタイムスタンプを採用しています。

 コンフリクトは複数の端末でアプリを操作した場合に発生しますし、単一端末でもアプリの作り次第では発生します。クラウドにデータを保存するのも、クラウドからデータを取得するのも、非同期で通信が行われるためです。

 分かりやすい例として、上図のようなシーケンスが考えられます。ネットワークが遅延するような環境で発生する可能性があります。

 複数端末で操作すると、狙ってコンフリクトを発生できます。ユーザーの利便性を考慮して、コンフリクト時の対応をきちんと設計しておくことが重要です。

OnStateDeleteListener

 必要がなくなった“状態”はクラウドから削除できます。OnStateDeleteListenerを実装しておくことで、削除の際、それが成功したかどうかをコールバックで知ることができます。

@Override
public void onStateDeleted(int statusCode, int stateKey) {
    Log.d(TAG, "onStateDeleted");
    if (statusCode == AppStateClient.STATUS_OK) {
        displayMessage("Deleted");
    } else {
        displayMessage("Delete error: " + statusCode);
    }
}

 ここでは失敗時に何もしていませんが、アプリ内にフラグを設けて失敗を管理したり、時間を置いてリトライしたりするのが、より良い実装でしょう。

クラウドとの状態同期

 リスナーを実装し終えたところで、クラウドとの状態の同期方法を説明します。

 今回のサンプルでは、クラウドとの状態同期はすべてボタンがトリガーになっています。switch-case文で実装してあるので、以下にまとめて引用します。

case R.id.button_save_to_cloud:
    // save to cloud
    long val = System.currentTimeMillis();
    getAppStateClient().updateState(STATE_LAST_UPDATE, toBytes(val));
    displayMessage("Save data: " + val);
    break;
case R.id.button_load_from_cloud:
    // load from cloud
    getAppStateClient().loadState(this, STATE_LAST_UPDATE);
    break;
case R.id.button_delete_from_cloud:
    // delete from cloud
    getAppStateClient().deleteState(this, STATE_LAST_UPDATE);
    break;

 上から保存、取得、削除です。

 保存は、現在時刻をlongからbyte[]に変換し、AppStateClient#updateState(int, byte[])に渡しています。第1引数のキーがデータを識別する値で、今回は定数で0を使用しています。複数の“状態”を管理する場合、キーがユニークになるようにアプリで管理する必要があります。loadState()メソッドには、リスナーを渡して保存が完了したかどうかをコールバックで受け取れるものも用意されています。必要に応じて使い分けてください。

 取得は、AppStateClient#loadState(OnStateLoadedListener, int)で行います。前述のとおり、非同期で取得したデータが第1引数のリスナーにコールバックされます。

 削除も取得と同様、AppStateClient#deleteState(OnStateDeleteListener, int)に渡した第1引数のリスナーに、結果が非同期でコールバックされます。

Copyright © ITmedia, Inc. All Rights Reserved.

ページトップに戻る