Camera
1 코드
1_1 토대 만들기
일단 먼저 activity_main.xml에 imageView와 button을 만들고 각각의 id 또한 부여했다.
그리고 camera와 storage에 접근에 대한 허가를 app\manifests\AndroidManifest.xml의 <manifest> </manifest> 안에 아래 코드를 작성한다.
1
2
3
|
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
cs |
그리고 권한을 묻는 창을 쉽게 띄우기 위해서 GradleScripts\build.gradle (:app)의 dependencies {} 안에
implementation 'gun0912.ted:tedpermission:2.0.0' 을 추가해준다.
1_2 권한 묻기
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
|
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TedPermission.with(getApplicationContext()) //권한을 체크해주는 팝업창
.setPermissionListener(permissionListener) //밑에서 permissionListener을 정의함
.setRationaleMessage("카메라 권한이 필요합니다.") //처음 뜨는 메시지
.setDeniedMessage("거부하셨습니다.") //거부 되었을 때 뜨는 메시지
.setPermissions(Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.CAMERA) //권한의 종류(?)
.check();
...
}
PermissionListener permissionListener = new PermissionListener() {
@Override
public void onPermissionGranted() { //권한이 허용됐을 때 이벤트
Toast.makeText(getApplicationContext(), "권한이 허용됨", Toast.LENGTH_SHORT).show();
}
@Override
public void onPermissionDenied(ArrayList<String> deniedPermissions) { //권한이 거부됐을 때 이벤트
Toast.makeText(getApplicationContext(), "권한이 거부됨", Toast.LENGTH_SHORT).show();
}
};
|
cs |
6~11열에서 권한을 붇는다.
7열에서 serPermissionListener()로 17~27열에서 구현되어있는 permissionListener을 가져온다.
그 permssionListener은
onPermissionGranted()
, onPermissionDenied()의 두 권한이 허용됐을 때와 권한이 거부됐을 때의 이벤트를 정의해줄 수 있다.
여기에서는 Toast를 띄우는 것으로 구현했다.
8열에서는 setRationaleMessage()로 팝업창에 뜰 메시지를 설정한다.
9열에서는 setDeniedMessage()로 권한 허용이 거부되었을 때의 메시지를 설정한다.
10열에서 setPermissions()으로 필요한 권한을 설정해줬다.
권한의 종류에는
Manifest.permission.WRITE_EXTERNAL_STORAGE
Manifest.permission.ACCESS_FINE_LOCATION
Manifest.permission.CAMERA 등이 있다.
11열에서 check()으로 권한의 승인을 요청한다.
1_3 촬영 버튼에 대한 이벤트
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
((Button)findViewById(R.id.capture)).setOnClickListener(new View.OnClickListener() { //촬영버튼을 눌렀을 때
@Override
public void onClick(View view) {
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); //카메라 앱을 띄움
if (intent.resolveActivity(getPackageManager()) != null){ //이 intent가 실행가능한지 확인
File photoFile = null; //파일을 쓸 때는 항상 try catch문을 사용해야한다.
try {
photoFile = createImageFile();
}catch (IOException e) {
}
if (photoFile != null) {
photoUri = FileProvider.getUriForFile(getApplicationContext(), getApplicationContext().getPackageName(), photoFile); //photoFile의 Uri을 변수에 저장하는 듯
intent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri); //Uri을 사진 저장하는 경로로 지정
startActivityForResult(intent, REQUEST_IMAGE_CAPTURE);
}
}
};
});
|
cs |
4 - Intent(MediaStore.ACTION_IMAGE_CAPTURE)를 이용해서 카메라 앱을 띄우기 위한 intent를 만든다.
8 - 밑의 코드로 정의한 createImageFile() 호출해서 jpg 파일을 받아서 photoFile을 만든다.
7~11 - try {} 안의 코드나 createImageFile()에서 일어나는 IOException가 만약 일어나도 강제 종료시키지 않는다.
createImageFile()에서 일어나는 예외까지 처리하는 이유는 밑의 코드 1열의 throws IOException 때문이다.
try catch 문에 대한 것은 밑에 있다.
15 - uri을 반환하는 것 같다. 이 코드를 사용하려면 manifest에 밑의 밑의 코드를 작성해야 한다.
16 - intent에 사진을 저장할 Uri을 지정해주는 것 같다.
17 - 실제로 intent를 실행시킨다. (2021-07-27 기준으로 더 이상 사용되지 않는 코드이다.)
1
2
3
4
5
6
7
8
9
10
11
12
|
private File createImageFile() throws IOException { //throws를 이용해서 IOException이 일어났을 때 호출 지점에 예외가 일어나도록 한다. 즉 그래서 위 try catch 문 으로 해결 할 수 있다.
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()); //날짜와 시간으로 문자열 만듦
String imageFileName = "TEST_" + timeStamp + "_"; //위에서 만든 문자열로 이름을 만듦
File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES); //이 앱에서만 볼 수 있는 Directory를 만듦
File image = File.createTempFile( //임시 파일을 만든다.
imageFileName,
".jpg",
storageDir
);
imageFilePath = image.getAbsolutePath();//onActivityResult()에서 사용하기 위해서 저장해놓는다.
return image; // 만든 임시 jpg 파일을 반환한다.
}
|
cs |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
...
<application
...
<provider
android:authorities="com.example.cameraexample"
android:name="androidx.core.content.FileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"/>
</provider>
</application>
</manifest>
|
cs |
14열에서 지정해준 파일의 경로에 대한 xml파일도 만들어줘야 한다.
1
2
3
4
5
6
|
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path
name="my_images"
path="Android/data/com.example.cameraexample/files/Pictures"/>
</paths>
|
cs |
위 코드는 위에서 말한 xml 파일이다. 경로를 표시해준다.
* try catch 문
여기서 사용되는 try catch문의 구조는 try { } catch( ) { } 이러한데 try { }에 시도할 코드를 작성하고 catch ( ) { }의 그냥 괄호에는 예외가 났을 때 넘길 예외의 종류를 적는 듯하다
예를 들어서
NullPointerException ex //null 값에 접근하려고 할 때
ArithmaticException ex //수학적 계산 과정에서 에러가 발생할 때
IndexOutOfBoundsException ex //배열의 범위를 넘어갔을 때 (?)
등이 있고
중괄호에는 예외가 일어났을 때 실행할 부분이다.
1_4 startActivityForResult의 결과
1
2
3
4
5
6
7
8
9
|
@Override
public void onActivityResult(ActivityResult result) {
if (result.getResultCode() == Activity.RESULT_OK) {
Bitmap bitmap = BitmapFactory.decodeFile(imageFilePath);
ImageView iv_result = (ImageView)findViewById(R.id.iv_result);
iv_result.setImageBitmap(bitmap);
}
}
|
cs |
2 - onActivityResult는 startActivityForResult로 intent를 실행시켰다가 다시 그 전의 activity로 돌아오면 호출되는 함수이다.
5 - intent를 이용해서 찍은 사진의 위치와 BitmapFactory.decodeFile()을 이용해서 bitmap을 받아온다.
(bitmap은 디지털 이미지를 저장하는데 쓰이는 이미지 파일 포맷이다.)
6~7 - 아까 만들었던 imageView에 방금 받아온 bitmap과 setImageBitmap을 이용해서 사진을 띄운다.
이렇게까지만 하면 오류가 나면서 실행이 안 된다. 그 오류를 고치는 내용은 아래에 있다.
2 오류 해결하기
2_1 에러 메시지 분석
먼저 2021-07-26 기준으로 위 youtube 강의를 그대로 따라 한다면 아래와 같은 에러가 뜬다.
(아래 링크의 페이지들을 보면 더 정확한 정보들이 있다.)
1
2
3
4
5
6
7
8
9
10
11
12
|
Duplicate class android.support.v4.app.INotificationSideChannel found in modules core-1.5.0-runtime (androidx.core:core:1.5.0) and support-v4-23.1.1-runtime (com.android.support:support-v4:23.1.1)
Duplicate class android.support.v4.app.INotificationSideChannel$Stub found in modules core-1.5.0-runtime (androidx.core:core:1.5.0) and support-v4-23.1.1-runtime (com.android.support:support-v4:23.1.1)
Duplicate class android.support.v4.app.INotificationSideChannel$Stub$Proxy found in modules core-1.5.0-runtime (androidx.core:core:1.5.0) and support-v4-23.1.1-runtime (com.android.support:support-v4:23.1.1)
Duplicate class android.support.v4.os.IResultReceiver found in modules core-1.5.0-runtime (androidx.core:core:1.5.0) and support-v4-23.1.1-runtime (com.android.support:support-v4:23.1.1)
Duplicate class android.support.v4.os.IResultReceiver$Stub found in modules core-1.5.0-runtime (androidx.core:core:1.5.0) and support-v4-23.1.1-runtime (com.android.support:support-v4:23.1.1)
Duplicate class android.support.v4.os.IResultReceiver$Stub$Proxy found in modules core-1.5.0-runtime (androidx.core:core:1.5.0) and support-v4-23.1.1-runtime (com.android.support:support-v4:23.1.1)
Duplicate class android.support.v4.os.ResultReceiver found in modules core-1.5.0-runtime (androidx.core:core:1.5.0) and support-v4-23.1.1-runtime (com.android.support:support-v4:23.1.1)
Duplicate class android.support.v4.os.ResultReceiver$1 found in modules core-1.5.0-runtime (androidx.core:core:1.5.0) and support-v4-23.1.1-runtime (com.android.support:support-v4:23.1.1)
Duplicate class android.support.v4.os.ResultReceiver$MyResultReceiver found in modules core-1.5.0-runtime (androidx.core:core:1.5.0) and support-v4-23.1.1-runtime (com.android.support:support-v4:23.1.1)
Duplicate class android.support.v4.os.ResultReceiver$MyRunnable found in modules core-1.5.0-runtime (androidx.core:core:1.5.0) and support-v4-23.1.1-runtime (com.android.support:support-v4:23.1.1)
|
cs |
사실 이 에러를 거의 아예 이해하지 못했지만 android.support.~~ 이기 때문에 androidx 형태로 라이브러리가 구현되어있는 것이 아닌 것이 문제라고 생각했다.
2_2 라이브러리, 버전 업그레이드 관련 오류 해결
그래서 gradle.properties 에 android.enableJetifier=true를 추가해줘서 androidx의 라이브러리가 아닌 라이브러리를 androidx의 종속 항목으로 만들도록 했다.
https://pythonq.com/so/java/402281
그러고 나서는
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
<application
...
<provider
android:authorities="com.example.cameraexample"
android:name="androidx.core.content.FileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"/>
</provider>
</application>
|
cs |
app\manifests\AndroidManifest.xml 에서 <provider>의 android:name에서 오류가 생기기에
android:name="androidx.core.content.FileProvider"와 같이 바꿨다.
https://blog.naver.com/websearch/221738662288
2_3 startActivityForResult() 대체하기
이렇게 강제 종료되는 것은 막았지만 사실 제대로 실행되지는 않는다.
그리고 이제 startActivityForResult()을 사용할 수 없기 때문에 startActivityResult.launch(intent);으로 바꾸고
onActivityResult() 함수를 밑의 코드로 바꾸어줬다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
ActivityResultLauncher<Intent> startActivityResult = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
new ActivityResultCallback<ActivityResult>() {
@Override
public void onActivityResult(ActivityResult result) {
if (result.getResultCode() == Activity.RESULT_OK) {
// There are no request code
Bitmap bitmap = BitmapFactory.decodeFile(imageFilePath);
ImageView iv_result = (ImageView)findViewById(R.id.iv_result);
iv_result.setImageBitmap(bitmap);
}
}
});
|
cs |
2_4 intent.resolveActivity(getPackageManager()) 가 null을 반환하는 문제 해결
하지만 계속 제대로 실행은 되지 않았다.
그래서 Log.d(tagName, message)를 이용해서 확인해보니깐 intent.resolveActivity(getPackageManager()) != null 가 false가 되어서 실행이 안 된 것이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.cameraexample">
...
<queries>
<intent>
<action android:name="android.media.action.IMAGE_CAPTURE" />
</intent>
</queries>
<application
...
</application>
</manifest>
|
cs |
그래서 app\manifests\AndroidManifest.xml에 5~8열의 코드를 추가해줬다.
https://stackoverflow.com/questions/62535856/intent-resolveactivity-returns-null-in-api-30
2_5 오류 해결
그렇게 하니깐 정상적이게 실행이 됐다.
옆의 사진과 같이 촬영 버튼을 눌러서 기본 카메라 앱으로 사진을 찍으면
그 사진이 외부에서 접근할 수 없는 Directory에 저장되고
그 사진을 가져와서 imageView에 띄워준다.
'Android App' 카테고리의 다른 글
Fragment 전환 in Android Studio (0) | 2021.08.02 |
---|---|
recyclerView in Android Studio (0) | 2021.08.02 |
webViewWithEditText in Android Studio (0) | 2021.08.02 |
sharedPreferences, webView, Navigation in Android Studio (0) | 2021.07.19 |
Fragment 전환, layout, activity 전환 in Android Studio (0) | 2021.07.13 |