❮
[Android] 디바이스 저장소에 파일 저장하기
20190725
[Android] 디바이스 저장소에 파일 저장하기
출처 App data & Files - Save files on device storage
안드로이드는 다른 디스크 기반 파일 시스템과 비슷합니다. 여기서는 File API를 사용해서 파일 시스템을 읽고 쓰는 방법을 살펴보겠습니다.
File 객체는 큰 규모의 데이터를 처음부터 끝까지 완전히 저장해야 할 때 사용할 수 있습니다. (예를 들면 이미지 파일이나 네트워크를 통한 어떤 것들)
파일이 저장되는 정확한 위치는 장치 별로 다를 수 있습니다. 그러니 절대 위치를 사용하기 보다는 이 페이지에 있는 방법들을 이용해서 내부, 외부 저장소 경로에 접근해야 합니다.
장치에서 파일들을 보려면 File.getAbsolutePath() 와 같은 메소드들을 이용해서 파일 위치를 기록해야 합니다. 그리고 안드로이드 스튜디오에 있는 Device File Explorer 를 이용해서 장치 파일들을 탐색해야 합니다.
내부 또는 외부 선택하기
모든 안드로이드 장치는 “
internal(내부)” 와 “
external(외부)” 두 개의 파일 저장 영역을 가집니다. 이 이름은 대부분의 장치들이 내장 비활성 메모리 (내부 저장소) 에다 micro SD카드와 같은 제거 가능한 중간 메모리 (외부 저장소) 를 가지던 안드로이드 초창기 때부터 사용되었고, 이제는 대부분의 장치는 영구 저장소 공간을 내부와 외부 파티션으로 분리합니다. 그러니 이제 제거 가능한 중간 메모리 없이도 이 두 개의 저장 공간이 항상 존재하고, API 의 동작도 외부 저장소가 해제되어도 똑같이 일어납니다.
외부 저장소가 제거 가능하기 때문에, 다음 두 옵션 간에 약간의 차이가 있습니다.
내부 저장소
- 항상 사용할 수 있습니다.
- 여기에 저장된 파일들은 사용자의 어플리케이션에서만 접근 가능합니다.
- 유저가 앱을 삭제하면 시스템은 내부 저장소에서 모든 앱의 파일들을 제거합니다.
내부 저장소는 다른 앱에서 접근하기를 원하지 않을 때 사용하면 좋습니다.
외부 저장소
- 항상 사용할 수는 없습니다. USB 저장소나 그 밖의 다른 것들을 이용하기 때문에 장치에서 분리할 수 있습니다.
- 모든 곳에서 읽을 수 있습니다. 그래서 여기 저장된 파일들은 통제 범위 밖에서도 접근 가능합니다.
- 앱을 삭제 했을 때, 시스템은 getExternalFilesDir() 에서 디렉토리에 저장한 경우에만 앱의 파일을 제거합니다.
외부 저장소는 특별한 접근 제한이 필요 없는 파일들을 위한 장소 입니다. 다른 앱이나 컴퓨터에서 접근할 수 있게 합니다.
기본적으로 앱들은 내부 저장소에 저장됩니다. 매니페스트 파일에서 android:installLocation 속성을 지정해서 외부 저장소에 설치되도록 만들 수 있습니다. 유저들은 APK 크기가 매우 큰 경우에 이 옵션을 좋아할 것입니다. App Install Location 참조
내부 저장소에 파일을 저장하기
앱의 내부 저장소 디렉토리는 다음 API들에서 접근 가능한 안드로이드 파일 시스템의 특정 장소에 있는 앱의 패키지 이름에 의해 지정됩니다.
외부 저장소 디렉토리와 다르게, 앱은 이 메소드들에서 리턴되는 내부 저장소에 읽고 쓰기 위해서 어떠한 시스템 퍼미션도 필요하지 않습니다.
파일 쓰기
내부 저장소에 파일을 저장할 때, 두 메소드 중 하나를 호출해서 적절한 디렉토리를 파일 형태로 얻을 수 있습니다.
getFilesDir() 앱을 위한 내부 디렉토리를 나타내는 파일을 리턴함
getCacheDir() 앱의 임시 캐시 파일을 위한 내부 디렉토리를 나타내는 파일을 리턴합니다. 파일이 필요 없게 되었을 때 확실히 제거됩니다. 특정 시간에 사용할 메모리의 크기 제한을 합리적으로 설정해주세요.
용량이 부족할 때, 캐시 파일은 경고 없이 삭제될 수 있습니다.
내부 디렉토리에 새로운 파일을 생성하기 위해서, File() 생성자를 사용할 수 있습니다.
openFileOutput()
또는, openFileOutput() 을 FileOutputStream을 얻기 위해 호출할 수 있습니다. FileOutputStream에서 내부 디렉토리에 있는 파일에 쓰기를 할 수 있습니다.
openFileOutput() 메소드는 file mode 매개변수를 필요합니다. - MODE_PRIVATE (always) - MODE_WORLD_READABLE - MODE_WORLD_WRITEABLE
아래 2개는 API 17 부터 deprecated 되었습니다. API 24 부터는 이걸 사용하면 SecurityException을 throw 하게 됩니다.
PRIVATE 파일을 다른 앱과 공유하고 싶다면
FileProvider 를 사용하세요.
캐시 파일 쓰기
createTempFile()
파일을 캐시할 때는 createTempFile() 을 사용할 수 있습니다.
파일 열기
fileList() : String[]
내부 디렉토리 모든 파일 이름을 array로 반환
팁 : 설치 할 때 접근 가능한 앱의 파일들을 패키지화 해야 한다면, 프로젝트의 파일을 res/raw 디렉토리에다가 저장하자. 여기 있는 파일들은 openRawResource() 에 R.raw.filename 를 전달해서 열 수 있다. 이 메소드는 InputStream을 리턴한다. 원본 파일에 쓰기는 할 수 없습니다.
디렉토리 열기
내부 파일 시스템에 있는 디렉토리를 열 수 있습니다.
getFilesDir()
앱과 유일하게 연관되어 있는 파일 시스템의 디렉토리를 파일 형태로 리턴한다.
getDir(name, mode)
앱의 유일한 파일 시스템 디렉토리 안에 새로운 디렉토리를 만들거나 기존의 디렉토리를 엽니다. 이 새로운 디렉토리는 getFileDir() 에서 얻을 수 있는 디렉토리 안에 나타납니다.
getCacheDir()
앱과 유일하게 연관된 파일 시스템 안에 있는 캐시 디렉토리를 파일 형태로 리턴합니다. 이 디렉토리는 임시 파일들을 의미하기 때문에, 때때로 정리될 수 있습니다. 디스크 공간이 부족한 상황에서 파일을 시스템이 삭제할 수 있습니다. 그러니 읽기 전에 파일이 아직 존재하는 지를 확인하세요.
이런 디렉토리에 파일을 새로 만드려면 File() 생성자를 사용하세요.
외부 저장소에 파일 저장하기
다른 앱이나 유저가 컴퓨터에서 접근할 수 있는 파일은 외부 저장소에 저장하는 것이 좋다. 접근 권한을 얻은 다음, 저장소가 사용 가능한지 판별하고 나면 2가지 유형 중에서 선택해야 한다.
- public : 유저와 다른 앱에서 제한 없이 사용 가능한 파일, 앱을 삭제해도 유저가 사용할 수 있도록 남겨두어야 합니다.
- private : 앱에 속해있는 파일들이고 앱을 삭제하면 파일들도 사라집니다. 이런 파일들은 기술적으로는 유저와 다른 앱에서 접근할 수 있습니다. 하지만 별도의 값을 앱 바깥에 있는 사용자에게 제공하지는 않습니다.
주의 : 외부 저장소는 유저가 연결을 끊거나 SD 카드를 제거하는 등의 이유로 인해서 사용 불가능하게 될 수도 있습니다. 또한, READ_EXTERNAL_STORAGE 권한을 가진 다른 앱이나 유저들은 파일들을 확인할 수 있습니다. 따라서 앱의 기능이 이러한 파일들에 종속되어있거나 엄격한 접근권한을 부여하고 싶다면 내부 저장소를 사용하는게 낫습니다.
외부 저장소 접근 권한 얻기
WRITE_EXTERNAL_STORAGE (읽기 쓰기 권한을 모두 포함한다.)
읽기만 하려면 READ_EXTERNAL_STORAGE 만 지정해주면 된다.
API 19 부터, 외부 저장소 디렉토리에 파일을 읽고 쓰려면 getExtenalFilesDir() 를 사용합니다. 이 메소드는 특별한 권한을 필요로 하지 않습니다. 그러니 앱이 API 18 레벨 이하를 지원하고 private 외부 저장소에 접근해야만 한다면 권한을 부여할 때 maxSdkVersion 속성을 추가해주어야 합니다.
외부 저장소 사용 가능한지 확인하기
외부 저장소는 사용할 수 없을 때가 있기 때문에 사용하기 전에 항상 사용가능한 볼륨인지를 확인해야 합니다.
getExternalStoragestate()
- MEDIA_MOUNTED : 파일 읽고 쓰기 모두 가능.
- MEDIA_MOUNT_READ_ONLY : 파일 읽기만 가능.
public 디렉토리에 저장하기
getExternalStoragePublicDirectory()
외부 저장소에 있는 디렉토리를 파일 형태로 얻기 위한 메소드. 이 메소드는 파일의 유형을 지정하는 인수를 가진다. 다른 public 파일들과 조직화해서 저장할 수 있게 해준다. DIRECTORY_MUSIC 이나 DIRECTORY_PICTURES
예시
미디어 스캐너로 부터 파일을 숨기기 위해서는 외부 파일 디렉토리에 .nomedia 라는 빈 파일을 포함시켜야 합니다. 이렇게 하면 미디어 파일을 읽고 다른 앱에 MediaStore 콘텐츠 제공자를 통해서 제공되는 것을 방지해줍니다.
private 디렉토리에 저장하기
다른 앱이나 MediaStore 컨텐츠 제공자에서 접근이 불가능한 외부 저장소에 파일을 저장하기 위해서는 getExternalFilesDir() 를 사용하는 방법이 있습니다.
파일에 알맞은 서브 디렉토리의 이름이 정의 되지 않았다면 getExternalFilesDir() 에 null을 전달해서 호출할 수 있습니다. 이렇게 하면 외부 저장소에 있는 private 디렉토리의 루트 디렉토리를 리턴합니다.
getExternalFilesDir() 로 만든 디렉토리는 유저가 앱을 삭제하면 제거됩니다. 앱을 제거해도 남아있게 하려면 public 디렉토리에 저장해야 합니다.
getExternalStoragePublicDirectory()나 getExternalFilesDir() 를 사용할 때는 DIRECTORY_PICTURES 같은 API 상수와 같은 디렉토리 이름을 사용하는게 중요합니다. 이러한 디렉토리 이름은 시스템이 적절하게 파일을 다룰수 있게 보장해줍니다.
여러 개의 저장소 중에서 선택하기
getExternalFilesDirs() 각각의 저장소 위치를 나타내는 File의 배열을 리턴해준다. API 19 보다 이전 버전을 지원할 경우에는 ContextCompat.getExternalFilesDirs() 를 사용해야 합니다. 하지만 기기가 API 19 이전 버전에서 돌아가는 경우에는 어차피 하나의 저장소에만 접근 가능합니다.
남은 공간 확인하기
현재 용량을 확인해야 합니다.
getFreeSpace(), getTotalSpace()
하지만 getFreeSpace() 를 해서 나온 바이트 수 만큼 사용할 수 있는 것은 아닙니다. 저장할 데이터 용량보다 수 MB 정도 적거나 파일 시스템이 90% 이하로 채워져 있다면 그냥 진행해도 괜찮습니다.
파일을 저장하기 전에 사용 가능한 용량을 확인하지 않아도 됩니다. 그냥 바로 파일을 쓰는 것을 시도하고 IOException 을 잡아내는 것이 낫습니다. 얼마나 많은 용량이 필요할 지 예측할 수 없는 경우 유용합니다. 예를 들면 PNG 파일을 JPEG 파일로 변환해서 저장할 때 JPEG 파일 크기가 얼마나 될 지 모를 수 있습니다.
파일 삭제하기
필요 없어진 파일을 삭제해야합니다. File 객체에서 delete() 를 호출하는 것이 가장 직관적인 방법입니다.
파일을 내부 저장소에 저장했다면 Context 를 통해서 제거해야 합니다.
앱을 사용자가 삭제하면 안드로이드 시스템은 아래 파일들을 삭제합니다. - 내부 저장소에 저장된 모든 파일 - getExternalFilesDir() 사용하는 외부 저장소에 있는 모든 파일
수시로 getCacheDir() 로 만들어 지는 모든 캐시 파일들을 수동으로 삭제해야 합니다. 사용하지 않는 다른 파일들도 삭제해주어야 합니다.