Cloud Save(Google Play App State)とは
アプリのデータをGoogleのサーバ上に保存することができます。
・保存できるデータサイズは128KB × 4スロット (最大512KB)
・データフォーマットはバイト配列
・保存できるデータサイズは128KB × 4スロット (最大512KB)
・データフォーマットはバイト配列
公式ドキュメント
Google Play Game Services — Google Developers
今回クライアント側はAndroidですが、iOS/Webアプリからも利用できるようです。
ドキュメントではGoogle Play Game Servicesの括りで紹介されていますが、ゲーム以外でも活用できるんじゃないかと思います。
Androidアプリ向けのDeveloper's Guideはこちら。
Cloud Save in Android ― Google Play Game Services — Google Developers
Cloud Saveサービス側のセットアップ
1. Google API ConsoleにアクセスしてAPI Projectを作成。
2. Servicesから[Google Play App State]を有効化。
3. API Accessから[Create an OAuth 2.0 client ID...]をクリック。
4. [Produce name]を入力して[Next]をクリック。
5. Application typeは[Installed application]を選択。
Installed application typeは[Android]を選択。
[Pacage name]/[Signing certificate fingerprint]を入力。
[Create client ID]をクリック。
そうするとClient IDがこんな感じで生成されますので、このClient IDを後述のAndroidManifest.xmlに設定します。
Androidアプリ側のセットアップ
1. Google Play servicesライブラリ(rev.7)をインストールしてEclipseにインポート
2. アプリから上記ライブラリを参照
サンプルコード
MainActivity.java
package com.example.cloudsave;
import android.app.Activity;
import android.content.Intent;
import android.content.IntentSender;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.EditText;
import android.widget.TextView;
import com.google.android.gms.appstate.AppStateClient;
import com.google.android.gms.appstate.OnStateLoadedListener;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesClient.ConnectionCallbacks;
import com.google.android.gms.common.GooglePlayServicesClient.OnConnectionFailedListener;
import com.google.android.gms.common.Scopes;
public class MainActivity extends Activity implements ConnectionCallbacks, OnConnectionFailedListener,
OnStateLoadedListener, OnClickListener {
private AppStateClient mAppStateClient;
private boolean mConnecting;
private TextView mTextView;
private EditText mEditText;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
String[] scopes = new String[] {
Scopes.APP_STATE
};
mAppStateClient = new AppStateClient.Builder(this, this, this).setScopes(scopes).create();
findViewById(R.id.button_update).setOnClickListener(this);
findViewById(R.id.button_load).setOnClickListener(this);
mTextView = (TextView) findViewById(R.id.textview);
mEditText = (EditText) findViewById(R.id.edittext);
}
@Override
protected void onStart() {
super.onStart();
if (!mConnecting) {
mConnecting = true;
mAppStateClient.connect();
}
}
@Override
protected void onStop() {
super.onStop();
mAppStateClient.disconnect();
}
@Override
public void onConnected(Bundle bundle) {
mConnecting = false;
mAppStateClient.loadState(this, 0);
}
@Override
public void onDisconnected() {
}
@Override
public void onConnectionFailed(ConnectionResult connectionResult) {
if (connectionResult.hasResolution()) {
try
{
mConnecting = true;
connectionResult.startResolutionForResult(this, 0);
} catch (IntentSender.SendIntentException e) {
mAppStateClient.connect();
}
} else {
// TODO:接続失敗時の処理
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK) {
mAppStateClient.connect();
}
}
@Override
public void onStateConflict(int stateKey, String resolvedVersion, byte[] localData, byte[] serverData) {
mAppStateClient.resolveState(this, 0, resolvedVersion, serverData);
}
@Override
public void onStateLoaded(int statusCode, int stateKey, byte[] data) {
if (data == null) {
return;
}
mTextView.setText("Cloud Saveサービス上のデータ:" + new String(data));
mEditText.setText("");
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.button_update:
String message = mEditText.getText().toString();
mAppStateClient.updateStateImmediate(this, 0, message.getBytes());
break;
case R.id.button_load:
mAppStateClient.loadState(this, 0);
break;
default:
break;
}
}
}
ざっくり解説
【データセーブ】
データのセーブはupdateStateImmediate()で行う。
更新成功するとonStateLoaded()が呼ばれる。
コンフリクト発生時はonStateConflict()が呼ばれるのでresolveState()で解決する。
【データロード】
データのロードはloadState()で行う。
読み込みが完了するとonStateLoaded()が呼ばれる。
このコードではデータのセーブにAppStateClient.updateStateImmediate()を使っていますが、AppStateClient.updateState()を使った方がバッテリー・ネットワークに優しいそうです。
【データセーブ】
データのセーブはupdateStateImmediate()で行う。
更新成功するとonStateLoaded()が呼ばれる。
コンフリクト発生時はonStateConflict()が呼ばれるのでresolveState()で解決する。
【データロード】
データのロードはloadState()で行う。
読み込みが完了するとonStateLoaded()が呼ばれる。
このコードではデータのセーブにAppStateClient.updateStateImmediate()を使っていますが、AppStateClient.updateState()を使った方がバッテリー・ネットワークに優しいそうです。
AndroidManifest.xml
※レイアウトXMLは割愛
<application>
<meta-data
android:name="com.google.android.gms.appstate.APP_ID"
android:value="<Your Client ID>" />
</application>
<application>タグの中にClient IDを設定する。※レイアウトXMLは割愛
動かしてみた
ActivityのonStart()でconnect()を読んでいるので
アプリを起動するとログインを求めるダイアログが出ます。
サンプルアプリの動きは下記の通りです。
[Update State]ボタンでEditTextに入力した内容をCloud Saveに保存
[Load State]ボタンでCloud Saveからデータを読み込みTextViewに表示
2台の端末からデータのセーブを行うケースで、ローカルの情報が古い状態でデータのセーブを行うとコンフリクトが発生します。
サンプルでは、コンフリクト発生時はサーバ側のデータを採用するようにしています。
コンフリクトの解決についての公式ドキュメントはこちらです。#まだ読んでません...
Resolving Cloud Save Conflicts | Android Developers http://developer.android.com/training/cloudsave/conflict-res.html
最後に
API Consoleを見ると、20,000,000 requests/dayとあります。
上限を超過したときってどうなるんでしょう?
ご存知の方がおられましたら教えてください。






