Android에서 Cloud Storage로 파일 업로드

Cloud Storage for Firebase을(를) 사용하면 파일을 빠르고 쉽게 버킷 Cloud Storage개 제공됨 Firebase가 관리합니다

파일 업로드

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에서 다음을 추론할 수 없습니다. Cloud Storageapplication/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.