안드로이드 웹뷰나 일반 앱에서 사진 및 파일을 업로드할 때 Intent.createChooser로 시스템 앱 선택기를 띄운다. 구글 포토, 내 파일, 기본 갤러리, 또는 커스텀 카메라 중 어떤 앱을 선택해서 사진을 가져왔는지 구분이 필요했다.
처음 시도했던 방식은 결과를 리턴받은 Uri의 Provider 스킴 패턴을 분석하는 것이었다.
// 구글 포토
content://com.google.android.apps.photos.contentprovider/.../ORIGINAL/NONE/image%2Fjpeg/160815479
// Samsung 내 파일
content://0@media/external/images/media/97
// 기본 Media Provider
content://com.android.providers.media.documents/document/image%3A97
// 커스텀 카메라 / FileProvider
content://com.example.myapp.fileprovider/files/Pictures/PHOTO_20260129.jpg
registerForActivityResult 결과로 들어온 content:// URI의 주소를 분석하면 대략 어떤 ContentProvider를 거쳐왔는지 짐작은 가능하다. 하지만 이 방식은 단말기 제조사, 안드 버전, 구글 포토 버전에 따라 URI 스킴 구조가 언제든 바뀔 수 있어 안정적이지 않다.
더 직관적인 해결책은 안드로이드 5.1(API 22) 이상에서 지원하는 Intent.createChooser(targetIntent, title, sender) API와 BroadcastReceiver를 조합하는 방식이다.
실제 환경에서는 원하는 대로 작동하지 않아 어떤 앱을 선택했는지 받지 못했다. 이런 방법이 있다는 정도로 정리한다.
![]()
시스템 앱 선택기에서 유저가 특정 앱 아이콘을 터치하는 순간, OS는 오버로드된 IntentSender로 브로드캐스트를 전송한다. 이때 수신된 Intent에서 Intent.EXTRA_CHOSEN_COMPONENT 파라미터를 꺼내면 유저가 선택한 앱의 정확한 ComponentName 을 얻을 수 있다.
class AppChooserReceiver : BroadcastReceiver() {
companion object {
private const val TAG = "AppChooserReceiver"
}
override fun onReceive(context: Context?, intent: Intent?) {
// 유저가 Chooser에서 선택한 대상 컴포넌트 추출
val targetComponent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.33) {
intent?.getParcelableExtra(Intent.EXTRA_CHOSEN_COMPONENT, ComponentName::class.java)
} else {
@Suppress("DEPRECATION")
intent?.getParcelableExtra(Intent.EXTRA_CHOSEN_COMPONENT) as? ComponentName
}
val packageName = targetComponent?.packageName
val className = targetComponent?.className
Log.d(TAG, "선택된 앱 패키지: $packageName, 클래스: $className")
}
}
우선 선택 이벤트를 수신할 BroadcastReceiver를 작성한다.
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<!-- Chooser 선택 이벤트를 수신하는 Receiver -->
<receiver
android:name=".AppChooserReceiver"
android:exported="false" />
</application>
</manifest>
AndroidManifest.xml에 브로드캐스트 리시버를 등록한다. android:exported="false" 옵션은 이 앱만 통신.
private fun createChooserPendingIntent(): PendingIntent? {
val intent = Intent(activity, AppChooserReceiver::class.java)
val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
// OS가 Intent.EXTRA_CHOSEN_COMPONENT 데이터를 채워 넣을 수 있도록 MUTABLE 지정
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
} else {
PendingIntent.FLAG_UPDATE_CURRENT
}
return PendingIntent.getBroadcast(
activity,
0,
intent,
flags
)
}
// 웹뷰 onShowFileChooser 또는 일반 앱 공유 Intent 호출 시
override fun onShowFileChooser(
webView: WebView,
filePathCallback: ValueCallback<Array<Uri>>,
fileChooserParams: FileChooserParams
): Boolean {
val pendingIntent = createChooserPendingIntent()
val pickIntent = Intent(Intent.ACTION_GET_CONTENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "image/*"
}
// PendingIntent의 intentSender를 Intent.createChooser의 세 번째 인수로 전달
val chooserIntent = Intent.createChooser(
pickIntent,
"사용할 앱을 선택해주세요.",
pendingIntent?.intentSender
)
launcher.launch(chooserIntent)
return true
}
createChooser 를 사용하면, 유저가 사진을 선택하기 위해 앱 선택기 UI에서 갤러리나 카메라를 누르는 즉시 AppChooserReceiver.onReceive()가 호출된다.
createChooser 를 구성할 때 intentSender를 세 번째 인수로 넘겨준다.
Android 12(API 31) 이상에서는
PendingIntent생성 시FLAG_MUTABLE또는FLAG_IMMUTABLE플래그를 명시해야 한다. OS가 시스템 Chooser 내부에서 선택된 컴포넌트 정보(EXTRA_CHOSEN_COMPONENT)를PendingIntent에 채워 넣어 브로드캐스트를 쏴야해서, 반드시PendingIntent.FLAG_MUTABLE플래그를 지정해야 정상 동작한다.
반환받은 URI 형태를 억지로 정규식 파싱하는 것보다 Intent.EXTRA_CHOSEN_COMPONENT에서 리턴해 주는 실제 패키지명을 확인하는 게 정확한데,, 이게 작동이 안된다.
댓글 0개