在 Android 裝置上使用 Cloud Storage 上傳檔案

Cloud Storage for Firebase 可讓您快速輕鬆地將檔案上傳至 Firebase 提供並管理的 Cloud Storage 儲存體。

上傳檔案

如要將檔案上傳至 Cloud Storage,請先建立檔案完整路徑的參照項目,包括檔案名稱。

Kotlin+KTX

// Create a storage reference from our app
val storageRef = storage.reference

// Create a reference to "mountains.jpg"
val mountainsRef = storageRef.child("mountains.jpg")

// Create a reference to 'images/mountains.jpg'
val mountainImagesRef = storageRef.child("images/mountains.jpg")

// While the file names are the same, the references point to different files
mountainsRef.name == mountainImagesRef.name // true
mountainsRef.path == mountainImagesRef.path // false

Java

// Create a storage reference from our app
StorageReference storageRef = storage.getReference();

// Create a reference to "mountains.jpg"
StorageReference mountainsRef = storageRef.child("mountains.jpg");

// Create a reference to 'images/mountains.jpg'
StorageReference mountainImagesRef = storageRef.child("images/mountains.jpg");

// While the file names are the same, the references point to different files
mountainsRef.getName().equals(mountainImagesRef.getName());    // true
mountainsRef.getPath().equals(mountainImagesRef.getPath());    // false

建立適當的參照後,您可以呼叫 putBytes()putFile()putStream() 方法,將檔案上傳至 Cloud Storage

您無法上傳參照 Cloud Storage 值區根目錄的資料。參照項目必須指向子網址。

從記憶體中的資料上傳

putBytes() 方法是將檔案上傳至 Cloud Storage 最簡單的方式。putBytes() 會接收 byte[],並傳回 UploadTask,您可以使用 UploadTask 管理及監控上傳作業的狀態。

Kotlin+KTX

// Get the data from an ImageView as bytes
imageView.isDrawingCacheEnabled = true
imageView.buildDrawingCache()
val bitmap = (imageView.drawable as BitmapDrawable).bitmap
val baos = ByteArrayOutputStream()
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos)
val data = baos.toByteArray()

var uploadTask = mountainsRef.putBytes(data)
uploadTask.addOnFailureListener {
    // Handle unsuccessful uploads
}.addOnSuccessListener { taskSnapshot ->
    // taskSnapshot.metadata contains file metadata such as size, content-type, etc.
    // ...
}

Java

// Get the data from an ImageView as bytes
imageView.setDrawingCacheEnabled(true);
imageView.buildDrawingCache();
Bitmap bitmap = ((BitmapDrawable) imageView.getDrawable()).getBitmap();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);
byte[] data = baos.toByteArray();

UploadTask uploadTask = mountainsRef.putBytes(data);
uploadTask.addOnFailureListener(new OnFailureListener() {
    @Override
    public void onFailure(@NonNull Exception exception) {
        // Handle unsuccessful uploads
    }
}).addOnSuccessListener(new OnSuccessListener<UploadTask.TaskSnapshot>() {
    @Override
    public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) {
        // taskSnapshot.getMetadata() contains file metadata such as size, content-type, etc.
        // ...
    }
});

由於 putBytes() 會接受 byte[],因此需要應用程式一次在記憶體中保留檔案的完整內容。建議您使用 putStream()putFile(),以節省記憶體用量。

從串流上傳

putStream() 方法是上傳檔案至 Cloud Storage 最通用的方式。putStream() 會接收 InputStream,並傳回 UploadTask,您可以使用 UploadTask 管理及監控上傳作業的狀態。

Kotlin+KTX

val stream = FileInputStream(File("path/to/images/rivers.jpg"))

uploadTask = mountainsRef.putStream(stream)
uploadTask.addOnFailureListener {
    // Handle unsuccessful uploads
}.addOnSuccessListener { taskSnapshot ->
    // taskSnapshot.metadata contains file metadata such as size, content-type, etc.
    // ...
}

Java

InputStream stream = new FileInputStream(new File("path/to/images/rivers.jpg"));

uploadTask = mountainsRef.putStream(stream);
uploadTask.addOnFailureListener(new OnFailureListener() {
    @Override
    public void onFailure(@NonNull Exception exception) {
        // Handle unsuccessful uploads
    }
}).addOnSuccessListener(new OnSuccessListener<UploadTask.TaskSnapshot>() {
    @Override
    public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) {
        // taskSnapshot.getMetadata() contains file metadata such as size, content-type, etc.
        // ...
    }
});

從本機檔案上傳

您可以使用 putFile() 方法,上傳裝置上的本機檔案,例如相機拍攝的相片和影片。putFile() 會接收 File,並傳回 UploadTask,您可以使用 UploadTask 管理及監控上傳狀態。

Kotlin+KTX

var file = Uri.fromFile(File("path/to/images/rivers.jpg"))
val riversRef = storageRef.child("images/${file.lastPathSegment}")
uploadTask = riversRef.putFile(file)

// Register observers to listen for when the download is done or if it fails
uploadTask.addOnFailureListener {
    // Handle unsuccessful uploads
}.addOnSuccessListener { taskSnapshot ->
    // taskSnapshot.metadata contains file metadata such as size, content-type, etc.
    // ...
}

Java

Uri file = Uri.fromFile(new File("path/to/images/rivers.jpg"));
StorageReference riversRef = storageRef.child("images/"+file.getLastPathSegment());
uploadTask = riversRef.putFile(file);

// Register observers to listen for when the download is done or if it fails
uploadTask.addOnFailureListener(new OnFailureListener() {
    @Override
    public void onFailure(@NonNull Exception exception) {
        // Handle unsuccessful uploads
    }
}).addOnSuccessListener(new OnSuccessListener<UploadTask.TaskSnapshot>() {
    @Override
    public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) {
        // taskSnapshot.getMetadata() contains file metadata such as size, content-type, etc.
        // ...
    }
});

取得下載網址

上傳檔案後,您可以呼叫 StorageReference 上的 getDownloadUrl() 方法,取得下載檔案的網址:

Kotlin+KTX

val ref = storageRef.child("images/mountains.jpg")
uploadTask = ref.putFile(file)

val urlTask = uploadTask.continueWithTask { task ->
    if (!task.isSuccessful) {
        task.exception?.let {
            throw it
        }
    }
    ref.downloadUrl
}.addOnCompleteListener { task ->
    if (task.isSuccessful) {
        val downloadUri = task.result
    } else {
        // Handle failures
        // ...
    }
}

Java

final StorageReference ref = storageRef.child("images/mountains.jpg");
uploadTask = ref.putFile(file);

Task<Uri> urlTask = uploadTask.continueWithTask(new Continuation<UploadTask.TaskSnapshot, Task<Uri>>() {
    @Override
    public Task<Uri> then(@NonNull Task<UploadTask.TaskSnapshot> task) throws Exception {
        if (!task.isSuccessful()) {
            throw task.getException();
        }

        // Continue with the task to get the download URL
        return ref.getDownloadUrl();
    }
}).addOnCompleteListener(new OnCompleteListener<Uri>() {
    @Override
    public void onComplete(@NonNull Task<Uri> task) {
        if (task.isSuccessful()) {
            Uri downloadUri = task.getResult();
        } else {
            // Handle failures
            // ...
        }
    }
});

新增檔案中繼資料

您也可以在上傳檔案時加入中繼資料。這項中繼資料包含一般檔案中繼資料屬性,例如 namesizecontentType (通常稱為 MIME 類型)。putFile() 方法會自動從 File 副檔名推斷 MIME 類型,但您可以在中繼資料中指定 contentType,藉此覆寫自動偵測的類型。如果您未提供 contentType,且 Cloud Storage 無法從副檔名推斷預設值,Cloud Storage 會使用 application/octet-stream。如要進一步瞭解檔案中繼資料,請參閱「使用檔案中繼資料」一節。

Kotlin+KTX

// Create file metadata including the content type
var metadata = storageMetadata {
    contentType = "image/jpg"
}

// Upload the file and metadata
uploadTask = storageRef.child("images/mountains.jpg").putFile(file, metadata)

Java

// Create file metadata including the content type
StorageMetadata metadata = new StorageMetadata.Builder()
        .setContentType("image/jpg")
        .build();

// Upload the file and metadata
uploadTask = storageRef.child("images/mountains.jpg").putFile(file, metadata);

管理上傳項目

除了開始上傳作業,您還可以使用 pause()resume()cancel() 方法暫停、繼續及取消上傳作業。暫停和繼續播放事件會分別觸發 pauseprogress 狀態變更。取消上傳會導致上傳失敗,並顯示上傳已取消的錯誤訊息。

Kotlin+KTX

uploadTask = storageRef.child("images/mountains.jpg").putFile(file)

// Pause the upload
uploadTask.pause()

// Resume the upload
uploadTask.resume()

// Cancel the upload
uploadTask.cancel()

Java

uploadTask = storageRef.child("images/mountains.jpg").putFile(file);

// Pause the upload
uploadTask.pause();

// Resume the upload
uploadTask.resume();

// Cancel the upload
uploadTask.cancel();

監控上傳進度

您可以新增事件監聽器,處理上傳工作的成功、失敗、進度或暫停情形:

事件監聽器類型 常見用途
OnProgressListener 這個事件監聽器會在資料傳輸時定期呼叫,可用於填入上傳/下載指標。
OnPausedListener 每當工作暫停時,系統就會呼叫這個事件監聽器。
OnSuccessListener 工作完成後,系統會呼叫這個事件監聽器。
OnFailureListener 上傳失敗時,系統會呼叫這個事件監聽器。這可能是因為網路逾時、授權失敗,或是您取消了工作。

OnFailureListener 會使用 Exception 例項呼叫。其他事件監聽器會使用 UploadTask.TaskSnapshot 物件呼叫。這個物件是事件發生時,對工作不可變動的檢視畫面。UploadTask.TaskSnapshot 包含下列屬性:

屬性 類型 說明
getDownloadUrl String 可用於下載物件的網址。這是可與其他用戶端共用的公開網址,無法被猜測。上傳完成後,系統會填入這個值。
getError Exception 如果工作失敗,這個值就會是例外狀況。
getBytesTransferred long 擷取此快照時已傳輸的位元組總數。
getTotalByteCount long 預期上傳的位元組總數。
getUploadSessionUri String 可用於透過另一個 putFile 呼叫繼續此工作的 URI。
getMetadata StorageMetadata 在上傳完成之前,這就是傳送至伺服器的中繼資料。上傳完成後,這是伺服器傳回的中繼資料。
getTask UploadTask 建立此快照的工作。您可以使用這項工作來取消、暫停或繼續上傳。
getStorage StorageReference 用於建立 UploadTaskStorageReference

UploadTask 事件監聽器提供簡單又強大的上傳事件監控方式。

Kotlin+KTX

// Observe state change events such as progress, pause, and resume
// You'll need to import com.google.firebase.storage.component1 and
// com.google.firebase.storage.component2
uploadTask.addOnProgressListener { (bytesTransferred, totalByteCount) ->
    val progress = (100.0 * bytesTransferred) / totalByteCount
    Log.d(TAG, "Upload is $progress% done")
}.addOnPausedListener {
    Log.d(TAG, "Upload is paused")
}

Java

// Observe state change events such as progress, pause, and resume
uploadTask.addOnProgressListener(new OnProgressListener<UploadTask.TaskSnapshot>() {
    @Override
    public void onProgress(UploadTask.TaskSnapshot taskSnapshot) {
        double progress = (100.0 * taskSnapshot.getBytesTransferred()) / taskSnapshot.getTotalByteCount();
        Log.d(TAG, "Upload is " + progress + "% done");
    }
}).addOnPausedListener(new OnPausedListener<UploadTask.TaskSnapshot>() {
    @Override
    public void onPaused(UploadTask.TaskSnapshot taskSnapshot) {
        Log.d(TAG, "Upload is paused");
    }
});

處理活動生命週期變更

即使活動生命週期變更 (例如顯示對話方塊或旋轉螢幕),上傳作業仍會在背景繼續進行。您附加的任何事件監聽器也會保留附加狀態。如果在活動停止後呼叫這些方法,可能會導致非預期的結果。

您可以透過活動範圍訂閱事件監聽器,以便在活動停止時自動取消註冊。接著,在活動重新啟動時使用 getActiveUploadTasks 方法,取得仍在執行或最近完成的上傳工作。

以下範例說明這項功能,並示範如何儲存所使用的儲存空間參照路徑。

Kotlin+KTX

override fun onSaveInstanceState(outState: Bundle) {
    super.onSaveInstanceState(outState)

    // If there's an upload in progress, save the reference so you can query it later
    outState.putString("reference", storageRef.toString())
}

override fun onRestoreInstanceState(savedInstanceState: Bundle) {
    super.onRestoreInstanceState(savedInstanceState)

    // If there was an upload in progress, get its reference and create a new StorageReference
    val stringRef = savedInstanceState.getString("reference") ?: return

    storageRef = Firebase.storage.getReferenceFromUrl(stringRef)

    // Find all UploadTasks under this StorageReference (in this example, there should be one)

    val tasks = storageRef.activeUploadTasks

    if (tasks.size > 0) {
        // Get the task monitoring the upload
        val task = tasks[0]

        // Add new listeners to the task using an Activity scope
        task.addOnSuccessListener(this) {
            // Success!
            // ...
        }
    }
}

Java

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);

    // If there's an upload in progress, save the reference so you can query it later
    if (mStorageRef != null) {
        outState.putString("reference", mStorageRef.toString());
    }
}

@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
    super.onRestoreInstanceState(savedInstanceState);

    // If there was an upload in progress, get its reference and create a new StorageReference
    final String stringRef = savedInstanceState.getString("reference");
    if (stringRef == null) {
        return;
    }
    mStorageRef = FirebaseStorage.getInstance().getReferenceFromUrl(stringRef);

    // Find all UploadTasks under this StorageReference (in this example, there should be one)
    List<UploadTask> tasks = mStorageRef.getActiveUploadTasks();
    if (tasks.size() > 0) {
        // Get the task monitoring the upload
        UploadTask task = tasks.get(0);

        // Add new listeners to the task using an Activity scope
        task.addOnSuccessListener(this, new OnSuccessListener<UploadTask.TaskSnapshot>() {
            @Override
            public void onSuccess(UploadTask.TaskSnapshot state) {
                // Success!
                // ...
            }
        });
    }
}

getActiveUploadTasks 會擷取提供參照項目中和下方的所有有效上傳工作,因此您可能需要處理多項工作。

在程序重新啟動時繼續上傳

如果程序關閉,所有進行中的上傳作業都會中斷。不過,您可以透過與伺服器恢復上傳工作階段,繼續上傳。這樣一來,系統就不會從檔案開頭開始上傳,可節省時間和頻寬。

如要這麼做,請透過 putFile 開始上傳。在產生的 StorageTask 上呼叫 getUploadSessionUri,並將產生的值儲存在永久性儲存空間 (例如 SharedPreferences) 中。

Kotlin+KTX

uploadTask = storageRef.putFile(localFile)
uploadTask.addOnProgressListener { taskSnapshot ->
    sessionUri = taskSnapshot.uploadSessionUri
    if (sessionUri != null && !saved) {
        saved = true
        // A persisted session has begun with the server.
        // Save this to persistent storage in case the process dies.
    }
}

Java

uploadTask = mStorageRef.putFile(localFile);
uploadTask.addOnProgressListener(new OnProgressListener<UploadTask.TaskSnapshot>() {
    @Override
    public void onProgress(UploadTask.TaskSnapshot taskSnapshot) {
        Uri sessionUri = taskSnapshot.getUploadSessionUri();
        if (sessionUri != null && !mSaved) {
            mSaved = true;
            // A persisted session has begun with the server.
            // Save this to persistent storage in case the process dies.
        }
    }
});

在程序重新啟動並中斷上傳後,請再次呼叫 putFile。但這次也要傳遞已儲存的 Uri。

Kotlin+KTX

// resume the upload task from where it left off when the process died.
// to do this, pass the sessionUri as the last parameter
uploadTask = storageRef.putFile(
    localFile,
    storageMetadata { },
    sessionUri,
)

Java

//resume the upload task from where it left off when the process died.
//to do this, pass the sessionUri as the last parameter
uploadTask = mStorageRef.putFile(localFile,
        new StorageMetadata.Builder().build(), sessionUri);

工作階段持續一週。如果您嘗試在工作階段到期後恢復工作階段,或工作階段發生錯誤,您會收到失敗的回呼。您有責任確保檔案在兩次上傳之間沒有任何異動。

處理錯誤

上傳時可能發生錯誤的原因有很多,包括本機檔案不存在,或是使用者沒有權限上傳所需檔案。如要進一步瞭解錯誤,請參閱說明文件的「處理錯誤」一節。

完整範例

以下是上傳內容的完整範例,其中包含進度監控和錯誤處理功能:

Kotlin+KTX

// File or Blob
file = Uri.fromFile(File("path/to/mountains.jpg"))

// Create the file metadata
metadata = storageMetadata {
    contentType = "image/jpeg"
}

// Upload file and metadata to the path 'images/mountains.jpg'
uploadTask = storageRef.child("images/${file.lastPathSegment}").putFile(file, metadata)

// Listen for state changes, errors, and completion of the upload.
// You'll need to import com.google.firebase.storage.component1 and
// com.google.firebase.storage.component2
uploadTask.addOnProgressListener { (bytesTransferred, totalByteCount) ->
    val progress = (100.0 * bytesTransferred) / totalByteCount
    Log.d(TAG, "Upload is $progress% done")
}.addOnPausedListener {
    Log.d(TAG, "Upload is paused")
}.addOnFailureListener {
    // Handle unsuccessful uploads
}.addOnSuccessListener {
    // Handle successful uploads on complete
    // ...
}

Java

// File or Blob
file = Uri.fromFile(new File("path/to/mountains.jpg"));

// Create the file metadata
metadata = new StorageMetadata.Builder()
        .setContentType("image/jpeg")
        .build();

// Upload file and metadata to the path 'images/mountains.jpg'
uploadTask = storageRef.child("images/"+file.getLastPathSegment()).putFile(file, metadata);

// Listen for state changes, errors, and completion of the upload.
uploadTask.addOnProgressListener(new OnProgressListener<UploadTask.TaskSnapshot>() {
    @Override
    public void onProgress(UploadTask.TaskSnapshot taskSnapshot) {
        double progress = (100.0 * taskSnapshot.getBytesTransferred()) / taskSnapshot.getTotalByteCount();
        Log.d(TAG, "Upload is " + progress + "% done");
    }
}).addOnPausedListener(new OnPausedListener<UploadTask.TaskSnapshot>() {
    @Override
    public void onPaused(UploadTask.TaskSnapshot taskSnapshot) {
        Log.d(TAG, "Upload is paused");
    }
}).addOnFailureListener(new OnFailureListener() {
    @Override
    public void onFailure(@NonNull Exception exception) {
        // Handle unsuccessful uploads
    }
}).addOnSuccessListener(new OnSuccessListener<UploadTask.TaskSnapshot>() {
    @Override
    public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) {
        // Handle successful uploads on complete
        // ...
    }
});

檔案上傳完成後,請參閱這篇文章,瞭解如何從 Cloud Storage下載檔案