일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- spring exception stackTrace remove
- IT 직무 변경
- 운영체제 커널모드
- 개발 포지션 변경
- android 타이머
- 운영체제 공룡책
- 백엔드 직무 변경
- ec2 scp 파일 전송
- 백엔드 포지션 변경
- 운영체제 개념
- 개발 직무 변경
- 운영체제 멀티 태스킹
- spring responseEntity response stackTrace
- OS 자원관리
- spring exceptionHandler reposnse stackTrace
- spring dynamic query sql injection
- spring sql injection 방지
- spring 동적 쿼리 주의사항
- spring paging sort sql injection
- spring responseEntity response cause
- 운영체제 다중모드
- spring sql injection
- 운영체제 멀티 프로그래밍
- spring exceptionHandler response cause
- 운영체제 자원관리
- 운영체제 작동방식
- Android Timer
- aws ec2 scp 파일 전송
- IT 포지션 변경
- spring exception cause remove
- Today
- Total
오늘도 삽질중
AndroidQ 카메라 및 앨범예제 본문
Android 버전 Q부터 저장소 정책이 변경되어
현재 많은 블로그에 있는 기존 카메라 및 앨범예제가 작동되지 않습니다.
저 또한 현재 서비스중인 앱에 카메라와 앨범기능이 존재하기 때문에
급하게 찾아봤지만 제가 원하는 카메라 및 앨범예제는 존재하지 않았습니다.
android:requestLegacyExternalStorage="true"
임시방편으로 위 플래그를 추가하면 기존 소스도 정상동작하지만 Q 이후 버전부터는 해당 플래그를 무시한다고 해서 대응을 해줘야 합니다.
실제로 위 플래그를 제거하면 Q 버전에서도 에러가 발생합니다.
그래서 정리목적으로 직접 샘플소스를 구현했습니다.
해당 소스는 APi 23레벨(6.0) 기종은 테스트가 안될수 있습니다.
단순히 SAF로 인해 바뀐 저장소 정책에 대응한 소스입니다.
변경된 로직의 핵심은 카메라 및 앨범으로 이미지를 선택시 별도의 캐시파일을 만들어서 업로드 및 이후 작업을 진행하게 됩니다.
그래서 반드시 캐시파일을 지워주는 로직이 필요합니다.
* 위 방법보다 더 좋은방법이나 효율적인 방법을 아시는 분은 댓글좀 부탁드립니다.
저는 도저희 못찾았습니다 ㅠㅠ
카메라 및 앨범 작업전에 해야되는 선행작업 2가지
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="your package name">
//권한추가(저장소 권한 없이도 정상동작확인)
<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/AppTheme"
tools:ignore="GoogleAppIndexingWarning">
<activity android:name=".AndroidQ_CameraAlbum_EX">
<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}"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>
</application>
</manifest>
res -> xml(디렉토리생성) -> provider_paths.xml
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<root-path
name="root"
path="." />
<cache-path
name="cache"
path="." /> <!--Context.getCacheDir() 내부 저장소-->
<files-path
name="files"
path="." /> <!--Context.getFilesDir() 내부 저장소-->
<external-path
name="external"
path="."/> <!-- Environment.getExternalStorageDirectory() 외부 저장소-->
<external-cache-path
name="external-cache"
path="."/> <!-- Context.getExternalCacheDir() 외부 저장소-->
<external-files-path
name="external-files"
path="."/> <!-- Context.getExternalFilesDir() 외부 저장소-->
</paths>
보통 위 2가지는 다 하셨을꺼라 생각합니다.
이건 여러 블로그에도 내용이 많으니 궁금하신분은 찾아보시면 됩니다.
위 2가지 다 하셨다면 반드시 app Level build.gradle에 추가해주세요.
파일스트림을 복사하는 IOUtils을 사용했습니다.
implementation 'commons-io:commons-io:2.4'
AndroidQ_CameraAlubm.xml
단순히 버튼 3개와 이미지뷰만 들어가 있습니다.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<ImageView
android:id="@+id/iamgeView"
android:layout_width="300dp"
android:layout_height="300dp"
android:layout_centerInParent="true" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true">
<Button
android:id="@+id/btnCamera"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="카메라" />
<Button
android:id="@+id/btnAlbum"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="앨범" />
<Button
android:id="@+id/btnCacheClear"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="캐시파일제거" />
</LinearLayout>
</RelativeLayout>
AndroidQ_CameraAlbum_EX.java
소스가 길지만 반복 소스가 일부분 존재하고 , 주석을 잘 참고하시면 도움이 될꺼라 생각합니다.
public class AndroidQ_CameraAlbum_EX extends AppCompatActivity implements View.OnClickListener {
private static final int REQUEST_CAMERA = 100;
private static final int REQUEST_ALBUM = 101;
//서버에 이미지 업로드를 파일로 하는 경우 해당 cacheFile 로 업로드 요청을 합니다.
// 흔히 RealPath 라 불리는 경로로 보내면 퍼미션 에러가 나서 업로드 진행이 안됩니다.
private String cacheFilePath = null;
ImageView imageView;
Button btnCamera;
Button btnAlbum;
Button btnCacheClear;
@Override
protected void onCreate( @Nullable Bundle savedInstanceState ) {
super.onCreate( savedInstanceState );
setContentView( R.layout.activity_main );
imageView = findViewById( R.id.iamgeView );
btnCamera = findViewById( R.id.btnCamera );
btnAlbum = findViewById( R.id.btnAlbum );
btnCacheClear = findViewById( R.id.btnCacheClear );
btnCamera.setOnClickListener( this );
btnAlbum.setOnClickListener( this );
btnCacheClear.setOnClickListener( this );
}
@Override
public void onClick( View v ) {
if ( v == btnCamera ) {
//권한요청
requestPermissions( new String[]{ Manifest.permission.CAMERA }, REQUEST_CAMERA );
} else if ( v == btnAlbum ) {
onAlbum( REQUEST_ALBUM );
} else if ( v == btnCacheClear ) {
//캐시파일에 존재하는 카메라 및 앨범 캐시이미지 제거
cacheDirFileClear( );
}
}
@Override
public void onRequestPermissionsResult( int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults ) {
super.onRequestPermissionsResult( requestCode, permissions, grantResults );
if ( requestCode == REQUEST_CAMERA ) {
for ( int g : grantResults ) {
if ( g == PackageManager.PERMISSION_DENIED ) {
//권한거부
return;
}
}
//임시파일 생성
File file = createImgCacheFile( );
cacheFilePath = file.getAbsolutePath( );
//카메라 호출
onCamera( REQUEST_CAMERA, file );
}
}
@Override
protected void onActivityResult( int requestCode, int resultCode, @Nullable Intent data ) {
super.onActivityResult( requestCode, resultCode, data );
if ( requestCode == REQUEST_CAMERA && resultCode == RESULT_OK ) {
AlbumAdd( cacheFilePath );
imageView.setImageBitmap( getBitmapCamera( imageView, cacheFilePath ) );
} else if ( requestCode == REQUEST_ALBUM && resultCode == RESULT_OK ) {
Uri albumUri = data.getData( );
String fileName = getFileName( albumUri );
try {
ParcelFileDescriptor parcelFileDescriptor = getContentResolver( ).openFileDescriptor( albumUri, "r" );
if ( parcelFileDescriptor == null ) return;
FileInputStream inputStream = new FileInputStream( parcelFileDescriptor.getFileDescriptor( ) );
File cacheFile = new File( this.getCacheDir( ), fileName );
FileOutputStream outputStream = new FileOutputStream( cacheFile );
IOUtils.copy( inputStream, outputStream );
cacheFilePath = cacheFile.getAbsolutePath( );
imageView.setImageBitmap( getBitmapAlbum( imageView, albumUri ) );
} catch ( Exception e ) {
e.printStackTrace( );
}
} else if ( requestCode == REQUEST_CAMERA && resultCode == RESULT_CANCELED ) {
fileDelete( cacheFilePath );
cacheFilePath = null;
}
}
/**
* 카메라 및 앨범관련 작업함수
*/
//캐시파일 생성
public File createImgCacheFile( ) {
File cacheFile = new File( getCacheDir( ), new SimpleDateFormat( "yyyyMMdd_HHmmss", Locale.US ).format( new Date( ) ) + ".jpg" );
return cacheFile;
}
//카메라 호출
public void onCamera( int requestCode, File createTempFile ) {
Intent intent = new Intent( MediaStore.ACTION_IMAGE_CAPTURE );
if ( intent.resolveActivity( getPackageManager( ) ) != null ) {
if ( createTempFile != null ) {
Uri photoURI = FileProvider.getUriForFile( this, BuildConfig.APPLICATION_ID, createTempFile );
intent.putExtra( MediaStore.EXTRA_OUTPUT, photoURI );
startActivityForResult( intent, requestCode );
}
}
}
//앨범 호출
public void onAlbum( int requestCode ) {
Intent intent = new Intent( Intent.ACTION_PICK );
intent.setType( MediaStore.Images.Media.CONTENT_TYPE );
startActivityForResult( intent, requestCode );
}
//앨범 저장
public void AlbumAdd( String cacheFilePath ) {
if ( cacheFilePath == null ) return;
BitmapFactory.Options options = new BitmapFactory.Options( );
ExifInterface exifInterface = null;
try {
exifInterface = new ExifInterface( cacheFilePath );
} catch ( Exception e ) {
e.printStackTrace( );
}
int exifOrientation;
int exifDegree = 0;
//사진 회전값 구하기
if ( exifInterface != null ) {
exifOrientation = exifInterface.getAttributeInt( ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL );
if ( exifOrientation == ExifInterface.ORIENTATION_ROTATE_90 ) {
exifDegree = 90;
} else if ( exifOrientation == ExifInterface.ORIENTATION_ROTATE_180 ) {
exifDegree = 180;
} else if ( exifOrientation == ExifInterface.ORIENTATION_ROTATE_270 ) {
exifDegree = 270;
}
}
Bitmap bitmap = BitmapFactory.decodeFile( cacheFilePath, options );
Matrix matrix = new Matrix( );
matrix.postRotate( exifDegree );
Bitmap exifBit = Bitmap.createBitmap( bitmap, 0, 0, bitmap.getWidth( ), bitmap.getHeight( ), matrix, true );
ContentValues values = new ContentValues( );
//실제 앨범에 저장될 이미지이름
values.put( MediaStore.Images.Media.DISPLAY_NAME, new SimpleDateFormat( "yyyyMMdd_HHmmss", Locale.US ).format( new Date( ) ) + ".jpg" );
values.put( MediaStore.Images.Media.MIME_TYPE, "image/*" );
//저장될 경로
values.put( MediaStore.Images.Media.RELATIVE_PATH, "DCIM/AndroidQ" );
values.put( MediaStore.Images.Media.ORIENTATION, exifDegree );
values.put( MediaStore.Images.Media.IS_PENDING, 1 );
Uri u = MediaStore.Images.Media.getContentUri( MediaStore.VOLUME_EXTERNAL );
Uri uri = getContentResolver( ).insert( u, values );
try {
ParcelFileDescriptor parcelFileDescriptor = getContentResolver( ).openFileDescriptor( uri, "w", null );
if ( parcelFileDescriptor == null ) return;
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream( );
exifBit.compress( Bitmap.CompressFormat.JPEG, 100, byteArrayOutputStream );
byte[] b = byteArrayOutputStream.toByteArray( );
InputStream inputStream = new ByteArrayInputStream( b );
ByteArrayOutputStream buffer = new ByteArrayOutputStream( );
int bufferSize = 1024;
byte[] buffers = new byte[ bufferSize ];
int len = 0;
while ( ( len = inputStream.read( buffers ) ) != -1 ) {
buffer.write( buffers, 0, len );
}
byte[] bs = buffer.toByteArray( );
FileOutputStream fileOutputStream = new FileOutputStream( parcelFileDescriptor.getFileDescriptor( ) );
fileOutputStream.write( bs );
fileOutputStream.close( );
inputStream.close( );
parcelFileDescriptor.close( );
getContentResolver( ).update( uri, values, null, null );
} catch ( Exception e ) {
e.printStackTrace( );
}
values.clear( );
values.put( MediaStore.Images.Media.IS_PENDING, 0 );
getContentResolver( ).update( uri, values, null, null );
}
//이미지뷰에 뿌려질 앨범 비트맵 반환
public Bitmap getBitmapAlbum( View targetView, Uri uri ) {
try {
ParcelFileDescriptor parcelFileDescriptor = getContentResolver( ).openFileDescriptor( uri, "r" );
if ( parcelFileDescriptor == null ) return null;
FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor( );
if ( fileDescriptor == null ) return null;
int targetW = targetView.getWidth( );
int targetH = targetView.getHeight( );
BitmapFactory.Options options = new BitmapFactory.Options( );
options.inJustDecodeBounds = true;
BitmapFactory.decodeFileDescriptor( fileDescriptor, null, options );
int photoW = options.outWidth;
int photoH = options.outHeight;
int scaleFactor = Math.min( photoW / targetW, photoH / targetH );
if ( scaleFactor >= 8 ) {
options.inSampleSize = 8;
} else if ( scaleFactor >= 4 ) {
options.inSampleSize = 4;
} else {
options.inSampleSize = 2;
}
options.inJustDecodeBounds = false;
Bitmap reSizeBit = BitmapFactory.decodeFileDescriptor( fileDescriptor, null, options );
ExifInterface exifInterface = null;
try {
if ( Build.VERSION.SDK_INT >= Build.VERSION_CODES.N ) {
exifInterface = new ExifInterface( fileDescriptor );
}
} catch ( IOException e ) {
e.printStackTrace( );
}
int exifOrientation;
int exifDegree = 0;
//사진 회전값 구하기
if ( exifInterface != null ) {
exifOrientation = exifInterface.getAttributeInt( ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL );
if ( exifOrientation == ExifInterface.ORIENTATION_ROTATE_90 ) {
exifDegree = 90;
} else if ( exifOrientation == ExifInterface.ORIENTATION_ROTATE_180 ) {
exifDegree = 180;
} else if ( exifOrientation == ExifInterface.ORIENTATION_ROTATE_270 ) {
exifDegree = 270;
}
}
parcelFileDescriptor.close( );
Matrix matrix = new Matrix( );
matrix.postRotate( exifDegree );
Bitmap reSizeExifBitmap = Bitmap.createBitmap( reSizeBit, 0, 0, reSizeBit.getWidth( ), reSizeBit.getHeight( ), matrix, true );
return reSizeExifBitmap;
} catch ( Exception e ) {
e.printStackTrace( );
return null;
}
}
//이미지뷰에 뿌려질 카메라 비트맵 반환
public Bitmap getBitmapCamera( View targetView, String filePath ) {
int targetW = targetView.getWidth( );
int targetH = targetView.getHeight( );
// Get the dimensions of the bitmap
BitmapFactory.Options bmOptions = new BitmapFactory.Options( );
bmOptions.inJustDecodeBounds = true;
BitmapFactory.decodeFile( filePath, bmOptions );
int photoW = bmOptions.outWidth;
int photoH = bmOptions.outHeight;
double scaleFactor = Math.min( photoW / targetW, photoH / targetH );
if ( scaleFactor >= 8 ) {
bmOptions.inSampleSize = 8;
} else if ( scaleFactor >= 4 ) {
bmOptions.inSampleSize = 4;
} else {
bmOptions.inSampleSize = 2;
}
bmOptions.inJustDecodeBounds = false;
Bitmap originalBitmap = BitmapFactory.decodeFile( filePath, bmOptions );
ExifInterface exifInterface = null;
try {
if ( Build.VERSION.SDK_INT >= Build.VERSION_CODES.N ) {
exifInterface = new ExifInterface( filePath );
}
} catch ( IOException e ) {
e.printStackTrace( );
}
int exifOrientation;
int exifDegree = 0;
//사진 회전값 구하기
if ( exifInterface != null ) {
exifOrientation = exifInterface.getAttributeInt( ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL );
if ( exifOrientation == ExifInterface.ORIENTATION_ROTATE_90 ) {
exifDegree = 90;
} else if ( exifOrientation == ExifInterface.ORIENTATION_ROTATE_180 ) {
exifDegree = 180;
} else if ( exifOrientation == ExifInterface.ORIENTATION_ROTATE_270 ) {
exifDegree = 270;
}
}
Matrix matrix = new Matrix( );
matrix.postRotate( exifDegree );
Bitmap reSizeExifBitmap = Bitmap.createBitmap( originalBitmap, 0, 0, originalBitmap.getWidth( ), originalBitmap.getHeight( ), matrix, true );
return reSizeExifBitmap;
}
//앨범에서 선택한 사진이름 가져오기
public String getFileName( Uri uri ) {
Cursor cursor = getContentResolver( ).query( uri, null, null, null, null );
try {
if ( cursor == null ) return null;
cursor.moveToFirst( );
String fileName = cursor.getString( cursor.getColumnIndex( OpenableColumns.DISPLAY_NAME ) );
cursor.close( );
return fileName;
} catch ( Exception e ) {
e.printStackTrace( );
cursor.close( );
return null;
}
}
//파일삭제
public void fileDelete( String filePath ) {
if ( filePath == null ) return;
try {
File f = new File( filePath );
if ( f.exists( ) ) {
f.delete( );
}
} catch ( Exception e ) {
e.printStackTrace( );
}
}
//실제 앨범경로가 아닌 앱 내에 캐시디렉토리에 존재하는 이미지 캐시파일삭제
//확장자 .jpg 필터링해서 제거
public void cacheDirFileClear( ) {
File cacheDir = new File( getCacheDir( ).getAbsolutePath( ) );
File[] cacheFiles = cacheDir.listFiles( new FileFilter( ) {
@Override
public boolean accept( File pathname ) {
return pathname.getName( ).endsWith( "jpg" );
}
} );
if ( cacheFiles == null ) return;
for ( File c : cacheFiles ) {
fileDelete( c.getAbsolutePath( ) );
}
}
}
실행결과
카메라로 사진을 찍고 저장하면
앨범에 AndroidQ 라는 폴더가 생성되어 사진이 저장되있습니다.
코틀린은 나중에 귀찮..
'안드로이드' 카테고리의 다른 글
Android retrofit Rxjava httpCode 확장함수로 편하게 처리하기 (0) | 2020.11.05 |
---|---|
Android 디자인패턴이 그렇게 중요할까? (0) | 2020.06.30 |
android CircleImageView background Color 변경하기 (0) | 2020.05.22 |
android google-services.json file 교체 후 MismatchSenderId 가 발생 할 때 (0) | 2020.02.27 |
android PopupMenu 사용법 및 스타일 적용 (0) | 2020.02.12 |