본문 바로가기
개발/Android

[Android] Worker를 이용해 파일 저장하기

by 준그래머 2023. 10. 31.
반응형

시작

기존에는 Thread를 이용해 파일을 저장하도록 했는데, 이 방법 보다는 Worker를 이용해 앱이 종료되더라도 파일 저장이 될 수 있도록 Worker를 사용해 코드를 수정했다.

WorkerManager로 작업 요청하기

fun saveFiles(context: Context, slideshowModel: SlideshowModel): LiveData<List<WorkInfo>> {

    // 1 WorkerManager를 생성
    val workManager = WorkManager.getInstance(context) 

    // 2 저장할 파일들의 Uri와 Name을 갖고 있을 ArrayList를 각 각 초기화
    val saveInputDataMediaUris = ArrayList<String>()
    val saveInputDataMediaNames = ArrayList<String>()

    // 3 저장할 파일에서 Uri와 Name을 뽑아 ArrayList에 넣어줌
    for(i in 0 until slideshowModel.size){
        val slideModel = slideshowModel[i]
        for(j in 0 until slideModel.size){
            val mediaModel = slideModel[j]
            if(mediaModel is TextModel) continue
            val uriAndName = getMediaUriAndName(context, mediaModel)
            saveInputDataMediaUris.add(uriAndName[0])
            saveInputDataMediaNames.add(uriAndName[1])
        }
    }

    // 4 Unique 한 아이디를 생성
    val UNIQUE_TAG = UUID.randomUUID().toString()

    // 5 OneTimeWorkRequestBuilder의 제네릭 타입을 CoroutineWorker를 상속 받는 SaveFileWorke로 생성
    val saveWorkRequest = OneTimeWorkRequestBuilder<SaveFileWorker>()

    // 6 ArrayList를 String 배열로 바꾸고 Data 객체에 담아 Request의 InputData로 넣어줌
    saveWorkRequest.setInputData(createInputDataFromList(saveInputDataMediaUris, saveInputDataMediaNames))

    // 7 4에서 생성한 아이디를 tag에 넣어줌
    saveWorkRequest.addTag(UNIQUE_TAG)

    // 8 아이디와 동일한 기존 작업이 있으면 대체하도록 하고 작업을 진행 시킨다.
    workManager.beginUniqueWork(UNIQUE_TAG, ExistingWorkPolicy.REPLACE, saveWorkRequest.build())
        .enqueue()

    return workManager.getWorkInfosByTagLiveData(UNIQUE_TAG) // 9 작업의 LiveData에 담긴 결과를 리턴
}

private fun createInputDataFromList(uris: ArrayList<String>, names: ArrayList<String>): Data {
    val builder = Data.Builder()
    val uriArray = arrayOfNulls<String>(uris.size)
    val nameArray = arrayOfNulls<String>(uris.size)
    builder.putStringArray(SaveFileWorker.KEY_ATTACHMENT_URI, uris.toArray(uriArray))
    builder.putStringArray(SaveFileWorker.KEY_ATTACHMENT_NAME, names.toArray(nameArray))
    return builder.build()
}
  1. WorkerManager를 생성
  2. 저장할 파일들의 Uri와 Name을 갖고 있을 ArrayList를 각 각 초기화
  3. 저장할 파일에서 Uri와 Name을 뽑아 ArrayList에 넣어줌
  4. Unique 한 아이디를 생성
  5. OneTimeWorkRequestBuilder의 제네릭 타입을 CoroutineWorker를 상속 받는 SaveFileWorke로 생성
  6. ArrayList를 String 배열로 바꾸고 Data 객체에 담아 Request의 InputData로 넣어줌
  7. 4에서 생성한 아이디를 tag에 넣어줌
  8. 아이디와 동일한 기존 작업이 있으면 대체하도록 하고 작업을 진행 시킨다.
  9. 작업의 LiveData에 담긴 결과를 리턴

SaveFileWorker 구현

class SaveFileWorker(context: Context, parameters: WorkerParameters) : CoroutineWorker(context, parameters){
    companion object{
        private const val TAG = "SaveFileWorker"
        const val KEY_SAVE_SUCCESS = "KEY_SAVE_SUCCESS"
        const val KEY_SAVE_ERROR_COUNT = "KEY_SAVE_ERROR_COUNT"
        const val KEY_ATTACHMENT_NAME = "KEY_ATTACHMENT_NAME"
        const val KEY_ATTACHMENT_URI = "KEY_ATTACHMENT_URI"
    }

    override suspend fun doWork(): Result {
        // 1 Data 에 담긴 Uri와 Name 들을 받는다.
        var attachmentNames = inputData.getStringArray(KEY_ATTACHMENT_NAME)
        val attachmentUris = inputData.getStringArray(KEY_ATTACHMENT_URI) ?: return Result.failure()
        if(attachmentNames==null){
            attachmentNames = arrayOfNulls<String>(attachmentUris.size)
            for(i in attachmentUris.indices){
                attachmentNames[i] = applicationContext.getString(R.string.unknown_file_name) + System.currentTimeMillis() + "_" + i.toString()
            }
        }

        return try{
            val totalCount = attachmentUris.size
            var successCount = 0
            var errorCount = 0
            // 2 기존 파일 저장 함수에 Uri와 Name을 전달해 파일 저장
            for((index, uri) in attachmentUris.withIndex()){
                val saveFile = saveAttachment(applicationContext, attachmentNames[index], Uri.parse(uri)
                )
                // 3 저장된 파일의 수와 실패한 파일의 수를 카운트
                if(saveFile != null) { successCount++ }
                else{ errorCount++ }
            }

            // 4 저장된 파일의 수와 전체 파일의 수가 같으면 성공 다르면 실패를 리턴
            if(successCount == totalCount){
                val output = workDataOf(KEY_SAVE_SUCCESS to successCount)
                return Result.success(output)
            }
            else {
                val output = workDataOf(KEY_SAVE_ERROR_COUNT to errorCount)
                return Result.failure(output)
            }
            // 4
        }
        catch (exception: Exception){
            logE(TAG, "doWork: Error save file", exception)
            return Result.failure(workDataOf(KEY_SAVE_ERROR_COUNT to 0))
        }
    }
}
  1. Data 에 담긴 Uri와 Name 들을 받는다.
  2. 기존 파일 저장 함수에 Uri와 Name을 전달해 파일 저장
  3. 저장된 파일의 수와 실패한 파일의 수를 카운트
  4. 저장된 파일의 수와 전체 파일의 수가 같으면 성공 다르면 실패를 리턴

 

파일 저장 결과를 처리

saveFiles(this, slideshowModel).observe(this, Observer<List<WorkInfo>> { workInfoList ->
    // 1 전달 받은 결과가 없으면 리턴
    if(workInfoList.isNullOrEmpty()) {
        logW(TAG, "workInfoList is null or empty")
        return@Observer
    }

    // 2 WorkInfo 리스트의 마지막 데이터를 가져옴
    val workInfo = workInfoList[workInfoList.size-1]

    // 3 저장 결과가 성공이면 성공했다는 메시지를 띄움
    if(workInfo.state == WorkInfo.State.SUCCEEDED){
        showMsg( R.string.copy_to_sdcard_success)
    }
    // 4 결과가 실패라면 실패한 파일 수를 실패 메시지와 같이 띄워줌
    else if(workInfo.state == WorkInfo.State.FAILED) {
        val count = workInfo.outputData.getInt(KEY_SAVE_ERROR_COUNT, 0)
        if(count == 0) showMsg( R.string.copy_to_sdcard_fail)
        else {
            val message = getString(R.string.copy_to_sdcard_fail_count, count)
            showMsg(message)
        }
    }
})
  1. 전달 받은 결과가 없으면 리턴
  2. WorkInfo 리스트의 마지막 데이터를 가져옴
  3. 저장 결과가 성공이면 성공했다는 메시지를 띄움
  4. 결과가 실패라면 실패한 파일 수를 실패 메시지와 같이 띄워줌
반응형