向 Cloud Storage 上传文件 (Android)

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,您可以用它来管理和监控上传状态。

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,您可以用它来管理和监控上传状态。

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,您可以用它来管理和监控上传状态。

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.
        // ...
    }
});

获取下载网址

上传文件后,您可以调用 StorageReferencegetDownloadUrl() 方法,获取下载文件的网址:

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 如果任务失败,此属性将会获得“Exception”类型的原因。
getBytesTransferred long 截取此快照时已传输的总字节数。
getTotalByteCount long 预期应上传的总字节数。
getUploadSessionUri String 该 URI 可用于通过再次调用 putFile 来继续此任务。
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");
    }
});

处理 activity 生命周期变更

即使 activity 生命周期发生变化(例如显示对话框或旋转屏幕),上传也将继续在后台进行。您连接的所有监听器也都会保持连接状态。如果在 activity 停止后调用这些监听器,有可能产生意外的结果。

您可以采取以下方法来解决此问题:向 activity 作用域订阅您的监听器,以便在 activity 停止时自动取消注册。然后,在 activity 重新启动后,使用 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 下载文件