Android - 카메라를 호출해서 사진을찍고 특정 폴더에 저장하기 (Kotlin)

업데이트:

  • 연구주제 : Android - 카메라를 호출해서 사진을찍고 특정 폴더에 저장하기 (Kotlin)
  • 연구목적 : 안드로이드에서의 코틀린 활용
  • 연구일시 : 2020년 03월 13일 09:00~17:00
  • 연구자 : 이재환 ljh951103@naver.com
  • 연구장비 : HP EliteDesk 800 G4 TWR, Kotlin, Android studio, IntelliJ
  • 관련연구 : Java, Android, Kotlin, Camera


서론

개발중인 앱에서 카메라를 호출해서 사진을 찍고, 사진을 특정 폴더에 저장하는 부분까지 다루도록 하겠다.
카메라는 안드로이드에 기본적으로 내장된 카메라를 이용한다.

지난 포스팅에서 위험권한에 대해 다루었는데, 특정 폴더에 접근하는것이 거기에 해당한다.
따라서 지난 포스팅의 권한얻는 코딩을 진행해여 정상적으로 본 코드가 동작한다.


본론

1. 권한 허용

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

매니페스트에 본 코드를 작성하여 권한을 준다.


2. 프로바이더 작성

프로바이더란 앱과 앱간의 데이터를 주고받기위한 규약 및 컴포넌트이다.

    <provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="com.shuvic.alumni.cameraalbum"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/file_paths" />
    </provider>

마찬가지로 매니페스트에 다음과 같이 코드를 작성한다.


3. file_path 작성

file_path는 res폴더 밑에 file_paths라는 xml파일을 생성하여 다음과 같이 작성한다.

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


4. 임시 파일 생성

사진을 찍기 전, 사진이 저장되는 임시 파일을 생성한다.
파일의 형식을 지정한다.

 @Throws(IOException::class)
    fun createImageFile(): File? { // Create an image file name
        val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss").format(Date())
        val imageFileName = "JPEG_$timeStamp.jpg"
        var imageFile: File? = null
        val storageDir = File(
            Environment.getExternalStorageDirectory().toString() + "/Pictures",
            "Wimmy"
        )
        if (!storageDir.exists()) {
            Log.i("mCurrentPhotoPath1", storageDir.toString())
            storageDir.mkdirs()
        }
        imageFile = File(storageDir, imageFileName)
        mCurrentPhotoPath = imageFile.absolutePath
        return imageFile
    }


5. 카메라 실행 및 데이터 전달

카메라 실행 버튼을 누를 떄, 이전에 만들었던 임시 파일을 가지오고 이 정보를 카메라로 넘기며 실행시킨다.

private fun captureCamera() {
        val takePictureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
        if (takePictureIntent.resolveActivity(packageManager) != null) {

            var photoFile: File? = null
            try {
                photoFile = createImageFile()
            } catch (ex: IOException) {
                Log.e("captureCamera Error", ex.toString())
                return
            }
            if (photoFile != null) { // getUriForFile의 두 번째 인자는 Manifest provier의 authorites와 일치해야 함
                val providerURI = FileProvider.getUriForFile(this, packageName, photoFile)
                // 인텐트에 전달할 때는 FileProvier의 Return값인 content://로만!!, providerURI의 값에 카메라 데이터를 넣어 보냄
                takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, providerURI)
                startActivityForResult(takePictureIntent, REQUEST_TAKE_PHOTO)
            }
        }
    }


**6. 카메라에서 찍은 사진을 결과로 호출 **

카메라로 사진을 찍고 확인을 누를 때, 결과 여부를 결정한다.

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        when (requestCode) {
            REQUEST_TAKE_PHOTO -> {
                Log.i("REQUEST_TAKE_PHOTO", "${Activity.RESULT_OK}" + " " + "${resultCode}");
                if (resultCode == RESULT_OK) {
                    try {
                        galleryAddPic();
                    } catch (e: Exception) {
                        Log.e("REQUEST_TAKE_PHOTO", e.toString());
                    }

                } else {
                    Toast.makeText(this@MainActivity, "사진찍기를 취소하였습니다.", Toast.LENGTH_SHORT).show();
                }
            }
        }
    }


7. 이미지 로컬 폴더에 저장

카메라로 사진을 찍고 확인할 경우, 해당 이미지를 로컬 경로에 저장한다.

private fun galleryAddPic() {
        Log.i("galleryAddPic", "Call");
        val mediaScanIntent: Intent = Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
        // 해당 경로에 있는 파일을 객체화(새로 파일을 만든다는 것으로 이해하면 안 됨)
        val f: File = File(mCurrentPhotoPath);
        val contentUri: Uri = Uri.fromFile(f);
        mediaScanIntent.setData(contentUri);
        sendBroadcast(mediaScanIntent);
        Toast.makeText(this, "사진이 앨범에 저장되었습니다.", Toast.LENGTH_SHORT).show();
    }


8. 카메라 호출 리스너 작성

여기서 checkPermission() 메소드는 전 포스팅에서 작성했던 권한허용 메소드이다.
버튼을 통해 카메라를 호출한다.

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.main_activity)

        ...
        ...
    
        val go_camera = findViewById(R.id.main_camera_button) as ImageView
        go_camera.setOnClickListener {
            captureCamera()
        }
        checkPermission()
    }


결론

구성 로직은 다음과 같다.

  1. 권한 검사

  2. 임시 파일 생성

  3. 생성된 파일 경로와 함께 카메라 실행

  4. 사진을 찍은 후 해당 경로로 이미지 저장


image

버튼을 누르면 카메라로 이동한다.


image

확인을 누를 경우, 해당 사진이 성공적으로 저장되었음을 확인할 수 있다.


향후과제


참고자료

https://g-y-e-o-m.tistory.com/48?category=247534


Writer: Jae-Hwan Lee

댓글남기기