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

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

上傳檔案

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

Kotlin

// 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,您可以使用該物件管理及監控上傳狀態。

Kotlin

// 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

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,您可以使用該物件管理及監控上傳狀態。

Kotlin

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

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

// 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

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 上傳失敗時,系統會呼叫這個事件監聽器。這可能是因為網路逾時、授權失敗或取消工作所致。

系統會使用 Exception 執行個體呼叫 OnFailureListener。其他接聽程式會使用 UploadTask.TaskSnapshot 物件呼叫。這個物件是事件發生時工作的不變檢視畫面。 UploadTask.TaskSnapshot 包含下列屬性:

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

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

Kotlin

// 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

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

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

// 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

// 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 下載檔案。