본문 바로가기

Android - 기능

[Andoird] 카메라 촬영/갤러리 이미지 가져오기

아래 3가지 방법으로 가져온 이미지를 이미지뷰에 보여주는 예제를 통해 사용법을 익혀보도록 하겠습니다

 

  1. 갤러리 이미지 
  2. 카메라 촬영 이미지(thumbnail)
  3. 카메라 촬영 이미지 (full-size photo)

3가지 모두 Intent 요청 후 onActivityResult로 결과를 받는 형태 입니다

(registeractivityresult를 사용했을 경우라면 ActivityResultCallback)

 


# 1 : 갤러리 이미지 

  • 이미지를 선택해서 불러오는 기능

1)요청

아래 2가지 방법이 많이 쓰입니다

  1. pickImg_01 :  Intent.ACTION_PICK 사용  /  URI를 기반으로 이미지 선택 앱 호출 
  2. pickImg_02 :  Intent.ACTION_GET_CONTENT 사용  / mime type을 지원하는 앱 호출(IntentChooser 사용해서)

'Intent.ACTION_GET_CONTENT'를 사용해서 사진을 가져올 앱을 사용자로 하게끔 선택하는 pickImg_02() 방법을 권고

    const val REQ_SELECT_IMG = 200 //request code
    
    private fun pickImg_01() {
        Intent(Intent.ACTION_PICK).apply {
            data = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
            startActivityForResult(this, REQ_SELECT_IMG)
        }
    }
    
    private fun pickImg_02() {
        Intent(Intent.ACTION_GET_CONTENT).apply {
            type = "image/*"
            startActivityForResult(
                Intent.createChooser(this, "Get Album"),
                REQ_SELECT_IMG
            )
        }
    }

 

2)결과

onActivityResult로 반환되는 intent의 data에 선택한 이미지 URL이 담겨 오고.

불러온 이미지를 그대로 쓸 수도 있지만, 보통 Resize도 하고 Rotate정보에 따라 회전 시켜서 보여줍니다

  1. setImgUri() : Uri를 기반으로 원본 이미지 그대로 이미지뷰에 셋
  2. setAdjImgUri() : Resize + Rotate가 필요할 경우 사용
더보기

'setAdjImgUri '함수 내부 에서 보기 쉬우려고 InputStream을 여러번 열었지만 성능상 좋을 리 없으므로...

실제 적용할 땐 최소한으로 사용하도록 고려

    override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) {
        super.onActivityResult(requestCode, resultCode, intent)
        
        if (resultCode == Activity.RESULT_OK) {
            when (requestCode) {
                REQ_SELECT_IMG -> {   
                
                    val currentImageUri = intent?.data ?: return //이미지 URL
                    
                    val needAdjust = true
                    if (needAdjust) {
                        setAdjImgUri(currentImageUri) //방법 2
                    } else {
                        setImgUri(currentImageUri) //방법 1
                    }
                }
           
            }
        }
    }
    
     /**
     * Set Img 1) Uri를 사용해 이미지 셋 (Full-Size)
     */
    private fun setImgUri(imgUri: Uri) {
        imgUri.let {
            val bitmap: Bitmap
            if (Build.VERSION.SDK_INT < 28) {
                bitmap = MediaStore.Images.Media.getBitmap(
                    this.contentResolver,
                    imgUri
                )
                binding.ivImg.setImageBitmap(bitmap)
            } else {
                val source =
                    ImageDecoder.createSource(this.contentResolver, imgUri)
                bitmap = ImageDecoder.decodeBitmap(source)
                binding.ivImg.setImageBitmap(bitmap)
            }
        }
    }
    
    
     /**
     * Set Img 2) Uri를 사용해 이미지 셋 (Resize + Rotate가 필요할 경우 사용)
     */
    private fun setAdjImgUri(imgUri: Uri) {
        //1)회전할 각도 구하기
        var degrees = 0f
        contentResolver.openInputStream(imgUri)?.use { inputStream ->
            degrees = getExifDegrees(ExifInterface(inputStream))
        }

        //2)Resizing 할 BitmapOption 만들기
        val bmOptions = BitmapFactory.Options().apply {
            // Get the dimensions of the bitmap
            inJustDecodeBounds = true
            contentResolver.openInputStream(imgUri)?.use { inputStream ->
                //get img dimension
                BitmapFactory.decodeStream(inputStream, null, this)
            }

            // Determine how much to scale down the image
            val targetW: Int = 1000 //in pixel
            val targetH: Int = 1000 //in pixel
            val scaleFactor: Int = Math.min(outWidth / targetW, outHeight / targetH)           

            // Decode the image file into a Bitmap sized to fill the View
            inJustDecodeBounds = false
            inSampleSize = scaleFactor
        }

        //3) Bitmap 생성 및 셋팅 (resized + rotated)
        contentResolver.openInputStream(imgUri)?.use { inputStream ->
            BitmapFactory.decodeStream(inputStream, null, bmOptions)?.also { bitmap ->
                val matrix = Matrix()
                matrix.preRotate(degrees, 0f, 0f)
                binding.ivImg.setImageBitmap(
                    Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, false)
                )
            }
        }
    }
    
    
/**
* 이미지 회전 데이터 반환
*/    
private fun getExifDegrees(ei: ExifInterface): Float {
    val orientation: Int =
        ei.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED)
    return when (orientation) {
        ExifInterface.ORIENTATION_ROTATE_90 -> 90f
        ExifInterface.ORIENTATION_ROTATE_180 -> 180f
        ExifInterface.ORIENTATION_ROTATE_270 -> 270f
        else -> 0f
    }
}

 

 

# 2 : 카메라 촬영 이미지 (thumbnail)

  • 촬영 이미지를 저장 하지 않고 얻어올 수 있는 방법
  • 대신 이 이미지는 촬영 원본 사이즈가 아닌 Resize된 작은 이미지이므로 썸네일이나 아이콘으로 사용하기엔 좋지만 더 큰 이미지가 필요하다면 다른 방법이 필요  ->'#3: 카메라 촬영 이미지 (full-size photo)'

1)요청

const val REQ_IMG_CAPTURE = 300
private fun takePicture() {
        val pictureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
        pictureIntent.resolveActivity(packageManager)?.also {
            startActivityForResult(pictureIntent, REQ_IMG_CAPTURE)
        }
    }

 

2)결과

onActivityResult로 반환되는 intent의 extra에 썸네일 크기의 사진 데이터가 담겨 옴

Bitmap 변환 후 사용

    override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) {
        super.onActivityResult(requestCode, resultCode, intent)
        
        if (resultCode == Activity.RESULT_OK) {
            when (requestCode) {
                REQ_IMG_CAPTURE -> {
                    val extras = intent?.extras
                    val bitmap = extras?.get("data") as Bitmap?
                    binding.ivImg.setImageBitmap(bitmap)
                }
           
            }
        }
    }

 

 

# 3 : 카메라 촬영 이미지 (full-size photo)

* 이 예제는 targetSdk 30이상 이고 'Scoped Storage'를  바탕으로 합니다.

* 예제에서는 Permission 획득/체크를 따로 하지 않습니다. 획득 된 것으로 가정합니다. 따로 구현 해주세옹.

  • 촬영 이미지을 저장해야 함
  • 촬영 이미지가 저장될 빈 File을 미리 만들어두고, 그 File의 URI를 Intent에 실어 카메라를 호출
  • 촬영을 하게 되면 해당 File에 데이터가 써지고, URI를 통해 이미지를 읽어와 사용

이게 복잡합니다... 

Android10(Q)에서 Scoped Storage가 적용되면서 공용 공간에 접근할 수 있는 API 들이 deprecated 됐고,

대신 MediaStore API 를 사용하도록 권장되고 있습니다.

더이상 File 절대경로를 통한 공용 공간 접근을 허용하지 않으며, MediaStore를 통해 접근 해야 한다는 뜻입니다.

더보기

Media Store란? - https://developer.android.com/reference/android/provider/MediaStore

    - Media Provider 와 application 간의 contract

    - 안드로이드 시스템에서 제공하는 기능이며

    - Media Data들을 Indexing 해서 미디어 DB로 관리합니다

 

MediaStroe를 이용해 데이터 저장을 할 시 저장 권한은 필요하지 않습니다.(단, 'Downloads' 폴더를 제외하곤, 파일 타입에 따라 저장 할 수 있는 폴더가 정해져 있습니다)

 

저장이 아닌 읽을 때는 READ_EXTERNAL_STORAGE 권한이 필요합니다

 

우선 어느 저장소를 사용할 것인지 결정하시고 시작하는게 좋겠습니다

저장소 관련해서는 아래 디벨러퍼를 참고 하시기 바랍니다

[Developer - 데이터 및 파일 저장소 개요] https://developer.android.com/training/data-storage?hl=ko

 

  1. 공용 공간 선택 시 : Scoped Storage를 고려해야하며 Q 미만에서 'WRITE_EXTERNAL_STORAGE' 권한 필요
  2. 앱 전용 공간 선택 시 : Android 4.3(Api level 18) 까지는 'WRITE_EXTERNAL_STORAGE' 권한이 필요하면 4.4 부터는 권한이 필요 없음 

전용 공간부터 확인 해보도록 하겠습니다(그나마 간단...)

#3-1 카메라 촬영 (앱 전용 공간)

  • context.getExternalFilesDir(Environment.DIRECTORY_PICTURES)를 통한 외부 저장소를 예제로 함
  • 경로 = '/storage/emulated/0/Android/data/패키지명/files/Pictures/'
  • 파일의 URI는 FileProvider를 통해 생성

1) 사전 준비 

- 퍼미션 선언, FileProvider 선언 

- FileProvider를 위한 filepaths.xml 생성

 

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.imageuploadtest">
    
    <!-- 앱 전용 공간에 저장이 필요 할 경우 ex)'getExternalFilesDir()'-->
    <!-- 4.3 버전 까지는 퍼미션 필요 -->
   <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
        android:maxSdkVersion="18"/>
                         ...        
    <application
                         ...
                         
        <!-- FileProvider 선언 -->
        <provider
            android:name="androidx.core.content.FileProvider"
            android:authorities="${applicationId}.fileprovider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/filepaths" /> 
        </provider>
    </application>

</manifest>

 

filepaths.xml

<?xml version="1.0" encoding="utf-8"?>
<paths>

    <!--<file-path> =  내부, 앱 스토리지 / getFileDir -->
    <!--<cache-path> =  내부, 앱 캐시 스토리지 / getCacheDir -->
    <!--<external-files-path> = 외부, 앱 전용 스토리지 file / getExternalFilesDir 사용시-->
    <!--<external-cache-path> = 외부, 앱 전용 스토리지 cache / getExternalCacheDir 사용시-->
    <!--<external-path> = 외부, 공용 저장소 사용 시 / Environment.getExternalStorageDirectory-->

    //사진은 'getExternalFilesDir' 아래 'Pictures' 폴더 아래 저장되어 있을 예정    
    <external-files-path name="cameraImg" path="Pictures/"/> 

</paths>

 

2) 카메라 호출

- 저장소에 빈 파일을 생성

- FileProvider를 이용해 빈 파일의 Uri를 얻음

- 얻은 photoURI를 Intent의 'MediaStore.EXTRA_OUTPUT' 속성 value값으로 put

- Intent를 이용해 카메라 호출

- 얻은 photoURI는 촬영 후, 이미지 가져올 때 사용해야 하니.. 챙겨두기

lateinit var photoURI: Uri 

private fun takePictureFullSize() {
    photoURI = Uri.EMPTY
    val fullSizePictureIntent = getPictureIntent_App_Specific(applicationContext)
    fullSizePictureIntent.resolveActivity(packageManager)?.also {
        startActivityForResult(fullSizePictureIntent, REQ_IMG_CAPTURE_FULL_SIZE)
    }
}

/**
 * 카메라 호출할 Intent 생성
 */
fun getPictureIntent_App_Specific(context: Context): Intent {

    val fullSizeCaptureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)

    //1) File 생성 - 촬영 사진이 저장 될
    //photoFile 경로 = /storage/emulated/0/Android/data/패키지명/files/Pictures/
    val photoFile: File? = try {
        createImageFile(context.getExternalFilesDir(Environment.DIRECTORY_PICTURES))
    } catch (ex: IOException) {
        // Error occurred while creating the File
        ex.printStackTrace()       
        null
    }
    
    photoFile?.also {
        //2) 생성된 File로 부터 Uri 생성 (by FileProvider)
        //URI 형식 EX) content://com.example.img.fileprovider/cameraImg/JPEG_20211124_202832_6573897384086993610.jpg
        photoURI = FileProvider.getUriForFile(
            context,
            BuildConfig.APPLICATION_ID + ".fileprovider",
            it
        )
        
        //3) 생성된 Uri를 Intent에 Put
        fullSizeCaptureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI)
    }
    return fullSizeCaptureIntent
}


/**
 * 빈 파일 생성
 */
@Throws(IOException::class)
private fun createImageFile(storageDir: File?): File {
    // Create an image file name
    val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss").format(Date())
    return File.createTempFile(
        "JPEG_${timeStamp}_", /* prefix */
        ".jpg", /* suffix */
        storageDir /* directory */
    ).apply {
        Log.i("syTest", "Created File AbsolutePath : $absolutePath")
    }
}

 

3)결과

setAdjImgUri()함수는 '#1갤러리 이미지' 예제에서의 함수와 동일

    override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) {
        super.onActivityResult(requestCode, resultCode, intent)
        if (resultCode == Activity.RESULT_OK) {
            when (requestCode) {
                REQ_IMG_CAPTURE_FULL_SIZE -> {
                    setAdjImgUri(photoURI) // -> 챙겨 두었던 photoUrI를 사용합니다
                }
            }
        }
    }

 

 

#3-2 카메라 촬영 (공용 공간)

  • Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) 를 통한 외부 저장소를 예제로 함
  • 경로 = '/storage/emulated/0/Pictures/' ( == sdcard/Pictures/)
  • Q 이상일 경우 : 파일의 URI는 MediaStore API를 이용해 획득
  • Q 미만일 경우 : 파일의 URI는 FileProvider를 통해 획득
  • Q 미만일 경우 WRITE_EXTERNAL_STORAGE 권한 필요
  • Q = Android 10 = API Level 29

 

1) 사전 준비 (Q 미만 동작을 위해)

- 퍼미션 선언, FileProvider 선언 

- FileProvider를 위한 filepaths.xml 생성

 

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.imageuploadtest">
    
    <!-- 공용 공간에 저장이 필요 할 경우 ex)'getExternalStoragePublicDirectory()'-->
    <!-- 9.0 버전 까지는 퍼미션 필요 -->
    <uses-permission
        android:name="android.permission.WRITE_EXTERNAL_STORAGE"
        android:maxSdkVersion="28" />
                         ...
    <application>                         
                         ...  
        <!-- FileProvider 선언 -->
        <provider
            android:name="androidx.core.content.FileProvider"
            android:authorities="${applicationId}.fileprovider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/filepaths" /> 
        </provider>
    </application>

</manifest>

 

filepaths.xml

<?xml version="1.0" encoding="utf-8"?>
<paths>

    <!--<file-path> =  내부, 앱 스토리지 / getFileDir -->
    <!--<cache-path> =  내부, 앱 캐시 스토리지 / getCacheDir -->
    <!--<external-files-path> = 외부, 앱 전용 스토리지 file / getExternalFilesDir 사용시-->
    <!--<external-cache-path> = 외부, 앱 전용 스토리지 cache / getExternalCacheDir 사용시-->
    <!--<external-path> = 외부, 공용 저장소 사용 시 / Environment.getExternalStorageDirectory-->

    //사진은 'Environment.getExternalStorageDirectory' 아래 'Pictures' 폴더 아래 저장되어 있을 예정    
    <external-path name="cameraImgShared" path="Pictures/"/>

</paths>

 

2) 카메라 호출

- Q 미만 : 저장소에 빈 파일을 생성+ FileProvider를 이용해 빈 파일의 Uri를 획득(위의, 앱 전용 공간 저장 예제와 파일 저장 위치만 다르고 동일합니다)

- Q 이상 :  ContentResolver를 이용해 Uri 획득

- 얻은 URI를 Intent의 'MediaStore.EXTRA_OUTPUT' 속성 value값으로 put

- Intent를 이용해 카메라 호출

- 얻은 URI는 촬영 후, 이미지 가져올 때 사용해야 하니.. 챙겨두기

    const val REQ_IMG_CAPTURE_FULL_SIZE_SHARED_UNDER_Q = 500 //공용공간 - Q미만, Need WRITE Permission
    const val REQ_IMG_CAPTURE_FULL_SIZE_SHARED_Q_AND_OVER = 600//공용공간 Q이상
    
    lateinit var photoSharedURI_UNDER_Q: Uri
    lateinit var photoSharedURI_Q_N_OVER: Uri

    private fun takePictureFullSize_Shared() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            val fullSizePictureIntent =
                getPictureIntent_Shared_Q_N_Over(applicationContext)
            fullSizePictureIntent.resolveActivity(packageManager)?.also {
                startActivityForResult(
                    fullSizePictureIntent,
                    REQ_IMG_CAPTURE_FULL_SIZE_SHARED_Q_AND_OVER
                )
            }
        } else {
            //TODO Permission Check 필요
            val fullSizePictureIntent =
                getPictureIntent_Shared_Under_Q(applicationContext)
            fullSizePictureIntent.resolveActivity(packageManager)?.also {
                startActivityForResult(
                    fullSizePictureIntent,
                    REQ_IMG_CAPTURE_FULL_SIZE_SHARED_UNDER_Q
                )
            }
        }
    }
    
    
@Throws(IOException::class)
private fun createImageFile(storageDir: File?): File {
    // Create an image file name
    val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss").format(Date())
    return File.createTempFile(
        "JPEG_${timeStamp}_", /* prefix */
        ".jpg", /* suffix */
        storageDir /* directory */
    ).apply {
        Log.i("syTest", "Created File AbsolutePath : $absolutePath")
    }
}

/**
 * 공유 영역 저장
 * Android Q 미만 일 경우 ( ~ Android 9.0)
 * if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
 */
fun getPictureIntent_Shared_Under_Q(context: Context): Intent {
    photoSharedURI_UNDER_Q = Uri.EMPTY
    val fullSizeCaptureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)

    //1) File 생성 - 촬영 사진이 저장 될
    //photoFile 경로 = /storage/emulated/0/Pictures/
    val photoFile: File? = try {
        //getExternalStoragePublicDirectory => deprecated in 29(Q) = 28(Android 9)까지 사용 가능
        createImageFile(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)) 
    } catch (ex: IOException) {
        // Error occurred while creating the File
        ex.printStackTrace()
        null
    }

    photoFile?.also {
        //2) 생성된 File로 부터 Uri 생성 (by FileProvider)
        photoSharedURI_UNDER_Q = FileProvider.getUriForFile(
            context,
            BuildConfig.APPLICATION_ID + ".fileprovider",
            it
        )
        //3) 생성된 Uri를 Intent에 Put
        fullSizeCaptureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoSharedURI_UNDER_Q)
    }
    return fullSizeCaptureIntent
}

/**
 * 공유 영역 저장
 * Android Q 이상일 경우, API 29 ~ (Android 10.0 ~ )
 * if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
 */
@RequiresApi(Build.VERSION_CODES.Q)
fun getPictureIntent_Shared_Q_N_Over(context: Context): Intent {
    photoSharedURI_Q_N_OVER = Uri.EMPTY
    val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss").format(Date())
    val contentValues = ContentValues()
    contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, "UriForAndroidQ_${timeStamp}.jpg")
    contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
    //'RELATIVE_PATH', RequiresApi Q
    contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH,Environment.DIRECTORY_PICTURES) 

    //URI 형식 EX) content://media/external/images/media/1006
    photoSharedURI_Q_N_OVER = context.contentResolver.insert(
        MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
        contentValues
    ) ?: Uri.EMPTY

    val fullSizeCaptureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
    fullSizeCaptureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoSharedURI_Q_N_OVER)
    return fullSizeCaptureIntent
}

 

 

 

 

#덧붙이는 말

* 많은 기술 블로그에서 사진 촬영 시 카메라 권한이 필요하다고 하지만, 공식 문서엔 관련 내용을 찾을 수 없었고

  테스트를 해봐도 카메라 권한 없이 잘 동작하는 것을 확인했습니다. 

  사진 촬영 관련해서 공식 문서에 잘 정리되어 있습니다. 

[Developer - 사진 촬영]  https://developer.android.com/training/camera/photobasics?hl=en

 

* 사진 촬영 시 MediaStore API를 이용하지 않는다면, 카메라 촬영 하지 않고 뒤로가기로 돌아오면 빈 파일이 저장소에    남아 있습니다. 삭제 코드가 추가로 필요합니다.

 

*'3-1 카메라 촬영(앱 전용 공간)'의 경우도 MediaStore API를 사용할 수 있을 것 같습니다(테스트 필요)

 

*발생했던 ISSUE -  Android 11 / 'getPictureIntent_Shared_Q_N_Over'

  MediaStore API 사용하고 Camera 호출/촬영 했을 때

  촬영 이미지 저장이 안되면서 resultCode가 0 으로 반환되는 현상이 있었습니다(정상이라면 -1로 반환 되어야 합니다)

  Error : java.lang.IllegalStateException: Only owner is able to interact with pending item 

더보기
E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.sec.android.app.camera, PID: 4602
    java.lang.IllegalStateException: Only owner is able to interact with pending item content://media/external/images/media/1063
        at android.os.Parcel.createExceptionOrNull(Parcel.java:2393)
        at android.os.Parcel.createException(Parcel.java:2369)
        at android.os.Parcel.readException(Parcel.java:2352)
        at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:190)
        at android.database.DatabaseUtils.readExceptionWithFileNotFoundExceptionFromParcel(DatabaseUtils.java:153)
        at android.content.ContentProviderProxy.openAssetFile(ContentProviderNative.java:704)
        at android.content.ContentResolver.openAssetFileDescriptor(ContentResolver.java:1826)
        at android.content.ContentResolver.openOutputStream(ContentResolver.java:1528)
        at android.content.ContentResolver.openOutputStream(ContentResolver.java:1504)
        at com.sec.android.app.camera.util.Util.writeImageDataToRequestedUri(Util.java:1748)
        at com.sec.android.app.camera.AttachFragment.setImageResult(AttachFragment.java:374)
        at com.sec.android.app.camera.AttachFragment.onOkay(AttachFragment.java:339)
        at com.sec.android.app.camera.AttachFragment.lambda$onCreateView$1$AttachFragment(AttachFragment.java:104)
        at com.sec.android.app.camera.-$$Lambda$AttachFragment$1HW1j9LtVq7oh4YzjMEcPRnc9Io.onClick(Unknown Source:2)
        at android.view.View.performClick(View.java:8160)
        at android.widget.TextView.performClick(TextView.java:16222)
        at android.view.View.performClickInternal(View.java:8137)
        at android.view.View.access$3700(View.java:888)
        at android.view.View$PerformClick.run(View.java:30236)
        at android.os.Handler.handleCallback(Handler.java:938)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:246)
        at android.app.ActivityThread.main(ActivityThread.java:8633)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:602)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1130)

 

원인은

ContetnValue에 Pending을 걸었기 때문...

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        contentValues.put(MediaStore.Images.Media.IS_PENDING, 1)
    }

Pending을 풀기전까지 다른 앱에서의 파일 접근을 막으려고 쓰는 기능인데, Pending을 풀지도 않고 카메라 호출을 해버려서 발생한 에러로 보입니다


*참고 블로그

[안드로이드 - MediaStore에 미디어 파일 저장하는 방법]

https://codechacha.com/ko/android-mediastore-insert-media-files/

 

*예제 프로젝트는 아래 Git 주소에서 실행해 보실 수 있습니다 ( Utils -> SetImgGalleryCamera)

https://github.com/suyoung1613/Utils.git

 


내용에 문제 있다면 알려주시기 바랍니다~

 

좋은 하루 되세요!