System directories
Environment.getDataDirectory() | /data |
Environment.getDownloadCacheDirectory() | /cache |
Environment.getRootDirectory() | /system |
External storage directories
Environment.getExternalStorageDirectory() | /storage/sdcard0 |
Environment.getExternalStoragePublicDirectory(DIRECTORY_ALARMS) | /storage/sdcard0/Alarms |
Environment.getExternalStoragePublicDirectory(DIRECTORY_DCIM) | /storage/sdcard0/DCIM |
Environment.getExternalStoragePublicDirectory(DIRECTORY_DOWNLOADS) | /storage/sdcard0/Download |
Environment.getExternalStoragePublicDirectory(DIRECTORY_MOVIES) | /storage/sdcard0/Movies |
Environment.getExternalStoragePublicDirectory(DIRECTORY_MUSIC) | /storage/sdcard0/Music |
Environment.getExternalStoragePublicDirectory(DIRECTORY_NOTIFICATIONS) | /storage/sdcard0/Notifications |
Environment.getExternalStoragePublicDirectory(DIRECTORY_PICTURES) | /storage/sdcard0/Pictures |
Environment.getExternalStoragePublicDirectory(DIRECTORY_PODCASTS) | /storage/sdcard0/Podcasts |
Environment.getExternalStoragePublicDirectory(DIRECTORY_RINGTONES) | /storage/sdcard0/Ringtones |
Application directories
getCacheDir() | /data/data/package/cache |
getFilesDir() | /data/data/package/files |
getFilesDir().getParent() | /data/data/package |
Application External storage directories
getExternalCacheDir() | /storage/sdcard0/Android/data/package/cache |
getExternalFilesDir(null) | /storage/sdcard0/Android/data/package/files |
getExternalFilesDir(DIRECTORY_ALARMS) | /storage/sdcard0/Android/data/package/files/Alarms |
getExternalFilesDir(DIRECTORY_DCIM) | /storage/sdcard0/Android/data/package/files/DCIM |
getExternalFilesDir(DIRECTORY_DOWNLOADS) | /storage/sdcard0/Android/data/package/files/Download |
getExternalFilesDir(DIRECTORY_MOVIES) | /storage/sdcard0/Android/data/package/files/Movies |
getExternalFilesDir(DIRECTORY_MUSIC) | /storage/sdcard0/Android/data/package/files/Music |
getExternalFilesDir(DIRECTORY_NOTIFICATIONS) | /storage/sdcard0/Android/data/package/files/Notifications |
getExternalFilesDir(DIRECTORY_PICTURES) | /storage/sdcard0/Android/data/package/files/Pictures |
getExternalFilesDir(DIRECTORY_PODCASTS) | /storage/sdcard0/Android/data/package/files/Podcasts |
getExternalFilesDir(DIRECTORY_RINGTONES) | /storage/sdcard0/Android/data/package/files/Ringtones |
권한요청
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull @NotNull String[] permissions, @NonNull @NotNull int[] grantResults) {
if(requestCode ==100){
int length = permissions.length;
for(int i=0; i<length; i++){
if(grantResults[i]== PackageManager.PERMISSION_GRANTED){
Log.d("kcn","권한 허용 : "+permissions[i]);
}
}
}
}
public void checkSelfPermission(){
ArrayList<String> permsisions = new ArrayList<String>();
String temp = "";
if(ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED){
permsisions.add(Manifest.permission.READ_EXTERNAL_STORAGE);
}
if(ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED){
permsisions.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
}
if(permsisions.size()>0){
String[] reqPermissions = permsisions.toArray(new String[permsisions.size()]);
ActivityCompat.requestPermissions(this, reqPermissions,100);
}else{
Toast.makeText(this, "모든권한 오케이", Toast.LENGTH_SHORT).show();
}
}
안드로이드 Q - 새로운 저장소 정책, Scoped Storage 알아보기
https://codechacha.com/ko/android-q-scoped-storage/
안드로이드 Q에서 외부 저장소를 다루는 새로운 정책, Scoped Storage가 소개되었습니다. Q 이전의 기존 정책은 앱이 디바이스의 어떤 파일에 접근할 때 사용자에게 저장소 권한만 얻으면 되었습니다. 그럼 앱은 모든 폴더를 탐색할 수 있었고 파일의 Path를 얻어 데이터를 읽고 쓸 수 있었습니다.
하지만, Q의 Scoped Storage는 기본적으로 앱이 디바이스의 모든 파일을 탐색하지 못한다는 전제가 깔려있습니다. 공용 미디어 파일들은 READ_EXTERNAL_STORAGE 권한이 있어야 접근이 가능하고, 그 외의 파일들은 사용자에게 접근 권한을 개별로 받아야 합니다. 앱이 자유롭게 접근할 수 있는 것은 자신의 개인 앱 데이터 뿐입니다.
구글의 이런 정책은 요즘 트렌드인 보안을 향상시키기 위한 목적입니다. 사용자는 앱이 알 필요가 없는 파일들에 대한 접근 권한을 주지 않아도 되어, 개인 정보를 보호할 수 있습니다.
Scoped Storage에 대해서 알아보기 전에, 지금까지 사용해왔던 외부 저장소 정책에 대해서 먼저 알아보고, Scoped Storage를 알아보겠습니다.
내부 저장소 vs 외부 저장소
안드로이드의 저장소는 크게 내부 저장소(Internal Storage)와 외부 저장소(External Storage)로 나뉩니다. 내부 저장소는 /data/ 아래 경로의 파일들을 의미하고, 외부저장소는 /storage/ 아래 경로의 파일들을 의미합니다.
모든 앱들은 자신만의 데이터 폴더를 갖고 있습니다. 자신의 데이터 폴더는 자신만 접근할 수 있습니다. 즉, 어떤 앱이 다른 앱의 데이터 폴더에 접근할 수 없다는 것을 의미합니다. 데이터 폴더는 내부 저장소와 외부 저장소에 모두 존재하고 경로는 다음과 같습니다.
- 내부 저장소의 데이터 폴더: /data/data/[앱의 Package Name]/
- 외부 저장소의 데이터 폴더: /storage/emulated/0/Android/data/[앱의 package name]/
Scoped Storage는 외부 저장소에 대한 정책입니다. 그렇기 때문에 외부 저장소에 초점을 맞추어 확인하시면 좋을 것 같습니다.
Q 이전의 외부 저장소 정책
안드로이드 P 이하의 버전에서, 외부 저장소에는 다음과 같은 폴더들이 있습니다.
- 앱 데이터 폴더 : /storage/Android/data/[앱의 package name]/
- 공용 폴더(DCIM, Pictures 등): /storage/[폴더 이름]
앱 데이터 폴더는 앱 자신만 접근할 수 있습니다. 그리고 앱이 삭제될 때 데이터도 함께 삭제됩니다.
반면에 공용 폴더는 모든 앱이 접근할 수 있습니다. Pictures, DCIM 등의 폴더가 자동으로 생성되어있지만 다른 목적으로 사용할 폴더를 직접 만들 수도 있습니다. 이 폴더에 파일을 읽거나 쓰려면 READ_EXTERNAL_STORAGE 또는 WRITE_EXTERNAL_STORAGE 권한을 얻어야 합니다. 그리고 파일을 생성한 앱을 삭제해도 데이터는 삭제되지 않습니다.
그리고 시스템은 MediaStore를 통해 디바이스에 존재하는 미디어 데이터를 공유합니다. MediaStore는 시스템에서 미디어 데이터에 대한 정보를 제공하는 Provider입니다.
저장소 내부의 폴더 경로에 대해서 궁금하시다면 Android의 데이터 폴더 경로 및 내부/외부 저장소 설명을 참고해주세요.
Q의 Scoped Storage 정책
구글은 Scoped Storage를 다음 3개로 나누어서 설명하고 있습니다.
- 앱 데이터 폴더(App specific directory)
- 미디어 파일들(MediaStore)
- 공용 파일들(Storage Access Framework)
앱 데이터 폴더(App specific directory, Sandbox)
앱 데이터 폴더는 이전과 동일합니다. 읽고 쓰는데 권한이 필요없고, 다른 앱들은 자신의 데이터 폴더에 접근할 수 없습니다. 앱이 삭제되면 데이터 폴더도 함께 삭제됩니다.
미디어 파일들(MediaStore)
공용 폴더 안에 있는 미디어 파일들은 MediaStore를 통해 읽을 수 있습니다. 예를 들어, 사진 또는 동영상 파일을 찾고 싶으면 공용 폴더 아래의 모든 파일들을 탐색해서 찾는 것이 아니라 MediaStore에 쿼리를 하여 Uri 객체를 얻어 사용해야 합니다.
다음과 같은 콜렉션들에 저장된 파일들은 MediaStore를 통해 읽을 수 있습니다.
- MediaStore.Images: 사진 파일
- MediaStore.Video: 비디오 파일
- MediaStore.Audio: 음악 파일
또한, MediaStore를 통해 파일을 읽으려면 READ_EXTERNAL_STORAGE 권한이 필요합니다. 하지만 파일을 생성하는 것은 권한이 없어도 됩니다.
이미지 파일의 위치 정보를 읽으려면 ACCESS_MEDIA_LOCATION 권한이 필요합니다. 이 권한은 Android 10(SDK 29)부터 추가된 신규 퍼미션이기 때문에, SDK 28 이하로 빌드된 앱의 경우 READ_EXTERNAL_STORAGE 권한만 요청하면 ACCESS_MEDIA_LOCATION도 함께 부여됩니다.
공용 파일들(Downloads 폴더, Storage Access Framework)
공용 미디어 파일들을 제외하고, Downloads 폴더 등을 접근할 때는 SAF(Storage Access Framework)를 이용해야 합니다. SAF는 Android Lollipop 부터 소개되었지만, 시스템 구조가 직접 접근하는 방식보다 간단하지 않고 UX가 복잡해 많은 앱들이 사용하지 않았습니다. SAF는 폴더들을 관리하는 DocumentsProvider와, 사용자가 앱에 파일 접근을 허용할 수 있도록 UI를 제공하는 DocumentsUI로 이루어져 있습니다.
Q에서 앱들은 공용 파일들에 접근할 때 DocumentsUI의 액티비티를 띄워 사용자에게 어떤 파일에 대한 접근 권한을 부여받아야 합니다. 그 이후에, 그 파일을 읽거나 쓰도록 해야 합니다. 사용자가 파일 또는 폴더를 선택하기 때문에 사용자는 더 좁은 범위로 권한을 부여할 수 있게 되었습니다.
Android developers: scoped storage에 SAF를 이용하는 방식에 대한 소개가 있고, 케이스별로 어떻게 처리가 되어야 하는지 안내하고 있습니다. SAF에 대한 설명은 Android developers: document provider를 참고해주세요.
앱 업데이트
앱 개발자들은 바로 자신의 앱에 Scoped Storage를 적용하지 않아도 됩니다. Scoped Storage를 바로 적용할 준비가 안된 개발자들을 위해, 구글은 Q에서 이전 방식을 사용할 수 있도록 유예기간을 두었습니다.
앱이 Android 10 디바이스에서 Scoped storage 정책을 적용받지 않으려면 다음 중에 1가지 방법을 따라야 합니다.
- 안드로이드 API 28 이하를 Target SDK로 설정
- 안드로이드 API 29 이상을 Target SDK로 설정하는 경우, requestLegacyExternalStorage를 true로 설정합니다.
다음과 같이 앱의 AndroidManifest.xml에 requestLegacyExternalStorage 속성을 true로 설정하면 Scoped Storage 정책이 적용되지 않습니다.
<manifest ... > <!-- This attribute is "false" by default on apps targeting Android Q. --> <application android:requestLegacyExternalStorage="true" ... > ... </application> </manifest>
Android 11(R)을 타겟팅하는 앱은 Scoped Storage 정책이 강제로 적용됩니다. requestLegacyExternalStorage 속성으로 이전 방식을 선택할 수 없습니다. 하지만 Android 10을 타겟팅하는 앱은 requestLegacyExternalStorage 속성으로 선택이 가능합니다.
정리
장황하게 설명한 Scoped storage를 간단히 설명하면, 앱은 직접 파일에 접근할 수 없고 MediaStore와 SAF를 통해 파일에 대한 Uri을 얻어 접근해야 합니다. 이런 정책은 사용자가 앱이 필요로하는 파일에만 접근 권한을 부여할 수 있게 만들고, 보안을 향상시키려는 목적입니다. 설명을 읽는 것보다 구글에서 제공하는 샘플들을 실행해보고 코드를 분석해보는 것이 더 좋을 것 같습니다.
'android training' 카테고리의 다른 글
코틀린-안드로이드 정리 (0) | 2021.06.10 |
---|---|
ContextCompat (0) | 2021.05.18 |
안드로이드스튜디오 단축키 (0) | 2016.03.14 |