반응형
시작
기존에는 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()
}
- WorkerManager를 생성
- 저장할 파일들의 Uri와 Name을 갖고 있을 ArrayList를 각 각 초기화
- 저장할 파일에서 Uri와 Name을 뽑아 ArrayList에 넣어줌
- Unique 한 아이디를 생성
- OneTimeWorkRequestBuilder의 제네릭 타입을 CoroutineWorker를 상속 받는 SaveFileWorke로 생성
- ArrayList를 String 배열로 바꾸고 Data 객체에 담아 Request의 InputData로 넣어줌
- 4에서 생성한 아이디를 tag에 넣어줌
- 아이디와 동일한 기존 작업이 있으면 대체하도록 하고 작업을 진행 시킨다.
- 작업의 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))
}
}
}
- Data 에 담긴 Uri와 Name 들을 받는다.
- 기존 파일 저장 함수에 Uri와 Name을 전달해 파일 저장
- 저장된 파일의 수와 실패한 파일의 수를 카운트
- 저장된 파일의 수와 전체 파일의 수가 같으면 성공 다르면 실패를 리턴
파일 저장 결과를 처리
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)
}
}
})
- 전달 받은 결과가 없으면 리턴
- WorkInfo 리스트의 마지막 데이터를 가져옴
- 저장 결과가 성공이면 성공했다는 메시지를 띄움
- 결과가 실패라면 실패한 파일 수를 실패 메시지와 같이 띄워줌
반응형
'개발 > Android' 카테고리의 다른 글
[Android] Socket 통신 예제 (2) | 2023.12.05 |
---|---|
[Android] Fragment 간 이동 시 애니메이션 넣기 (0) | 2023.11.30 |
[Android] 바이트 관련 데이터를 형식에 맞춰 보여주기(파일 크기, 비트 전송률, 샘플링 주파수) (0) | 2023.10.26 |
[Android] EXTERNAL 저장소의 미디어 파일 가져오기 (2) | 2023.10.26 |
[Android][Shortcut] 바로 가기 위젯 만드는 법 (0) | 2023.10.23 |