저장소의 종류와 권한
✔ 안드로이드는 리눅스 기반의 파일 시스템으로 구성되어 있으며, 설치된 앱 하나당 사용자 아이디와 디렉터리가 할당되며, 디렉터리는 해당 사용자만 접근할 수 있다.
◽ KaKao 앱은 루트 디렉터리 밑 /data/data/KaKao 디렉터리에 설치된다.
◽ 앱을 설치할 때 KaKao라는 사용자도 함께 생성되는데, KaKao 사용자는 시스템의 다른 앱이나 디렉터리에는 접근할 수 없고, 오직 /data/data/KaKao 디렉터리에만 읽고 쓰는 권한이 있다.
내부 저장소(앱별 저장 공간)
✔ 앱을 설치하면 /data/data/[패키지명] 디렉터리가 자동으로 생성되며, 해당 디렉터리에 한해서만 권한 없이 읽고 쓸 수 있다.
✔ 내부 저장소 파일들은 스튜디오 오른쪽 하단의 Device File Explorer에서 직접 관리할 수 있으며, 변경 사항 적용 후에는 [ 마우스 오른쪽 클릭 -> Synchronize ]을 통해 동기화 해 주면 된다.
✔ 해당 디렉터리에 대한 파일 경로는 Context를 포함하는 액티비티에서 filesDir(자바 getFilesDir())를 통해 접근하면 된다. 따라서 MainActivity와 같이 초기에 반드시 거쳐가는 Context가 존재하는 액티비티에서 static 변수로 경로를 초기화해서 다른 클래스에서 사용하면 편리하다.
✔ 만약 ./files 디렉터리가 아닌 다른 디렉터리를 만들어서 접근하고 싶다면 /data/data/$packageName/[폴더명] 혹은 /data/user/0/$packageName/[폴더명]으로 path를 지정하면 된다. 이때, [폴더명]으로 되어있는 폴더는 아래와 같이 직접 생성해줘야 한다.
val path = "/data/data/$packageName"
val dir = "folderName"
val file = File(path, dir)
if (!file.exists()) file.mkdirs()
✔ 내부 저장소에 있는 데이터는 앱을 삭제할 시, 저장된 데이터도 삭제되기 때문에 주로 앱 내에서만 사용하는 데이터를 저장한다.
외부 저장소(공유 저장 공간)
✔ 외부 저장소는 모든 앱들이 접근할 수 있는 공간이기 때문에, 외부 저장소에 저장된 파일에 접근하려면 권한 설정이 필요하다.
AndroidManifest.xml
<!-- 외부 저장소 읽기 권한 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<!-- 외부 저장소 쓰기 권한 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
✔ 안드로이드에서 주어지는 외부 저장소에 접근하기 위해서는 Context를 포함하는 액티비티에서 getExternalFilesDir(type)을 사용하면 된다. 해당 경로는 /storage/emulated/0/Android/data/$packageName/files로 지정된다.
✔ 반면에, 직접 외부 저장소 폴더를 만들어서 사용하기 위해서는 /mnt/sdcard/[폴더명]로 지정하여 사용하면 된다.
✔ 외부 저장소에 기록되는 데이터는 사용자가 앱을 제거한 뒤에도 저장되어야 하는 데이터이거나 다른 앱도 접근할 수 있는 데이터이다.
✔ 안드로이드 11 이상부터는 보안이 강화되어 MediaStore를 통해서만 외부 저장소에 접근할 수 있으므로, 웬만한 데이터는 내부 저장소를 통해서 저장하는 편이 낫다.
내부 저장소 파일 읽기
✔ 파일을 활용할 때는 파일의 형태에 따라서 API가 달라지기 때문에 파일의 형태를 알아야 한다.
파일 사용하기
✔ 파일의 경로를 직접 입력해서 파일을 생성하는 방법
val file = File("/data/user/0/[package name]/[your directory]")
✔ 컨텍스트가 가지고 있는 fileDir 프로퍼티를 통해 내부 저장소의 files 디렉터리에 접근하는 방법
val file = File(baseContext.fileDir, "파일명")
// 액티비티에서 사용할 경우,
val file = File(fileDir, "파일명")
◽ 별도의 디렉터리를 만들지 않고 files에 계속 파일을 넣고 싶을 경우, fileDir를 사용해도 무방하다.
파일 관련 메서드
// exists : File의 존재 여부를 확인
if (file.exists()) { }
// isFile : File의 생성자에 전달된 경로가 파일인지를 확인
if (file.isFile) { }
// isDirectory : File의 생성자에 전달된 경로가 디렉터리인지를 확인
if (file.isDirectory) { }
// name : 생성된 파일 또는 디렉터리의 이름 반환
"${file.name}"
// createNewFile() : 해당 경로에 파일이 존재하지 않으면 파일을 생성
if (!file.exists()) {
file.createNewFile()
}
// mkdirs() : 중간 경로를 포함한 디렉터리를 생성
if (!file.exists()) {
file.mkdirs()
}
// delete() : 파일이나 디렉터리 삭제.
// 디렉터리의 경우 내부에 파일이 존재하면 삭제되지 않음
file.delete()
// absolutePath : 파일 또는 디렉터리의 절대경로를 반환
// 일반적으로 파일을 저장하거나 읽을 때는 절대 경로를 기준으로 사용
"${file.absolutePath}"
파일을 읽고 쓰는 스트림
✔ 파일로 실제 데이터를 읽고 쓰려면 스트림이라는 복잡한 클래스를 사용해야 한다.
✔ 스트림은 파일에 파이프를 하나 연결해 놓고 해당 파이프를 통해서 데이터를 꺼내오는 방식으로 동작한다.
✔ 파일의 크기를 특정할 수 없기 때문에 읽거나 쓸 때만 파이프를 연결하고 사용이 끝나면 파이프를 제거하는 방식으로 컴퓨터 자원을 효율적으로 사용한다.
✔ 스트림은 읽는 용도와 쓰는 용도가 구분되어 있으며 읽기 위해서는 읽기 전용 스트림을, 쓰기 위해서는 쓰기 전용 스트림을 사용해야 한다.
텍스트 파일 읽기
✔ 텍스트 파일을 읽을 떄는 Reader 계열의 스트림을 사용한다.
fun readTextFile(fullPath: String) :String {
val file = File(fullPath) // 파일 생성
if(!file.exists()) return "" // 실제 파일이 있는지 검사
val reader = FileReader(file) // FileReader로 파일을 읽고
val buffer = BufferedReader(reader) // BufferedReader에 담아서 속도 향상
var temp: String? = null // buffer를 통해 한 줄씩 읽은 내용을 저장할 temp
var result = "" // 모든 내용을 저장할 result
while(true) {
temp = buffer.readLine() // 버퍼에서 한 줄을 꺼내 temp에 담고
if(temp == null) break // 내용이 더 이상 없으면 빠져나가고,
else result += temp + "\n" // 값이 있다면 result 변수에 추가
}
buffer.close() // buffer 닫기
reader.close() // reader 닫기
return result
}
var content = readTextFile("${filesDir}/파일명.txt")
openFileInput을 사용해서 코드 축약하기
✔ 안드로이드는 파일을 읽어서 스트림으로 반환해주는 openFileInput을 읽기 메서드로 제공한다.
var contents = ""
context.openFileInput("파일 경로").bufferedReader().useLines { lines ->
contents = lines.joinToString("\n")
}
◽ openFileInput으로 시작하는 줄 끝의 lines에는 줄 단위의 텍스트가 저장되는데, 이것을 joinToString 메서드로 줄마다 NewLine을 추가한 후 contests 변수에 저장하고 사용할 수 있다.
텍스트 파일 쓰기
/* (파일을 생성할 디렉터리, 파일명, 작성할 내용) */
fun writeTextFile(directory: String, filename: String, content: String) {
/* directory가 존재하는지 검사하고 없으면 생성 */
val dir = File(directory)
if (!dir.exists()) {
dir.mkdirs()
}
val writer = FileWriter(directory + "/" + filename) // FileWriter 생성
Log.d("FileUtil","write dir=${directory + "/" + filename}")
val buffer = BufferedWriter(writer) // buffer에 담아서 속도 향상
buffer.write(content) // buffer로 내용 쓰고
buffer.close() // buffer 닫기
}
openFileOutput으로 쓰기 코드 축약하기
val contents = "Hello\nworld!"
context.openFileOutput("파일명", Context.MODE_PRIVATE).use { stream ->
stream.write(contents.toByteArray())
}
◽ Context.MODE_PRIVATE 대신 Context.MODE_APPEND를 사용하면 기존 동일한 파일명이 있을 경우 기존 내용에 이어서 새로운 내용을 저장할 수 있다.
외부 저장소를 직접 지정해서 텍스트 파일 작성 예제 코드
val EXTERNAL_PATH = "/mnt/sdcard/UserMobileApp"
val message = "Hello\nWorld"
val sdf = SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
val formatDate = sdf.format(System.currentTimeMillis())
val file = File("$EXTERNAL_PATH/$formatDate.txt")
if (!file.exists()) { file.createNewFile() }
val writer = FileWriter(file.absolutePath)
val buffer = BufferedWriter(writer)
buffer.write(message)
buffer.close()
◽ 이때, UserMobileApp 폴더에 위와 같이 접근하기 위해서는 폴더가 미리 존재해야 한다.
'Android > Concept' 카테고리의 다른 글
[Android] [Kotlin] Object (0) | 2021.11.05 |
---|---|
[Android] [Kotlin] Context (0) | 2021.11.05 |
[Android] [Kotlin] Retrofit (0) | 2021.09.25 |
[Android] [Kotlin] 네트워크 (0) | 2021.09.25 |
[Android] [Kotlin] 콘텐트 리졸버 (0) | 2021.09.23 |