본문 바로가기
🚀 Development/Android

[Android] startActivityForResult 메서드가 Deprecated된 이유와 해결방안 + 카메라 사용 및 이미지뷰 예제

by Jay Din 2024. 11. 21.
728x90
반응형

startActivityForResult가 Deprecated된 이유

startActivityForResult 메서드가 Deprecated(사용 중단)된 이유는 Activity Result API를 도입했기 때문입니다.

새로운 API는 더 간결하고 유지보수에 용이하며, 결과 처리를 Lifecycle-aware 방식으로 관리할 수 있어 현대적인 Android 개발에 적합합니다.

  • 더 안전한 결과 처리: startActivityForResult는 콜백을 onActivityResult로 전달했지만, Activity 또는 Fragment가 Lifecycle-aware 하지 않으면 메모리 누수 및 의도하지 않은 동작이 발생할 수 있었습니다.
  • 명확한 결과 관리: 새로운 API는 명시적으로 결과를 등록하고 Lifecycle 범위에서 실행되도록 설계되었습니다.
* Lifecycle-aware 란?
Lifecycle-awae는 Android 컴포넌트(Activity, Fragment 등)가 생명주기(Lifecycle)를 기준으로 동작을 제어할 수 있도록 설계된 개념입니다. 
즉, Android Lifecycle-aware 컴포넌트는 컴포넌트의 생명주기를 관촬하고, 생명주기에 맞게 동작을 자동으로 관리할 수 있도록 설계되었습니다.

 

최신 코드 작성법: Activity Result API 사용

registerForActivityResult를 사용하여 더 안전하게 Intent를 호출하고 결과를 처리할 수 있습니다.

(저장 기능은 구현하지 않음.)

 

 

1. res/xml/file_paths.xml 확인

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

 

2. AndroidManifest.xml에 provider 선언

<provider
    android:name="androidx.core.content.FileProvider"
    android:authorities="${applicationId}.provider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_paths" />
</provider>

 

3. AndroidManifest.xml 이미지 파일 접근 권한

<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

 

4. Kotlin 코드

import android.content.pm.PackageManager
import android.os.Bundle
import android.widget.Button
import android.widget.ImageView
import android.widget.Toast
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import android.Manifest
import android.graphics.BitmapFactory
import android.net.Uri
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.content.FileProvider
import java.io.File


class TakingQaCard : AppCompatActivity() {

    // 카메라 실행 버튼
    private lateinit var cameraButton: Button
    // 사진을 보여줄 ImageView
    private lateinit var imageView: ImageView
    // 카메라 권한 요청 코드
    private val REQUEST_CAMERA_PERMISSION = 100
    // 사진 파일 저장 경로와 URI
    private lateinit var photoFile: File
    private var photoUri: Uri? = null

    // ActivityResultLauncher를 사용하여 카메라 촬영 후 결과를 처리
    private val takePictureLauncher = registerForActivityResult(ActivityResultContracts.TakePicture()) { success ->
        if (success) {
            if (photoFile.exists()) {
                // 사진 촬영이 성공적으로 완료되었을 경우
                val bitmap = BitmapFactory.decodeFile(photoFile.absolutePath)
                // Bitmap을 ImageView에 설정
                imageView.setImageBitmap(bitmap)
            } else {
                Toast.makeText(this, "사진 파일이 생성되지 않았습니다.", Toast.LENGTH_SHORT).show()
            }
        } else {
            Toast.makeText(this, "사진 촬영이 취소되었습니다.", Toast.LENGTH_SHORT).show()
        }
    }


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContentView(R.layout.activity_taking_qa_card)
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
            val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
            insets
        }

        // 버튼과 이미지 뷰 초기화
        cameraButton = findViewById(R.id.cameraButton)
        imageView = findViewById(R.id.imageView)

        // 카메라 권한 확인 후 요청
        checkCameraPermission()
        // 카메라 실행
        cameraButton()

    }

    // 카메라 실행 버튼
    private fun cameraButton(){
        cameraButton.setOnClickListener {
            // 앱 내부 저장소에 사진 파일 생성
            photoFile = File(getExternalFilesDir(null), "photo.jpg")
            // FileProvider를 통해 URI 생성 (보안 강화)
            photoUri = FileProvider.getUriForFile(
                this,
                "${packageName}.provider",
                photoFile
            )

            // 생성된 URI로 사진 촬영 시작
            takePictureLauncher.launch(photoUri)
        }
    }

    // 카메라 권한 확인 및 요청
    private fun checkCameraPermission() {
        // 카메라 권한이 부여되어 있는지 확인
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
            // 권한이 없으면 요청
            ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CAMERA), REQUEST_CAMERA_PERMISSION)
        }
    }


    // 권한 요청 결과를 처리
    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        if (requestCode == REQUEST_CAMERA_PERMISSION) {
            if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                // 사용자가 권한을 허용한 경우
                Toast.makeText(this, "카메라 권한이 허용되었습니다.", Toast.LENGTH_SHORT).show()
            } else {
                // 사용자가 권한을 거부한 경우
                Toast.makeText(this, "카메라 권한이 필요합니다.", Toast.LENGTH_SHORT).show()
            }
        }
    }


}

 

5. xml 코드

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".TakingQaCard">

    <!-- Title Layout -->
    <LinearLayout
        android:id="@+id/titleLayout"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:layout_marginTop="50dp"
        android:layout_marginHorizontal="16dp">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="파일 제목: "
            android:layout_marginEnd="8dp" />

        <EditText
            android:id="@+id/edittextTitle"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:hint="제목 입력" />
    </LinearLayout>

    <!-- ImageView -->
    <ImageView
        android:id="@+id/imageView"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/titleLayout"
        app:layout_constraintBottom_toTopOf="@+id/cameraButton"
        android:layout_margin="20dp"
        android:scaleType="centerCrop"
        android:background="@drawable/border_imageview"
        android:contentDescription="이미지" />

    <!-- Camera Button -->
    <Button
        android:id="@+id/cameraButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Camera"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintBottom_toTopOf="@+id/saveButton"
        android:layout_marginBottom="20dp" />

    <!-- Save Button -->
    <Button
        android:id="@+id/saveButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Save"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        android:layout_marginBottom="50dp" />

</androidx.constraintlayout.widget.ConstraintLayout>

실행화면

 

주요 변경점

  1. registerForActivityResult 사용
    • registerForActivityResult를 통해 결과를 받아 올 수 있습니다.
    • ActivityResultContracts.StartActivityForResult()를 사용해 결과를 처리합니다.
  2. ActivityResultLauncher
    • ActivityResultLauncher.launch()를 호출하여 Intent를 실행합니다.
    • 콜백 처리가 Lifecycle-aware 방식으로 이루어지므로 더 안전합니다
  3. 간결하고 유지보수 용이
    • 결과를 처리하는 로직이 한곳에 집중되어 관리하기 쉬워졌습니다.
    • Deprecated된 onActivityResult를 사용할 필요가 없습니다.

 

장점

 

  • Lifecycle-aware: Fragment나 ViewModel에서도 동일하게 동작하며 Lifecycle에 맞게 안전하게 실행됩니다.
  • 분리된 로직: 코드가 더 간결하며 결과 처리 로직을 명확히 구분할 수 있습니다.
  • 호환성: AndroidX에서 제공하는 API이므로 모든 최신 Android 버전에서 사용할 수 있습니다.

 

 

 

728x90
반응형