AWS 入門
建置 Android 應用程式
使用 AWS Amplify 建立簡單的 Android 應用程式
第五單元︰新增儲存影像的功能
在本單元中,您將新增儲存體至應用程式,並讓影像能夠與應用程式中的筆記關聯。
簡介
現在,記事應用程式開始工作了,讓我們新增將影像與每個筆記關聯的功能。在本單元中,您將使用 Amplify CLI 和程式庫來建立利用 Amazon S3 的儲存服務。最後,您將更新 Android 應用程式以實現影像上傳擷取和呈現。
您將學到的內容
- 建立儲存服務
- 更新您的 Android 應用程式 - 上傳和下載影像的邏輯
- 更新您的 Android 應用程式 - 使用者界面
主要概念
儲存服務 - 儲存和查詢影像和影片之類的檔案是大多數應用程式的常見要求。一種選擇是對檔案進行 Base64 編碼,並作為字串傳送以儲存在資料庫中。這樣做的缺點是,編碼檔案比原始二進位檔案大,操作在運算上成本高昂,並且由於需要正確編碼和解碼,所以複雜性有所增加。另一種選擇是專門為檔案儲存建立和優化儲存服務。諸如 Amazon S3 之類的儲存服務能夠讓這個過程盡可能簡單、高效能且廉價。
完成時間
10 分鐘
使用的服務
實作
-
建立儲存服務
要新增影像儲存功能,我們將使用 Amplify 儲存類別:
amplify add storage ? Please select from one of the below mentioned services: accept the default Content (Images, audio, video, etc.) and press enter ? Please provide a friendly name for your resource that will be used to label this category in the project: type image and press enter ? Please provide bucket name: accept the default and press enter ? Who should have access: accept the default Auth users only and press enter ? What kind of access do you want for Authenticated users? select all three options create/update, read, delete using the space and arrows keys, then press enter ? Do you want to add a Lambda Trigger for your S3 Bucket? accept the default No and press enter
一段時間後,您應看到:
Successfully added resource image locally
-
部署儲存服務
若要部署我們剛建立的儲存服務,請前往您的終端機並執行命令:
amplify push
按 Y 進行確認,一段時間後,您應看到:
✔ Successfully pulled backend environment amplify from the cloud.
-
將 Amplify 儲存程式庫新增至 Android Studio 專案
在前往程式碼之前,請返回 Android Studio,然後將以下相依項以及之前新增的其他 `amplifyframework` 實作,新增至單元的 `build.gradle` 中,並在提示時按一下 Sync Now (立即同步):
dependencies { ... // Amplify core dependency implementation 'com.amplifyframework:core:1.4.0' implementation 'com.amplifyframework:aws-auth-cognito:1.4.0' implementation 'com.amplifyframework:aws-api:1.4.0' implementation 'com.amplifyframework:aws-storage-s3:1.4.0' }
-
在執行時間初始化 Amplify Storage 外掛程式
返回 Android Studio,在 java/example.androidgettingstarted 下,開啟 Backend.kit,並在 initialize() 方法的 Amplify 初始化序列中新增一行。完整的程式碼區塊應如下所示:
try { Amplify.addPlugin(AWSCognitoAuthPlugin()) Amplify.addPlugin(AWSApiPlugin()) Amplify.addPlugin(AWSS3StoragePlugin()) Amplify.configure(applicationContext) Log.i(TAG, "Initialized Amplify") } catch (e: AmplifyException) { Log.e(TAG, "Could not initialize Amplify", e) }
-
將 Image CRUD 方法新增至後端類別
仍在 Backend.kt 中。在後端類別的任何位置,新增以下三種方法可從儲存體上傳、下載和刪除影像:
fun storeImage(filePath: String, key: String) { val file = File(filePath) val options = StorageUploadFileOptions.builder() .accessLevel(StorageAccessLevel.PRIVATE) .build() Amplify.Storage.uploadFile( key, file, options, { progress -> Log.i(TAG, "Fraction completed: ${progress.fractionCompleted}") }, { result -> Log.i(TAG, "Successfully uploaded: " + result.key) }, { error -> Log.e(TAG, "Upload failed", error) } ) } fun deleteImage(key : String) { val options = StorageRemoveOptions.builder() .accessLevel(StorageAccessLevel.PRIVATE) .build() Amplify.Storage.remove( key, options, { result -> Log.i(TAG, "Successfully removed: " + result.key) }, { error -> Log.e(TAG, "Remove failure", error) } ) } fun retrieveImage(key: String, completed : (image: Bitmap) -> Unit) { val options = StorageDownloadFileOptions.builder() .accessLevel(StorageAccessLevel.PRIVATE) .build() val file = File.createTempFile("image", ".image") Amplify.Storage.downloadFile( key, file, options, { progress -> Log.i(TAG, "Fraction completed: ${progress.fractionCompleted}") }, { result -> Log.i(TAG, "Successfully downloaded: ${result.file.name}") val imageStream = FileInputStream(file) val image = BitmapFactory.decodeStream(imageStream) completed(image) }, { error -> Log.e(TAG, "Download Failure", error) } ) }
這三種方法只需叫用其 Amplify 對應物即可。Amplify 儲存體具有三個檔案保護級別:
- 公有所有使用者均可存取
- 受保護所有使用者可讀取,但僅建立使用者可寫入
- 私有僅建立使用者可讀取和寫入
對於此應用程式,我們想讓影像適用於附註擁有者,我們將使用 StorageAccessLevel.PRIVATE 屬性。
-
新增 UI 程式碼以擷取影像
下一步是修改 UI,以允許使用者在 AddnoteACtivity 上的 "Add image" (新增影像) 按鈕時,從電話庫中選取影像。
需要進行兩項變更:變更 "Add Note" (新增附註) 活動版面配置,以新增 "Add Image" (新增影像) 按鈕和影像檢視,並在活動類別中新增處理程式的程式碼。
在 Android Studio 的 res/layout 下,開啟 activity_add_note.xml 檔案,並在 Add Note (新增附註) 按鈕上方新增此按鈕元素:
<!-- after the description EditText --> <com.google.android.material.imageview.ShapeableImageView android:id="@+id/image" android:layout_width="match_parent" android:layout_height="280dp" android:layout_margin="16dp" android:scaleType="centerCrop" /> <!-- after the Space --> <Button android:id="@+id/captureImage" style="?android:attr/buttonStyleSmall" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:backgroundTint="#009688" android:text="Add image" />
在 Android Studio 的 java/example.androidgettingstarted 下,開啟 AddnoteACtivity.kt 檔案,然後在 onCreate() 方法中新增此程式碼:
// inside onCreate() // Set up the listener for add Image button captureImage.setOnClickListener { val i = Intent( Intent.ACTION_GET_CONTENT, MediaStore.Images.Media.EXTERNAL_CONTENT_URI ) startActivityForResult(i, SELECT_PHOTO) } // create rounded corners for the image image.shapeAppearanceModel = image.shapeAppearanceModel .toBuilder() .setAllCorners(CornerFamily.ROUNDED, 150.0f) .build()
在 Intent、MediaStore 和 CornerFamily 上新增所需的匯入。
此外還會將此常量值新增至伴隨物件中:
// add this to the companion object private const val SELECT_PHOTO = 100
最後,新增用於接收所選影像並將其存放至臨時檔案的程式碼。
將以下程式碼新增至 AddnoteACtivityclass 中的任意位置:
//anywhere in the AddNoteActivity class private var noteImagePath : String? = null private var noteImage : Bitmap? = null override fun onActivityResult(requestCode: Int, resultCode: Int, imageReturnedIntent: Intent?) { super.onActivityResult(requestCode, resultCode, imageReturnedIntent) Log.d(TAG, "Select photo activity result : $imageReturnedIntent") when (requestCode) { SELECT_PHOTO -> if (resultCode == RESULT_OK) { val selectedImageUri : Uri? = imageReturnedIntent!!.data // read the stream to fill in the preview var imageStream: InputStream? = contentResolver.openInputStream(selectedImageUri!!) val selectedImage = BitmapFactory.decodeStream(imageStream) val ivPreview: ImageView = findViewById<View>(R.id.image) as ImageView ivPreview.setImageBitmap(selectedImage) // store the image to not recreate the Bitmap every time this.noteImage = selectedImage // read the stream to store to a file imageStream = contentResolver.openInputStream(selectedImageUri) val tempFile = File.createTempFile("image", ".image") copyStreamToFile(imageStream!!, tempFile) // store the path to create a note this.noteImagePath = tempFile.absolutePath Log.d(TAG, "Selected image : ${tempFile.absolutePath}") } } } private fun copyStreamToFile(inputStream: InputStream, outputFile: File) { inputStream.use { input -> val outputStream = FileOutputStream(outputFile) outputStream.use { output -> val buffer = ByteArray(4 * 1024) // buffer size while (true) { val byteCount = input.read(buffer) if (byteCount < 0) break output.write(buffer, 0, byteCount) } output.flush() output.close() } } }
上面的程式碼兩次將所選影像用作 InputStream。第一個 InputStream 建立可在 UI 中顯示的點陣圖影像,第二個 InputStream 儲存臨時檔案以傳送至後端。
此單元將處理臨時檔案,因為 Amplify API 使用了 Fileobjects。雖然不是最有效的設計,但程式碼很簡單。
若要驗證是否一切如預期,請建置專案。按一下 Build (建置) 功能表,然後選取 Make Project (製作專案),或者在 Mac 上,鍵入 ⌘F9。應不會發生錯誤。
-
建立附註時儲存影像
建立附註時,我們從後端叫用儲存方法。開啟 AddnoteActivity.kt 並修改 addnote.setOnClickListener() 方法,以在建立附註物件之後新增以下程式碼。
// add this in AddNoteACtivity.kt, inside the addNote.setOnClickListener() method and after the Note() object is created. if (this.noteImagePath != null) { note.imageName = UUID.randomUUID().toString() //note.setImage(this.noteImage) note.image = this.noteImage // asynchronously store the image (and assume it will work) Backend.storeImage(this.noteImagePath!!, note.imageName!!) }
-
載入附註時載入影像
若要載入影像,我們將修改附註資料類別上的 static from 方法。這樣一來,每次將 API 返回的 noteData 物件轉換為附註物件時,都會平行載入影像。載入影像後,我們會通知 LiveData 的 UserData 來讓觀察者了解變更。這會觸發 UI 重新整理。
開啟 UserData.kt 並修改附註資料類別的伴隨物件,如下所示:
// static function to create a Note from a NoteData API object companion object { fun from(noteData : NoteData) : Note { val result = Note(noteData.id, noteData.name, noteData.description, noteData.image) if (noteData.image != null) { Backend.retrieveImage(noteData.image!!) { result.image = it // force a UI update with(UserData) { notifyObserver() } } } return result } }
-
刪除附註時刪除影像
最後一步是自行清除,即當使用者刪除附註時從雲端儲存體中刪除影像。如果您不需要這樣做來節省儲存空間,請考慮您的 AWS 帳單而進行此操作,因為 Amazon S3 根據每月每 GB 收取資料存放費用 (前 5 GB 免費,執行本教學無需付費)。
開啟 SwipeCallback.kt,並在 onSwipe() 方法末尾新增以下程式碼:
if (note?.imageName != null) { //asynchronously delete the image (and assume it will work) Backend.deleteImage(note.imageName!!) }
-
建置和測試
若要驗證是否一切如預期,請建置並執行專案。按一下工具列中的 Run (執行) 圖示 ▶️,或鍵入 ^ R。應不會發生錯誤。
假設您仍處於登入狀態,該應用程式會從上一部分未刪除的註冊清單中開始。再次使用 Add Note (新增附註) 按鈕來建立附註。這次,新增從本機影像存放區中選取的圖片。
退出該應用程式,然後重新啟動以驗證影像是否正確載入。
這就是完整的流程。
結論
您已使用 AWS Amplify 建置 Android 應用程式! 您已將身份驗證新增到應用程式中,從而允許使用者註冊、登入和管理其帳戶。該應用程式還具有可擴展的 GraphQL API 以及 Amazon DynamoDB 資料庫,允許使用者建立和刪除筆記。您還使用 Amazon S3 新增了檔案儲存體,允許使用者上傳影像並在應用程式中進行檢視。
在最後一部分,您會找到有關重新使用或刪除剛建立後端的指導。
-
在多個專案間分享您的後端
Amplify 可讓您在多個前端應用程式間輕鬆共用單一後端。
在終端機中,導覽至您的其他專案目錄並執行以下命令:
mkdir other-project cd other-project amplify pull ? Do you want to use an AWS profile? accept the default Yes and press enter ? Please choose the profile you want to use select the profile you want to use and press enter ? Which app are you working on? select the backend you want to share and press enter ? Choose your default editor: select you prefered text editor and press enter ? Choose the type of app that you're building select the operating system for your new project and press enter ? Do you plan on modifying this backend? most of the time, select No and press enter. All backend modifications can be done from the original iOS project.
幾秒鐘後,您會看到以下訊息:
Added backend environment config object to your project. Run 'amplify pull' to sync upstream changes.
您可以看到已提取的兩個組態檔案。若您對「您打算修改此後端嗎?」問題的回答為「是」,則還會看到一個 Amplify 目錄
➜ other-project git:(master) ✗ ls -al total 24 drwxr-xr-x 5 stormacq admin 160 Jul 10 10:28 . drwxr-xr-x 19 stormacq admin 608 Jul 10 10:27 .. -rw-r--r-- 1 stormacq admin 315 Jul 10 10:28 .gitignore -rw-r--r-- 1 stormacq admin 3421 Jul 10 10:28 amplifyconfiguration.json -rw-r--r-- 1 stormacq admin 1897 Jul 10 10:28 awsconfiguration.json
-
刪除您的後端
針對測試或原型,或者僅出於學習目的建立後端時,就像按照本教學進行操作一樣,您需要刪除已建立的雲端資源。
雖然免費方案涵蓋在本教學內容中使用此資源,但最好是清除雲端中未使用的資源。
若要清除您的 Amplify 專案,請在終端機中執行以下命令:
amplify delete
稍後,您會看到以下訊息,確認已刪除所有雲端資源。
✔ Project deleted in the cloud Project deleted locally.
恭喜您!
您已成功在 AWS 上建置 Android 應用程式! 下一步,您將深入了解特定的 AWS 技術,並將您的應用程式提升到一個新的水平。