상세 컨텐츠

본문 제목

android [안드로이드] 6.0 마시멜로우 권한 메세지, 권한 최적화 관련

스마트기기개발관련/안드로이드 개발

by AlrepondTech 2016. 4. 15. 17:34

본문

반응형
728x170

 

 

 

=================================

=================================

=================================

 

 

 

 

출처: http://googledevkr.blogspot.kr/2015/09/testyourapponandroid60.html

 

안드로이드 6.0 마시멜로의 공식 버전 SDK와 최종 개발자 프리뷰 시스템 이미지가 공개된 지 딱 한 달이 지났습니다. 여러분의 앱을 안드로이드 6.0 마시멜로에서 테스트해보셨나요? 안드로이드 6.0에는 여러분이 꼭 테스트 해보셔야 할 여러 가지 변경사항이 있습니다.

 


구글 코리아에서는 지난 한 달 동안 개발자분들이 직접 자신이 만든 앱을 테스트해볼 수 있도록 ‘안드로이드 M 오픈 테스트 랩' 행사를 진행했습니다. 총 다섯 번에 걸쳐 70개의 앱을 개발자분들이 직접 테스트해 보셨습니다. 결과가 어떻게 나왔는지 궁금하신가요?

 

보시는 것처럼, 약 절반 정도의 앱은 다행히 별다른 문제 없이 정상적으로 동작했지만, 나머지 절반 정도의 앱에서는 문제가 발견되었습니다. 특히 10% 정도는 실행 자체가 불가능하거나, 실행 중 앱이 비정상 종료되었습니다.  

추가로 구글은 다양한 디바이스와 OS 버전에서 앱을 자동으로 테스트할 수 있는 서비스 테스티드에 M 프리뷰 테스트에 필요한 장비를 지원하였습니다. 테스티드는 한국 플레이 마켓 순위에 올라와 있는 300여 개의 앱을 안드로이드 M 디바이스에서 테스트해보았습니다. 

자동으로 앱의 여러 화면을 탐색하고, 특정 권한을 제거해가며 앱의 안정성을 테스트해 본 결과, 약 7.5 %의 앱이 실행되지 않거나, 동작 중 비정상 종료되는 문제가 발견되었습니다.


무엇이 원인일까요?

지난 한달간 진행된 테스트 중에는 런타임 권한 모델, Doze 모드, 제거된 OpenSSL 라이브러리로 인해 문제가 발생하는 경우가 많았습니다.

런타임 권한 모델

안드로이드 6.0 마시멜로를 타겟으로 빌드된 앱의 경우, 앱 설치 시가 아니라, 앱 실행 중에 필요한 권한을 사용자에게 요청해야 합니다. 또한, 기존 앱의 경우에도 사용자가 설정 메뉴를 통해 각각의 앱이 가진 권한을 조정할 수 있습니다. 필요한 권한이 없는 경우, 기존 앱은 이른바 Legacy 모드로 동작하게 되는데, 이때, 특정 권한이 요구되는 API를 호출하면, 아래 표와 같이 민감한 사용자 정보를 포함하지 않도록 수정된 결과과 반환됩니다. 이런 예외 상황을 적절히 처리하지 못하면 앱이 올바르게 동작하지 못할 수도 있습니다.

제거된 권한그룹
메서드 이름
결과
PHONE
TelephonyManager#getLine1Number()
null
STORAGE
MediaStore 에 콘텐츠를 요청하는 경우
Empty Cursor
STORAGE
외부 디렉토리에 파일을 읽거나 쓰려고 하는 경우
throw
FileNotFoundException
CAMERA
CameraManager#openCamera()
throw CameraAccessException
CONTACTS
주소록 정보 요청 시
Empty Cursor
LOCATION
LocationManager#getAllProviders()
Empty Provider List
SMS
텍스트 메세지 정보 요청 시
Empty Cursor
SMS
SMS_DELIVER 브로드캐스트 인텐트 수신 시도 시
수신 할 수 없음

Doze 모드

디바이스가 사용 중이 아닌 상태로 몇 시간 가만히 있는 경우, 불필요한 배터리 소모를 줄이기 위하여 Doze 모드에 진입하게 됩니다. 이때, 디바이스에 설치된 모든 앱은 다음과 같은 제약을 받게 됩니다.

사용자가 화면을 켜거나 디바이스가 크게 움직이는 경우, 혹은 배터리를 충전하게 되면, 자동으로 Doze 모드에서 빠져나옵니다. 하지만 Doze 모드에서도 네트워크를 사용해야 하는 앱이나, 사용자가 수면 중에도 주기적으로 센서값을 확인해야 하는 수면 관리 앱, 혹은 정확한 시간에 알람을 울려야 하는 일정 관리, 알람 시계 등의 기능을 제공하는 앱들은 문제가 발생할 수 있습니다.

사라진 API

기존 OpenSSL 이 BoringSSL 로 교체됨에 따라 발생하는 문제도 있습니다. 특히, 앱에서 Android NDK를 사용하는 경우, OpenSSL 라이브러리에 대해 링크를 직접 연결하는 경우 앱이 정상적으로 동작하지 않는 경우도 종종 볼 수 있었습니다. (예:libcrypto.so 및 libssl.so) 여러분의 앱이 만일 시작하자마자 죽는다면 이 부분을 먼저 확인해 보시기 바랍니다.  

그 외에도 ApacheHttpClient 나 브라우저 북마크 등 지원이 중단된 API 도 있습니다. 혹시 여러분의 앱이 마시멜로 타겟으로 정상적으로 빌드되지 않는다거나, 기존에는 없던 오류가 발생한다면, 다음 링크를 통해 혹시 삭제된 API 를 사용하고 있는건 아닌지 확인해 보시기 바랍니다.


안드로이드 M 오픈 테스트 랩

안드로이드 6.0 마시멜로는 이번 가을 공식 공개될 예정입니다. 혹시 아직 여러분의 앱을 마시멜로에서 테스트해보지 않으셨다면, 테스트를 시작해보는 게 어떨까요? 어느새 하늘이 높고 바람이 선선한 날씨가 되었습니다.

안드로이드 M 오픈 테스트 랩은 개발 중이거나 서비스 중인 앱과 게임이 새로운 안드로이드 M 버전에서 잘 동작하는지 직접 테스트해보고, M의 새로운 기능들에 관해 궁금한 점이나 적용 방법에 관해 이야기 나눌 수 있는 행사입니다. 테스트를 위한 디바이스가 없어서 어려움이 있거나, 어떤 식으로 테스트를 진행해야 할지 도움이 필요하거나, 문제를 해결하기 위한 조언이 필요한 개발자 여러분들, 아니면 마시멜로의 새로운 기능에 관심 있는 분들은 이번 9월 24일 다시 한 번 진행될 안드로이드 M 오픈 테스트 랩 행사를 놓치지 마시기 바랍니다.

 

 

 

=================================

=================================

=================================

 

 

 

출처: http://gun0912.tistory.com/55

 

 

지난 2015년 5월에 열린 Google I/O에서 안드로이드 6.0 마시멜로우가 공개되었습니다.

여러가지 개선사항들중 우리 개발자들에게 큰 영향력을 끼칠수 있는 권한획득 방식이 변경되었습니다.

물론, 2016년 2월을 기준으로 현재 마시멜로우이상의 기기는 1.2%밖에 되지 않지만 앞으로 그 비율은 점점 늘어날 것입니다.(안드로이드 OS 점유율)

 

현재 운영중인 앱에서 안드로이드 6.0 M(MarshMellow)버전과 관련된 오류가 없을수도 있지만 해당버전에 대해서 대응해 놓지 않을경우 점점 수많은 오류를 맞이하게 될것입니다.

이번 포스팅에서는 Permission획득 방식과 어떻게하면 좀더 효율적이게 사용할수 있을지에 대해 알아 보겠습니다.

 

 

 

 

 

Runtime Permission

 

이전까지 우리는 안드로이드의 권한을 관리하는것에 대해서 큰 어려움을 느끼지 않았습니다.

사용하는 권한들을 AndroidManifest.xml에 선언해두고 사용자가 앱을 설치하는 시점에 한번만 동의를 받으면 그 이후에는 문제없이 해당 권한들을 사용할 수 있었기 때문입니다.

 

 

 

 

 

사용자가 해당 권한을 이 앱에서 사용하는것에 대해서 꺼림직해 하거나 맘에 안든다면 앱을 사용하지 않는 방법밖에 없습니다.

그로인해 수많은 피싱앱이 생기기도 했고 일부 앱에서는 과도한 권한을 요구하는경우도 있었습니다.

(앱하나 다운받았더니 '접근권한' 44개 요구…'유리폰' 방지법 발의)

 

 

하지만 이제부터 안드로이드 6.0 마시멜로우버전 이상에서는 설치할때 권한허용 여부를 묻지 않습니다.

앱에서 해당 권한이 필요할때마다 사용자로부터 권한을 허가받도록 변경되었습니다.

또한, 사용자가 권한을 허가했더라도 사용자는 설정화면(설정 > 애플리케이션 > 앱이름 > 권한)을 통해 언제든지 권한을 허용/거부 할 수 있습니다.

그래서 우리는 해당 권한이 실행될때마다 권한을 사용할 수 있는지 확인해야하고, 권한을 사용할 수 없는경우에는 사용자로부터 권한을 허가받는 기능을 추가해주어야 합니다.

 

 

            

 

                    

       

 

 

 

 

 

 

아직 마시멜로우에 대응할 마음의 준비가 안되셨나요?

 

 

지금 마시멜로우 폰에서 플레이스토어에 있는 내 앱을 설치하면 오류가 발생할까요?

그렇지 않습니다.

안드로이드에서 targetSdkVersion가 23버전보다 아래라면 앱이 설치되면 모든 권한이 허용되어있는 상태에서 시작합니다.

아직 마음의 준비가 안되셨다면 targetSdkVersion을 22로 두고 개발하세요.

단, 이미 targetSdkVersion을 23으로 올리셨다면 22로 내릴수 없습니다.

저는 이사실도 모르고 23으로 바로 올려버려서 마시멜로대응을 최대한 빨리 하는 상황을 맞이하였습니다..

 

하지만 위에서 언급한것처럼 사용자가 직접 설정페이지에서 해당 권한을 거부할 수도 있습니다.

사용자가 수동으로 설정페이지에서 권한을 없애버리면 내 앱은 오류가 발생할까요?

앱이 오류가 발생해서 죽지는 않습니다. 해당 권한을 사용하는 기능을 사용하지 못할뿐입니다.

현재 배포되어있는 앱은 오류를 방지하기위한 최소한의 장치로 생각하고 우리는 최대한 빨리 마시멜로우 권한획득에 관한 대응을 해놓아야 합니다.

 

 

 

 

 

 

모든 권한에 대해서 체크해야 할까요?

 

그렇지 않습니다.

당연히 AndroidManifest.xml에서 선언한 모든 권한에 대해서 허가를 받아올 필요는 없습니다.

구글이 정의한 Normal Permission과 Dangerous Permission중 Dangerous Permission에 대해서만 권한을 체크해주면 됩니다.

Normal and Dangerous Permissions

 

아래 표는 꼭 Permission을 체크하고 허가를 받아야 하는 Dangerous permissions와 permissions groups입니다.

이외의 Permission들은 체크하지 않으셔도 됩니다.

 

 

Permission Group

Permissions
CALENDAR
CAMERA
CONTACTS
LOCATION
MICROPHONE
PHONE
SENSORS
SMS
STORAGE


 

 

권한 체크하고 요청하기

 

우리는 이제 권한을 체크하고 사용자에게 요청한뒤 다시 해당 결과값을 받아와야합니다.

이러한 과정에서 우리는 아래 함수들을 사용해야 합니다.

 

 

 

 

-권한을 가지고있는지 체크하기

 

ContextCompat.checkSelfPermission()


int permissionCheck = ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_CALENDAR);

if(permissionCheck== PackageManager.PERMISSION_DENIED){

// 권한 없음
}else{
// 권한 있음
}

 

 

 

 

 

 

- 해당 권한이 필요한 이유 설명해야하는 경우인지 알아오기

 

shouldShowRequestPermissionRationale()

FragmentCompat와 ActivityCompat 클래스에서 이 함수를 사용해서 체크할 수 있습니다.

해당 함수가 true를 리턴하는경우 우리는 왜 해당 권한이 필요한지를 설명해주고나서 권한 허가를 요청해야 합니다.

만약 권한허가요청 다이어로그에서 사용자가 [다시 묻지 않기]를 체크했다면 이 함수는 항상 false를 리턴합니다.

처음 권한을 요청하는경우에 이 함수는 항상 false를 요청합니다.

즉 사용자가 [다시 묻지 않기]를 체크하지 않고, 1번이상 권한요청에 대해 거부한 경우에만 true를 리턴해주고 있습니다.

왜 처음 권한을 요청할때 true를 리턴하지 않는지 이해가 가지 않습니다.

아래에서 설명하겠지만 저는 이 함수를 활용하지 않습니다.

 

 

 

 

 

 

- 권한 허가 요청하기

 

requestPermissions()

FragmentCompat와 ActivityCompat 클래스에서 이 함수를 사용해서 사용자에게 권한허가를 요청하는 다이어로그를 띄울수 있습니다.

 

(READ_CONTACTS 권한허가를 요청할때의 예제)


// Activity에서 실행하는경우
if (ContextCompat.checkSelfPermission(this,Manifest.permission.READ_CONTACTS)!= PackageManager.PERMISSION_GRANTED) {

// 이 권한을 필요한 이유를 설명해야하는가?
if (ActivityCompat.shouldShowRequestPermissionRationale(this,Manifest.permission.READ_CONTACTS)) {

// 다이어로그같은것을 띄워서 사용자에게 해당 권한이 필요한 이유에 대해 설명합니다

// 해당 설명이 끝난뒤 requestPermissions()함수를 호출하여 권한허가를 요청해야 합니다

} else {

ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.READ_CONTACTS},
MY_PERMISSIONS_REQUEST_READ_CONTACTS);

// 필요한 권한과 요청 코드를 넣어서 권한허가요청에 대한 결과를 받아야 합니다

}
}

 

 

 

 

 

 

권한허가 요청후 결과 가져오기

 

onRequestPermissionsResult()

이 함수는 Activity의 onActivityResult()와 비슷한 개념입니다.

위의 예제에서 MY_PERMISSIONS_REQUEST_READ_CONTACTS로 보낸 요청코드에 대해서 결과값을 가져오고 그에대한 처리를 해주어야 합니다.


@Override
public void onRequestPermissionsResult(int requestCode,
String permissions[], int[] grantResults) {
switch (requestCode) {
case MY_PERMISSIONS_REQUEST_READ_CONTACTS:

if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
         // 권한 허가
// 해당 권한을 사용해서 작업을 진행할 수 있습니다
         } else {
         // 권한 거부
// 사용자가 해당권한을 거부했을때 해주어야 할 동작을 수행합니다
}
return;
}
}

 

 

 

 

 

 

 

 

최적화하기

 

 

- Intent를 활용하자

 

사용자에게 하는 권한허가요청을 줄이는것이 사용자와 개발자 모두의 정신겅강에 좋습니다.

그래서 꼭 해당 권한을 사용하지 않고 intent로 대체해서 사용할 수 있다면 그렇게 해주는것이 좋습니다.

만약 카메라로 사진을 찍고 이미지를 가져와야하는경우 카메라뷰를 커스텀해서 사용하는것이 아니라면 CAMERA권한을 허가요청할 필요 없이 ACTION_IMAGE_CAPTURE를 이용해 intent를 날리면 좋습니다.

Intent중 권한이 필요없는 것들을 살펴보시고 권한허가를 요청하지 않고 구현해보시기 바랍니다.

안드로이드에서 제공하는 Intent목록

 

 

 

 

- 안드로이드 4.4 킷캣(API 19)이상이라면 READ_EXTERNAL_STORAGE 권한이 필요 없습니다

 

4.4 이전버전에서는 파일을 읽어오려면 READ_EXTERNAL_STORAGE 권한이 꼭 필요 했었습니다.

권한이 없다면 getExternalStoragePublicDirectory()를 사용할 수 없었습니다.

하지만 4.4 킷캣이상부터는 권한이 필요 없이 getExternalFilesDir(String), getExternalCacheDir()를 통해 파일을 가져올 수 있습니다.

Android 4.4 APIs Important Behavior Changes

 

 

 

 

 

- 필요한 시점에만 권한을 요청하자

 

네 맞습니다. 사실 귀찮습니다.

개발자 입장에서 생각해보면 권한을 사용할때마다 권한을 체크하고, 사용자로부터 허가를 요청하고 또 그에대한 처리를 해주는 과정이 너무 복잡하고 귀찮습니다.

그래서 일부 앱에서는 앱을 시작할때부터 모든 권한을 요구하는 경우도 있습니다.

가장 많은 사용자가 있을것 같은데 [올레 고객센터]앱은 이렇게 만들어져 있습니다.

 

 

 

 

 

앱을 시작하자마자 사용자로부터 6개의 권한허용을 요구합니다.

개발자 입장에서는 앱 처음에만 권한을 검사해서 처리하면 편하지만, 사용자입장에서는 굉장이 불쾌하고 불편하게 만듭니다.

왜 각각 6개의 권한이 여기서 쓰이는지에 대한 설명도 없을뿐더러, 아직 사용하지도 않았고 사용하지 않을지도 모르는 권한들을 애초에 요청한다는것 자체가 좋지 않은 방법 이기 때문입니다.

그렇기 때문에 우리는 해당권한을 사용하게 되는 시점에 해당 권한 허가여부를 체크하고 사용자에게 권한허가를 요청해야합니다.

 

 

 

 

 

 

- 왜 이 권한이 필요한지 알려주자

 

위에서 말씀드린것처럼 shouldShowRequestPermissionRationale()를 이용하면 이유를 알려줘야 하는 시점을 알 수 있습니다.

하지만 맨처음 권한을 요청하는경우에 이 함수는 항상 false를 리턴하기때문에 사용자에게 권한이 필요한 이유를 설명하는데 이 함수는 필요 없다고 판단했습니다.

그래서 사용자가 해당 권한을 거부했을때 왜 이 권한이 필요한지 알려주고, 

이미 거부를 했더라도 사용자가 권한을 허가해주길 원하는경우 직접 [설정]에 들어가서 권한을 추가해줄 수 있도록 다이얼로그를 만들어주었습니다.

 

 

 

 

 

그래도 여전히 귀찮습니다

 

위의 최적화하기에서 말씀드린것처럼 우리는 권한을 사용하는 시점에 권한을 체크하고 사용자에게 권한허가를 요청해야 합니다.

네 알고 있죠 암요...알고 있습니다.

하지만 우리는 너무 귀찮습니다.

권한체크 이벤트가 발생할때마다

- 권한을 체크해야 하고(checkSelfPermission)

- 권한을 요청해야 하고(requestPermissions)

- 권한요청에 대한 결과를 받아서 판단해야합니다(onRequestPermissionsResult)

- 또한 위의 최적화하기에서 사용자가 권한을 허용하지 않았더라도 다이얼로그에서 설정에서 직접가서 권한을 허용할수 있게 안내할수 있는데 그러한 경우에 사용자가 설정화면에서 다시 앱으로 돌아온경우(onActivityResult)를 확인해서 권한이 허용되었는지 아닌지를 판단해야만 합니다.

 

물론 잘하시는 개발자분들은 공통으로 사용하는 부분들을 BaseActivity나 PermissionHelper라는 개념의 클래스를 만들어서 최대한 코딩량을 줄이시려고 노력하고 계실겁니다.

하지만 그래도 여전히 뭔가 불편함을 느끼시고 계실겁니다.

어떠한 방법을 쓰더라도 결국엔 BaseActivity와 BaseFragment에 각각 onRequestPermissionsResult()를 만들어주어야 할 것이고, 그 외에도 어쩔수 없이 반복해야하는 작업들이 있을겁니다.

저도 정말 수많은 고민을 하고 이 귀찮음 때문에 잠을 이루지 못했습니다.

사실 잠은 잘 잤습니다.

 

다른 개발자분들의 최적화에 대한 고민 담긴 블로그입니다.

리멤버의 안드로이드 6.0 M버전 대응기

 

 

 

반응형

 

 

728x90

 

 

 

 

그래서 TedPermission라이브러리를 만들었습니다

 

TedPermission: Easy check permission library for Android Marshmallow

 

 

사용방법은 간단합니다.

권한요청에 대한 결과를 받아오는 리스너를 만들고, 권한을 체크하기만 하면 됩니다.

TedPermission이 알아서 다 해줍니다.

 



PermissionListener permissionlistener = new PermissionListener() {
    @Override
    public void onPermissionGranted() {
        Toast.makeText(MainActivity.this, "Permission Granted", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onPermissionDenied(ArrayList<String> deniedPermissions) {
        Toast.makeText(MainActivity.this, "Permission Denied\n" + deniedPermissions.toString(), Toast.LENGTH_SHORT).show();
    }


};


new TedPermission(this)
        .setPermissionListener(permissionlistener)
        .setDeniedMessage("If you reject permission,you can not use this service\n\nPlease turn on permissions at [Setting] > [Permission]")
        .setPermissions(Manifest.permission.READ_CONTACTS, Manifest.permission.ACCESS_FINE_LOCATION)
        .check();


 

 

 

 

경우의수

 

아래내용은 TedPermission에서 사용하는 권한체크 Workflow입니다.

 

 

 

 

 

 

 

 

 

1. Permission 체크 > 권한이 허용되어 있는 경우

: onPermissionGranted()를 호출합니다.

 

2. Permission 체크 > 권한이 허용되어 있지 않은 경우

: 권한허가를 요청하는 다이얼로그가 실행됩니다.

 

 

 

 

 

3. 권한허가 다이얼로그 > 허용

: onPermissionGranted()를 호출합니다.

 

 

4. 권한허가 다이얼로그 > 거부

: 권한이 거부되는경우 위에서 설명한대로 해당 권한이 필요한 이유와 함께 사용자가 설정에서 수동으로 권한을 추가해줄수 있도록 [설정]버튼을 함께 보여줍니다.

 

 

 

 

 

5. 권한거부 및 권한이유설명 다이얼로그 > 닫기

: onPermissionDenied()를 호출합니다.

 

 

6. 권한거부 및 권한이유설명 다이얼로그 > 설정

: 앱의 설정페이지로 이동합니다. 이때 startActivityForResult()로 실행해주기 때문에 다시 앱으로 돌아올때는 onActivityResult()에서 받을 수 있습니다.

 

 

 

 

 

7. 설정 액티비티 > onActivityResult()

: 해당권한이 허용되어있는지를 체크합니다.

 

8. 해당권한 허용여부 체크 > 허용되어있음

: onPermissionGranted()를 호출합니다.

 

9. 해당권한 허용여부 체크 > 허용되어있지 않음

: onPermissionDenied()를 호출합니다.

 

 

 

 

TedPermission 라이브러리의 상세 구현방법에 대해는 다음번 포스팅에서 다뤄보도록 하겠습니다.

물론 GitHub에서 데모 프로젝트를 다운받으신뒤에 구조를 파악하셔도 좋습니다.

사실 여기에 다 올리려고 했으나 체력적인 한계로 인해 다음포스팅으로 미룹니다.

 

 

 

 

지금까지 안드로이드 6.0 마시멜로우버전에서 변경된 권한획득 방법에 대해 포스팅 해보았습니다.

어때요 참 쉽죠?

 

 

 

 

권한체크 방식으로 인해 소스코드 대부분을 뒤집어 엎어야 하는 상황이 발생할수도 있지만 구글형이 까라면 까야죠..

사실 마시멜로우에서 변경된 Doze모드에 대한 대응도 아직 하지 않았죠.

아직도 갈길이 머네요..

앞으로의 버전에서는 또 어떻게 바뀔지 기대(걱정)됩니다.

 

 

감사합니다.

 

 

 

=================================

=================================

=================================

 

 

 

출처: 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. 

 Github : https://github.com/taehwandev/MAppPermission

 

 

=================================

=================================

=================================

 

 

반응형
그리드형


관련글 더보기

댓글 영역