2020년 11월 1일 부로 안드로이드 Target SDK 레벨이 29 이상이어야만 업데이트가 가능합니다.
이로 인해서 Target SDK 를 올리는 작업을 하기로 결정하게 되었고, 이왕 작업하는 거 Target SDK를 30으로 올리자고 의사결정했습니다.
Target SDK 30 으로 올리기 위해 작업한 내용들을 공유하고자 합니다.
Scoped Storage 적용, Custom Toast 를 시스템 Toast로 전환, 백그라운드 서비스 권한 부여 및 처리 등을 진행했습니다.
이번 시간에는 Scoped Storage 를 적용한 것을 공유합니다.
과거 상태
회사에서 개발하고 있는 앱에서 접근 하고 있는 파일들의 구조를 간략하게 설명해보면 아래와 같습니다.
외부 저장공간(Environment.getExternalStorageDirectory()) 의 DigitalPage 라는 폴더안에 temp, cash, resource 등의 폴더로 구분해 임시파일, 리소스 파일 등 을 저장해 사용하고 있었고, TargetSDK 28로 설정이 되어 있었습니다.
어떤 것들이 변경되는가? Scoped Storage 간략한 정리
파일이 저장소에 자유롭게 저장되다보니 파일이 많아지면서 복잡하게 되고, 보안상의 이슈도 발생할 수 있기 때문에 도입되었다고 합니다.
Android 10(API 수준 29) 이상을 타겟팅하는 앱은 기본적으로 외부 저장소로 범위가 지정된 액세스 권한을 가지게 되고, 범위 지정 저장소가 부여됩니다. 이러한 앱은 외부 저장소의 앱별 디렉터리와 앱에서 만든 특정 유형의 미디어에만 액세스할 수 있게 됩니다.
1. 앱 고유의 저장소(범위저장소) 애는 제한 없이 접근이 가능합니다. 외부 저장소(SD 카드) 에도 생성이 가능합니다.
2. 공유저장소(DCIM, Pictures 등) 의 미디어파일은 다운로드 및 수정에 제약이 없습니다.
DCIM 폴더에 이미지 파일을 File API 로 저장이 가능합니다.
단, 공유저장소의 다른 앱이 만든 파일을 수정 할 때에는 사용자 확인을 받아야 합니다.
3. 다른 앱이 만든 파일들은 공유저장 공간에서만 접근 가능하고, 실행 시에 얻은 권한으로는 공유저장소의 미디어 파일 읽기만 가능합니다.
4. MediaStore를 통해 외부 저장소의 파일에 접근, 수정 가능 합니다.
5. 미디어 파일 이외 파일의 접근은 저장소 엑세스 프레임워크(SAF) 를 이용합니다.
그 외 상세한 사항은 아래 레퍼런스를 참조하세요
- 데이터 저장소의 개요 : developer.android.com/training/data-storage
- 안드로이드 11의 저장소 업데이트 : developer.android.com/about/versions/11/privacy/storage
- Meetup Android 11 호환성 확보하기 : youtu.be/dOscKi6kwrM
- 안드로이드 11 - 저장소 정책 변경사항 정리 : codechacha.com/ko/android11-storage-updates/
그래서 어떻게 변경 했는데??
1. gradle의 targetSdkVersion 30 으로 변경합니다.
2. Manifast 파일에 <application android:requestLegacyExternalStorage="true"> 로 설정합니다.
파일 이전을 위해 안드로이드 10 기기에서 기존 외부저장소에 접근할 수 있어야 하기 때문입니다.
안드로이드 11의 경우는 android:requestLegacyExternalStorage 플래그는 무시됩니다.
2. 외부 저장소에 디렉토리 구조를 만들도록 설정된 기본 경로 상수들을 모두 앱 내부 저장소 안에서 구현되도록 새로운 경로 상수를 추가했습니다.
기존 : HOME_DIRECTORY = Environment.getExternalStorageDirectory().getPath()
추가 : NEW_HOME_DIRECTORY = cotext.getExternalFilesDir().getPath()
그리고, 하위 디렉토리 생성과 참조가 모두 NEW_HOME_DIRECTORY 경로에서 일어나도록 수정했습니다.
3. loading 후 MainActivity 진입 시 기존 사용하던 경로의 폴더가 존재하는 지와 Preferance 에 저장한 마이그레이션을 진행 여부 플래그를 보고 마이그레이션을 위한 Activity를 띄워 기존 경로의 파일들을 새로운 경로로 이동시킵니다.
여기서 Preferance에 마이그레이션 진행 여부 플래그를 함께 사용하는 이유는 안드로이드 11 기기에서 앱을 업데이트 할 경우에는 기존 폴더/파일의 이동 및 삭제가 가능합니다만, 앱을 지운 후 재설치하면 기존 폴더 / 파일이 이동 및 삭제가 되지 않았습니다.
그래서, 기존 경로 존재 유무만 보고 마이그레이션을 진행하면 안드로이드 11 기기에서 앱을 재설치 한 경우 매번 마이그레이션이 진행되어 진행 여부 플래그도 함께 보고 판단하도록 하였습니다.
또한, 예상치 못한 예외가 발생할 경우 앱의 기본 기능은 사용해야 되기 때문에 사용자에게 오류가 발생했음을 알리고,
재시도나 마이그레이션 자체를 건너 뛸 수 있도록 하였습니다.
4. 모든 이미지를 선택해 앱에 에디터에 입력하기 위해서 갤러리와 유사한 Activity에서 이미지 미디어 파일을 읽어오기 위해서 ContentReserver를 이용하고 있었습니다.
이 때 Query 입력시 Projection 에 distinct 가 사용되고 있었고, 또 다른 곳에는 order에 LIMIT 키워드도 사용되고 있었습니다.
Target SDK 30으로 올리자 이 두 곳에서 Exception 이 발생했고, 각각 Query 결과를 받은 후 각 키워드와 같은 기능을 하는 로직을 추가해 해결했습니다.
이렇게 변경 후에 정상적으로 동작하는 것을 확인했습니다.
글을 쓰고 보니 조금 더 Scope Storage 의 동작사항을 자세히 적지 못한 것 같기도 합니다만, 이글이 조금이나마 Scope Storage 를 적용하시려는 분들께 도움이 되었으면 좋겠습니다.
'Tech & Programming > 모바일(Android, Flutter)' 카테고리의 다른 글
Google I/O 2021 Keynote 키워드 요약 (0) | 2021.05.27 |
---|---|
Android Context (0) | 2020.11.13 |
Fragment에서 호출한 Activity의 결과 받기(onActivityResult 호출 원리) (0) | 2020.09.08 |
org.apache.commons.io 라이브러리에서 java.lang.NoSuchMethodError 에러 해결 (0) | 2019.11.14 |
android.app.RemoteServiceException: Context.startForegroundService() did not then call Service.startForeground() 오류 해결 (0) | 2019.10.01 |