[Mobile/Kotlin] - [Kotlin] 카메라 사용하기
위의 포스팅에서 카메라를 사용하기 위한 절차와 권한 처리에 대해서 알아보았습니다. 이 내용을 계속이어서 이번에는 찍은 사진을 갤러리에 저장하고 저장된 사진을 가져오는 과정에 대해서 살펴보도록 하겠습니다. 참고로 카메라를 통해 촬영된 사진은 내부저장소나 외부 저장소에 저장할 수 있는데 이번에는 외부 저장소에 파일을 저장하고 가져오는 방식을 사용해 보고자 합니다.
app -> manifests -> AndroidManifest.xml 에서 카메라에 관한 권한 추가와 더불어 저장소에 대한 2개의 태그를 추가합니다.
<uses-permission android:name="android.permission.CAMERA"></uses-permission>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"></uses-permission>
MainActivity 클래스에서는 상단에 STORAGE권한 처리에 필요한 변수를 추가하고
val CAMERA = arrayOf(Manifest.permission.CAMERA)
val STORAGE = arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE)
val CAMERA_CODE = 98
val STORAGE_CODE = 99
onRequestPermissionsResult 메서드 내부에서도 기존 카메라 권한을 확인할 때와 동일한 방식으로 저장소 접근 권한을 확인하도록 합니다. 권한 확인 후 권한이 승인되지 않았다면 Toast를 통해 관련 메시지를 표시하도록 되어 있는데 경우에 따라서는 finish()를 호출하여 권한 승인이 이루어지지 않았을 때 앱을 강제 종료하도록 처리할 수도 있습니다.
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray)
{
when(requestCode) {
CAMERA_CODE -> {
for (grant in grantResults) {
if (grant != PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this, "카메라 권한을 승인해 주세요.", Toast.LENGTH_LONG).show()
}
}
}
STORAGE_CODE -> {
for (grant in grantResults) {
if (grant != PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this, "저장소 권한을 승인해 주세요.", Toast.LENGTH_LONG).show()
//finish() 앱을 종료함
}
}
}
}
}
checkPermission 메서드도 카메라, 저장소등 다른 권한들도 확인이 가능하도록 아래와 같이 수정합니다.
fun checkPermission(permissions: Array<out String>, type: Int): Boolean
{
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
for (permission in permissions) {
if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, permissions, type)
return false;
}
}
}
return true;
}
checkPermission 메서드의 매개변수가 추가되었으므로 기존 카메라의 권한을 확인하기 위한 메서드 호출 부분도 역시 아래와 같이 바꿔야 하며 더불어 저장소에 관한 권한 처리도 동시에 수행할 수 있도록 checkPermission 메서드 호출 부분도 추가합니다.
fun CallCamera()
{
if (checkPermission(CAMERA, CAMERA_CODE) && checkPermission(STORAGE, STORAGE_CODE)) {
val itt = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
startActivityForResult(itt, CAMERA_CODE)
}
}
촬영된 사진을 저장하기 위해서 파일을 저장하는 메서드를 작성합니다. 메서드는 파일명과 mime타입, 비트맵을 매개변수로 받고 Uri를 반환하도록 합니다.
fun saveFile(FileName: String, mimeType: String, bitmap: Bitmap): Uri?
{
}
메서드 내부에서는 MediaStore에 저장할 파일명과 mimeType을 지정합니다. MediaStore는 외부 저장소를 관리하는 데이터베이스로서 Android Q(10)부터 외부 저장소에 파일을 읽고 쓰기 위해서 꼭 필요한 외부 저장소 매개체입니다.
fun saveFile(fileName: String, mimeType: String, bitmap: Bitmap): Uri?
{
var CV = ContentValues()
CV.put(MediaStore.Images.Media.DISPLAY_NAME, fileName)
CV.put(MediaStore.Images.Media.MIME_TYPE, mimeType)
}
만약 다른 곳에서 현재 사용하려는 데이터 사용 요청이 오면 이를 무시할 수 있도록 합니다. 꼭 필요한 부분은 아니지만 앱이 동작하는 안정성을 위해서 넣어둡니다.
fun saveFile(fileName: String, mimeType: String, bitmap: Bitmap): Uri?
{
var CV = ContentValues()
CV.put(MediaStore.Images.Media.DISPLAY_NAME, fileName)
CV.put(MediaStore.Images.Media.MIME_TYPE, mimeType)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
CV.put(MediaStore.Images.Media.IS_PENDING, 1)
}
}
MediaStore에 파일을 저장합니다.
fun saveFile(fileName: String, mimeType: String, bitmap: Bitmap): Uri?
{
var CV = ContentValues()
CV.put(MediaStore.Images.Media.DISPLAY_NAME, fileName)
CV.put(MediaStore.Images.Media.MIME_TYPE, mimeType)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
CV.put(MediaStore.Images.Media.IS_PENDING, 1)
}
val uri = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, CV)
}
위에서 가져온 uri를 통해 파일 스크립터를 가져옵니다. 이 파일스크립터를 통해 파일을 읽거나 쓸 수 있게 되는데 예제에서는 모드를 "w"로 지정해 쓰기 위한 것임을 나타내고 있습니다.
fun saveFile(fileName: String, mimeType: String, bitmap: Bitmap): Uri?
{
var CV = ContentValues()
CV.put(MediaStore.Images.Media.DISPLAY_NAME, fileName)
CV.put(MediaStore.Images.Media.MIME_TYPE, mimeType)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
CV.put(MediaStore.Images.Media.IS_PENDING, 1)
}
val uri = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, CV)
if (uri != null) {
var scriptor = contentResolver.openFileDescriptor(uri, "w")
}
}
scriptor를 사용해 FileOutputStream으로 Bitmap파일을 저장합니다. 이때 사용된 100은 압축을 이며 이 수치가 클수록 화질이 좋아지지만 그만큼 파일의 크기가 커지게 됩니다.
fun saveFile(fileName: String, mimeType: String, bitmap: Bitmap): Uri?
{
var CV = ContentValues()
CV.put(MediaStore.Images.Media.DISPLAY_NAME, fileName)
CV.put(MediaStore.Images.Media.MIME_TYPE, mimeType)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
CV.put(MediaStore.Images.Media.IS_PENDING, 1)
}
val uri = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, CV)
if (uri != null) {
var scriptor = contentResolver.openFileDescriptor(uri, "w")
if (scriptor != null) {
val fos = FileOutputStream(scriptor.fileDescriptor)
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos)
fos.close()
}
}
}
마지막으로 IS_PENDING을 0으로 돌리고 MediaStore를 초기한 뒤 uri를 반환합니다.
{
var CV = ContentValues()
CV.put(MediaStore.Images.Media.DISPLAY_NAME, fileName)
CV.put(MediaStore.Images.Media.MIME_TYPE, mimeType)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
CV.put(MediaStore.Images.Media.IS_PENDING, 1)
}
val uri = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, CV)
if (uri != null) {
var scriptor = contentResolver.openFileDescriptor(uri, "w")
if (scriptor != null) {
val fos = FileOutputStream(scriptor.fileDescriptor)
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos)
fos.close()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
CV.clear()
CV.put(MediaStore.Images.Media.IS_PENDING, 0)
contentResolver.update(uri, CV, null, null)
}
}
}
return uri;
}
onActivityResult 메서드를 수정해 사진을 찍으면 사진 파일을 저장하고 저장된 사진을 불러오도록 합니다.
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == Activity.RESULT_OK) {
when (requestCode) {
CAMERA_CODE -> {
if (data?.extras?.get("data") != null) {
val img = data?.extras?.get("data") as Bitmap
val uri = saveFile(RandomFileName(), "image/jpg", img)
binding.imageView.setImageURI(uri)
}
}
}
}
}
참고로 예제에서 파일명을 RandomFileName() 메서드를 사용하고 있는데 이는 사진을 여러 장 찍을 경우 파일명이 중복될 수 있으므로 파일명이 랜덤 하게 생성될 수 있도록 하기 위한 것입니다. 파일명은 자체는 시간에 기반합니다.
fun RandomFileName() : String
{
val fineName = SimpleDateFormat("yyyyMMddHHmmss").format(System.currentTimeMillis())
return fineName
}
이제 앱을 실행해 사진을 찍어보겠습니다.
카메로로 찍은 사진이 정상적으로 표시됨을 확인할 수 있습니다.
파일 탐색기를 열어서 위의 사진이 저장되어 있는지 확인해 보겠습니다.
물론 위에서 처럼 저장된 사진을 가져올 수도 있습니다. 이를 확인해 보기 위해 Activity에 다음과 같이 버튼 하나를 추가합니다.
코드 창에서 저장된 사진의 목록을 불러올 수 있는 메서드를 작성합니다. 우선 권한을 확인하고 Intent에 ACTION_PICK을 매개변수로 전달하였습니다. 이렇게 하면 MediaStore에서 지정한 종류의 데이터를 목록에서 선택할 수 있도록 합니다. 에제에서는 Image를 지정하였으므로 Image만 가져와 보여주게 됩니다.
fun GetAlbum()
{
if (checkPermission(STORAGE, STORAGE_CODE)) {
val itt = Intent(Intent.ACTION_PICK)
itt.type = MediaStore.Images.Media.CONTENT_TYPE
startActivityForResult(itt, STORAGE_CODE)
}
}
onActivityResult 메서드에서 STORAGE_CODE 값을 추가해 위에서 불러온 목록 중 선택된 사진을 불러와 ImageView에 표시하도록 합니다.
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == Activity.RESULT_OK) {
when (requestCode) {
CAMERA_CODE -> {
if (data?.extras?.get("data") != null) {
val img = data?.extras?.get("data") as Bitmap
val uri = saveFile(RandomFileName(), "image/jpg", img)
binding.imageView.setImageURI(uri)
}
}
STORAGE_CODE -> {
val uri = data?.data
binding.imageView.setImageURI(uri)
}
}
}
}
마지막으로 위에서 추가한 버튼을 클릭하면 GetAlbum() 메서드를 호출할 수 있도록만 해주면 됩니다.
binding.btnAlbum.setOnClickListener() {
GetAlbum()
}
앱을 다시 실행시켜 봅니다.
사진버튼을 클릭하면 갤러리안에 사진목록을 불러와 보여주게 됩니다.
사진을 선택하면 해당 사진을 ImageView에 표시합니다.
'Mobile > Kotlin' 카테고리의 다른 글
[Kotlin] AsyncTask (0) | 2021.01.06 |
---|---|
[Kotlin] 프로세스(Process)와 스레드(Thread) (0) | 2021.01.05 |
[Kotlin] 카메라 사용하기 (0) | 2020.12.31 |
[kotlin] ORM라이브러리 Room (0) | 2020.12.30 |
[kotlin] SQLite - 연결및 사용하기 (0) | 2020.12.30 |