자바에서 카메라를 활용한 기능을 사용할 때에는 카메라 사용 권한과 OS 버전에 맞는 이미지 파일 생성 방법을 사용해야합니다.
앱에서는 사용자가 앱을 사용하면서 만든 미디어 콘텐츠(예: 사진이나 동영상)를 표시할 수 있습니다. 이때 앱이 Android 10 이상을 타겟팅하는 한 Android 10(API 수준 29) 이상을 실행하는 기기에서는 READ_EXTERNAL_STORAGE 권한을 사용하지 않아도 됩니다.
앱이 Android 10을 타겟팅하면 범위 지정 저장소를 선택 해제하세요.
이전 기기와의 호환성을 위해 READ_EXTERNAL_STORAGE 권한을 선언하고 android:maxSdkVersion을 28로 설정합니다.
미디어 저장소에 잘 알려진 다음 컬렉션 중 하나에서 파일을 찾습니다.
다음과 같이 미디어 저장소를 이용해서 이미지 파일을 생성하는데, 해당 파일 접근은 ContentResolver를 통해 진행합니다.
우선 이미지 파일을 임시로 생성해둡니다.
private File createImageFile() throws IOException {
File imageFile = File.createTempFile("JPEG_", ".jpg", getCacheDir());
return imageFile;
}
File imgFile = null;
try {
imgFile = createImageFile();
} catch (IOException e) {
e.printStackTrace();
}
SDK 버전이 24이상인 경우 FileProvider을 통해서 imgFile을 Uri 형태로 만드는 작업을 합니다.
미만인 경우는 바로 해당 파일의 Uri를 추출해냅니다.
// 이미지 파일은 미리 생성해두고 이걸 Uri로 변환작업을 한다.
if (Build.VERSION.SDK_INT >= 24)
imageUri = FileProvider.getUriForFile(getApplicationContext(), BuildConfig.APPLICATION_ID, imgFile);
else
imageUri = Uri.fromFile(imgFile);
안드로이드 11 버전 이상부터는 패키지를 공개 상태로 변환하기 때문에 AndroidManifest.xml에서 <queries> 요소를 추가해서 쿼리할 패키지 이름 또는 인텐트 필터 서명을 포함해야합니다.
PackageManager에서 queryIntentActivities로 접근하는 것은 인자로 전달되는 인텐트에 맞는 액티비티를 찾아주는 역할을 합니다.
리턴되는 액티비티는 ResolveInfo에 담기며, 여기서 시작할 액티비티의 내용을 가지고 오면 됩니다.
List<Intent> cameraIntents = new ArrayList<>();
Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); // 이미지 캡처 인텐트 외에 다른것들을 넣기 위해서 설정
List<ResolveInfo> listCam = getPackageManager().queryIntentActivities(cameraIntent, 0);
for (ResolveInfo res : listCam) {
final String packageName = res.activityInfo.packageName;
final Intent intent = new Intent(cameraIntent);
intent.setComponent(new ComponentName(packageName, res.activityInfo.name));
intent.setPackage(packageName);
intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
cameraIntents.add(intent);
}
가령 MediaStore.ACTION_IMAGE_CAPTRUE 처럼 이미지 캡처를 위한 인텐트를 만들어놓고 해당 인텐트를 실행할 수
있는지를 체크하기 위해서 queryIntentActivities에 내용을 담습니다.
getPackageManager() 의 역할은 Activity, Service, Provider, Receiver 정보를 얻을 수 있는 Query api를 제공하기 때문에
Package 정보 및 App의 Component 정보들을 얻는데 사용됩니다.
따라서 위의 예제에서는 카메라 인텐트를 사용하는 액티비티의 정보를 PackageManager에 담은뒤, 해당 결과를
ResolveInfo로 접근하는 것입니다.
ResolveInfo에 담긴 정보는 아래와 같습니다.
https://developer.android.com/reference/android/content/pm/ResolveInfo
IntentChooser는 갤러리나 카메라 등 선택해서 하는 것이기 때문에 위 카메라 촬영 접근 인텐트 외에 갤러리 접근
인텐트도 추가합니다.
외부 저장소 URI 접근을 넣고 Intent.createChooser로 내용을 만듭니다.
관련 URI 읽기 쓰기 권한을 같이 준 다음에 권한 부여 ActivityResultLauncher를 통해 IntentChooser를 보냅니다.
Intent galleryIntent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
galleryIntent.addCategory(Intent.CATEGORY_OPENABLE);
String[] mimeTypes = new String[]{"image/*"};
galleryIntent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes);
galleryIntent.setAction(Intent.ACTION_GET_CONTENT);
galleryIntent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, imageUri);
Intent chooser = Intent.createChooser(galleryIntent, "이미지 선택");
chooser.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
chooser.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, cameraIntents.toArray(new Parcelable[cameraIntents.size()]));
activityResultLauncher.launch(chooser);
권한을 부여하면서 해당 권한이 부여되었을 경우 작업 처리를 하는건 registerForActivityResult에서 구현하면 됩니다.
private void initResultCallback() {
activityResultLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
if (result.getResultCode() == Activity.RESULT_OK) {
// camera
boolean isCamera; // 카메라로 선택했는지 갤러리로 선택했는지 확인하기 위함
Bitmap photo = null; // 사진 가지고 오기
String action = "";
// 갤러리를 통해서 넘어온 것이라면 data값이 null이 아님
if (result.getData() == null) {
isCamera = true;
}
// 갤러리인 경우
else {
action = result.getData().getAction();
if (action == null) {
isCamera = false;
} else {
isCamera = action.equals(MediaStore.ACTION_IMAGE_CAPTURE);
}
}
if (isCamera) {
// 카메라로 가지고 온 경우 SDK 28버전 미만과 이상인 값으로 체크
if (Build.VERSION.SDK_INT < 28) {
try {
photo = MediaStore.Images.Media.getBitmap(
getContentResolver(),
imageUri
);
} catch (IOException e) {
e.printStackTrace();
}
}
// 28 이상
else {
ImageDecoder.Source source = ImageDecoder.createSource(getContentResolver(), imageUri);
try {
photo = ImageDecoder.decodeBitmap(source);
} catch (IOException e) {
e.printStackTrace();
}
}
}
// gallery
else {
imageUri = result.getData().getData() == null ? null : result.getData().getData();
if (Build.VERSION.SDK_INT < 28) {
try {
photo = MediaStore.Images.Media.getBitmap(
getContentResolver(),
imageUri
);
} catch (IOException e) {
e.printStackTrace();
}
}
// SDK 29 이상
else {
ImageDecoder.Source source = ImageDecoder.createSource(getContentResolver(), imageUri);
try {
photo = ImageDecoder.decodeBitmap(source);
} catch (IOException e) {
e.printStackTrace();
}
}
}
// 이미지 setting --> 추후에 scale 관리 예정
diaryImgView.setImageBitmap(photo);
}
});
}
여기서 체크하고 넘어갈 것은 SDK 버전이 28이상인 경우 ImageDecoder를 사용해서 ContentResolver에 imageUri에 담긴
사진 uri 정보를 소스로 만든 다음에 비트맵으로 디코딩 작업을 하여 사진으로 만드는 것입니다.
'Android' 카테고리의 다른 글
Jetpack Compose란..? (2) | 2024.06.30 |
---|---|
FCM 메시지가 최신화가 되지 않을때 (0) | 2024.06.27 |
Firebase Messaging 관리 (2) | 2023.01.17 |
Kotlin 문법2 - Class (0) | 2023.01.17 |
Kotlin 문법1 - 변수/Scope (0) | 2023.01.17 |