android 11+ 에서 카메라 & 갤러리 인텐트로 이미지 선택 -- Android


android 10에서 android 11로 업그레이드 되면서 카메라나 갤러리 인텐트를 이용해 이미지를 선택할 때 변경사항이 있다.
실제로 android 10으로 된 앱을 업데이트 해야 하는데, 

2022년 현재 minSdkVersion과 targetSdkVersion을 올려야 하는 요구가 필요하여 아래와 같이 하기로 했다.

compileSdkVersion 31
minSdkVersion 23
targetSdkVersion 31



[1] android targetSdkVersion을 29로 올렸을 때 

갤러리나 외부 폴더/파일 접근하는데 오류가 발생한다면, AndroidManifest.xml의 application에 requestLegacyExternalStorage를 추가해야 Android 10이 설치된 핸드폰에서 정상 동작을 하게 된다. 

이는 android 10부터 발표된 Scoped Storage 정책을 targetSdkVersion 29에서 무력화할 수 있는 방식이다.

Scoped Storage 정책은 흡사 아이폰처럼 보안을 위해 앱에서 접근하는 스토리지를 앱 영역으로 제한하는 것이다.
예전에는 앱에서 public한 모든 스토리지를 접근할 수 있었는데, 이는 보안 상 취약하다고 할 수 있으므로 앱 영역 내 (private)의 영역에는 파일 쓰기에 제한이 없으나, 메모리카드의 다른 영역에는 제약이 따른다.

<application
...    
android:requestLegacyExternalStorage="true">


[2] android targetSdkVersion을 30으로 올렸을 때

위의 requestLegacyExternalStorage 옵션은 targetSdkVersion 30일 때 무시된다. 

2-1) 
또한, WRITE_EXTERNAL_STORAGE 권한 역시 targetSdkVersion 30부터는 사용할 수 없으므로 아래와 같이 하위 버전만 사용하도록 지정해 주었다.

<uses-permission 
android:name="android.permission.WRITE_EXTERNAL_STORAGE" 
android:maxSdkVersion="29" />


2-2)
이미지 파일을 저장하기 위해 아래와 같이 getExternalFilesDir()를 사용하였다.

기존 : Environment.getExternalStorageDirectory()
변경 : context.getExternalFilesDir(Environment.DIRECTORY_PICTURES)


2-3)
이미지 선택 시 카메라 인텐트를 통해 사진 촬영 후 해당 이미지를 선택한 기능이 있었는데, 
사진 촬영 후 확인을 하면 onActivityResult()의 resultCode가 항상 0 ( RESULT_CANCELD)가 되었다.

이를 해결하기 위해 아래와 같이 수정하였다.


[카메라 인텐트 만들 때]

Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
...
Uri cameraPhotoURI = FileProvider.getUriForFile(context, "kr.co.your.company.fileprovider", photoFile);
intent.putExtra(MediaStore.EXTRA_OUTPUT, cameraPhotoURI);


[AndroidManifest.xml]

<manifest
...
<uses-feature 
android:name="android.hardware.camera" 
android:required="true" />
...
<uses-permission android:name="android.permission.CAMERA" />
...
<application
            ...
<provider 
android:name="android.support.v4.content.FileProvider"
android:authorities="kr.co.your.company.fileprovider
android:exported="false"
android:grantUriPermissions="true"> 
<meta-data 
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths">
</meta-data> 
</provider>
</application>
</manifest>


[res\xml\file_paths.xml]

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <cache-path
        name="cache"
        path="." />

    <files-path
        name="files"
        path="." />

    <external-path
        name="external"
        path="." />

    <external-cache-path
        name="external-cache"
        path="." />

    <external-files-path
        name="external-files"
        path="." />
</paths>


[3] 기타) 

3-1)
compileSdkVersion 31로 올렸을 때 androidStudio에서 Gradle JDK를 11로 올려야 한다.

Settings > Build, Execution, Deployment > Build Tools > Gradle > Gradle JDK


3-2)
targetSdkVersion 31로 올렸을 때 activity, service, receiver, provider 등에 exported를 설정해야 한다.

<activity
android:name=".MainActivity"
android:screenOrientation="sensorPortrait"
android:exported="true">


[4] 기타) 전화번호 조회

앱에서 전화번호를 조회하는 기능이 있는데 Android 10 (API 29)까지는 READ_PHONE_STATE 권한으로 조회가 가능했다. 

그런데 targetSdkVersion 30 이상으로 올리면 READ_PHONE_NUMBERS 권한을 받아야 한다.

<uses-permission 
android:name="android.permission.READ_PHONE_STATE" 
android:maxSdkVersion="29" />

<uses-permission 
android:name="android.permission.READ_PHONE_NUMBERS" />


[권한을 받을 때 버전 별로 분리]

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
...
Permission.requestPermission(
context,
new String[]{
android.Manifest.permission.READ_PHONE_NUMBERS
},
PERMISSION_READ_PHONE_STATE);
}
else {
...
Permission.requestPermission(
context,
new String[]{
android.Manifest.permission.READ_PHONE_STATE
},
PERMISSION_READ_PHONE_STATE);
}



1 2 3 4 5 6 7 8 9 10 다음