[Android / Java]
Camera 촬영 및 내부/외부 저장소에 저장
* 목표 : 안드로이드의 Camera 기능을 이용하여 촬영을 하고 저장 및 불러오기를 해보자.
안드로이드의 내장 기능인 Camera 기능을 이용해 볼 것이다.
먼저 새 액티비티를 만들어준다.
activity_camera.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
tools:context=".MainActivity">
<ImageView
android:id="@+id/ivCapture"
android:layout_width="500dp"
android:layout_height="500dp"
android:padding="30dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal">
<Button
android:id="@+id/btnCapture"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="CAMERA" />
<Button
android:id="@+id/btnSave"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="SAVE" />
</LinearLayout>
</LinearLayout>
새 액티비티를 만들면 manifest 파일에 액티비티를 만들었다고 알려줘야 한다.
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.ming_gu">
<!--Permission-->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Ming_gu">
<activity android:name=".MainActivity">
</activity>
<activity android:name=".CameraActivity">
<!--앱 최초 실행 시 열릴 Activity 선언-->
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<!--파일경로 xml 생성-->
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_path"/>
</provider>
</application>
</manifest>
Camera 기능 및 내/외부 저장소에 저장 할 수 있도록 permission을 설정해준다.
또한 <intent-filter> 태그를 이용해 앱 최초 실행 시의 CameraActivity가 열리도록 한다.
fileProvider를 통해 생성한 파일을 앱 내외부로 공유할 수 있도록 선언해준다.
file_path.xml
<?xml version="1.0" encoding="utf-8"?>
<paths>
<!--Context.getCacheDir() 내부 저장소-->
<cache-path
name="cache"
path="." />
<!--Context.getFilesDir() 내부 저장소-->
<files-path
name="Capture"
path="." />
<!-- Environment.getExternalStorageDirectory() 외부 저장소-->
<external-path
name="external"
path="." />
<!-- Context.getExternalCacheDir() 외부 저장소-->
<external-cache-path
name="external-cache"
path="." />
<!-- Context.getExternalFilesDir() 외부 저장소-->
<external-files-path
name="external-files"
path="." />
</paths>
권한 관련 내용은 앞선 포스팅을 참조하자.
이제 카메라 버튼을 클릭하면 카메라 기능을 실행할 메서드를 만들어본다.
private void captureCamera() {
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
// 인텐트를 처리 할 카메라 액티비티가 있는지 확인
if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
// 촬영한 사진을 저장할 파일 생성
File photoFile = null;
try {
//임시로 사용할 파일이므로 경로는 캐시폴더로
File tempDir = getCacheDir();
//임시촬영파일 세팅
String timeStamp = new SimpleDateFormat("yyyyMMdd").format(new Date());
String imageFileName = "Capture_" + timeStamp + "_"; //ex) Capture_20201206_
File tempImage = File.createTempFile(
imageFileName, /* 파일이름 */
".jpg", /* 파일형식 */
tempDir /* 경로 */
);
// ACTION_VIEW 인텐트를 사용할 경로 (임시파일의 경로)
mCurrentPhotoPath = tempImage.getAbsolutePath();
photoFile = tempImage;
} catch (IOException e) {
//에러 로그는 이렇게 관리하는 편이 좋다.
Log.w(TAG, "파일 생성 에러!", e);
}
//파일이 정상적으로 생성되었다면 계속 진행
if (photoFile != null) {
//Uri 가져오기
Uri photoURI = FileProvider.getUriForFile(this,
getPackageName() + ".fileprovider",
photoFile);
//인텐트에 Uri담기
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
//인텐트 실행
startActivityForResult(takePictureIntent, REQUEST_TAKE_PHOTO);
}
}
}
카메라 촬영을 하기 위해선 임시 파일을 만들어야 한다. 만든 임시파일에 촬영한 사진을 저장하고 최종적으로 startActivityForResult를 실행하여 액티비티로 다시 Request를 보내고, onActivityResult를 통해 요청받은 Request 내용을 받아오도록 하면 된다.
@Override
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
super.onActivityResult(requestCode, resultCode, intent);
try {
//after capture
switch (requestCode) {
case REQUEST_TAKE_PHOTO: {
if (resultCode == RESULT_OK) {
File file = new File(mCurrentPhotoPath);
Bitmap bitmap = MediaStore.Images.Media
.getBitmap(getContentResolver(), Uri.fromFile(file));
if (bitmap != null) {
ExifInterface ei = new ExifInterface(mCurrentPhotoPath);
int orientation = ei.getAttributeInt(ExifInterface.TAG_ORIENTATION,
ExifInterface.ORIENTATION_UNDEFINED);
// //사진해상도가 너무 높으면 비트맵으로 로딩
// BitmapFactory.Options options = new BitmapFactory.Options();
// options.inSampleSize = 8; //8분의 1크기로 비트맵 객체 생성
// Bitmap bitmap = BitmapFactory.decodeFile(file.getAbsolutePath(), options);
Bitmap rotatedBitmap = null;
switch (orientation) {
case ExifInterface.ORIENTATION_ROTATE_90:
rotatedBitmap = rotateImage(bitmap, 90);
break;
case ExifInterface.ORIENTATION_ROTATE_180:
rotatedBitmap = rotateImage(bitmap, 180);
break;
case ExifInterface.ORIENTATION_ROTATE_270:
rotatedBitmap = rotateImage(bitmap, 270);
break;
case ExifInterface.ORIENTATION_NORMAL:
default:
rotatedBitmap = bitmap;
}
//Rotate한 bitmap을 ImageView에 저장
ivCapture.setImageBitmap(rotatedBitmap);
}
}
break;
}
}
} catch (Exception e) {
Log.w(TAG, "onActivityResult Error !", e);
}
}
//카메라에 맞게 이미지 로테이션
public static Bitmap rotateImage(Bitmap source, float angle) {
Matrix matrix = new Matrix();
matrix.postRotate(angle);
return Bitmap.createBitmap(source, 0, 0, source.getWidth(), source.getHeight(),
matrix, true);
}
마지막으로 저장버튼을 눌러 파일을 저장한다.
//이미지저장 메소드
private void saveImg() {
try {
//저장할 파일 경로
File storageDir = new File(getFilesDir() + "/capture");
if (!storageDir.exists()) //폴더가 없으면 생성.
storageDir.mkdirs();
String filename = "캡쳐파일" + ".jpg";
// 기존에 있다면 삭제
File file = new File(storageDir, filename);
boolean deleted = file.delete();
Log.w(TAG, "Delete Dup Check : " + deleted);
FileOutputStream output = null;
try {
output = new FileOutputStream(file);
BitmapDrawable drawable = (BitmapDrawable) ivCapture.getDrawable();
Bitmap bitmap = drawable.getBitmap();
bitmap.compress(Bitmap.CompressFormat.JPEG, 70, output); //해상도에 맞추어 Compress
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
try {
assert output != null;
output.close();
} catch (IOException e) {
e.printStackTrace();
}
}
Log.e(TAG, "Captured Saved");
Toast.makeText(this, "Capture Saved ", Toast.LENGTH_SHORT).show();
} catch (Exception e) {
Log.w(TAG, "Capture Saving Error!", e);
Toast.makeText(this, "Save failed", Toast.LENGTH_SHORT).show();
}
}
저장 경로는 현재 내부저장소로 세팅했다. 외부저장소는 Path만 getExternalFilesDirs()로 바꿔주면 된다.
이미 저장된 파일이 있다면 앱 실행시 이미지 파일을 로드해오는 것까지 만들면 끗
private void loadImgArr() {
try {
File storageDir = new File(getFilesDir() + "/capture");
String filename = "캡쳐파일" + ".jpg";
File file = new File(storageDir, filename);
Bitmap bitmap = BitmapFactory.decodeStream(new FileInputStream(file));
ivCapture.setImageBitmap(bitmap);
} catch (Exception e) {
Log.w(TAG, "Capture loading Error!", e);
Toast.makeText(this, "load failed", Toast.LENGTH_SHORT).show();
}
}
최종 CameraActivity.java
package com.example.ming_gu;
import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.ColorStateList;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.graphics.drawable.BitmapDrawable;
import android.media.ExifInterface;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.MediaStore;
import android.util.Log;
import android.view.ViewManager;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.core.content.FileProvider;
import androidx.core.content.res.ResourcesCompat;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
public class CameraActivity extends AppCompatActivity {
private static final String TAG = "CameraActivity";
public static final int REQUEST_TAKE_PHOTO = 10;
public static final int REQUEST_PERMISSION = 11;
private Button btnCamera, btnSave;
private ImageView ivCapture;
private String mCurrentPhotoPath;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_camera);
checkPermission(); //권한체크
ivCapture = findViewById(R.id.ivCapture); //ImageView 선언
btnCamera = findViewById(R.id.btnCapture); //Button 선언
btnSave = findViewById(R.id.btnSave); //Button 선언
loadImgArr();
//촬영
btnCamera.setOnClickListener(v -> captureCamera());
//저장
btnSave.setOnClickListener(v -> {
try {
BitmapDrawable drawable = (BitmapDrawable) ivCapture.getDrawable();
Bitmap bitmap = drawable.getBitmap();
//찍은 사진이 없으면
if (bitmap == null) {
Toast.makeText(this, "저장할 사진이 없습니다.", Toast.LENGTH_SHORT).show();
} else {
//저장
saveImg();
mCurrentPhotoPath = ""; //initialize
}
} catch (Exception e) {
Log.w(TAG, "SAVE ERROR!", e);
}
});
}
private void captureCamera() {
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
// 인텐트를 처리 할 카메라 액티비티가 있는지 확인
if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
// 촬영한 사진을 저장할 파일 생성
File photoFile = null;
try {
//임시로 사용할 파일이므로 경로는 캐시폴더로
File tempDir = getCacheDir();
//임시촬영파일 세팅
String timeStamp = new SimpleDateFormat("yyyyMMdd").format(new Date());
String imageFileName = "Capture_" + timeStamp + "_"; //ex) Capture_20201206_
File tempImage = File.createTempFile(
imageFileName, /* 파일이름 */
".jpg", /* 파일형식 */
tempDir /* 경로 */
);
// ACTION_VIEW 인텐트를 사용할 경로 (임시파일의 경로)
mCurrentPhotoPath = tempImage.getAbsolutePath();
photoFile = tempImage;
} catch (IOException e) {
//에러 로그는 이렇게 관리하는 편이 좋다.
Log.w(TAG, "파일 생성 에러!", e);
}
//파일이 정상적으로 생성되었다면 계속 진행
if (photoFile != null) {
//Uri 가져오기
Uri photoURI = FileProvider.getUriForFile(this,
getPackageName() + ".fileprovider",
photoFile);
//인텐트에 Uri담기
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
//인텐트 실행
startActivityForResult(takePictureIntent, REQUEST_TAKE_PHOTO);
}
}
}
//이미지저장 메소드
private void saveImg() {
try {
//저장할 파일 경로
File storageDir = new File(getFilesDir() + "/capture");
if (!storageDir.exists()) //폴더가 없으면 생성.
storageDir.mkdirs();
String filename = "캡쳐파일" + ".jpg";
// 기존에 있다면 삭제
File file = new File(storageDir, filename);
boolean deleted = file.delete();
Log.w(TAG, "Delete Dup Check : " + deleted);
FileOutputStream output = null;
try {
output = new FileOutputStream(file);
BitmapDrawable drawable = (BitmapDrawable) ivCapture.getDrawable();
Bitmap bitmap = drawable.getBitmap();
bitmap.compress(Bitmap.CompressFormat.JPEG, 70, output); //해상도에 맞추어 Compress
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
try {
assert output != null;
output.close();
} catch (IOException e) {
e.printStackTrace();
}
}
Log.e(TAG, "Captured Saved");
Toast.makeText(this, "Capture Saved ", Toast.LENGTH_SHORT).show();
} catch (Exception e) {
Log.w(TAG, "Capture Saving Error!", e);
Toast.makeText(this, "Save failed", Toast.LENGTH_SHORT).show();
}
}
private void loadImgArr() {
try {
File storageDir = new File(getFilesDir() + "/capture");
String filename = "캡쳐파일" + ".jpg";
File file = new File(storageDir, filename);
Bitmap bitmap = BitmapFactory.decodeStream(new FileInputStream(file));
ivCapture.setImageBitmap(bitmap);
} catch (Exception e) {
Log.w(TAG, "Capture loading Error!", e);
Toast.makeText(this, "load failed", Toast.LENGTH_SHORT).show();
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
super.onActivityResult(requestCode, resultCode, intent);
try {
//after capture
switch (requestCode) {
case REQUEST_TAKE_PHOTO: {
if (resultCode == RESULT_OK) {
File file = new File(mCurrentPhotoPath);
Bitmap bitmap = MediaStore.Images.Media
.getBitmap(getContentResolver(), Uri.fromFile(file));
if (bitmap != null) {
ExifInterface ei = new ExifInterface(mCurrentPhotoPath);
int orientation = ei.getAttributeInt(ExifInterface.TAG_ORIENTATION,
ExifInterface.ORIENTATION_UNDEFINED);
// //사진해상도가 너무 높으면 비트맵으로 로딩
// BitmapFactory.Options options = new BitmapFactory.Options();
// options.inSampleSize = 8; //8분의 1크기로 비트맵 객체 생성
// Bitmap bitmap = BitmapFactory.decodeFile(file.getAbsolutePath(), options);
Bitmap rotatedBitmap = null;
switch (orientation) {
case ExifInterface.ORIENTATION_ROTATE_90:
rotatedBitmap = rotateImage(bitmap, 90);
break;
case ExifInterface.ORIENTATION_ROTATE_180:
rotatedBitmap = rotateImage(bitmap, 180);
break;
case ExifInterface.ORIENTATION_ROTATE_270:
rotatedBitmap = rotateImage(bitmap, 270);
break;
case ExifInterface.ORIENTATION_NORMAL:
default:
rotatedBitmap = bitmap;
}
//Rotate한 bitmap을 ImageView에 저장
ivCapture.setImageBitmap(rotatedBitmap);
}
}
break;
}
}
} catch (Exception e) {
Log.w(TAG, "onActivityResult Error !", e);
}
}
//카메라에 맞게 이미지 로테이션
public static Bitmap rotateImage(Bitmap source, float angle) {
Matrix matrix = new Matrix();
matrix.postRotate(angle);
return Bitmap.createBitmap(source, 0, 0, source.getWidth(), source.getHeight(),
matrix, true);
}
@Override
public void onResume() {
super.onResume();
checkPermission(); //권한체크
}
//권한 확인
public void checkPermission() {
int permissionCamera = ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA);
int permissionRead = ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE);
int permissionWrite = ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE);
//권한이 없으면 권한 요청
if (permissionCamera != PackageManager.PERMISSION_GRANTED
|| permissionRead != PackageManager.PERMISSION_GRANTED
|| permissionWrite != PackageManager.PERMISSION_GRANTED) {
if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CAMERA)) {
Toast.makeText(this, "이 앱을 실행하기 위해 권한이 필요합니다.", Toast.LENGTH_SHORT).show();
}
ActivityCompat.requestPermissions(this, new String[]{
Manifest.permission.CAMERA, Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_PERMISSION);
}
}
@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
switch (requestCode) {
case REQUEST_PERMISSION: {
// 권한이 취소되면 result 배열은 비어있다.
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this, "권한 확인", Toast.LENGTH_LONG).show();
} else {
Toast.makeText(this, "권한 없음", Toast.LENGTH_LONG).show();
finish(); //권한이 없으면 앱 종료
}
}
}
}
}
'Andorid' 카테고리의 다른 글
[Android] Layout / View Group의 종류 2. Frame Layout (1) | 2020.12.11 |
---|---|
[Android] Activity와 Fragment (2) | 2020.12.09 |
[Android / Java] 권한설정 (1) | 2020.12.06 |
[Android] Layout / View Group의 종류 1. Linear Layout (1) | 2020.12.02 |
[Android / Java] ViewHolder를 이용한 Custom Adapter 만들기 (2) | 2020.11.27 |