출처 - http://thdev.net/634
Android Marshmallow 에서 권한 설정이 적용되었습니다. 기존에는 M Preview에 적용된 내용을 살펴보았는데 이제 정식 버전이네 그에 따른 내용을 일부 수정해보겠습니다.
정식 버전의 API 문서는 아래의 링크로 화인이 가능합니다.
Android API 문서
Android Permission : https://developer.android.com/training/permissions/index.html
Android Permission
Android Permission은 Android Marshmallow 6.0 부터 전체적으로 적용됩니다. Target이 6.0(23)이든 아니든 사용자는 언제든지 설정을 변경할 수 있습니다.
앱 개발 Target이 API 23
API 23으로 설정할 경우 당연히 Android Permission을 적용해야 합니다.
앱 개발 Target이 23보다 작을 경우
API에 상관 없이 사용자는 언제든 설정을 변경할 수 있습니다. 앱은 쉽게 망가질 수 있겠죠. 이러한 부분은 개발자가 인지를 하고 있어야 합니다.
그로 인해 사용자의 선택을 할 수 있는 부분은 아래 캡쳐와 같습니다. 이 메뉴는 레퍼런스 기기에만 존재할 수도 있지만 한번에 수정이 가능한 메뉴입니다.
주요 기능으로 Sensor, 달력, 카메라, 연락처, 위치 정보, 마이크, 전화, 문자, 저장소 외에 기본앱에서 가지는 일부 기능
아래와 같은 권한은 사용자가 언제든지 변경이 가능합니다.
Android 6.0을 사용하고 있지만 생각보다 발빠르게 권한 획득 창을 적용한 앱들이 많습니다. 특히나 SNS앱들은 빠르게 적용을 하였습니다.
제가 본 최악의 적용 방법을 살펴보려고 합니다.
최악의 적용 방법
최악의 적용 방법을 벌써 소개해드리게 될지는 몰랐습니다. 이앱은 최초 실행시 모든 권한을 획득하고 있습니다.
총 6개의 권한을 모두 획득합니다. 6개라면 위에서 나열한 권한 모두 가져가는것과 마찬가지입니다.
구글의 개인정보 관련 정책입니다. 참고하셔서 아래 적용방법도 함께 확인해보시면 좋을것 같습니다.
- 대략 아래와 같은 경우 앱이 내려갈 수 있습니다. 아무런 설명도 없이 개인정보를 요구하고 있기 때문에 구글 정책상 문제가 될 수 있어보입니다.
구글 개인정보 관련 정책 : https://support.google.com/googleplay/android-developer/answer/4450969?hl=ko
이런 권한을 역이용한 Olleh의 최악의 사례를 이야기로 풀이해보겠습니다.
6개의 권한을 각각 순차적으로 물어봅니다. 마지막 6번째 권한은 개발자가 지정한 권한입니다.
이 중 하나라도 DENY(차단)을 할 경우 이 앱은 마지막 권한 획득에서 아무런 사유도 없이 앱을 종료합니다.
정말 최악의 방법으로 사용자에게 권한을 강요하고 있습니다. 절대로 이런 방법으로 앱을 사용하게 하면 안되는 것이죠.
사용자가 선택해서 앱 권한을 허용할 수 있는것인데 이들은 무슨 생각으로 하나도 허용하지 않으면 앱을 종료하게 만드는 것일까요? 개발의 편의성??
개발의 편의성은 좋습니다. 근데 이 통신사 앱은 사용자가 접속해서 사용율을 확인하거나, 요금을 확인하기 위해서 만들어진 앱으로 알고 있습니다.
그런데 무슨 권한으로 사용자의 연락처, 전화 등 권한을 가져가는 걸까요? 더군다나 이앱은 웹뷰형태로 만들어진 앱입니다.
통신사의 머리로는 가능한 일이지만 100% 권한을 허용해주길 원하더라도 이런식으로 만들면 안됩니다. 최악의 적용방식을 선택한 Olleh 앱...
Twitter에 글은 남겼으나 수정되더라도 몇달은 소요될거고, 아마 수정 안될 확률도 높습니다.
Android Marshmallow을 이용하여 카메라 앱을 만든다면??
우선 카메라 앱을 예로 들어보겠습니다. 필요한 권한이 어떤것이 있을까요?
- 카메라 접근
- 마이크 사용
- 위치 정보 사용
- 저장소 읽기 쓰기
- 센서가 필요할 수 있음
카메라에 필요한 권한은 더 많은아 Marshamllow에서 사용자에게 동의를 얻어야할 퍼미션은 위와 같을것 같습니다.
기본으로 카메라 권한입니다. 사용자가 허용하지 않으면 사실 이앱은 오류가 나는것은 당연합니다.
예를 들어 Barcode Scanner을 보면 아래와 같습니다.
이 앱은 Android API가 6.0이 아닌 그 이하입니다. 이 경우 왼쪽과 같이 사용자가 앱 권한을 변경할 수 있습니다.
카메라 권한을 OFF 하였기에 스캐너 실행시에 오른쪽과 같은 오류 창이 보이게 됩니다. 이는 바코드 스캐너가 예외처리를 하였기에 아래와 같이 오류가 표시되는것이죠.
예외처리가 가능한 API 수준이 있고, 직접 해주셔야 하는 API 수준이 있습니다. Android 6.0을 Target으로 설정하였을 경우는 로그상 오류가 표시될 수 있고, 앱이 오류가 날 수 있습니다. API에 따라 당연히 설정을 해주셔야 합니다.
반대로 Target이 23보다 작을 경우에는 일부 하드웨어 관련 장치를 사용할 때는 오류가 날 수 있으며, 그외 연락처, 캘린더, 전화번호를 가져오는 등에는 공백의 데이터가 return 될 수 있습니다. 오류는 나지 않지만 예외처리가 되어 있는 앱에서는 사용이 불가능하겠죠.
꼭 필요한 정보가 있다면, Target을 Android 6.0의 23 수준으로 올리시고 미리 미리 적용해주시는게 더 안전할 수 있습니다.
Example Codec.
Github : https://github.com/taehwandev/MAppPermission
Permission
M 버전부터 적용되는 Permission 체크를 해야 하는 목록은 아래와 같습니다. 아래의 목록의 퍼미션은 앞으로 권한 획득 창을 뛰어 권한 획득을 받아야만 합니다.
앞에 있는 Group으로 한 번에 해당 권한을 모두 받을 수 있고, 또는 뒤쪽의 Permission을 각각 필요에 따라 받을 수 있습니다.
Permission Group | Permissions |
android.permission-group.CALENDAR | android.permission.READ_CALENDAR android.permission.WRITE_CALENDAR |
android.permission-group.CAMERA | android.permission.CAMERA |
android.permission-group.CONTACTS | android.permission.READ_CONTACTS android.permission.WRITE_CONTACTS android.permission.READ_PROFILE android.permission.WRITE_PROFILE |
android.permission-group.LOCATION | android.permission.ACCESS_FINE_LOCATION android.permission.ACCESS_COARSE_LOCATION |
android.permission-group.MICROPHONE | android.permission.READ_AUDIO |
android.permission-group.PHONE | android.permission.READ_PHONE_STATE android.permission.CALL_PHONE android.permission.READ_CALL_LOG android.permission.WRITE_CALL_LOG com.android.voicemail.permission.ADD_VOICEMAIL android.permission.USE_SIP android.permission.PROCESS_OUTGOING_CALLS |
android.permission-group.SENSORS | android.permission.BODY_SENSORS android.permission.USE_FINGERPRINT |
android.permission-group.SMS | android.permission.SEND_SMS android.permission.RECEIVE_SMS android.permission.READ_SMS android.permission.RECEIVE_WAP_PUSH android.permission.RECEIVE_MMS android.permission.READ_CELL_BROADCASTS |
android.permission-group.STORAGE | android.permission.READ_EXTERNAL_STORAGE android.permission.WRITE_EXTRANAL_STORAGE |
위와 같은 권한은 모두 획득하여야 정보 획득이 가능합니다. 이게 조금 애매한 부분이 있습니다.
개인정보와 관련된 정보입니다. 예를 들면 기기에 대한 정보입니다.
기기에서 사용하고 있는 통신사 정보 : 개인정보는 아니라서 그냥 가져오는게 가능합니다.
IMEI 정보, 전화번호 등 : 개인정보에 해당되어 위와 같은 권한을 획득하고나서 가져오는게 가능합니다.
사실 이게 개인정보에 해당되는지 아닌지는 개발자가 판단하기 쉽지 않습니다.
아직까지 구글에서 별도로 설명하는 내용은 없습니다. 그렇기에 전체적으로 권한 획득을 하고 데이터를 가져오는게 좋다고 생각됩니다.
권한 획득을 위한 주요 API
안드로이드 M Preview의 예제코드 중 주요 코드를 살펴보겠습니다.
권한 획득하기 전 권한 유효성 체크
checkSelfPermission(String) != PackageManager.PERMISSION_GRANTED
위와 가은 코드를 통해 허용돼 사용 가능한지 불가능한지에 대한 permission을 체크할 수 있습니다. 현재 API에서는 아쉽게도 1번에 1개밖에 하지 못하는군요.
설명이 필요할 경우 처리
shouldShowRequestPermissionRationale(String)
권한 획득이 필요한 이유를 설명해야 한다면 다음 옵션을 추가하여 별도 처리가 가능합니다.
권한 획득을 위한 API
Activity.requestPermissions(String[], int)
위의 권한 중 Group과 permission 2가지를 선택적으로 던질 수 있습니다. 한 번에 1개가 아닌 String[] 배열로 넘겨 한 번에 필요한 permission을 한 번에 획득할 수 있습니다.
requestPermission의 경우 callback으로 return 됩니다.
onRequestPermissionResult(int, String[], int[])
권한 획득에 대한 성공/실패에 대한 정보를 담은 callback입니다. 다음 함수 내에서 배열로 전달되므로 필요한 퍼미션이 잘 받아졌는지 확인하여 이후 처리가 가능합니다.
예제 코드 살펴보기
위의 주요 API를 통해 권한을 받아오고, 이미 권한을 받아왔을 경우에 대한 처리가 가능합니다.
우선 permission을 받지 않고 접근할 경우 어떠한 오류가 나는지 살펴보겠습니다.
AndroidManifest.xml에 다음 퍼미션을 적용하였지만 아래와 같은 오류가 발생하였습니다.
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
외장 스토리지에 파일을 읽고 쓰는 것조차 권한이 없으면.. 힘들게 되었습니다.
? W/ActivityManager﹕ getRunningAppProcesses: caller 10041 does not hold REAL_GET_TASKS; limiting output
net.thdev.mapppermission W/System﹕ ClassLoader referenced unknown path: /data/app/net.thdev.mapppermission-1/lib/arm
net.thdev.mapppermission W/System.err﹕ java.io.IOException: open failed: EACCES (Permission denied)
net.thdev.mapppermission W/System.err﹕ at java.io.File.createNewFile(File.java:939)
net.thdev.mapppermission W/System.err﹕ ... 14 more
Write/Read external storage 접근 방법
Write/Read external storage를 접근하려면 이제 Permission 접근 방법을 통해 접근해야 합니다.
- permission-group으로 접근
- Storage를 접근하려고 시도해보았지만 permission deny 만 뜨는군요. 현재 Preview 2에서는 정상 동작하지 않는 것 같습니다.
- 개개의 permission으로 접근
- Storage로의 접근을 하기 위해서는 2개의 퍼미션을 필요로 합니다. Read/Write에 대한 퍼미션 2개를 각각 처리하는 방법으로 접근이 가능합니다.
Permission 체크 예제 코드
퍼미션 체크는 아래와 같이 진행합니다. 체크해야 할 퍼미션을 아래와 같이 입력합니다. Storage 권한을 사용하기 위해서는 read/write 2개 모두 적용해야 퍼미션 오류가 나지 않더군요.
그래서 저는 아래와 같이 작성하였습니다. 마지막에 requestPermissions 함수에 Read/Write 2개의 권한을 적용하였습니다.
/**
* Permission check.
*/
private void checkPermission() {
if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED
|| checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
// Should we show an explanation?
if (shouldShowRequestPermissionRationale(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
// Explain to the user why we need to write the permission.
Toast.makeText(this, "Read/Write external storage", Toast.LENGTH_SHORT).show();
}
requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE},
MY_PERMISSION_REQUEST_STORAGE);
// MY_PERMISSION_REQUEST_STORAGE is an
// app-defined int constant
} else {
// 다음 부분은 항상 허용일 경우에 해당이 됩니다.
writeFile();
}
}
else로 넘어가는 경우 퍼미션을 항상 허용한 경우에 해당됩니다.
아래와 같은 이미지에서 다시 보지 않기를 눌렀을 경우에 항상 허용이 되며, 허용이 된 경우에만 else 로직을 타게 됩니다.
Permission 처리후 callback.
위에서 deny을 하거나 항상 deny을 하게 되면 아래 로직의 else를 타게 됩니다.
그렇지 않으면 if 로직을 타게 되죠. if와 else 일 경우에만 권한을 사용할 수 있으니 그때에만 권한 관련 로직을 처리하시면 됩니다.
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
switch (requestCode) {
case MY_PERMISSION_REQUEST_STORAGE:
if (grantResults[0] == PackageManager.PERMISSION_GRANTED
&& grantResults[1] == PackageManager.PERMISSION_GRANTED) {
writeFile();
// permission was granted, yay! do the
// calendar task you need to do.
} else {
Log.d(TAG, "Permission always deny");
// permission denied, boo! Disable the
// functionality that depends on this permission.
}
break;
}
}
파일을 쓰기 위한 테스트 로직
writeFile에 대한 예제입니다. 파일을 읽고 쓰는 모든 권한이 필요하여 Read/Write 2개 권한 모두 동작해주어야 합니다.
/**
* Create file example.
*/
private void writeFile() {
File file = new File(Environment.getExternalStorageDirectory().getPath() + File.separator + "temp.txt");
try {
Log.d(TAG, "create new File : " + file.createNewFile());
} catch (IOException e) {
e.printStackTrace();
}
}
마무리
안드로이드 Marshamllow 에서의 권한 획득은 상당히 큰 부분입니다. 개발자는 사실 쉽게 가져오면 좋지만 개인들은 개인정보를 최대한 주지 않으려고 할것이니깐요. 그에 따른 예외처리와 기획상 이 정보가 왜 필요한지에 대한 내용을 최대한 설명할 수 있어야 합니다. 이제 Nexus 시리즈부터 Marshmallow 적용이 시작되었고, 일반 회사용 단말기에서도 서서히 적용될 예정입니다. 특히 삼성이나 LG는 요즘 대응이 빠르기에 곧 업데이트가 될것이라고 생각됩니다.
그에 따른 Marshmallow 대응은 제가 소개한 최악의 방법만 적용하지 않았으면 합니다. 저도 개발자이면서 사용자입니다. 사용자 입장이 되어보면 이게 뭐하는 짓인가 할 정도입니다. 중간자 정도의 마음가짐이라서 더 그럴지 모르지만 전 힘들어도 저렇게 적용하지는 않을것이니깐요.(기획이 엉망이라면 설득해야죠...)
M Preview에서는 아래와 같이 적었었네요.
한 번의 허용이 앱 전체에 영향을 미치게 되니 초기에 미리 권한을 받아두고 진행하는게 좋을것으로 생각됩니다.
여기에서 잘못된 내용입니다. 중간에도 사용자가 나가서 권한 설정을 변경할 수 있습니다. 이건 최악의 상태죠.
그러므로 권한이 필요한 화면에서는 언제 어디서든 권한 획득을 즉시 할 수 있는 프로세스를 가져가셔야 합니다. 좀 힘들겠지만 잘 정리하셔서 좋은 앱을 만들 수 있었으면 합니다.
Example Codec.
'개발 > 안드로이드' 카테고리의 다른 글
안드로이드 비트맵 drawable 간에 변환하기 (0) | 2016.04.07 |
---|---|
GCM 푸시서버 구현하기(node.js & android) (0) | 2016.04.05 |
ClipToPadding() 메서드의 비밀 (0) | 2016.03.31 |
안드로이드 clipToPadding 속성 (0) | 2016.03.31 |
Android] RatingBar (별점수) (0) | 2016.03.21 |
댓글