AWS S3にあるデータをAndroidから操作していくための方法です。
まずは一番基本的な、ユーザーを使ったアクセスをやってみます。
開発環境はAndroid Studio2.3、AWS Mobile SDK 2.6.2です。
目次
アクセスするユーザーを作成する
S3の操作をする前に、そのためのユーザーを作成します。
AWSのコンソールを開いて、「すべてのサービス > セキュリティ、 アイデンティティ、 コンプライアンス > IAM」に移動します。
この「Identity and Access Management 」が、S3だけでなくAmazon全体のサービスに対するアクセスを管理しているもののようです。
左のメニューより「ユーザー」を選択して「ユーザーを追加」に進みます。
ユーザー名を入力して、アクセスの種類は「プログラムによるアクセス」にします。ユーザー名は「awsSample」としました。
アクセス権の設定では、ひとまずS3にアクセスするようにしたいのでフルアクセスを設定します。
「既存のポリシーを直接アタッチ」をすると用意されたポリシーが出てくるので、「AmazonS3FullAccess」を選択します。
最後に確認されますので、「ユーザーの作成」を押します。
ここで表示される、アクセスキーIDとシークレットアクセスキーを使用してプログラムからS3にアクセスすることになるので、これを控えておきます。
特にシークレットアクセスキーは後から確認することができません。
もし分からなくなってしまったら、アクセスキーIDとシークレットアクセスキーのペアを新しく作ることはできるので、それで対応することになります。(その場合にはアクセスキーIDも変更することになってしまいます)
S3に保存先 (バケット/Buchet)を用意する
S3のほうでは、ファイルをアップロードするための保存先(バケット)を用意しておきます。
AWSマネジメントコンソールより「すべてのサービス > ストレージ > S3」を開きます。
そして「バケットを作成する」で保存先を作成していきます。
バケット名は「jp-scriptlife-awssample」、リージョンは「アジアパシフィック(東京)」にしました。
S3ではストレージの内容をバケット単位で管理しているようですから、ルートディレクトリみたいなものです。
ここでの注意点は、このバケット名がそのままアクセスするURLになるようで、アカウント内ではなくS3全体で重複が許可されていません。「jp-scriptlife」と入れているのはその重複よけです。
次はバケットの機能についての設定になります。
全部オフのままでもかまいませんが、ログ記録だけはオンにしてみました。
ターゲットバケットは自身を指定し、ターゲットプレフィックスに「logs/」と入れます。
最後に「/」を入れておくと、そのディレクトリを作成してログを保存していってくれるので入れておくと良いです。
最後にアクセス許可の設定です。これもそのまま。
ここにある「ユーザーID」とは先ほど作ったIAMのものではなく、AWSアカウントのことを指しているようです。
完全に別のアカウントからアクセスを許可する場合に設定することになるのでしょう。
あとは確認して「バケットを作成」とします。
Android StudioにAWS Mobile SDK ( Android/Fire OS)を追加する
アクセスするIDと保存先のバケットを準備出来たら、Android StudioでAWS開発をできるようにします。
モバイル向けのSDKがリリースされており、これを使用することになりますが、方法が2通りあります。
Gradleに設定して取得してもらう方法
Module.appのbuild.gradleに以下の設定を追加します。
dependencies { compile 'com.amazonaws:aws-android-sdk-s3:2.2.+' }
追加したら、上に出てくるメッセージにある「Sync Now」を行うと、ライブラリが自動的に読み込まれます。
ライブラリファイルを組み込む方法
「AWS Mobile SDK」から「AWS Mobile SDK: Android/Fire OS」をダウンロードします。
zipファイルを解凍し、「lib/aws-android-sdk-s3-2.6.2.jar」をAndroid Studioプロジェクトの「\app\libs」以下ににコピーします。
Manifest.xmlを修正する
取得するパーミッションを追加する
インターネットアクセスとステータスの取得が必要になるので、以下のuses-permissionを追加します。
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"></uses-permission>
サービスを追加する
転送はサービスが請け負っているようなので、これを追加します。
<service android:name="com.amazonaws.mobileconnectors.s3.transferutility.TransferService" android:enabled="true" />
<service>は<application>の下にします。ここを間違えるとserviceが見つからず、以下のエラーが出ます。
W/ActivityManager(864): Unable to start service Intent { act=add_transfer cmp=jp.co.sundenshi.acereal.awssample/com.amazonaws.mobileconnectors.s3.transferutility.TransferService (has extras) } U=0: not found
ファイルをアップロードしてみる
準備が整ったらファイルをアップロードしてみます。
コードはざっくり以下のように。
// アップロード用のファイルを作成 File uploadFile = null; try { uploadFile = File.createTempFile("upload-sample", "txt"); try (BufferedWriter bw = new BufferedWriter(new FileWriter(uploadFile))) { bw.write("test"); bw.flush(); } } catch (IOException e) { e.printStackTrace(); } String accessKey = "(アクセスキーIDを書く)"; String secKey = "(シークレットキーを書く)"; String buchet = "jp-scriptlife-awssample"; // 認証情報の作成 BasicAWSCredentials basicAWSCredentials = new BasicAWSCredentials(accessKey, secKey); // 作成した認証情報でクライアント接続用オブジェクトを作成 AmazonS3Client s3Client = new AmazonS3Client(basicAWSCredentials); TransferUtility transferUtility = new TransferUtility(s3Client, getApplicationContext()); // ファイルを指定してアップロードを行う TransferObserver observer = transferUtility.upload(buchet, "upload-sample.txt", uploadFile); // コールバックを登録しておく observer.setTransferListener(new TransferListener() { @Override public void onStateChanged(int id, TransferState state) { Log.d("AwsSample", "status: "+state); } @Override public void onProgressChanged(int id, long bytesCurrent, long bytesTotal) { Log.d("AwsSample", "progress: "+id+" bytesCurrent:"+bytesCurrent+" bytesTotal:"+bytesTotal); } @Override public void onError(int id, Exception ex) { ex.printStackTrace(); } });
accessKey, secKeyにはそれぞれ、IAMで作成したアクセスキーIDとシークレットアクセスキーを書きます。
buchetにはアップロードしたいバケット名を書きます。
実行すると
D/AwsSample: status: IN_PROGRESS
D/AwsSample: progress: 3 bytesCurrent:0 bytesTotal:4
D/AwsSample: progress: 3 bytesCurrent:4 bytesTotal:4
D/AwsSample: progress: 3 bytesCurrent:4 bytesTotal:4
D/AwsSample: progress: 3 bytesCurrent:4 bytesTotal:4
D/AwsSample: status: COMPLETED
とログが流れて、最後「COMPLETED」で完了することが分かります。
またS3のバケットをコンソールから見てみると、登録されていることが確認できます。
アクセスする方法の種類
AWSを操作する基本的な流れとしては「認証情報を作成→それを使って操作用のオブジェクトを作成」の段階を踏むようになっているようです。
「認証情報を作成」で使用できる方法は以下の3通りあります。
- アクセスキーIDとシークレットアクセスキーによる認証
- ロールを使用した権限の付与
- Amazon Cogniteを用いた認証
今回試したのは1.の方法になります。一番オーソドックスな方法ですね。
2. については認証自体はアクセスキーIDとシークレットアクセスキーを用いて行います。しかしS3へのアクセスはそのユーザーには許可されておらず、「ロール」とよばれるアクセス権の設定情報を使用する許可がユーザーにはされています。それで認証したユーザーは「ロール」を使用することでS3にアクセスするようになっています。
3.は認証用の別サービスですね。TwitterとかFacebookとかのアカウントを利用してユーザーを作成したりするみたい。
androidTestで実行するときの注意
開発するときには、androidTestのほうでユニットテストとして実行するときもあると思います。
それを行ったときに、このまま書いて実行したら途中で次のエラーが発生して、うまくアップロードできませんでした。
I/ActivityManager(863): Force stopping jp.co.sundenshi.acereal.awssample appid=10093 user=0: finished instI/ActivityManager(863): Killing 3992:jp.co.sundenshi.acereal.awssample/u0a93 (adj 0): stop jp.co.sundenshi.acereal.awssampleI/ActivityManager(863): Force stopping service ServiceRecord{41e62958 u0 jp.co.sundenshi.acereal.awssample/com.amazonaws.mobileconnectors.s3.transferutility.TransferService}
おそらくtransferUtility.uploadは内部的にサービスに処理を依頼して、実際のアップロードが終わる前に処理を返してしまっているから、テストが抜けたところでアプリが終了してしまい、結果アップロード処理が中途半端になってしまうのだろうと思います。
これについてはステータスが終了するまでまってやればよさそうだったので、とりあえず
while ( false == (observer.getState() == TransferState.COMPLETED || observer.getState() == TransferState.FAILED || observer.getState() == TransferState.CANCELED) ) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } }
としてメソッドの中で終了を待つようにしてみました。
androidTestはこれでうまく待機できましたが、ActivityのonCreateでやったらアプリからの応答がなくなってしまったので注意です。