Android App/Kotlin
카메라 및 갤러리 사용, 권한처리(CAMERA-지원중단)
AppJinny
2022. 11. 28. 07:04
*카메라 및 갤러리 사용, 권한처리
-카메라 및 갤러리 사용 : ActivityResultLauncher 사용
-권한처리 : 안드로이드 6.0버전(API 23, Sdk 23) 버전 이후부터 카메라 관련작업 위험권한 분류되어 부가적 코드처리 필요
-카메라 Camera는 지원중단되어 CameraX를 권장하고 있음... 추후 공부하기...!
*권한처리하여 카메라 및 갤러리 사용하기
-build.gradle
-- android{} 에 뷰바인딩 추가
//뷰바인딩
buildFeatures{
viewBinding true
}
-MainActivity.kt
import android.Manifest
import android.net.Uri
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.os.Environment
import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.content.FileProvider
import com.heeyjinny.cameraandgallery.databinding.ActivityMainBinding
import java.io.File
//1
//카메라 UI화면 만들고 권한 요청하기
//1-1
//activity_main.xml 화면 수정 및 배치
//1-2
//카메라 권한과 촬영한 사진에 대한 접근권한 선언
//app - manifests - AndroidManifest.xml
class MainActivity : AppCompatActivity() {
//3-4
//cameraLauncher가 실행되고 TakePicture()의 실제 사진정보를 저장할 변수생성
//사진 촬영이 정상일 경우 사진정보를 가지고 있는 변수 photoUri를 화면에 세팅
var photoUri:Uri? = null
//2
//권한 처리를 위한 런처 미리 선언하기
//카메라 및 촬영한 사진을 저장할 외부 저장소의 권한을 요청하는 코드 작성
//런처와 관련된 변수 3개 선언
//실제 촬영 이미지를 외부저장소에 저장하기 때문에
//카메라 권한과 저장소 권한 처리 런처를 저장해둘 변수 2개 선언하고 카메라 요청 런처 선언
//2-1
//카메라 권한: 런처의 컨트랙트로 RequestPermission 사용하기 때문에 제네릭 문자 String 지정
lateinit var cameraPermission:ActivityResultLauncher<String>
//2-2
//저장소 권한: 런처의 컨트랙트로 RequestPermission 사용하기 때문에 제네릭 문자 String 지정
lateinit var storagePermission:ActivityResultLauncher<String>
//2-3
//카메라 앱 호출 런처: TakePicture를 사용하기 따문에 제네릭 Uri 지정
lateinit var cameraLauncher:ActivityResultLauncher<Uri>
//7-2
//갤러리 호출 런처: GetContent()를 사용하므로 제네릭 String 사용
lateinit var galleryLauncher:ActivityResultLauncher<String>
//뷰바인딩
val binding by lazy { ActivityMainBinding.inflate(layoutInflater) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
//3-1
//저장소 권한 변수 초기화
storagePermission = registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
//3-4
//TakePicture()의 결과값은 Boolean타입이어서 실제 사진정보는 얻을 수 없음
//그래서 사진 정보를 담을 변수 프로퍼티 미리 선언해야함 onCreate()메서드 위에 전역변수로 생성
//5
//외부 저장소 권한처리 런처코드 작성
//권한요청이 정상적으로 승인(true)되었다면 setView()메서드 호출해 카메라 권한요청 실행
if(isGranted) {
setViews()
} else {
Toast.makeText(baseContext, "외부저장소 권한을 승인해야 앱을 사용할 수 있습니다.", Toast.LENGTH_LONG).show()
finish()
}
}
//6
//기본코드 작성 끝... 하지만 실행 시 권한오류가 발생함
//이는 카메라에서 외부저장소에 촬영 결과물을 저장하고 결과물의 Uri를 사용하기 위해서는
//부가적으로 FileProvider를 통한 권한 처리가 필요함
//FileProvider설정을 위한 xml생성하여 작성...
//res - xml폴더 작성 - file_paths.xml 파일 생성
//3-2
//카메라 권한 변수 초기화
cameraPermission = registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
//5-1
//카메라 권한처리 런처 코드 작성
//권한요청이 정상적으로 승인(true)되었다면 openCamera()메서드 호출해 카메라 실행
if(isGranted) {
openCamera()
} else {
Toast.makeText(baseContext, "카메라 권한을 승인해야 카메라를 사용할 수 있습니다.", Toast.LENGTH_LONG).show()
}
}
//3-3
//카메라 앱 호출런처 초기화
//실제 카메라의 촬영 결과물을 주고받는 용도로 사용되는 TakePicture()사용
//런처의 결과값으로 사진 촬영이 정상일경우 true, 오류가 있을경우 false가 넘어옴
cameraLauncher = registerForActivityResult(ActivityResultContracts.TakePicture()) { isSuccess ->
if(isSuccess) { binding.imagePreview.setImageURI(photoUri) }
}
//7-3
//갤러리 호출런처 초기화
//GetContent()사용
//이미지의 uri를 화면에 불러와 세팅
galleryLauncher = registerForActivityResult(ActivityResultContracts.GetContent()) { uri ->
binding.imagePreview.setImageURI(uri)
}
storagePermission.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)
}//onCreate
//3
//카메라 앱 권한 호출
//외부저장소 권한 승인이 되었을 때 호출할 setViews()메서드 생성하여
//카메라 버튼 클릭 시 카메라 권한을 요청하는 코드 작성
//미리 선언했던 변수 cameraPermission를 런치하여 프로퍼티에 카메라 권한을 담아 호출
fun setViews() {
binding.buttonCamera.setOnClickListener {
cameraPermission.launch(Manifest.permission.CAMERA)
}
//7
//갤러리버튼 클릭 시 openGallery()메서드 호출코드 작성
binding.buttonGallery.setOnClickListener {
openGallery()
}
}
//4
//카메라 요청하는 메서드 작성(openCamera())
fun openCamera() {
//4-1
//사진 촬영 후 저장할 임시파일 생성후 변수에 담아둠
//임시파일 prefix: IMG_, suFFix: .jpg
//파일의 이름 앞 뒤로 고정이고 중간은 자동랜덤생성됨: ex) /tmp/IMG_1224234243.jpg
//4-2
//앱 내에만 있는 사용자에게 가치를 제공하는 미디어 파일과 앱이 호환되면 다음 코드 스니펫과 같이
//외부 저장소 내 앱별 디렉터리에 미디어 파일을 저장하는 것이 가장 좋음
//DIRECTORY_PICTURES같은 API 상수에서 제공하는 디렉터리 이름을 사용
val photoFile = File.createTempFile(
"IMG_",
".jpg",
getExternalFilesDir(Environment.DIRECTORY_PICTURES)
)
//4-3
//앞에서 생성한 파일의 Uri생성하여 실제 사진 정보를 정장하는 변수 photoUri에 저장
photoUri = FileProvider.getUriForFile(
this,
"${packageName}.provider",
photoFile
)
//4-4
//카메라 앱을 호출하는 cameraLauncher에 런치launch()하여
//실제 사진정보를 가지고 있는 변수 photoUri 전달해 카메라 호출
cameraLauncher.launch(photoUri)
}
//7-1
//갤러리 버튼을 클릭했을 때 갤러리 이미지 불러오는 메서드 openGallery() 생성
//우선 이미지를 불러오는 갤러리런처 전역변수로 생성...
fun openGallery() {
//7-4
//galleryLauncher 프로퍼티에 지정한 제네릭은 String이지만
//launch()메서드의 파라미터는 권한문자열이 아닌 마임타입 형태로 파라미터 입력
//모든종류(*)를 불러올 수 있도록 "image/*" 입력
galleryLauncher.launch("image/*")
}
}//MainActivity
-activity_main.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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<!-- 버튼 및 이미지뷰 추가 -->
<!-- 추가 후 MainActivity.kt 코드작성 -->
<Button
android:id="@+id/buttonCamera"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:text="카메라"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/buttonGallery"
app:layout_constraintStart_toStartOf="parent" />
<ImageView
android:id="@+id/imagePreview"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@+id/buttonCamera"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:srcCompat="@tools:sample/avatars" />
<!-- 갤러리 버튼 추가 -->
<!-- 추가 후 MainActivity.kt 코드작성 -->
<Button
android:id="@+id/buttonGallery"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:text="갤러리"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/buttonCamera" />
</androidx.constraintlayout.widget.ConstraintLayout>
-AndroidManifest.xml
--<manifest/> 에 접근 권한설정
<!-- 카메라 권한 -->
<uses-permission android:name="android.permission.CAMERA"/>
<!-- 저장소 읽기 권한 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<!-- 저장소 쓰기 권한 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<!-- 카메라 사용하기 위한 필수 설정 -->
<uses-feature android:name="android.hardware.camera.any"/>
--<application>에 provider설정 추가
<!-- provider설정 추가 -->
<!-- 설정 후 실행하면 카메라 권한 창이 뜨고 권한설정하여 카메라 앱 실행가능 -->
<!-- 다음으로...갤러리에서 사진 가져오는 코드 작성 activity_main.xml 수정 -->
<provider
android:authorities="${applicationId}.provider"
android:name="androidx.core.content.FileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"/>
</provider>
-file_paths.xml
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 설정해둔 경로에 한해서 FileProvider를 통해 접근이 가능하도록 설정 -->
<external-path
name="my_images"
path="Android/data/com.heeyjinny.cameraandgallery/files/Pictures"/>
<!-- 생성한 path를 AndroidManifest.xml 에 provider설정 추가 -->
</paths>
-결과
*참고
-디렉터리 사용에 대한 앱별 파일 엑세스 컬렉션 사용
https://developer.android.com/training/data-storage/app-specific?hl=ko
-카메라 앱 사용 시 카메라X를 권장... 카메라X사용 참고
https://developer.android.com/training/camerax
이 포스팅에 작성한 내용은 고돈호, ⌜이것이 안드로이드다⌟, 한빛미디어(주), 2022 에서 발췌하였습니다.