Android(안드로이드)는 Linux(리눅스)를 기반으로 합니다. 그래서 Filesystem(파일 시스템)도 리눅스와 파일 시스템과 동일한데 리눅스 파일 시스템의 특징은 System(시스템)을 사용하는 계정별로 권한을 다루게 부여할 수 있다는 것입니다.
이러한 특징을 통해 Android에서는 앱하나당 하나의 내부저장소인 디렉터리를 마련해 두고 앱에 해당하는 계정 하나를 생성해 해당 저장소에 접근하도록 권한을 부여해 줍니다. 당연히 앱의 계정으로는 자신의 저장소에 자유롭게 접근할 수 있지만 다른 앱의 저장소에는 권한이 제한되어 있으므로 접근이 불가능하게 됩니다.
반면 모든앱이 접근할 수 있는 저장공간이 있는데 이를 외부 저장소라고 합니다. 위에서 언급한 내부 저장소는 만약 앱이 삭제되면 함께 삭제되어 안에 있던 데이터를 모두 제거하지만 외부 저장소는 기기 자체를 포맷하거나 임의로 데이터를 삭제하지 않은 이상 그래도 남아 있게 됩니다.
앱의 입장에서는 내부 저장소는 별다른 사전 작업이 없어도 자유롭게 이용이 가능하지만 외부 저장소는 일단 미디어스토어라는 파일 관리자를 통해서만 접근이 가능하며 매니페스트에 접근권한을 명시하고 사용자에게 접근 허가를 받아야 하만 접근할 수 있는 등 과정이 다소 까다롭습니다. 그래서 앱 내부적으로 필요한 데이터만 내부 저장소를 이용하고 사용자 입장에서 계속 남아 있어야 하거나 모든 앱이 공통적으로 사용되어야 하는 파일을 남겨야 하는 경우만 외부 저장소를 사용합니다.
저장소는 이렇게 내부저장소와 외부저장소로 나뉘게 되는데 이번에는 내부 저장소에서 파일을 읽거나(파일 입력) 파일을 쓰는(파일 출력) 동작을 구현해서 내부 저장소에서의 파일 처리에 관한 간단한 내용을 알아보도록 하겠습니다.
우선 Design에서 아래와 같이 버튼 하나를 생성합니다.
버튼을 클릭했을때의 리스너를 작성하고 만들 파일명을 통해 FileWriter를 생성합니다. 만약 내부 저장소에 특정한 디렉터리를 만들어 두고 그 안에 파일을 생성하려면 디렉터리와 함께 파일의 전체 경로를 지정해 주면 됩니다.
btnFile.setOnClickListener {
val fileWriter = FileWriter("${filesDir}/test.txt")
}
위 예제에서 사용된 filesDir은 현재 앱의 내부 저장소의 경로를 알 수 있도록 해주며 최종적으로 test.txt파일을 내부저장소에 작성할 수 있도록 합니다.
그다음 생성한 FileWriter객체를 BufferWriter로 전달합니다.
btnFile.setOnClickListener {
val fileWriter = FileWriter("${filesDir}/test.txt")
val bufferWriter = BufferedWriter(fileWriter)
}
파일의 내용을 buffer에 있는 writer메서드로 전달해 원하는 내용을 파일에 쓰고 buffer를 닫으면 해당 내용으로 파일이 생성됩니다.
btnFile.setOnClickListener {
val fileWriter = FileWriter("${filesDir}/test.txt")
val bufferWriter = BufferedWriter(fileWriter)
bufferWriter.write("안뇽")
bufferWriter.close()
}
이번에는 생성한 파일을 다시 읽어 보도록 하겠습니다. 아래와 같이 버튼 하나를 더 만들고
다시 새롭게 만든 버튼의 리스너를 작성해 파일 개체를 생성합니다.
btnFile2.setOnClickListener {
val file = File("${filesDir}/test.txt")
val reader = file.bufferedReader()
val iterator = reader.lineSequence().iterator()
}
파일을 읽는건 단순히 쓰기와 반대지만 코드상으로 약간 내용이 더 추가되어야 합니다. 우선 파일내용을 읽어서 저장할 StringBuffer개체인 content를 만들고 while문을 통해 파일을 한 줄씩 읽어 content에 담는 동작을 수행합니다.
btnFile2.setOnClickListener {
val file = File("${filesDir}/test.txt")
val reader = file.bufferedReader()
val iterator = reader.lineSequence().iterator()
val content = StringBuffer()
while(iterator.hasNext()) {
content.append(iterator.next())
}
reader.close()
Toast.makeText(this, content.toString(), Toast.LENGTH_LONG).show()
}
파일의 모든 내용을 읽으면 while문을 빠져나오게 되는데 이때 reader를 닫고 content의 내용을 가져오면 됩니다.
참고로 파일을 읽을때 다음과 같이 File개체를 생성한 것을 볼 수 있는데
val file = File("${filesDir}/test.txt")
이 파일 개체를 활용하면 파일을 다룰때 도움이 될만한 여러 가지 메서드를 활용할 수 있습니다. 예를 들어 아래와 같이 파일이나 디렉터리 경로가 존재하는지를 확인할 수 있으며
val file = File("${filesDir}/test.txt")
if (!file.exists()) {
Toast.makeText(this, "파일없음", Toast.LENGTH_LONG).show()
file.createNewFile()
}
else {
Toast.makeText(this, "파일있음", Toast.LENGTH_LONG).show()
}
디렉터리인 경우 디렉터리가 존재하지 않으면 mkdirs() 메서드를 통해 디렉터리를 생성하는 것도 가능합니다. 예제에서는 디렉터리를 생성하는데 mkdirs() 메서드를 사용했으나 mkdir()이라는 메서드도 활용할 수 있습니다. 다만 지정한 디렉터리 경로 중간에 존재하지 않는 디렉터리가 있다면 지정한 디렉터리 전체를 생성하지 않는다는 단점이 있으므로 잘 사용하지는 않습니다.
val directory = File("${filesDir}/tmp")
if (!directory.exists()) {
directory.mkdirs()
}
반대로 delete()메서드를 사용하면 파일 혹은 디렉터리를 삭제합니다. 다만 디렉터리인 경우 내부에 파일이 존재하면 삭제하지 않습니다.
val directory = File("${filesDir}/tmp")
if (directory.exists()) {
directory.delete()
}
그밖에 지정한 대상이 파일인지 디렉터리인지에 대한 구분도 가능하며
val target = File("${filesDir}/tmp")
if (target.isFile()) {
Log.d("test", "파일")
}
if (target.isDirectory()) {
Log.d("test", "디렉토리")
}
Name속성을 사용하면 지정한 경로의 파일이름을 가져올 수 있고 absolutePath는 루트(/)부터 시작하는 절대 경로 전체를 가져올 수 있도록 합니다.
val file = File("${filesDir}/test.txt")
Log.d("test", "파일(디렉토리)명 : ${file.name}")
Log.d("test", "파일(디렉토리) 절대경로 : ${file.absolutePath}")
이밖에도 유용한 다수의 메서드가 존재하니 한 번씩 확인해 보시기 바랍니다.
'Mobile > Kotlin' 카테고리의 다른 글
[kotlin] androidx.preference (0) | 2020.12.28 |
---|---|
[kotlin] Preferences (0) | 2020.12.28 |
[kotlin] 권한 처리하기 (0) | 2020.12.23 |
[Kotlin] 위젯 - ViewPager2 (0) | 2020.12.23 |
[Kotlin] Widget 만들기 (0) | 2020.12.22 |