対象者
- Androidアプリで画像アップロード機能を実装したい人
- 基本的なAndroid開発、AWS(S3、IAM等)の知識がある人
概要
AndroidでS3を使用した写真投稿機能を実装しようとした時に意外とやることが多かったので、整理して手順をまとめました。
以下、***「画像を選択して、S3にアップロードする」***までの今回の実装の流れです。
- 「画像の選択」
- Intentでピッカーを起動する
- 選択した画像のUriを取得する
- 「アップロード用の一時ファイル作成」
- UriからBitmapを生成
- アップロード用のFileを作成
- 「S3へのアップロード」
- TransferUtilityでアップロードを実行して、TransferObserverを取得する
- TransferListenerをアップロード状況にあわせて実装し、TransferObserverにセットする
他にもいろいろやり方がありそうですが、今回はACTION_GET_CONTENT
で画像を選択して、AWS Mobile SDK を使用してS3にアップロードしています。
注意点としては、1で選択した画像がUri
で、3でS3アップロード時に指定するのがFile
とインタフェースが異なるため、2でUriからBitmapを生成してアップロード用の一時Fileを作成しています。
一時ファイル作成時に画像のリサイズ等の加工が可能です。
1. 画像の選択
Intentでピッカーを起動する
以下のパラメータでIntentを作成して、画像選択用のピッカーを起動します。
// Create Intent to pick up an image.
Intent i = new Intent();
i.setType("image/*");
i.addCategory(Intent.CATEGORY_OPENABLE);
i.setAction(Intent.ACTION_GET_CONTENT);
startActivityForResult(i, REQUEST_PICK_UP_IMAGE);
参考: https://developer.android.com/reference/android/content/Intent.html
※ ACTION_GET_CONTENT with MIME type */* and category CATEGORY_OPENABLE
を参照
選択した画像のUriを取得する
onActivityResult
で選択した画像のUri
を受け取ります。
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK && requestCode == REQUEST_PICK_UP_IMAGE) {
Uri uri = data.getData();
}
}
2. アップロード用の一時ファイル作成
UriからBitmapを生成
UriからBitmapを生成します。
今回はアップロード用にリサイズも行っています。
dependencies {
compile 'com.squareup.picasso:picasso:2.5.2'
}
private ImageView mPreview;
private File mUploadFile;
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_PICK_UP_IMAGE && resultCode == RESULT_OK) {
Uri uri = data.getData();
Picasso.with(ImageUploadActivity.this).load(uri).resize(200, 200).centerCrop().into(new Target() {
@Override
public void onBitmapLoaded(final Bitmap bitmap, Picasso.LoadedFrom from) {
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
mUploadFile = createUploadFile(ImageUploadActivity.this, bitmap);
mPreview.setImageBitmap(bitmap);
}
});
}
@Override
public void onBitmapFailed(Drawable errorDrawable) {
// TODO handle error
}
@Override
public void onPrepareLoad(Drawable placeHolderDrawable) {
// Do nothing
}
});
}
}
今回はPicassoを使いましたが、Glideや標準のBitmapFactoryでも代替可能です。
BitmapFactoryを使用する場合はBitmapFactory.Options
を使用してサイズの大きい画像でOutOfMemoryにならないように気をつけたほうがよさそうです。
参考: https://developer.android.com/topic/performance/graphics/load-bitmap.html
また、端末によっては画像の向きが正しく反映されないこともあるので、今回の実装例ではできていませんが、Exif情報を参照して向きを補正する処理を入れた方がベターだと思います。
参考: http://qiita.com/murapon/items/1a39746cd4aab7b2c245
アップロード用のFileを作成
Bitmap生成後、アップロード用のファイルをアプリのキャッシュ用ストレージに作成します。
private static final String TEMP_FILE_NAME = "temp_upload_image";
private File createUploadFile(Context context, Bitmap bitmap) {
File file = new File(new File(String.valueOf(context.getExternalCacheDir())), TEMP_FILE_NAME);
FileOutputStream fos = null;
try {
file.createNewFile();
fos = new FileOutputStream(file);
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);
} catch (IOException e) {
e.printStackTrace();
// TODO handle error
} finally {
try {
if (fos != null) {
fos.flush();
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
// TODO handle error
}
}
return file;
}
getExternalCacheDir()
を使うと、Androidの「設定」->アプリのキャッシュ削除等でファイルが消される可能性があるため、残したい場合は、getExternalFilesDir()
等を使用した方がよいです。
参考: https://developer.android.com/guide/topics/data/data-storage.html
3. S3へのアップロード
TransferUtilityでアップロードを実行して、TransferObserverを取得する
AWS Mobile SDKのTransferUtility
を使用します。
dependencies {
compile 'com.amazonaws:aws-android-sdk-s3:2.+'
}
<service
android:name="com.amazonaws.mobileconnectors.s3.transferutility.TransferService"
android:enabled="true" />
private void uploadFile(final Context context, File file) {
CognitoCachingCredentialsProvider credentialsProvider = AWSUtil.getCredentialsProvider(context);
final AmazonS3 s3 = new AmazonS3Client(credentialsProvider);
TransferUtility transferUtility = new TransferUtility(s3, context.getApplicationContext());
TransferObserver transferObserver = transferUtility.upload("my_bucket", "my_images/01.jpg", file, CannedAccessControlList.PublicRead);
...省略
}
TransferUtility.upload()
の第4引数でアップロードするファイルのアクセス権限を指定できます。
403エラーになる場合は、CognitoIdentityPoolに紐付いているIAMロールでs3:PutObject
とs3:PutObjectAcl
が許可されているか確認してください。
TransferListenerをアップロード状況にあわせて実装し、TransferObserverにセットする
private void uploadFile(final Context context, File file) {
...省略
TransferObserver transferObserver = transferUtility.upload("my_bucket", "my_images/01.jpg", file, CannedAccessControlList.PublicRead);
transferObserver.setTransferListener(new TransferListener() {
@Override
public void onStateChanged(int id, TransferState state) {
switch (state) {
case COMPLETED:
Toast.makeText(context, "Completed to upload an image to S3.", Toast.LENGTH_SHORT).show();
mUploadFile.delete();
break;
case IN_PROGRESS:
break;
default:
mUploadFile.delete();
}
}
@Override
public void onProgressChanged(int id, long bytesCurrent, long bytesTotal) {
// Do nothing
}
@Override
public void onError(int id, Exception ex) {
ex.printStackTrace();
Toast.makeText(context, "Failed to upload an image to S3.", Toast.LENGTH_SHORT).show();
mUploadFile.delete();
}
});
}
参考: http://docs.aws.amazon.com/ja_jp/mobile/sdkforandroid/developerguide/s3transferutility.html
まとめ
UriとかFileとか汎用的なインタフェースを扱うせいか、変換処理でコード量が多くなってしまい結構面倒でした。
向きの補正とかはまだできていないので、今後も改善しつつもっといい実装方法を模索したいです。
(画像扱うの大変。。)