[안드로이드] 이미지를 불러오는 다양한 방법과 permission

neoneoneo·2024년 7월 10일
0

android

목록 보기
16/16

안드로이드에서 이미지를 불러오는 방법은 다양하다.

  1. Activity Result API
  2. startActivityForResult
  3. ACTION_PICK
  4. ACTION_OPEN_DOCUMENT
  5. Storage Access Framework(ACTION_OPEN_DOCUMENT_TREE)
  6. MediaStore API

더 있을 수도 있겠지만, 이번 글에서는 위의 6가지 방법에 대해서 정리를 해본다.


1. Activity Result API

tartActivityForResult의 Deprecated로 인해 권장되는 방법

permission 획득 필요 없음

API 33 실행 시

API 28 실행 시

예시 코드

class ActivityResultAPIActivity : AppCompatActivity() {

    private val binding: ActivityResultApiactivityBinding by lazy {
        ActivityResultApiactivityBinding.inflate(layoutInflater)
    }

    // Activity Result 등록
    private val getContent = registerForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? ->
        uri?.let {
            // Uri 이미지 view에 표시
            binding.imageView.setImageURI(uri)
        } ?: run {
            Toast.makeText(this, "No image selected", Toast.LENGTH_SHORT).show()
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)

        binding.button.setOnClickListener {
            // 갤러리 열기
            getContent.launch("image/*")
        }
    }
}

2. startActivityForResult

이전부터 사용되던 방법으로 예제가 많고 모든 API 버전에서 사용이 가능하나, 현재는 Deprecated됨

permission 획득 필요 없음

API 33 실행 시

API 28 실행 시 : 선택할 수 있는 갤러리 앱이 없으면 앱 또는 액티비티가 죽는다.

예시 코드

class StartActivityForResultActivity : AppCompatActivity() {

    private val binding: ActivityStartForResultBinding by lazy {
        ActivityStartForResultBinding.inflate(layoutInflater)
    }

    private val PICK_IMAGE = 100

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)

        binding.button.setOnClickListener {
            val gallery = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.INTERNAL_CONTENT_URI)
            startActivityForResult(gallery, PICK_IMAGE)
        }
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)

        if (resultCode == Activity.RESULT_OK && resultCode == PICK_IMAGE) {
            binding.imageView.setImageURI(data?.data)
        }
    }
}

3. ACTION_PICK

permission 획득 필요 없음

API 33 실행 시

API 28 실행 시 : 선택할 수 있는 갤러리 앱이 없으면 앱 또는 액티비티가 죽는다.

예시 코드

class ActionPickActivity : AppCompatActivity() {

    private val binding: ActivityActionPickBinding by lazy {
        ActivityActionPickBinding.inflate(layoutInflater)
    }

    private val PICK_IMAGE = 100

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)

        binding.button.setOnClickListener {
            val gallery = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
            startActivityForResult(gallery, PICK_IMAGE)
        }
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)

        if (resultCode == Activity.RESULT_OK && requestCode == PICK_IMAGE) {
            binding.imageView.setImageURI(data?.data)
        }
    }
}

4. ACTION_OPEN_DOCUMENT

permission 획득 필요 없음

API 33 실행 시

API 28 실행 시

예시 코드

class ActionOpenDocumentActivity : AppCompatActivity() {

    private val binding: ActivityActionOpenDocumentBinding by lazy {
        ActivityActionOpenDocumentBinding.inflate(layoutInflater)
    }

    private val PICK_IMAGE = 100

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)

        binding.button.setOnClickListener {
            val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
            intent.addCategory(Intent.CATEGORY_OPENABLE)
            intent.type = "image/*"
            startActivityForResult(intent, PICK_IMAGE)
        }
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)

        if (resultCode == Activity.RESULT_OK && requestCode == PICK_IMAGE) {
            binding.imageView.setImageURI(data?.data)
        }
    }
}

5. Storage Access Framework(ACTION_OPEN_DOCUMENT_TREE)

permission 획득 필요 없음

API 33 실행 시

  • 이미지는 Pictures 폴더 안에 있다.

API 28 실행 시

  • 28 버전에서는 카메라로 촬영한 이미지가 저장된 경로를 찾기가 어렵다.

예시 코드

class StorageAccessFrameworkActivity : AppCompatActivity() {

    private val binding: ActivityStorageAccessFrameworkBinding by lazy {
        ActivityStorageAccessFrameworkBinding.inflate(layoutInflater)
    }

    private val PICK_DIRECTORY = 101

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)

        binding.button.setOnClickListener {
            val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
            startActivityForResult(intent, PICK_DIRECTORY)
        }
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)

        if (resultCode == Activity.RESULT_OK && requestCode == PICK_DIRECTORY) {
            val directoryUri = data?.data
            directoryUri?.let {
                val documentUri = DocumentsContract.buildDocumentUriUsingTree(it , DocumentsContract.getTreeDocumentId(it))
                binding.imageView.setImageURI(documentUri)
            }
        }
    }
}

6. MediaStore API

permission 획득 필요

API 33 실행 시

API 28 실행 시 : 선택할 수 있는 갤러리 앱이 없으면 앱 또는 액티비티가 죽는다.


예시 코드

class MediaStoreAPIActivity : AppCompatActivity() {

    private val binding: ActivityMediaStoreApiactivityBinding by lazy {
        ActivityMediaStoreApiactivityBinding.inflate(layoutInflater)
    }

    private val REQUEST_CODE_PERMISSION = 200
    private val REQUEST_CODE_IMAGE = 300

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)

        binding.button.setOnClickListener {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
                if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_MEDIA_IMAGES) != PackageManager.PERMISSION_GRANTED) {
                    ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.READ_MEDIA_IMAGES), REQUEST_CODE_PERMISSION)
                } else {
                    openImageSelector()
                }
            } else {
                if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
                    ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), REQUEST_CODE_PERMISSION)
                } else {
                    openImageSelector()
                }
            }
        }
    }

    private fun openImageSelector() {
        val intent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
        startActivityForResult(intent, REQUEST_CODE_IMAGE)
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)

        if (resultCode == Activity.RESULT_OK && requestCode == REQUEST_CODE_IMAGE) {
            binding.imageView.setImageURI(data?.data)
        }
    }

    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)

        if (requestCode == REQUEST_CODE_PERMISSION) {
            if ((grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)) {
                openImageSelector()
            } else {
                Toast.makeText(this, "Permission denied", Toast.LENGTH_SHORT).show()
            }
        }
    }
}

정리하자면,

  • 방식에 따라 이미지에 접근할 수 있는 UX가 달라진다.
    • 단일 이미지만을 선택하는 상황이라면 4, 5번을 제외한 방법을 적용하는 게 적절할 것 같다.
  • 안드로이드 버전에 따라 처리를 달리 해주어야 하는 부분들이 있다.
    • Permission, 갤러리 열기 등
  • 기기의 갤러리 앱에 의존하는 방식들이 있으므로 예외처리가 필요하다.
    • 2, 3, 6번 방식이 그러하다.

code repository
https://github.com/neoneoneo123/ImageSample


TIL-20240710

profile
우당탕ㅌ앙개발기록

0개의 댓글