AndroidでCloud Storage を使ってみる
Android studioでkotlinを使ってAndroidアプリ作成の勉強中。
写真や動画などを保管、提供することができるFirebase Cloud Storageを使ってみた。
プロジェクトは、
・ダウンロードボタンを押すとCloudStorageに保存された画像を取得してImageViewに表示
・アップロードボタンを押すとデバイス内のフォルダへ遷移し、写真を選択するとCloudStorageへアップロード
という簡単な構成で作った。
準備
これをもとにfirebaseをプロジェクトに追加する。
Android プロジェクトに Firebase を追加する | Android 向け Firebase
Firebaseコンソールを開き、Cloud StorageのRulesを設定する。
通常はFirebase Authenticationで認証されたユーザーのみにアクセス権をあげるのが良さそう。
Cloud Storage 用の Firebase セキュリティ ルールを理解する | Firebase Storage
service firebase.storage { match /b/{bucket}/o { match /{allPaths=**} { allow read, write: if request.auth != null; } } }
今回は自分で使ってみるだけなので、認証なしでアクセスできるようルールを変更。
rules_version = '2'; service firebase.storage { match /b/{bucket}/o { match /{allPaths=**} { allow read, write:if true; } } }
また画像のダウンロードを試すため、CloudStorageのFilesにimageフォルダを作成し、
その中に"sample.jpeg"という名前で画像を追加しておいた。
モジュールのbuild.gradleにCloud Storageの依存関係を追加する。
dependencies { //Firebase Android BoM(部品構成表)を使用すると、アプリは常に互換性のあるバージョンのライブラリを使用する implementation platform('com.google.firebase:firebase-bom:30.3.1') implementation 'com.google.firebase:firebase-storage-ktx' }
アップロードを試すためにデバイス内のフォルダにアクセスする必要があるため、
AndroidManifest内にパーミッションを追加。
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
activity_main.xml
ImageViewとButton2つのレイアウトを作成。
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:gravity="center"> <ImageView android:id="@+id/download_image" android:layout_width="300dp" android:layout_height="300dp" android:src="@drawable/ic_launcher_foreground" android:background="@color/cardview_dark_background"/> <Button android:id="@+id/download_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="8dp" android:text="Download Image"/> <Button android:id="@+id/upload_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="8dp" android:text="Upload Image"/> </LinearLayout> </androidx.constraintlayout.widget.ConstraintLayout>
CloudStorageの参照
//ストレージのインスタンスを取得 val storage = Firebase.storage //referenceを取得 var storageRef = storage.reference //child()を使って下位のフォルダやファイルも参照できる var imageRef: StorageReference? = storageRef.child("image") var sampleRef = storageRef.child("image/sample.jpeg")
ダウンロード
メモリにダウンロード
アプリが持つメモリより大きいファイルだとクラッシュしてしまうため、最大サイズを指定する。
val ONE_MEGABYTE: Long = 1024 * 1024 sampleRef.getBytes(ONE_MEGABYTE).addOnSuccessListener { val bitmap = BitmapFactory.decodeByteArray(it, 0, it.size) findViewById<ImageView>(R.id.download_image).setImageBitmap(bitmap) Log.d("cloud storage", "download success") }.addOnFailureListener { Log.d("cloud storage", "download failure $it") }
ローカルファイルにダウンロード
val localFile = File.createTempFile("images", "jpg") sampleRef.getFile(localFile).addOnSuccessListener { val bitmap = BitmapFactory.decodeFile(localFile.absolutePath) findViewById<ImageView>(R.id.download_image).setImageBitmap(bitmap) Log.d("cloud storage", "download ${localFile.absolutePath}") }.addOnFailureListener { Log.d("cloud storage", "download failure $it") }
アップロード
val stream = contentResolver.openInputStream(uri) //デバイス内の画像のURIからstreamに変換 val storageRef = storage.reference val userImageRef = storageRef.child("image/${uri.path}") //参照を作成して、画像の保存先にする val uploadTask = userImageRef.putStream(stream!!) //上記の参照にstreamを追加 uploadTask.addOnSuccessListener { Log.d("cloud storage", "upload success") stream.close() }.addOnFailureListener { error -> Log.d("cloud storage", "upload failure $error") }
MainActivity.kt全体コード
あらかじめアップロードしておいた画像がダウンロードされImageViewに表示、
選択した画像がCloud StorageのImageフォルダ内に保存されていることを確認した。
class MainActivity : AppCompatActivity() { private lateinit var storage:FirebaseStorage override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) storage = Firebase.storage val downloadButton = findViewById<Button>(R.id.download_button) downloadButton.setOnClickListener { downloadImage() } val uploadButton = findViewById<Button>(R.id.upload_button) uploadButton.setOnClickListener { checkOpenDocumentPermission() } } //Cloud Storageからダウンロード private fun downloadImage(){ val storageRef = storage.reference val pathReference = storageRef.child("image/sample.jpeg") val ONE_MEGABYTE: Long = 1024 * 1024 pathReference.getBytes(ONE_MEGABYTE).addOnSuccessListener { val bitmap = BitmapFactory.decodeByteArray(it, 0, it.size) findViewById<ImageView>(R.id.download_image).setImageBitmap(bitmap) Log.d("cloud storage", "download success") }.addOnFailureListener { Log.d("cloud storage", "download failure $it") } } //デバイス内ストレージのアクセス権 private fun checkOpenDocumentPermission(){ if(ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED){ selectPhoto() }else{ openDocumentLauncher.launch(Manifest.permission.READ_EXTERNAL_STORAGE) } } private val openDocumentLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()){ isGranted:Boolean -> if(isGranted){ selectPhoto() }else{ Toast.makeText(this, "デバイス内の写真やメディアへのアクセスが許可されませんでした。", Toast.LENGTH_SHORT).show() } } //デバイスから画像を選ぶ private fun selectPhoto() { val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply { addCategory(Intent.CATEGORY_OPENABLE) type = "image/*" } selectPhotoLauncher.launch(intent) } private val selectPhotoLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()){ result -> if(result.resultCode != RESULT_OK){ return@registerForActivityResult }else{ try { var bitmap: Bitmap? result.data?.data.also { uri -> bitmap = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P){ val source = ImageDecoder.createSource(contentResolver, uri!!) ImageDecoder.decodeBitmap(source) } else { MediaStore.Images.Media.getBitmap(contentResolver, uri) } findViewById<ImageView>(R.id.download_image).setImageBitmap(bitmap) if(uri != null) uploadImage(uri) } }catch (e:Exception){ Log.d("cloud storage", e.toString()) Toast.makeText(this, "select photo Error", Toast.LENGTH_SHORT).show() } } } //Cloud Storageへのアップロード private fun uploadImage(uri: Uri) { val storageRef = storage.reference val stream = contentResolver.openInputStream(uri) val userImageRef = storageRef.child("image/${uri.path}") val uploadTask = userImageRef.putStream(stream!!) uploadTask.addOnSuccessListener { Log.d("cloud storage", "upload success") stream.close() }.addOnFailureListener { error -> Log.d("cloud storage", "upload failure $error") } } }