Help us understand the problem. What is going on with this article?

【Android】Google DriveのApp Folderにデータを保存する

残念ながらDrive APIはDeprecatedとなりました。2019/12/6に完全廃止となるようです。今後は、Drive REST APIに移行するか、Firebaseなどクラウドストレージの利用を検討した方がいいでしょう。

App Folderとは

Google DriveのApp Folderは、他のアプリやユーザ自身でもデータの内容を閲覧・編集することができないアプリ専用フォルダです。そのため、アプリの設定値やゲームのセーブデータなどを保存するのにも使えそうです。ただし、ユーザのDrive容量を消費するため、大容量データを保存するのは控えた方が良いでしょう。

App Folderの操作には、Google Drive APIを使用します。この記事では、App Folderにデータを保存する実装例を紹介します。
Google Play Services SDKを利用しますので、まだダウンロードしていない場合はこちらを参照してDLしてください。

Google API Consoleでプロジェクトの作成

  1. Google API Consoleで新しいプロジェクトを作成します
  2. API Manager > 左パネルのライブラリから「Google Drive API」を有効化します

※ App Folderにはプロジェクト単位でデータが保存されるようなので、一つのアプリにつき一つのプロジェクトを作成した方が良いです。

Google API Consoleで認証情報を登録

次にGoogle API Consoleで認証情報を登録します。詳しくはこちらを読んでください。

<手順>
1. 作成したプロジェクトにて、「認証情報を作成」をクリック
2. OAuthクライアントIDを選択
3. Androidを選択
4. 名前は適当な名前を入力 (my-android-app など)
5. Releaseビルド時の署名証明書のフィンガープリント(SHA1)を入力 (後述)
6. アプリのパッケージ名を入力
7. 「作成」をクリック

認証情報の登録にはSHA1が必要になりますが、注意しないといけないのはDebugビルド用とReleaseビルド用でSHA1が異なります

Debugビルド用のSHA1は以下のコマンドで取得します。

$ keytool -exportcert -alias androiddebugkey -keystore ~/.android/debug.keystore -list -v

パスワードは android です。

Releaseビルド用のSHA1も同様に取得します。

$ keytool -exportcert -keystore <YOUR_KEYSTORE_PATH> -list -v

アプリに署名するエイリアスのSHA1をコピペしてください。
なお、Releaseビルド用のSHA1は別プロジェクトで登録済みだと、重複して登録できなかったので、アプリごとにKeystoreのエイリアスを分けるなど対策の必要がありそうです。

また、同じパッケージ名で複数認証情報を登録すると不具合が生じるようです
実際に、Releaseビルド用とDebugビルド用で同じパッケージ名で登録していたら、Releaseビルドではファイルが見つからないといったバグに悩まされました。。。(公式ドキュメントを読んでもよく分からず)
なので、僕はDebugビルドはパッケージ名を別名にして対策しています。

Androidアプリの作成

ここからはAndroidアプリの実装に入ります。Console上の設定は複雑でしたが、実装自体は比較的簡単です。

パッケージ名

上記の認証情報の作成時に入力したパッケージ名と同じにします。

build.gradleの設定

appのbuild.gradleにplay-servicesを追加します。

build.gradle
dependencies {
    compile 'com.google.android.gms:play-services:11.0.2'
}

実装(Java)

コード例を以下に貼ります。説明はコメントを参照していただければと思います。
この例はGoogle Driveにファイルを新規生成する例です。

MainActivity.java
public class MainActivity extends AppCompatActivity
    implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener {

    private static final String TAG = "DriveSample";

   private static final int REQUEST_CODE_RESOLUTION = 1;

    private GoogleApiClient googleApiClient = null;

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

        googleApiClient = new GoogleApiClient.Builder(this)
            .addApi(Drive.API)
            .addScope(Drive.SCOPE_FILE)
            .addScope(Drive.SCOPE_APPFOLDER)  // AppFolderの利用
            .addConnectionCallbacks(this)
            .addOnConnectionFailedListener(this)
            .build();

        // 接続ボタンの実装
        Button connectButton = (Button) findViewById(R.id.connectButton);
        connectButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (googleApiClient != null && !googleApiClient.isConnected()) {
                    // 接続開始
                    googleApiClient.connect();
                }
            }
        });

        // 接続解除ボタンの実装
        Button disconnectButton = (Button) findViewById(R.id.disconnectButton);
        disconnectButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (googleApiClient != null && googleApiClient.isConnected()) {
                    // 接続解除
                    googleApiClient.disconnect();
                }
            }
        });
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        if (requestCode == REQUEST_CODE_RESOLUTION && resultCode == RESULT_OK) {
                        // アクセス承認を得たので再接続開始
            googleApiClient.connect();
        }
    }

    @Override
    public void onConnected(@Nullable Bundle bundle) {
        // 接続完了したのでDriveに新しいコンテンツを生成する
        Drive.DriveApi.newDriveContents(googleApiClient)
            .setResultCallback(driveContentsResultCallback);
    }

    @Override
    public void onConnectionSuspended(int i) {
    }

    @Override
    public void onConnectionFailed(@NonNull ConnectionResult result) {
        if (!result.hasResolution()) {
            // show the localized error dialog.
            GoogleApiAvailability.getInstance().getErrorDialog(this, result.getErrorCode(), 0).show();
            return;
        }

        try {
            // ユーザにGoogle Driveへのアクセス承認ダイアログを表示する
            // onActivityResultに結果が通知されるので第二引数に指定したcodeで判別する
            result.startResolutionForResult(this, REQUEST_CODE_RESOLUTION);
        }
        catch (IntentSender.SendIntentException e) {
            // Exception while starting resolution activity
        }
    }

    private final ResultCallback<DriveContentsResult> driveContentsResultCallback = new ResultCallback<DriveContentsResult>() {
        @Override
        public void onResult(@NonNull DriveContentsResult result) {
            if (!result.getStatus().isSuccess()) {
                // Failed to create new content
                return;
            }

            final DriveContents driveContents = result.getDriveContents();

            OutputStream outputStream = driveContents.getOutputStream();
            Writer writer = new OutputStreamWriter(outputStream);

            try {
                // 適当なJSONデータを書き込む
                JSONObject jsonObject = new JSONObject();
                jsonObject.put("key1", true)
                    .put("key2", 100.0)
                    .put("key3", 123);
                writer.write(jsonObject.toString());
                writer.close();
            }
            catch (JSONException | IOException e) {

            }

            // "sample.json"というファイル名でファイル保存する
            MetadataChangeSet changeSet = new MetadataChangeSet.Builder()
                .setTitle("sample.json")
                .setMimeType("text/plain")
                .build();

            Drive.DriveApi.getAppFolder(googleApiClient)
                .createFile(googleApiClient, changeSet, driveContents)
                .setResultCallback(fileCallback);
        }
    };

    private final ResultCallback<DriveFileResult> fileCallback = new ResultCallback<DriveFileResult>() {
        @Override
        public void onResult(@NonNull DriveFileResult result) {
            if (!result.getStatus().isSuccess()) {
                // Failed to create a file
                return;
            }

            // ファイルの作成に成功するとDriveIdが発行される
            // このDriveIdを保存しておくと、次回以降DriveIdを使ってファイル検索できる
            Log.d(TAG, "Success to create a file. " + result.getDriveFile().getDriveId());
        }
    };
}

この他にもファイルの検索・取得・削除といった操作が可能ですので、公式ドキュメントを参照してみてください。

まとめ

App Folderの使い方を紹介しました。公式ドキュメントにわかりづらい部分もありますが、アプリデータのバックアップなどにも利用できるので、ぜひ使ってみてください。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした