Android에서 Cloud Storage로 파일 업로드

Firebase용 Cloud Storage를 사용하면 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 버킷의 루트를 가리키는 참조로는 데이터를 업로드할 수 없습니다. 참조는 하위 URL을 가리켜야 합니다.

메모리 데이터에서 업로드

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

다운로드 URL 가져오기

파일을 업로드한 후 StorageReference에서 getDownloadUrl() 메서드를 호출하면 파일 다운로드 URL을 가져올 수 있습니다.

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

파일 메타데이터 추가

파일을 업로드할 때 메타데이터를 포함할 수도 있습니다. 이 메타데이터는 name, size, contentType(통칭 MIME 유형) 등 일반적인 파일 메타데이터 속성을 포함합니다. putFile() 메서드는 File 확장자에서 MIME 유형이 자동으로 추론되지만 메타데이터에 contentType을 지정하면 자동으로 감지된 유형을 재정의할 수 있습니다. contentType을 지정하지 않았고 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 업로드가 실패했을 때 호출됩니다. 네트워크 시간 초과, 승인 실패, 작업 취소 등의 이유로 실패할 수 있습니다.

OnFailureListenerException 인스턴스와 함께 호출됩니다. 다른 리스너는 UploadTask.TaskSnapshot 객체와 함께 호출됩니다. 이 객체는 이벤트 발생 시점의 작업 상태로서 변경이 불가능합니다. UploadTask.TaskSnapshot은 다음 속성을 포함합니다.

속성 유형 설명
getDownloadUrl String 객체를 다운로드하는 데 사용할 수 있는 URL입니다. 다른 클라이언트와 공유할 수 있는 추측 불가능한 공개 URL로서 업로드가 완료되면 값이 채워집니다.
getError Exception 작업이 실패한 경우 그 원인을 Exception으로 처리합니다.
getBytesTransferred long 이 스냅샷을 생성한 시점까지 전송된 총 바이트 수입니다.
getTotalByteCount long 업로드가 예정된 총 바이트 수입니다.
getUploadSessionUri String putFile을 다시 호출하여 이 작업을 계속하는 데 사용할 수 있는 URI입니다.
getMetadata StorageMetadata 업로드가 완료되기 전에는 서버로 전송되는 메타데이터입니다. 업로드가 완료된 후에는 서버가 반환한 메타데이터입니다.
getTask UploadTask 이 스냅샷을 생성한 작업입니다. 이 작업을 사용하여 업로드를 취소, 일시중지 또는 재개할 수 있습니다.
getStorage StorageReference UploadTask를 만드는 데 사용된 StorageReference입니다.

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

세션은 1주일 동안 지속됩니다. 만료되었거나 오류가 발생한 세션을 재개하려고 하면 실패 콜백이 수신됩니다. 개발자는 업로드 중간에 파일이 변경되지 않도록 해야 합니다.

오류 처리

업로드 시 로컬 파일이 없는 경우, 사용자에게 원하는 파일을 업로드할 권한이 없는 경우 등 다양한 이유로 오류가 발생할 수 있습니다. 오류의 자세한 내용은 문서의 오류 처리 섹션을 참조하세요.

전체 예시

다음은 진행률 모니터링 및 오류 처리를 포함하는 업로드를 보여주는 예시입니다.

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에서 다운로드하는 방법을 알아보세요.