ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Android 7.0 (Nougat) 카메라(ACTION_IMAGE_CAPTURE) 및 사진 자르기(Crop) 구현
    Android 2019. 4. 1. 14:41

    Nougat 안드로이드 7.0 버전에서 변경된 사항들이 있다. 개인 파일 보안 강화를 위해 Android 7.0 이상을 대상으로 하는 앱의 개인 디렉토리는 액세스가 제한된다. 앱 외부에서 file:// URI의 노출을 금지하는 StrictMode API정책을 적용하여, 파일 URI를 포함하는 인텐트가 앱을 통해 보내지면 FileUriException이 발생한다

     

    애플리케이션 간에 파일을 공유하려면 content:// URI를 보내고URI에 대해 임시 액세스 권한을 부여해야한다. 이 권한을 가장 쉽게 부여하는 방법은 FileProvider 클래스를 사용하는 방법이 있다.

     

    Uri.fromFile()의 리턴형인 file://을 사용하지말고 FileProvider.getUriForFile()의 리턴형인 content://로 받아서  이경로를 인텐트에 전달해야한다.

     

     

    -기존 카메라를 호출하던 소스

    1
    2
    3
    4
    5
     
    Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    tempImageFile = new File(Environment.getExternalStorageDirectory(),
                    "tmp_" + String.valueOf(System.currentTimeMillis()) + ".jpg");
    intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(tempImageFile));
    startActivityForResult(intent, Definitions.PICK_FROM_CAMERA);
     
     

    - 변경한 카메라 호출하는 소스

    1
    2
    3
    4
     
    Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    Uri providerURI = FileProvider.getUriForFile( context ,getPackageName() , photoFile);
    intent.putExtra(MediaStore.EXTRA_OUTPUT , providerURI);
    startActivityForResult(intent, Definitions.PICK_FROM_CAMERA);
     

     

     

    ** onActivityResult에서 data값을 받아 getBitmapFromData(data)로 받아낸 bitamp은 썸네일 이미지이다.

    그대로 이용하려면 화질이 많이 떨어진다. Full 이미지를 받으려면 파일로 저장을 하여 읽어 와야한다.     

    1
    2
    3
    4
    5
    6
    7
     
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) {
            Bundle extras = data.getExtras();
            Bitmap imageBitmap = (Bitmap) extras.get("data");
        }
    }
     

     

     

     

     

     

    이제 Nougat( 안드로이드 7.0 )에서 카메라 구동과 사진자르기(crop)을 위한 소스를 살펴보자.

     

     

    1. Manifest에 권한 및 provider 추가

     
    1
    2
    3
    4
     
    <manifest ...>
        <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
        <uses-permission android:name="android.permission.CAMERA" />
    </manifest>
     
    Colored by Color Scripter
     
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
     
    <application>
       ...
       <provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="com.example.android.fileprovider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths"></meta-data>
        </provider>
        ...
    </application>
     
    Colored by Color Scripter

     

     

     

    2. file_paths.xml

    FileProvider는 미리 지정한 디렉토리에 위치할 파일에 대한 content URI만 생성할 수 있다.

    디렉토리를 지정하기 위해 <paths> element를 이용해 path와 저장 영역을 명시해야한다.

     

    name element

            - 보안을 강화를 위해 공유하는 하위 디렉토리 이름을 감춘다.

    path element

            - 파일을 공유하는 실제 하위 디렉토리

     

    <files-path>                         : 내부저장소  - Context.getFilesDir()

    <cache-path>                      : 내부저장소 - getCacheDir()

    <external-path>                  : 외부저장소 - Environment.getExternalStorageDirectory()

    <external-files-path>          : 외부저장소 - Context.getExternalFilesDir() 

    <external-cache-path>       : 외부저장소  - Context.getExternalCacheDir()

     

    1
    2
    3
    4
     
    <?xml version="1.0" encoding="utf-8"?>
         <external-path name="hidden" path="/Pictures/pic"/>
    </paths>
     
     

     

     

     

    3. Activty.java  -  카메라 구동  captureCamera() & createImageFile()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
     
    private static Uri imageUri;
     
    private File createImageFile() throws IOException {
            // Create an image file name
            String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
            String imageFileName = "JPEG_" + timeStamp + ".jpg";
     
            File imageFile = null;
            File storageDir = new File(Environment.getExternalStorageDirectory()+"/Pictures" , "pic");
     
            if(!storageDir.exists()){
                storageDir.mkdirs();
            }
            imageFile = new File(storageDir, imageFileName);
            
            return imageFile;
    }
     
     

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
     
    //카메라 작동
    private void captureCamera(){
     
        String state = Environment.getExternalStorageState();
     
        //외장메모리 검사
        if(Environment.MEDIA_MOUNTED.equals(state)){
     
            Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
     
            if(intent.resolveActivity( getPackageManager()) != null){
     
                File photoFile = null;
     
                try{
                    photoFile = createImageFile();
                }catch(IOException e){}
     
                if(photoFile != null){
     
                    //getUriForFile 의 두번째 인자는 매니패스트 provider의 authorites와 일치해야한다
                    Uri providerURI = FileProvider.getUriForFile( context , getPackageName() , photoFile);
                    imageUri = providerURI;
     
                    //인텐트 전달 할때는 FileProvider의 return값인 Content://로만 , providerURI 값에 카메라 데이터를 넣어보냄
                    intent.putExtra(MediaStore.EXTRA_OUTPUT , providerURI);
     
                    startActivityForResult(intent, Definitions.PICK_FROM_CAMERA);
                }
            }
        }else{
            return;
        }
    }
     
     

     

     

     

    4. Activty.java  -  사진 자르기(Crop)  onActivityResult() & cropImage()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
     
    public void onActivityResult(Integer requestCode, Integer resultCode, Intent data) {
        if (requestCode == Definitions.PICK_FROM_CAMERA) {
            //사진 자르기
            cropImage();
        }else if (requestCode == Definitions.CROP_FROM_CAMERA) {
            try {
     
                Bitmap photo = getBitmapFromUri(imageUri);
     
            } catch (Exception e) {
     
            }
        }
    }
     
     

    ** getUriforFile()이 return한 content URI에 대한 임시 접근권한을 승인하려면 grantUriPermission을 호출한다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
     
    //사진 자르기
    public void cropImage(){
     
        Intent intent = new Intent("com.android.camera.action.CROP");
     
        intent.setDataAndType(imageUri , "image/*");
        intent.putExtra("aspectX"1);
        intent.putExtra("aspectY"1);
        intent.putExtra("scale"true);
        intent.putExtra("output", imageUri);
        /**
         * getUriforFile()이 return한 content URI에 대한 접근권한을 승인하려면 grantUriPermission을 호출한다.
         * mode_flags 파라미터의 값에 따라. 지정한 패키지에 대해 content URI를 위한 임시 접근을 승인한다.
         * 권한은 기기가 리부팅 되거나 revokeUriPermission()을 호출하여 취소할때까지 유지.
         *
         */
        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
        grantUriPermission( getPackageName(), imageUri , Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
     
        startActivityForResult(intent, Definitions.CROP_FROM_CAMERA);
     
    }
     
     

     

     

     

     

    캐시영역으로 저장한 후 삭제가 필요하다면 아래 게시글을 확인하여 참고하면 된다.

     

    Android 카메라 사진 캐시영역(Cache-path) 저장하기

    댓글

Designed by Tistory.