지난 시간에는 파일 입출력에 대해서 알아보았다.
이번에는 좀 더 자세하게 들여다보자.
//userInfo라는 객체를 json 형태로 바꾼다.
val fileContents = Gson().toJson(userInfo)
//휴대폰의 내부저장소 중 캐시저장소에 파일을 만든다.
val file = File(context.cacheDir, "account_" + userInfo.email)
//파일에 준비한 json을 쓴다.
file.writeText(fileContents)
context.cacheDir은 주석에서 나와있듯이 내부저장소의 캐시저장소를 의미한다.
그럼 내부저장소는 뭘까?
잠시 공식문서의 설명을 보자.
내부 저장소 디렉터리: 이 디렉터리에는 영구 파일을 저장하는 전용 위치와 캐시 데이터를 저장하는 위치가 모두 포함되어 있습니다. 시스템은 다른 앱에서 이러한 위치에 액세스하는 것을 방지하고, Android 10(API 수준 29) 이상에서는 이러한 위치가 암호화됩니다. 이와 같은 특성으로 인해 앱 자체에서만 액세스할 수 있는 민감한 데이터를 저장하기에 좋습니다.
그러나 이러한 디렉터리는 작은 경향이 있습니다. 앱별 파일을 내부 저장소에 작성하기 전에 앱에서 기기의 여유 공간을 쿼리해야 합니다. 앱별 파일을 저장하는 데 내부 저장소 공간이 충분하지 않으면 대신 외부 저장소를 사용해보세요.
외부 저장소 디렉터리: 이 디렉터리에는 영구 파일을 저장하는 전용 위치와 캐시 데이터를 저장하는 위치가 모두 포함되어 있습니다.다른 앱에 적절한 권한이 있는 경우 이러한 디렉터리에 액세스할 수 있지만 디렉터리에 저장된 파일은 개발자의 앱에서만 사용하게 되어 있습니다. 다른 앱에서 액세스할 수 있는 파일을 만들려면 앱에서 이러한 파일을 외부 저장소의 공유 저장공간 부분에 대신 저장해야 합니다.(MediaStore 사용)
주의: 이러한 디렉터리의 파일은 액세스가 보장되지 않습니다.
예를 들어 이동식 SD 카드를 기기에서 꺼내면 액세스할 수 없게 됩니다.
앱의 기능이 이러한 파일에 의존하는 경우 파일을 대신 내부 저장소에 저장해야 합니다.
뭔가 복잡하다.
밑의 표를 참고하자
간단하게 보자면 내부저장소는 내 앱에서만 쓸 파일(사용자 설정, http 응답 캐시)들을 저장하고, 외부저장소에는 다른앱과 공유해도 되거나 공유하고 싶은 파일(이미지 파일등)을 저장한다. 이외에도 내부저장소에 저장하기에는 너무 큰 파일일 경우에는 외부저장소에 저장하기도 한다.
다만 공식문서의 설명에도 나와있듯이 외부저장소는 SD카드를 제거하면 엑세스 불가능하므로 주의하자.
외부저장소는 또 공개파일을 저장하는 저장소와 비공개 파일을 저장하는 저장소로 나뉜다.
공개 파일: 다른 앱에서 그리고 사용자가 자유롭게 사용할 수 있는 파일입니다. 사용자가 앱을 제거해도 공개 파일은 사용자가 계속 사용할 수 있어야 합니다. 예를 들어 앱에서 캡처한 사진은 공개 파일로 저장되어야 합니다.
비공개 파일: Context.getExternalFilesDir()를 사용하여 액세스하는 앱별 디렉터리에 저장된 파일입니다.
사용자가 그리고 다른 앱에서 기술적으로 액세스할 수는 있지만 앱 외부의 사용자에게 가치가 있는 것은 아닙니다. 다른 앱과 공유하지 않으려는 파일에 이 디렉터리를 사용하세요. 이 디렉터리에있는 파일은 앱을 지울때 삭제됩니다.
읽기/쓰기
내부저장소에 읽고 쓰는 것은 저번시간에 소개했던대로하면 된다.
외부저장소의 비공개디렉터리는 아래와 같이 접근한다.
아래 코드는 외부저장소에 있는 사진을 저장하는 디렉토리에 대한 참조를 가진, 고유한 이름의 File 객체를 생성하는 코드다.
@Throws(IOException::class)
fun createImageTempFile(context: Context): File {
val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss").format(Date())
val storageDir: File? = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES)
return File.createTempFile(
"JPEG_${timeStamp}_", /* prefix */
".jpg", /* suffix */
storageDir /* directory */
).apply {
//jvm이 내려갈때 임시파일을 지운다.
deleteOnExit()
}
}
하나하나 살펴보자.
val storageDir: File? = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES)
Environment.DIRECTORY_PICTURES 는 미리 정의된 타입으로, 이미지파일을 저장하기 위한 폴더이름이다.
public static String DIRECTORY_PICTURES = "Pictures";
context.getExternalFilesDir 는 외부저장소의 비공개디렉터리의 절대경로에 이 디렉터리를 추가하여 File 형태로 리턴한다. 이런식으로 만든 경로를 이용해 파일객체를 만들 수 있다.
return File.createTempFile(
"JPEG_${timeStamp}_", /* prefix */
".jpg", /* suffix */
storageDir /* directory */
).apply {
//jvm이 내려갈때 임시파일을 지운다.
deleteOnExit()
}
createTempFile은 파일이름에 prefix와 suffix를 추가할 수 있게 하는 것 외에는 그냥 File을 생성자로 만드는 거랑 다를게 없다. 아무튼 이런식으로 파일객체를 만들 수 있다.
* 이 밑의 내용들은 필자가 직접 구현해본게 아니라서 사실과 다를 수 있다.
하지만 외부저장소의 공개디렉터리는 좀 다르다.
다른 앱에서 액세스할 수 있는 외부 저장소에 파일을 저장하려면 다음 API 중 하나를 사용하세요.
사진이나 오디오 파일, 동영상을 저장하는 경우 MediaStore API를 사용합니다.
기타 다른 파일(예: PDF 문서)을 저장하는 경우 저장소 액세스 프레임워크의 일부인 ACTION_CREATE_DOCUMENT 인텐트를 사용합니다.
Android 10이상에서는 A앱이 외부저장소에 쓴 것을 B앱이 직접 접근할 수 없고 MediaStore API를 사용해야한다.
MediaStore API 라는 것은 A앱에서 B앱이 외부저장소에 쓴 파일을 읽을 수 있도록 도와주는 API 라고한다.
이 API는 A앱의 정보를 B앱이 보안성있게 접근할 수 있게해주고, 그 과정을 추상화한다.
만약 이 API가 없다면 B앱은 A앱의 데이터를 직접 접근을 해야할 것이다. 하지만 그것은 위험한 일이기 때문에 B앱이 정보를 요청하면 A앱이 정보를 보내주는 식으로 동작해야한다.
하지만 이 과정은 복잡하고 컨트롤이 필요하기 때문에 중앙에서 MediaStore API 처리해주는 것이다.
한마디로 말해서, 외부저장소에는 모든 앱이 접근 가능한 공유 레퍼지토리가 있고 그 레퍼지토리에 접근하기 위해서는 MediaStore API를 써야한다. 왜냐하면 아무앱이나 직접 접근해서 아무렇게나 파일을 쓰고 , 삭제하고 변경하면 보안성도 떨어지고 그 과정도 복잡하기 때문이다.
참고 : www.youtube.com/watch?v=-4GgzqMVrYc
예를들어
val query = ContentResolver.query(
MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
projection,
selection,
selectionArgs,
sortOrder
)
이렇게 간단하게 외부저장소에 있는 비디오파일 디렉터리에 접근할 수 있다.
참고 : developer.android.com/guide/topics/providers/content-provider-creating?hl=ko
'안드로이드 앱개발' 카테고리의 다른 글
안드로이드 서비스 (0) | 2020.11.20 |
---|---|
카메라로 사진찍기 (0) | 2020.10.27 |
파일입출력 1 : 쓰고 읽기 (File I/O in Kotlin) (0) | 2020.10.27 |
AdMob nativeAd 를 RecyclerView를 활용해 표시하기 (nativeAd with RecyclerView) (0) | 2020.10.27 |
volley로 이미지 업로드 (volley multipart request) (0) | 2020.10.27 |