안드로이드 내부 저장소 파일 입출력

손현수·2024년 9월 5일

블루투스를 통해 외부 기기로부터 가속도 센서, 지자기 센서, 자이로 센서 데이터를 넘겨받고 있는데 이 데이터들을 따로 파일에 저장할 수 있겠냐는 요청이 들어왔다
찾아보니, 내외부 저장소에 저장할 수 있는 방법이 둘 다 존재했고 나는 내부 저장소에 txt파일 형태로 데이터를 저장하기로 결정했다.

데이터가 넘어오면 파일 입력을 뷰모델에서 처리

현재 bleService를 통해 블루투스로 넘어오는 데이터 패킷을 파싱하여 필요한 센서 데이터를 추출하고 있다. 추출한 센서 데이터는 SensorDataManager 객체에 저장 후 뷰모델에서 가져오는 형식으로 사용하고 있다.

SettingViewModel.kt

@RequiresApi(Build.VERSION_CODES.O)
@HiltViewModel
class SettingViewModel @Inject constructor(
) : BaseViewModel<SettingPageState>(
    SettingPageState()
), SensorDataManager.SensorDataListener {
	
    // 생략

    init {
        SensorDataManager.addListener(this)
    }
    
    // 생략

    override fun onDataUpdated() {
        viewModelScope.launch {
            val acc = SensorDataManager.acc
            val gyro = SensorDataManager.gyro
            val mag = SensorDataManager.mag

            if (acc != null && gyro != null && mag != null) {
                updateState(
                    uiState.value.copy(
                        sensorData = SensorData(acc = acc, gyro = gyro, mag = mag)
                    )
                )
            }
        }
    }

    fun saveDataToFile(context: Context, fileName: String, data: Any) {
        try {
            val fileOutput = context.openFileOutput(fileName, Context.MODE_APPEND)
            fileOutput.write((data.toString() + "\n").toByteArray())
            fileOutput.close()
            Log.d("FileSave", "$fileName data saved successfully")
        } catch (e: Exception) {
            Log.e("FileSave", "Error saving data to $fileName: ${e.message}")
        }
    }

    override fun onCleared() {
        super.onCleared()
        SensorDataManager.removeListener(this)
    }
}
  • 위 코드는 뷰모델 코드의 일부분이다. onDataUpdated 메서드의 출처는 SensorDataManager에 인터페이스로 정의되어 있는 메서드를 오버라이드한 것이다. 블루투스 기기로부터 패킷이 실시간으로 넘어오고 있고 패킷을 파싱하여 추출한 데이터가 "유효한" 경우에만 SensorDataManager에 값을 전달하고 있다.
  • 즉, 모든 데이터를 전달하는 것이 아니기 때문에 데이터의 변화가 감지되면 호출할 함수가 필요하다. 이를 인터페이스로 정의한 것이고 그 메서드명이 onDataUpdated이다.
  • 결론적으로 SensorDataManager에 유효한 센서값이 넘어오면 이를 뷰모델에서 적절히 사용하여 상태 변수를 업데이트하고 있는 것이다.
  • 직접적으로 파일 입출력을 실행하는 메서드는 saveDataToFile이다.

SettingScreen

    LaunchedEffect(key1 = uiState.sensorData.acc) {
        withContext(Dispatchers.IO) {
            viewModel.saveDataToFile(context, "acc_data.txt", uiState.sensorData.acc)
        }
    }

    LaunchedEffect(key1 = uiState.sensorData.gyro) {
        withContext(Dispatchers.IO) {
            viewModel.saveDataToFile(context, "gyro_data.txt", uiState.sensorData.gyro)
        }
    }

    LaunchedEffect(key1 = uiState.sensorData.mag) {
        withContext(Dispatchers.IO) {
            viewModel.saveDataToFile(context, "mag_data.txt", uiState.sensorData.mag)
        }
    }
  • 파일 입력을 메인 스레드에서 실행하면 UX 측면에서 좋지 않을 것이라고 생각해서 비동기로 처리하였다. 이렇게 각각의 센서 데이터에 대해서 뷰모델 메서드를 호출하여 저장할 수 있었다.

결과 확인

내부 저장소에 데이터가 잘 저장되었는지 확인하는 방법은 View -> Tool Window -> Device Explorer를 연다. 다음으로는 데이터 저장을 수행한 에뮬레이터나 기기를 선택하고 /data/data/패키지명/files/ 경로를 확인해보면 설정한 파일명(acc_data.txt 등등)이 존재하고 이곳에 데이터가 저장되어 있다.

이렇게 데이터가 저장되는 것을 확인할 수 있다!

profile
안녕하세요.

0개의 댓글