[Android] Getting Started (6)

Android developer site study

  • Getting Started
    • Building Your First App
    • Supporting Different Devices
    • uilding a Dynamic UI with Fragments
    • Saving Data
    • Interacting with Other Apps
    • Working with System Permissions
https://developer.android.com/training/index.html

시스템 Permission 사용

시스템 무결성을 보장하고 사용자의 개인정보를 보호하기 위해 Android는 접근이 제한된 Sandbox에서 각각의 app을 실행한다.

App이 Sandbox 외부의 리소스나 정보를 사용하고자 할 경우 Permission을 명확히 요청해야 한다.

App이 요청하는 Permission 유형에 따라 시스템은 자동으로 Permission을 부여하거나 사용자에게 Permission 부여를 요청한다.

Permission 선언하기

App에 Permission이 필요함을 선언하기 위해 해당 Permission을 App Manifest에 나열해야 한다.

Permission이 얼마나 민감한지에 따라 시스템이 자동으로 Permission을 부여할 수도 있고, 기기 사용자가 요청을 승인해야 할 수도 있다.

예를 들어, 기기의 플래시를 켜는 Permission은 시스템이 자동으로 부여하지만 사용자의 연락처를 읽는 Permission은 해당 Permission의 승인을 사용자에게 요청한다.

Android 5.1 이하 버전에서는 App을 설치할 때 이런 Permission에 대해 묻게 되고, Android 6.0 이상의 버전에서는 App을 실행 중 Permission이 필요한 기능을 사용할 때 사용자에게 Permission을 요청하게 된다.

App에 필요한 Permission 판별하기

일반적으로 App이 직접 생성하지 않은 정보나 리소스를 사용하거나 기기 또는 기타 app의 동작에 영향을 미치는 동작을 수행할 때마다 Permission을 필요로 한다.

예를 들어, App이 인터넷에 접속하거나 카메라를 사용하거나 WiFi를 사용할 때 Permission이 필요하다.

App이 직접 수행하는 동작에 대해서만 Permission이 필요하며 다른 App에게 작업을 수행하거나 정보를 제공하도록 요청하는 경우에는 Permission을 필요로하지 않는다.

App이 사용자 주소록을 읽어야 하는 경우는 READ_CONTACTS Permission이 필요하지만 intent를 사용하여 연락처 app에 정보를 요청하는 경우 app에는 Permission이 필요없다. (연락처 app에는 Permission이 필요하다.)

Manifest에 Permission 추가하기

App에 권한이 필요함을 선언하려면 <uses-permission>요소를 최상위 <manifest> 요소의 하위 요소로 Manifest에 넣는다.

예를 들어 SMS 메시지를 보내야 하는 App은 manifest에 아래 줄이 있어야 한다.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.snazzyapp">
<uses-permission android:name="android.permission.SEND_SMS"/>
<application ...>
...
</application>
</manifest>

런타임에 permission 요청하기

Android 6.0 (API level 23)부터는 App이 설치될 때가 아니라 App이 실행되는 중에 permission을 부여한다.

App을 설치하거나 update할 때 permission을 부여할 필요가 없으므로 설치 절차가 간소화된다.

사용자는 언제든지 App 설정 화면으로 이동하여 권한을 취소할 수 있다.

시스템 permission은 normaldangerous 두 가지로 나눌 수 있다.

  • normal

    • 사용자 개인정볼르 직접 위험에 빠뜨리지 않는다. App이 manifest에 normal permission을 나열하는 경우, 시스템은 자동으로 permission을 부여한다.
  • dangerous

    • 사용자 기밀 데이터에 대한 접근은 app에 부여할 수 있다. App이 manifest에 normal permission을 나열하는 경우, 시스템은 자동으로 permission을 부여한다.
    • App이 manifest에 dangerous permission을 나열하는 경우, 사용자는 app에 대한 명시적 승인을 해야한다.

모든 버전의 Android에서 App은 manifest에 필요한 normal permission, dangerous permission을 모두 선언해야 한다. 그러나 이 선언의 효과는 시스템 버전과 App의 target SDK 버전에 따라 달라진다.

  • Android 5.1 이하의 버전 또는 App의 target SDK가 22 이하인 경우 사용자는 App을 설치할 때 permission을 부여해야 하며, permission을 부여하지 않는 경우 시스템이 app을 설치하지 않는다.
  • Android 6.0 이상의 기기이고 App의 target SDK가 23 이상인 경우 app이 실행되는 중에 필요한 권한을 요청해야 한다. 사용자는 각 permission을 부여하거나 거부할 수 있으며, 사용자가 permission 요청을 거부하더라도 제한된 성능으로 app이 계속 실행될 수 있다.

Android 6.0 (API 23)부터는 app이 더 낮은 API level을 대상으로 하더라도 사용자가 언제든지 모든 app에서 permission을 취소할 수 있다. 따라서 App을 제작할 때 필요한 permission이 없을 때에도 app이 올바르게 동작하는지 테스트 해야한다.

permission 체크하기

App에 dangerous permission이 필요한 경우 해당 permission이 요구되는 작업을 실행할 때마다 해당 permission의 보유 여부를 확인해야 한다. 사용자는 언제든지 permission을 취소할 수 있기 때문이다.

Permission 보유 여부를 확인하기 위해 ContextCompat.checkSelfPermission() 메서드를 호출한다.

아래 코드는 activity가 캘린더에 write permission을 가지고 있는지 확인한다.

// thisActivity가 현재의 activity라 가정한다.
int permissionCheck = ContextCompat.checkSelfPermission(thisActivity, Manifest.permission.WRITE_CALENDAR);

App이 permission을 보유한 경우 이 메서드는 PackageManager.PERMISSION_GRANTED를 반환하고, app은 계속 작업을 진행할 수 있다.

App에 권한이 없는 경우 이 메서드는 PERMISSION_DENIED를 반환하고 app이 사용자에게 명시적으로 권한을 요청해야 한다.

Permission 요청하기

App Manifest에 나열되어 있는 Dangerous permission들이 필요한 경우 사용자에게 permission 부여를 요청해야 한다.

Android에서는 permission 요청에 사용할 수 있는 여러 API를 제공하고 있으며, 이러한 메서드들을 호출하면 표준 Android dialog가 나타난다.

App에 permission이 필요한 이유를 설명하라

사용자가 사진 app을 실행하는 경우 사용자는 app이 카메라 permission을 요청한다고 해도 놀라지 않을 것이다. 하지만 app이 사용자의 위치나 연락처에 접근하려고 한다면 사용자가 이해하지 못할 것이다.

때문에 permission을 요청하기 전에 사용자에게 설명하는 것을 고려해야 한다.

이 때 사용할 수 있는 접근방법은 사용자가 해당 permission 요청을 이미 거절한 경우에만 설명을 제공하는 것이다. Permission이 요구되는 기능의 사용을 사용자가 계속 시도하면서도 계속하여 permission 요청을 거절한다면, 아마도 이 사용자는 해당 기능을 제공하기 위해 app에 permission이 필요한 이유를 모를 수도 있다. 이럴 때에는 설명을 하는 것이 좋을 수도 있다.

사용자가 설명이 필요할 수도 있는 상황을 찾도록 Android에서는 유틸리티 메서드인 shouldShowRequestPermissionRationale()을 제공하고 있다.

이전에 app이 해당 permission을 요청했고 사용자가 요청을 거부한 경우 이 메서드는 true를 반환한다.

과거에 사용자가 permission 요청을 거절하고 permission 요청 시스템 대화상자에서 Don't ask again 옵션을 선택한 경우, shouldShowRequestPermissionRationale() 메서드는 false를 반환한다.

App이 permission을 가지지 못하도록 기기 정책에서 금지하는 경웨도 이 메서드는 ‘false’를 반환한다.

필요한 permission 요청하기

App에 필요한 permission이 아직 없는 경우, requestPermission() 메서드 중 하나를 호출하여 적절한 permission을 요청해야 한다.

App이 원하는 permission을 전달하고, 이 permission 요청을 식별하기 위해 정수 형태의 request code도 전달한다.

이 메서드는 비동기식으로 작동하며 즉각 반환된다. 사용자가 대화상자에 응답한 후에 시스템은 그 결과를 가지고 app의 콜백 메서드를 호출한 후 app이 requestPermission()에 전달했던 것과 동일한 request code를 전달한다.

아래 코드는 App이 사용자의 연락처를 읽을 permission을 갖고 있는지 확인하고 필요한 경우 permission을 요청하는 코드이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// thisActivity 가 현재 activity이다.
if (ContextCompat.checkSelfPermission(thisActivity, Manifest.pemission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
// 설명이 필요한가?
if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity, Manifest.permission.READ_CONTACTS)) {
// 사용자에게 비동기 요청으로 "설명" 한다. - block하면 안된다.
// 이 스레드는 사용자의 응답에 대해 대기해야 한다.
// 사용자가 설명을 읽어본 후 permission을 다시 요청해야 한다.
} else {
// 설명이 필요하지 않다면 바로 permission을 요청한다.
ActivityCompat.requestPermissions(thisActivity,
new String[]{Manifest.permission.READ_CONTACTS},
MY_PERMISSIONS_REQUEST_READ_CONTACTS);
// MY_PERMISSIONS_REQUEST_READ_CONTACTS는 app이 정의한 상수이다.
// 콜백 메서드는 요청의 결과를 얻어온다.
}
}

App이 requestPermission()를 호출하는 경우 시스템은 표준 대화상자를 사용자에게 표시한다. App은 이 대화상자를 구성하거나 변경할 수 없다.

사용자에게 정보나 설명을 제공해야 하는 경우에는 requestPermissions()를 호출하기 전에 제공해야 한다.

Permission 요청에 대한 응답 처리하기

App이 permission을 요청하면 시스템이 사용자에게 대화상자를 표시하고 사용자가 응답하면 시스템은 app의 onRequestPermissionResult() 메서드를 호출하여 사용자 응답을 전달한다.

Permission이 부여되었는지 여부를 확인하려면 app의 이 메서드를 재정의하면 된다. 이 콜백에는 requestPermissions()에 전달한 것과 동일한 request code가 포함된다.

App이 READ_CONTACTS 접근을 요청하는 경우 아래와 같은 콜백 메서드를 가질 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions int[] grantResults) {
switch (requestCode) {
case MY_PERMISSIONS_REQUEST_READ_CONTACTS: {
// 요청이 취소되면 result 배열은 비어있다.
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// permission을 부여받았음.
// contacts와 관련된 작업을 수행하면 됨.
} else {
// permission을 부여받지 못하였음.
// permission과 관련 없는 작업을 진행하도록 함.
}
return;
}
// 추가 작업들을 수행.
}
}

시스템이 표시하는 대화상자에서는 app이 접근해야 하는 Permission Group에 대해 설명하지만 특정 permission을 나열하지는 않는다.

READ_CONTACTS를 요청해야 하는 경우, app이 기기의 연락처에 접근해야 한다는 메시지만 시스템 대화상자에 나타나게 된다. 사용자는 각 permission group에 대해 한 번만 permission을 부여해야 한다.

App이 해당 group에 있는 다른 permission(manifest에 작성해둔)을 요청하는 경우, 시스템이 자동으로 permission을 부여하게 된다.

Permission을 요청하는 경우 시스템은 사용자가 시스템 대화상자를 통해 요청을 명시적으로 부여했을 때와 동이한 방식으로 onRequestPermissionResult()를 호출하고 PERMISSION_GRANTED를 전달하게 된다.

사용자가 또 다른 permission을 같은 group에 이미 부여했다고 하더라도 app은 필요한 모든 permission을 명시적으로 요청해야 한다. 또한, permission을 group응로 묶는 것은 추후 Android 업데이트 시 변경될 수도 있다.

따라서 App에서 특정 permission이 같은 group에 있거나 없다는 가정을 하고 코딩을 해서는 안된다.

예를 들어, READ_CONTACTS, WRITE_CONTACTS를 둘 다 manifest에 작성했다고 해보자.

사용자가 READ_CONTACTS permission을 부여한 이후 app이 WRITE_CONTACTS permission을 요청하면 시스템은 사용자와의 상호작용 없이 즉시 app에 해당 permission을 부여하게 된다.

사용자가 permission을 거부하는 경우 app은 적절한 동작을 취해야 한다. 예를 들어, 사용자가 해당 permission이 필요한 작업을 요청한 경우 수행할 수 없는 이유를 설명해 줘야 한다.

시스템이 사용자에게 permission을 허용하도록 요청하면 사용자에게는 시스템에게 해당 permission을 다시는 요청하지 말라고 지시할 선택권이 있다. 그런 경우, app이 해당 permission을 요청하기 위해 requestPermissions()를 사용할 때마다 시스템이 즉시 이 요청을 거부한다.

시스템은 사용자가 명시적으로 요청을 다시 거절했을 때와 동일한 방식으로 onRequestPermissionsResult() 콜백 메서드를 호출하고 PERMISSION_DENIED를 전달한다.

즉, requestPermissions()를 호출하였더라도 사용자는 아무런 작업을 하지 않았을 수 있는 것이다.

Permission 사용 참고사항

자칫 사용자에게 필요한 기능 이상의 permission을 요청하게 될 수도 있다. 사용자는 혼란에 빠질 수도 있고 걱정에 잠길 수도 있다.

또한 사용자는 너무 많은 permission을 요청하는 경우 app을 사용하지 않게 될 수도 있다.

사용자 경험의 나쁜 예를 피하기 위해 아래의 글들을 참조하라.

Permission을 받는 대신 Intent를 사용하는 것을 고려하라.

많은 경우, 개발자는 두 가지 방법을 사용할 수 있다.

  1. permission을 부여받아 스스로 동작하는 방법
  2. 동작을 수행하기 위해 다른 app에게 intent로 요청하는 방법

예를 들어, app이 카메라를 사용하여 사진을 찍는다고 했을 때, app은 카메라에 직접 접근하기 위해 카메라 permission을 요청해야 한다. 그 후 app이 카메라 API를 사용하여 카메라를 제어하고 사진을 촬영한다.

이러한 방식을 사용하면 app에 사진 촬영 과정에 대해 완전한 제어권을 부여하고, 카메라 UI를 app에 통합할 수 있다.

하지만 이러한 완벽한 제어가 필요하지 않다면 ACTION_IMAGE_CAPTURE intent를 사용하여 이미지를 요청할 수 있다.

Intent를 보내면 시스템은 카메라 app을 선택하라는 메시지를 사용자에게 표시한다(기본 카메라 app이 없는 경우). 사용자는 선택한 카메라 app으로 사진을 촬영하고 app은 이 사진을 onActivityResult() 메서드로 반환한다.

이와 마찬가지로, 전화를 걸거나 사용자의 연락처에 접근해야 하는 경우 등에는 해당 intent를 생성하거나 permission을 요청하고 해당 객체에 직접 접근할 수 있다.

이 두 가지 방식에는 각각 장단점이 있다.

Permission을 사용하는 경우

  • 작업을 수행할 때 app이 사용자 환경에 대한 완벽한 제어권을 가진다. 하지만, 광범위한 제어권을 가지면 적절한 UI 를 디자인해야 하므로 작업이 복잡해진다.
  • Permission을 부여하라는 메시지는 runtime이나 설치 시에 한 번만 사용자에게 표시된다(Android 버전에 따라 다름). 그 이후는 app이 사용자로부터 더 이상의 상호작용을 요청하지 않아도 작업을 수행할 수 있다. 핮미ㅏㄴ, 사용자가 해당 permission을 부여하지 않는 경우 (또는 나중에 취소하는 경우), app은 해당 작업을 수행할 수 없게 된다.

Intent를 사용하는 경우

  • Intent를 처리하는 app이 UI를 제공하기 때문에 작업을 위해 UI를 디자인하지 않아도 된다. 하지만, 이는 사용자 환경을 제어할 수 없다는 뜻이기도 하다. 사용자는 어쩌면 본 적도 없는 app과 상호작용 하고 있을 수도 있다.
  • 사용자에게 해당 작업에 대한 기본 app이 없는 경우, 시스템은 사용자에게 app을 선택하라는 메시지를 표시한다. 사용자가 기본 handler를 지정하지 않으면, 해당 작업을 수행할 때마다 추가 대화상자를 거쳐야 할 수도 있다.

필요한 permission만 요청하기

Permission을 요청할 때마다 사용자 입장에서는 결정을 내리도록 강요받는 것과 같다. 때문에 이러한 요청을 하는 횟수를 최소한으로 줄이는 것이 좋다.

사용자가 Android 6.0(API level 23) 이상을 실행 중인 경우, permission이 요구되는 새로운 app 기능을 시도할 때마다 이 app의 permission 요청에 의해 사용자 작업이 중단되어야 한다.

사용자가 이전 버전의 Android를 사용 중인 경우, app 설치 시에 모든 app의 permission을 하나하나 부여해야 하는데 그 목록이 너무 길거나 부적절해 보인다면 사용자가 app을 설치하지 않을 수도 있다.

그렇기 때문에 app에 필요한 permission의 수를 최소화해야 한다.

사용자에게 부담을 주지 말 것

사용자에게 많은 permission 요청을 한 번에 하는 것보다 필요할 때마다 요청하는 것이 좋다.

일부 경우에는 app에 필요한 permission이 하나 이상 있을 수도 있다. 그런 경우에는 app이 시작되자마자 해당 권한을 모두 요청할 것을 권장한다.

예를 들어, 사진 app을 만드는 경우 app은 카메라에 접근할 수 있는 permission을 필요로 한다. 사용자가 app을 처음으로 시작할 때 카메라를 사용할 permission을 요청받아도 놀라지 않는다.

하지만 동일한 app에 사용자의 연락처와 사진을 공유하는 기능도 있다면, 이 경우 해당 READ_CONTACTS permission을 첫 시작 시에 요청하는 것은 바람직하지 않다.

대신 사용자가 “공유” 기능을 사용할 때까지 기다렸다가 그 때 permission을 요청하는 것이 좋다.

App이 튜토리얼을 제공하는 경우 튜토리얼이 끝날 무렵에 app의 필수 permission을 요청하는 것이 좋을 수 있다.

Permission이 필요한 이유를 설명하라.

개발자가 requestPermissions()를 호출할 때 시스템에 표시되는 permission 대화상자에는 app이 원하는 permission이 무엇인지는 나타나지만 그것이 필요한 이유는 설명하지 않는다.

사용자가 이러한 것을 의아하게 여기는 경우가 있기 때문에 app이 왜 그런 permission을 원하는지 설명한 후 requestPermissions()를 호출하는 것이 좋다.

예를 들어 사진 app이 위치 서비스를 이용하고자 할 수 있다. 그래야 사진에 지오태그를 표시할 수 있기 때문이다. 일반적인 사용자는 사진에 위치 정보를 담을 수 있다는 점을 모를 수도 있고 그러면 사진 app이 왜 위치를 알고싶어 하는지 궁금해할 수 있다. 그러므로 이런 경우에는 app이 사용자에게 이런 기능에 대해 미리 알려준 후 requestPermissions()를 호출하는 것이 좋다.

사용자에게 알리기 위한 방법 중 하나는 이러한 요청을 app 튜토리얼에 넣는 것이다. 튜토리얼에는 app의 각 기능을 표시할 수 있고 어느 permission이 필요한지 설명할 수도 있기 때문이다.

예를 들어 사진 app의 튜토리얼에서 “연락처 목록의 지인들과 사진 공유” 기능을 시연한 다음, app이 사용자의 연락처를 보려면 permission을 부여해야 한다고 사용자에게 알리면 된다.

그런 다음, app이 requestPermissions()를 호출하여 사용자에게 해당 접근을 요청할 수 있다.

두 가지 permission 모델을 시험하기

Android 6.0(API level 23)부터는 app 설치 시가 아닌 런타임 시 사용자가 app permission을 부여하고 취소해야 한다.

결과적으로 더욱 넓은 조건 하에서 app을 테스트 해야한다.

Android 6.0 이전에는 app이 실행 중인 경우 모든 permission이 app manifest에 선언된 것으로 충분히 가정할 수 있다.

새로운 permission 모델에서는 이러한 가정을 할 수 없다.

아래 팁은 API level 23 이상을 실행 중인 기기에서 permission 관련 코드 문제를 식별하는 데 도움이 된다.

  • App의 현재 permission 및 관련 코드 경로를 식별한다.
  • Permission 보호된 서비스 및 데이터 전반에 걸친 사용자 흐름을 테스트 한다.
  • 부여되거나 취소된 permission의 다양한 조합을 테스트 한다. 예를 들어, 카메라 app은 CAMERA, READ_CONTACTS, ACCESS_FINE_LOCATION을 manifest에 나열할 수도 있다. App이 모든 permission 구성을 정상적으로 처리할 수 있는지 확인하려면 각 permission을 켜고 끄면서 app을 테스트해야 한다.
  • adb 도구를 사용하여 permission을 관리한다.
    • Permission과 상태를 group별로 나열한다.
      $ adb shell pm list permissions -d -g
    • 하나 이상의 permission을 부여하거나 취소한다.
      $ adb shell pm [grant|revoke] <permission-name> ...

출처

Share