1. Scoped Storage란?
구글 플레이스토어에 앱을 업로드 하기 위해서는 Target SDK 30 이상을 맞춰야하는 이슈가 생겼다. SDK 30 이상부터는 기존 접근 권한 과는 다르게 파일에 접근해야 하는 이슈가 생겼는데 다음과 같다.
(1) API Levels
SDK 28 이하
- WRITE_EXTERNAL_STORAGE
- READ_EXTERNAL_STORAGE
- MANAGE_EXTERNAL_STORAGE
SDK 32 이하
- READ_EXTERNAL_STORAGE
SDK 33 이상
- READ_MEDIA_IMAGES
- READ_MEDIA_VIDEO
- READ_MEDIA_AUDIO
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" tools:ignore="ScopedStorage"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32"/>
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO"/>
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO"/>
(2) Permission 요청
SDK 28이하 에서는 권한 요청을 모두 해야 하고, 유저로부터 승인을 받아야 한다. 하지만 "Scoped Storage"가 적용된 뒤로 SDK 32 이하에서는 "WRITE_EXTERNAL_STORAGE" 권한을 사용할 수 없어 파일에 직접적으로 접근할 수 없게 되었다. "Scoped Storage"는 "Media Storage" 사용 및 파일 읽기만 가능하며, 파일 복사 및 이동은 Uri를 통해서만 가능하다.
- SDK 28 이하 : 요청
- SDK 32 이하 : 미요청
- SDK 33 이상 : 요청
※ Media Store : 안드로이드 시스템에서 제공하는 미디어 데이터
(3) ContentValue
SDK 28이하 "WRITE_EXTERNAL_STORAGE" 권한이 사용 가능할 때에는 파일 생성시 File 클래스를 사용하여 파일을 복사했다. 하지만 SDK 29이상에서는 "WRITE_EXTERNAL_STORAGE" 권한을 사용할 수 없어 "ContentValue"를 사용하여 파일을 생성해야한다. "ContentValue"는 파일 생성을 위해 Uri에 생성할 파일 데이터를 넣어준다.
- new FileOutputStream(new File());
- getContentResolver().openOutputStream(Uri);
Code
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q){
String outputPath = "";
ContentResolver contentResolver = getContentResolver();
ContentValues values = new ContentValues();
String cFileName = "";
String[] projection = {MediaStore.MediaColumns.DISPLAY_NAME};
Cursor cursor = contentResolver.query(documentUri, projection, null, null, null);
if (cursor != null && cursor.moveToFirst()) {
int nameIndex = cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME);
cFileName = cursor.getString(nameIndex);
cursor.close();
}
String fileName = "TEMP_" + cFileName;
values.put(MediaStore.Files.FileColumns.IS_PENDING,0);
values.put(MediaStore.Files.FileColumns.RELATIVE_PATH, Environment.DIRECTORY_DOCUMENTS + File.separator + "UC Download" + File.separator + "TEMP" + File.separator);
values.put(MediaStore.Files.FileColumns.DISPLAY_NAME, fileName);
values.put(MediaStore.Files.FileColumns.MIME_TYPE, "application/octet-stream");
Uri fileUri = contentResolver.insert(MediaStore.Files.getContentUri("external"), values);
OutputStream outputStream = contentResolver.openOutputStream(fileUri);
InputStream inputStream = contentResolver.openInputStream(documentUri);
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = inputStream.read(buffer, 0, 1024)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
outputStream.close();
inputStream.close();
outputPath = CmmAndUtil.getDocumentDir() + fileName;
}
2. 파일 전송 및 복사
"Scoped Storage"에서 파일을 전송하기 위해서는 파일을 복사해야 한다. 즉 원본 파일에는 접근할 수 없으며, 파일을 공용 공간에 복사하여 전송 후 삭제 하는 방식을 사용하도록 한다. 공용 공간에 접근하는 방식은 기존 방법과 달라진 부분이 있다. 외부 저장소에는 사진 및 동영상, 음악, 다운로드 폴더가 존재하며 각각의 용도에 따라 폴더가 나뉜다.
(1) SDK 28 이하
(2) SDK 29 이상
(3) 내부 저장소 폴더
안드로이드 스튜디오 Device Explorer에서 확인 가능하다. 앱이 설치되어 있는 폴더
getExternalFilesDir(Environment.DIRECTORY_PICTURES).getAbsolutePath //앱 개별 파일 공간
getExternalCacheDir().getAbsolutePath //앱 개별 캐시 파일 공간
(4) 외부 저장소 폴더
- Pictures
- Movies
- Documents
- Music
- Download
Code
public static String getDefaultDownDir() {//Download
return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath() + File.separator + UCAConstans.FILE_DOWN_DIR + File.separator;
}
public static String getPictureDir(){//Pictures
return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).getAbsolutePath() + File.separator + UCAConstans.FILE_DOWN_DIR + File.separator + UCAConstans.FILE_TEMP_DIR+ File.separator;
}
public static String getMovieDir(){//Movies
return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES).getAbsolutePath() + File.separator + UCAConstans.FILE_DOWN_DIR + File.separator + UCAConstans.FILE_TEMP_DIR+ File.separator;
}
public static String getDocumentDir(){//Documents
return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS).getAbsolutePath() + File.separator + UCAConstans.FILE_DOWN_DIR + File.separator + UCAConstans.FILE_TEMP_DIR+ File.separator;
}
Reference
'Android > Android Java' 카테고리의 다른 글
[Android Java] China Push, Doze Mode (0) | 2023.11.23 |
---|---|
[Android Java] 사진, 동영상 촬영(ActivityResultLauncher, Scoped Storage) (0) | 2023.11.22 |
[Android Java] Notification Action, RemoteInput (1) | 2023.11.13 |
[Android Java] Notification (0) | 2023.11.09 |
[Android Java] Clipboard 사용하기 (0) | 2023.11.03 |