はじめての投稿です。よろしくお願いします。
概要
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を適宜確認して最新のバージョンを取得してください。
dependencies {
compile 'com.dropbox.core:dropbox-core-sdk:3.0.5'
}
Manifestにpermissionを追加する
ネットワーク通信、及びファイルのアップロード・ダウンロードを行うため、下記のpermissionを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
を指定して登録したアプリと紐づけます。
<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を作成すると、エラーになります。
# 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.**
実装
処理の流れ
操作の流れに沿って、処理の流れを説明します。
(認証していない場合)
-
アップロードボタンを押す。
-
onClick(View v)
が呼ばれ、mDropboxManager.startAuthenticate()
を実行します。
-
-
認証画面(*1)が開くので「許可」を押す。
- アプリ画面に戻り、
onResume()
が呼ばれます。 -
mDropboxManager.getAccessToken();
で取得したトークンを使って、new DropboxManager(this, token);
でインスタンス化します。ここでトークンをPreferenceに保存します。 - 非同期タスク
AsyncHttpRequest
内でアップロードmDropboxManager.backup(srcFilePath, dstFilePath);
を実行します。
- アプリ画面に戻り、
(認証済みの場合)
- アップロードボタンを押す。
-
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
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
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
<?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アプリのサンプルコードもあります。