Android 7.0 (Nougat) 카메라(ACTION_IMAGE_CAPTURE) 및 사진 자르기(Crop) 구현
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();
}
}
|
이제 Nougat( 안드로이드 7.0 )에서 카메라 구동과 사진자르기(crop)을 위한 소스를 살펴보자.
1. Manifest에 권한 및 provider 추가
1
2
3
4
|
<manifest ...>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
</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");
}
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.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);
}
|
캐시영역으로 저장한 후 삭제가 필요하다면 아래 게시글을 확인하여 참고하면 된다.