Android Q OS 이전에는 아래와 같은 형태로 Storage가 관리되었다.
내부 저장소
개별 앱만 접근 가능
외부 저장소
외부 저장소 권한이 있으면 누구나 접근이 가능
- READ_EXTERNAL_STORAGE
- WRITE_EXTERNAL_STORAGE
하지만 Q OS 이후에는 Storage 관리가 아래와 같이 변경되었다.
샌드박스 저장공간
- 개별 앱만 접근 가능
- 읽고 쓰기 위한 별도의 권한 필요 없음
- Context.getExternalFilesDir(...)
- 앱 삭제 시 함께 삭제됨
- 앨범 아트, 썸네일 등 다른 앱과 공유할 필요가 없는 미디어 파일은 샌드박스 공간을 활용
공용 저장공간
- MediaStore.Audio
- MediaStore.Video
- MediaStore.Images
- 별도 권한 없이 파일 생성 가능
- 외부 저장소 권한으로 다른 앱이 생성한 파일 접근 가능
다운로드 저장공간
- MediaStore.Downloads
- 별도 권한 없이 파일 생성 가능
- 시스템 파일 선택기를 통해서만 다른 앱이 생성한 파일 접근 가능
Scoped mode
원활한 Migration을 위해 Scoped mode 적용 여부를 선택할 수 있는 플래그를 Manifest.xml파일에서 아래와 같이 제공하고 있다.
<application android:requestLegacyExternalStorage="true">
다음 Android 버전(Android 11 / R)에서는 Target SDK 버전과 관계없이 모든 앱에 Scoped mode를 적용될 예정이다.
단, 개발 앱의 Scoped mode 여부는 앱 설치 시 정해지며, 이후 변경되지 않는다.
다음 코드는 Android Q 이전에서 이미지 파일을 저장하는 예제다.
private void beforeQ() {
String strFolderPath = Environment.getExternalStorageDirectory().getAbsolutePath() + CAPTURE_PATH;
File folder = new File(strFolderPath);
if (!folder.exists()) {
folder.mkdirs();
}
OutputStream out = null;
String strFilePath = strFolderPath + "/";
try {
File fileCacheItem = new File(strFilePath + fileName);
fileCacheItem.createNewFile();
out = new FileOutputStream(fileCacheItem);
// 비트맵을 png로 변환
bmp.compress(Bitmap.CompressFormat.PNG, 100, out);
// 미디어 스캐닝을 하여 삭제,추가 정보를 알린다.
sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(fileCacheItem)));
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (out != null) {
out.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
다음 코드는 Android Q 이후에서 이미지 파일을 저장하는 예제다.
private void afterQ() {
ContentValues values = new ContentValues();
values.put(MediaStore.Images.Media.DISPLAY_NAME, fileName);
values.put(MediaStore.Images.Media.MIME_TYPE, "image/*");
// 파일을 write중이라면 다른곳에서 데이터요구를 무시하겠다는 의미입니다.
values.put(MediaStore.Images.Media.IS_PENDING, 1);
ContentResolver contentResolver = getContentResolver();
Uri collection = MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY);
// ContentResolver을 통해 insert를 해주고 해당 insert가 되는 위치의 Uri를 리턴받는다.
// 이후로는 해당 Uri를 통해 파일 관리를 해줄 수 있다.
Uri item = contentResolver.insert(collection, values);
try {
// Uri(item)의 위치에 파일을 생성해준다.
ParcelFileDescriptor pdf = contentResolver.openFileDescriptor(item, "w", null);
if (pdf == null) {
} else {
InputStream inputStream = getImageInputStram();
byte[] strToByte = getBytes(inputStream);
FileOutputStream fos = new FileOutputStream(pdf.getFileDescriptor());
fos.write(strToByte);
fos.close();
inputStream.close();
pdf.close();
contentResolver.update(item, values, null, null);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
values.clear();
// 파일을 모두 write하고 다른곳에서 사용할 수 있도록 0으로 업데이트를 해줍니다.
values.put(MediaStore.Images.Media.IS_PENDING, 0);
contentResolver.update(item, values, null, null);
}
private InputStream getImageInputStram() {
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
bmp.compress(Bitmap.CompressFormat.PNG, 100, bytes);
byte[] bitmapData = bytes.toByteArray();
ByteArrayInputStream bs = new ByteArrayInputStream(bitmapData);
return bs;
}
public byte[] getBytes(InputStream inputStream) throws IOException {
ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream();
int bufferSize = 1024;
byte[] buffer = new byte[bufferSize];
int len = 0;
while ((len = inputStream.read(buffer)) != -1) {
byteBuffer.write(buffer, 0, len);
}
return byteBuffer.toByteArray();
}
출처
'Basic > Android' 카테고리의 다른 글
[Android] Intent를 이용하여 Uri 얻어내기 (0) | 2020.05.29 |
---|---|
[Android] Scoped storage를 이용하여 txt 파일을 읽어오는 방법 (0) | 2020.05.21 |
[Android] build gradle Product flavor 설정 방법 (0) | 2020.04.15 |
[Android] 화면 회전 고정 시키기 (0) | 2020.03.23 |
ValueAnimator을 이용한 애니메이션 만들기 (0) | 2020.03.21 |