Fazer upload de arquivos com o Cloud Storage no Android

Com o Cloud Storage para Firebase, é possível fazer upload de arquivos com rapidez e facilidade para um bucket do Cloud Storage fornecido e gerenciado pelo Firebase.

Fazer upload de arquivos

Para fazer upload de um arquivo para o Cloud Storage, primeiro crie uma referência ao caminho completo do arquivo, incluindo o nome dele.

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

Depois de criar uma referência apropriada, chame os métodos putBytes(), putFile() ou putStream() para fazer upload do arquivo no Cloud Storage.

Não é possível fazer o upload de dados com uma referência à raiz do bucket do Google Cloud Storage. Sua referência precisa apontar para um URL filho.

Fazer upload de dados na memória

O método putBytes() é a maneira mais simples de fazer upload de um arquivo para o Cloud Storage. putBytes() recebe um byte[] e retorna um UploadTask que você pode usar para gerenciar e monitorar o status do upload.

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

Como putBytes() aceita um byte[], seu app precisa guardar todo o conteúdo de um arquivo na memória de uma só vez. Use putStream() ou putFile() para usar menos memória.

Fazer upload usando um stream

O método putStream() é a maneira mais versátil de fazer upload de um arquivo para o Cloud Storage. putStream() recebe um InputStream e retorna um UploadTask que você pode usar para gerenciar e monitorar o status do upload.

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

Fazer upload de um arquivo local

Use o método putFile() para fazer upload de arquivos locais no dispositivo, como fotos e vídeos da câmera. putFile() recebe um File e retorna um UploadTask, que pode ser usado para gerenciar e monitorar o status do upload.

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

Gerar um URL de download

Depois de fazer upload de um arquivo, é possível gerar um URL para fazer o download dele chamando o método getDownloadUrl() no StorageReference:

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

Adicionar metadados de arquivo

Você também pode incluir metadados ao fazer upload de arquivos. Em geral, esses metadados contêm propriedades típicas de arquivos, como name, size e contentType (frequentemente chamadas de tipo MIME). O método putFile() infere automaticamente o tipo MIME da extensão File, mas é possível substituir o tipo detectado automaticamente especificando contentType nos metadados. Se você não fornecer um contentType e o Cloud Storage não conseguir inferir um padrão pela extensão do arquivo, o Cloud Storage vai usar application/octet-stream. Veja mais informações sobre metadados de arquivos na seção Usar metadados de arquivos.

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);

Gerenciar uploads

Além de iniciar os uploads, você pode pausar, retomar e cancelar uploads usando os métodos pause(), resume() e cancel(). Eventos de pausa e retomada provocam as mudanças de estado pause e progress, respectivamente. Cancelar um upload faz com que ele falhe com um erro indicando que o upload foi cancelado.

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();

Monitorar o andamento do upload

Você pode adicionar listeners para gerenciar êxitos, falhas, progresso ou pausas na tarefa de upload:

Tipo de listener Uso típico
OnProgressListener Este listener é chamado periodicamente à medida que os dados são transferidos e pode ser usado para preencher um indicador de upload/download.
OnPausedListener Este listener é chamado sempre que a tarefa é pausada.
OnSuccessListener Este listener é chamado quando a tarefa é concluída com êxito.
OnFailureListener Este listener é chamado sempre que o upload falha. Isso pode acontecer devido ao tempo limite da rede, a falhas de autorização ou se você cancelar a tarefa.

OnFailureListener é chamado com uma instância Exception. Outros listeners são chamados com um objeto UploadTask.TaskSnapshot. Esse objeto é uma visualização imutável da tarefa no momento em que o evento ocorreu. Um UploadTask.TaskSnapshot contém as seguintes propriedades:

Propriedade Tipo Descrição
getDownloadUrl String Um URL que pode ser usado para fazer o download do objeto. Trata-se de um URL público que não pode ser adivinhado e que pode ser compartilhado com outros clientes. Esse valor é preenchido uma vez concluído o upload.
getError Exception Se a tarefa falhou, ela terá a causa como uma Exceção.
getBytesTransferred long O número total de bytes que foram transferidos quando o snapshot foi capturado.
getTotalByteCount long O número total de bytes esperados no upload.
getUploadSessionUri String Um URI que pode ser usado para continuar a tarefa com outra chamada para putFile.
getMetadata StorageMetadata Antes de um upload ser concluído, estes são os metadados enviados ao servidor. Após a conclusão do upload, estes são os metadados retornados pelo servidor.
getTask UploadTask A tarefa que criou o snapshot. Use esta tarefa para cancelar, pausar ou retomar o upload.
getStorage StorageReference O StorageReference usado para criar o UploadTask.

Os listeners do evento UploadTask fornecem uma maneira simples e poderosa de monitorar eventos de upload.

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");
    }
});

Processar mudanças no ciclo de vida de atividades

Os uploads continuam em segundo plano mesmo após mudanças no ciclo de vida de atividades (como apresentar uma caixa de diálogo ou girar a tela). Qualquer listener conectado permanecerá conectado. Isso poderá causar resultados inesperados se eles forem chamados após a interrupção da atividade.

Esse problema pode ser solucionado ao inscrever seus listeners com um escopo de atividade para cancelar o registro automaticamente quando a atividade for interrompida. Em seguida, use o método getActiveUploadTasks quando a atividade for reiniciada para receber as tarefas de upload que ainda estiverem em andamento ou que tiverem sido concluídas recentemente.

O exemplo abaixo demonstra isso e também mostra como manter o caminho de referência de armazenamento utilizado.

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 recupera todas as tarefas ativas de upload na referência fornecida e abaixo dela, portanto, talvez seja necessário processar diversas tarefas.

Como continuar uploads após reinicializações de processos

Se seu processo for encerrado, qualquer upload em andamento será interrompido. No entanto, você pode continuar o upload depois que o processo for reiniciado, retomando a sessão de upload com o servidor. Isso pode poupar tempo e largura de banda, já que não reinicia o upload do arquivo do zero.

Para fazer isso, comece a fazer o upload via putFile. Na StorageTask resultante, chame getUploadSessionUri e salve o valor resultante em um armazenamento permanente (como 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.
        }
    }
});

Depois que o processo for reiniciado com um upload interrompido, chame putFile novamente. Dessa vez, transfira também o URI que foi salvo.

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);

Sessões duram uma semana. Se tentar retomar uma sessão após a expiração ou se ela tiver apresentado um erro, você receberá um callback de falha. É sua responsabilidade garantir que o arquivo não seja alterado entre uploads.

Tratamento de erros

Há uma série de motivos pelos quais erros podem ocorrer no upload, incluindo arquivo local não existente ou o usuário não ter permissão para fazer upload do arquivo desejado. Encontre mais informações sobre erros na seção Tratamento de erros da documentação.

Exemplo completo

Um exemplo completo de upload com monitoramento de andamento e tratamento de erros é mostrado abaixo:

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

Agora que você já sabe fazer upload de arquivos, veja como fazer o download no Cloud Storage.