Android

Android 7.0 (Nougat) 카메라(ACTION_IMAGE_CAPTURE) 및 사진 자르기(Crop) 구현

강릉꿀주먹 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) 저장하기