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

Dropbox API v2をAndroidで使ってみる

More than 1 year has passed since last update.

はじめての投稿です。よろしくお願いします。

概要

Dropbox v1 APIの廃止に伴い、AndroidアプリでDropbox v2 APIを使ったファイルのアップロード・ダウンロード機能を実装したのでメモを残します。
今回、v1 APIを使って以前実装したものをv2版に置き換えたのですが、本記事では新たにDropbox APIを使う方向けに導入手順から記載しています。

環境

  • 開発環境

    • Android Studio 2.0
    • Gradle 2.10
    • JRE 1.7
  • 動作環境

    • 端末: SHV34
    • OS: Android7.0

導入手順

導入でやることは下記のとおりです。

  • DropboxのApp Consoleにアプリを登録する
  • Dropbox v2 APIを取得する
  • Manifestにpermissionを追加する
  • ManifestにDropbox Activityを追加する

DropboxのApp Consoleにアプリを登録する

(前提)Dropboxのアカウントが必要です。

1.DropboxのApp Consoleを開き、[My apps]→[Create app]を選択します。

2.入力フォームの上から[Dropbox API]→[Full Dropbox]を選択し、アプリ名を入力して[Create app]を押します。
アプリ名はユニークな文字列でないといけないようです。画面の例では"test_app"と入力したところ重複エラーが出たため、特に意味もなく頭に"super_"を付けています。

登録完了後、App keyが実装で必要になります。後述の実装でYOUR_DROPBOX_KEYと記載している箇所はこのApp keyを指定してください。
【補足】v1 APIを使った実装ではApp secretも必要でしたが、v2 APIでは使用しなくても最低限動作することを確認しました。

Dropbox v2 APIを取得する

(以降、Android Studioで実施する手順になります。)

Dropbox v2 APIを取得するため、build.gradle(Module: app)のdependenciesに下記を追加します。
ここではv3.0.5を取得していますが、Dropbox v2 APIを適宜確認して最新のバージョンを取得してください。

build.gradle
dependencies {
    compile 'com.dropbox.core:dropbox-core-sdk:3.0.5'
}

Manifestにpermissionを追加する

ネットワーク通信、及びファイルのアップロード・ダウンロードを行うため、下記のpermissionをAndroidManifest.xmlに追加します。

AndroidManifest.xml
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

ManifestにDropbox Activityを追加する

AndroidManifest.xmlにDropbox Activityを追加します。
これはDropboxが提供している認証用Activityで、ここで先ほどのApp keyを指定して登録したアプリと紐づけます。

AndroidManifest.xml
       <activity
            android:name="com.dropbox.core.android.AuthActivity"
            android:configChanges="orientation|keyboard"
            android:launchMode="singleTask">
            <intent-filter>
                <data android:scheme="YOUR_DROPBOX_KEY" />
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.BROWSABLE" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>

【補足】これを記述せずに、後述の認証要求Auth.startOAuth2Authentication(mContext, DROP_BOX_KEY);を実行すると、下記のエラーが出ます。

java.lang.IllegalStateException: URI scheme in your app's manifest is not set up correctly. You should have a com.dropbox.core.android.AuthActivity with the scheme: YOUR_KEY

ProGuardに追加する

proguard-rules.proに下記を追加します。
これを記述せずに署名付きapkを作成すると、エラーになります。

proguard-rules.pro
# OkHttp and Servlet optional dependencies
-dontwarn okio.**
-dontwarn okhttp3.**
-dontwarn com.squareup.okhttp.**
-dontwarn com.google.appengine.**
-dontwarn javax.servlet.**

# Support classes for compatibility with older API versions
-dontwarn android.support.**
-dontnote android.support.**

実装

処理の流れ

操作の流れに沿って、処理の流れを説明します。

(認証していない場合)

  1. アップロードボタンを押す。

    • onClick(View v)が呼ばれ、mDropboxManager.startAuthenticate()を実行します。
  2. 認証画面(*1)が開くので「許可」を押す。

    • アプリ画面に戻り、onResume()が呼ばれます。
    • mDropboxManager.getAccessToken();で取得したトークンを使って、new DropboxManager(this, token);でインスタンス化します。ここでトークンをPreferenceに保存します。
    • 非同期タスクAsyncHttpRequest内でアップロードmDropboxManager.backup(srcFilePath, dstFilePath);を実行します。

(認証済みの場合)

  1. アップロードボタンを押す。
    • onClick(View v)が呼ばれ、mDropboxManager.startAuthenticate()を実行します。
    • 保存したトークンを用いてnew DropboxManager(this, token);でインスタンス化します。
    • 非同期タスクAsyncHttpRequest内でアップロードmDropboxManager.backup(srcFilePath, dstFilePath);を実行します。

ダウンロード処理においても、処理の流れは同じです。

(*1) 認証画面

アップロード・ダウンロード処理について

Android端末内のファイル"test.splite"をDropboxに"db_data.bak"としてアップロードしています。
ダウンロードでは、逆にDropbox上の"db_data.bak"をダウンロードして、"test.splite"として保存しています。

ソースコードの記載について

本題以外のソースコードを最小限にするため、文字列は直接記載しており、例外処理やダイアログ・トースト表示も極力省略しています。

ソースコード

Activity

BackupActivity.java
public class BackupActivity extends Activity implements View.OnClickListener {

    private enum BackupAction {
        NONE,
        BACKUP_DROPBOX,
        RESTORE_DROPBOX,
    }

    private BackupAction mCurrentAction;
    private SharedPreferences mPref;
    private DropboxManager mDropboxManager;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_backup);

        mPref = PreferenceManager.getDefaultSharedPreferences(this);
        mCurrentAction = BackupAction.NONE;

        ((Button) findViewById(R.id.backup_backup_dropbox)).setOnClickListener(this);
        ((Button) findViewById(R.id.backup_restore_dropbox)).setOnClickListener(this);
    }

    /**
     * Dropboxの認証画面から戻った際に呼ばれる。
     * バックアップ・復元を実行する。
     */
    @Override
    protected void onResume() {
        super.onResume();
        switch (mCurrentAction) {
            case NONE:
                break;
            case BACKUP_DROPBOX:
                mCurrentAction = BackupAction.NONE;

                try {
                    String token = mDropboxManager.getAccessToken();
                    if(token == null) {
                        // 認証失敗
                        break;
                    }
                    mDropboxManager = new DropboxManager(this, token);

                    // 次回以降に認証を省くため、トークンを保存する
                    SharedPreferences.Editor edit = mPref.edit();
                    edit.putString("access_token", token);
                    edit.commit();

                    // アップロードを実行
                    new AsyncHttpRequest(this, BackupAction.BACKUP_DROPBOX).execute();

                } catch (IllegalStateException e) {
                    // IllegalStateException
                }
                break;
            case RESTORE_DROPBOX:
                mCurrentAction = BackupAction.NONE;

                try {
                    String token = mDropboxManager.getAccessToken();
                    if(token == null) {
                        // 認証失敗
                        break;
                    }

                    mDropboxManager = new DropboxManager(this, token);

                    // 次回以降に認証を省くため、トークンを保存する
                    SharedPreferences.Editor edit = mPref.edit();
                    edit.putString("access_token", token);
                    edit.commit();

                    // ダウンロードを実行
                    new AsyncHttpRequest(this, BackupAction.RESTORE_DROPBOX).execute();
                } catch (IllegalStateException e) {
                    // IllegalStateException
                }
                break;
            default:
                break;
        }
    }

    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.backup_backup_dropbox:
                // 認証済みの場合はアップロードを実行、未認証の場合は認証処理を行う
                if (mPref.contains("access_token")) {

                    if(mDropboxManager == null) {
                        String token = mPref.getString("access_token", null);
                        mDropboxManager = new DropboxManager(this, token);
                    }

                    // アップロードを実行
                    new AsyncHttpRequest(this, BackupAction.BACKUP_DROPBOX).execute();

                } else {
                    mCurrentAction = BackupAction.BACKUP_DROPBOX;

                    if(mDropboxManager == null) {
                        mDropboxManager = new DropboxManager(this);
                    }
                    // 認証ページを開く
                    mDropboxManager.startAuthenticate();
                }
                break;

            case R.id.backup_restore_dropbox:
                // 認証済みの場合はダウンロードを実行、未認証の場合は認証処理を行う
                if (mPref.contains("access_token")) {

                    if(mDropboxManager == null) {
                        String token = mPref.getString("access_token", null);
                        mDropboxManager = new DropboxManager(this, token);
                    }

                    // ダウンロードを実行
                    new AsyncHttpRequest(this, BackupAction.RESTORE_DROPBOX).execute();
                } else {
                    mCurrentAction = BackupAction.RESTORE_DROPBOX;

                    if(mDropboxManager == null) {
                        mDropboxManager = new DropboxManager(this);
                    }
                    // 認証ページを開く
                    mDropboxManager.startAuthenticate();
                }
                break;
            default:
                break;
        }
    }

    /**
     * 非同期処理終了後に呼び出される。
     * メッセージを表示する。
     */
    public void callback(BackupAction action, boolean result) {
        switch (action) {
            case BACKUP_DROPBOX:
                if (result) {
                    // success
                } else {
                    // failed
                }
                break;
            case RESTORE_DROPBOX:
                if (result) {
                    // success
                } else {
                    // failed
                }
                break;
            default:

                break;
        }
    }

    /**
     * アップロード・ダウンロードのHTTPリクエストを行う非同期処理タスク。
     */
    public class AsyncHttpRequest extends AsyncTask<Void, Void, Boolean> {

        private BackupActivity activity;
        private BackupAction action;
        private ProgressDialog progressDialog;

        public AsyncHttpRequest(BackupActivity activity, BackupAction action) {
            this.activity = activity;
            this.action = action;
        }

        @Override
        protected void onPreExecute() {
            // プログレスダイアログの生成
            this.progressDialog = new ProgressDialog(activity);
            switch (action) {
                case BACKUP_DROPBOX:
                    this.progressDialog.setTitle("アップロード中...");
                    this.progressDialog.setMessage("アップロード中です。このまましばらくお待ちください");
                    break;
                case RESTORE_DROPBOX:
                    this.progressDialog.setTitle("ダウンロード中...");
                    this.progressDialog.setMessage("ダウンロード中です。このまましばらくお待ちください");
                    break;
                default:
                    break;
            }
            this.progressDialog.setCancelable(false); // キャンセル不可にする
            this.progressDialog.show();
            return;
        }

        @Override
        protected Boolean doInBackground(Void... builder) {
            boolean isSuccess = false;
            String srcFilePath = "/data/data/" + getPackageName() + "/databases/test.splite"; // Android端末内のSQLiteのデータ
            String dstFilePath = "db_data.bak"; // Dropboxに保存される際のファイル名

            switch (action) {
                case BACKUP_DROPBOX: {
                    isSuccess = mDropboxManager.backup(srcFilePath, dstFilePath);
                    break;
                }
                case RESTORE_DROPBOX: {
                    isSuccess = mDropboxManager.restore(dstFilePath, srcFilePath);
                    break;
                }
                default:
                    break;
            }
            return isSuccess;
        }

        @Override
        protected void onPostExecute(Boolean result) {
            // プログレスダイアログを閉じる
            if (this.progressDialog != null && this.progressDialog.isShowing()) {
                this.progressDialog.dismiss();
            }             
            activity.callback(action, result);
        }
    }
}

DropboxManager

DropboxManager.java
public class DropboxManager {

    private Context mContext;
    private DbxClientV2 mClient;

    private final static String DROP_BOX_KEY = "YOUR_DROPBOX_KEY";

    /**
     * コンストラクタ。
     * @param context コンテキスト
     */
    public DropboxManager(Context context) {
        mContext = context;
    }

    /**
     * コンストラクタ。認証済みの場合はこちらを使用する。
     * @param context コンテキスト
     * @param token 認証済みトークン
     */
    public DropboxManager(Context context, String token) {
        mContext = context;
        DbxRequestConfig config = new DbxRequestConfig("Name/Version");
        mClient = new DbxClientV2(config, token);
    }

    /**
     * 認証処理を開始する。
     * 認証ページが開く。
     */
    public void startAuthenticate() {
        Auth.startOAuth2Authentication(mContext, DROP_BOX_KEY);
    }

    /**
     * 認証トークンを取得する。
     * @return 認証トークン。
     */
    public String getAccessToken() {
        return Auth.getOAuth2Token();
    }

    /**
     * ファイルをバックアップする。
     * @param srcFilePath 保存元のファイルパス(Android)
     * @param dstFilePath 保存先のファイルパス(Dropbox)
     * @return 成功した場合はtrue,失敗した場合はfalseを返す
     */
    public boolean backup(String srcFilePath, String dstFilePath) {
        try {
            File file = new File(srcFilePath);
            InputStream input = new FileInputStream(file);
            byte[] bytes = convertFileToBytes(file);
            // Dropboxのファイルパスは先頭に"/"と指定する必要があるため、ファイルパスの先頭に"/"をつける
            mClient.files().uploadBuilder("/" + dstFilePath)
                    .withMode(WriteMode.OVERWRITE)
                    .uploadAndFinish(input);

        } catch (Exception e) {
            Log.e("tag", "Upload Error: " + e);
            return false;
        }
        return true;
    }

    /**
     * ファイルを復元する。
     * @param srcFilePath 復元元のファイルパス(Dropbox)
     * @param dstFilePath 復元先のファイルパス(Android)
     * @return 成功した場合はtrue,失敗した場合はfalseを返す
     */
    public boolean restore(String srcFilePath, String dstFilePath) {
        try {
            // ファイルを検索する
            SearchResult result = mClient.files().search("", srcFilePath);
            List<SearchMatch> matches = result.getMatches();
            LogUtil.d("matches.size(): " + matches.size());

            Metadata metadata = null;
            for (SearchMatch match : matches) {
                metadata = match.getMetadata();
                break;
            }

            if(metadata == null) {
                // ファイルが見つからない場合
                Log.d("tag", "metadata not found");
                return false;
            } else {
                Log.d("tag", "metadata.getPathLower(): " + metadata.getPathLower());
            }

            // ダウンロードし、ファイルを置き換える
            File file = new File(dstFilePath);
            OutputStream os = new FileOutputStream(file);
            mClient.files().download(metadata.getPathLower()).download(os);

        } catch (Exception e) {
            Log.e("tag", "Download Error: " +  e);
            return false;
        }
        return true;
    }

    /**
     * Fileをbytes[]に変換する。
     * @param file ファイル
     * @return bytes
     * @throws IOException
     */
    private byte[] convertFileToBytes(File file) throws IOException {
        final long fileSize = file.length();
        final int byteSize = (int) fileSize;
        byte[] bytes = new byte[byteSize];
        try {
            RandomAccessFile raf = new RandomAccessFile(file, "r");
            try {
                raf.readFully(bytes);
            } finally {
                raf.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return bytes;
    }
}

activity_backup.xml

activity_backup.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:id="@+id/backup_backup_dropbox"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Dropboxにアップロードする" />

    <Button
        android:id="@+id/backup_restore_dropbox"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Dropboxからダウンロードする" />
</LinearLayout>

参考

Dropbox v2 API
examples/androidにAndroidアプリのサンプルコードもあります。

Why do not you register as a user and use Qiita more conveniently?
  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
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