Android 6.0 마시멜로 리뷰
안드로이드 마시멜로 이슈 분석
얼마전 Android 6.0 SDK 및 Developer Preview 3 가 발표되어 해당 내용으로 테스트 진행, 글 수정하였습니다.
결론부터 말씀드리면 마시멜로 릴리즈 전 거의 모든 앱들은 서비스 플로우 및 코드 수정이 필요하게 되었습니다. 제 경우는 그 범위가 어마어마 하네요.
이번에 진행할 Android 마시멜로 리뷰는 구글에서 발표한 내용과 이를 통해 발생할 예상 이슈를 살펴보고 어떤식으로 코드를 수정해야 할지에 대해 알아보도록 하겠습니다.
개발자 분들은 구글에서 발표한 마시멜로 관련문서들은 간략하게나마 읽어보셨을 것으로 판단됩니다.
여기서 집중적으로 살펴볼 것은 “새로운 앱 권한 모델" 과 “절전 최적화" 입니다. 구글 측 발표는 아무래도 새로운 기능을 발표할 때 긍정적인 부분을 중점적으로 이야기 할텐데 오늘 리뷰를 통해 긍정적인 측면과 부정적인 측면을 같이 제대로 이해하시는 시간이 되었으면 합니다.
새로운 앱 권한 모델 — 런타임 권한
테스트하실때 단말의 OS를 꼭 preview 3 으로 업데이트 하셔야 합니다. preview 2 의 경우 단말의 OS를 22로 리턴하여 마시멜로(23) 관련 테스트를 정상적으로 진행 하실 수 없습니다.
먼저 해당 모델에 대한 구글의 설명을 살펴 보겠습니다.
마시멜로 개발자 미리 보기에서는 새로운 앱 권한 모델을 소개하여 사용자가 앱을 설치하고 업그레이드하는 과정을 간소화할 수 있습니다. 미리 보기에서 실행되는 앱이 새 권한 모델을 지원하는 경우, 사용자가 앱을 설치하거나 업그레이드할 때 아무런 권한을 허용하지 않아도 됩니다. 그 대신, 앱이 필요할 때마다 권한을 요청하고 시스템이 사용자에게 해당 권한을 요청하는 대화창을 표시합니다.
언뜻보면 아 그렇구나 라고 넘어 갈 수 있는데 좀 더 생각해봅시다. “앱이 필요할 때마다 사용자에게 권한을 요청한다”라는 것을 무엇을 의미할까요? 안드로이드의 권한들은 protectionLevel 이라는, 중요도에 따라 분류되어지는 기준값을 가지고 있습니다. 이 중 특정 레벨(PROTECTION_DANGEROUS)로 선언되어있는 권한들의 경우 해당 사용자가 직접 허용을 해야 사용할 수 있다는 것을 의미합니다.
허용이 필요한 권한 그룹 및 권한 리스트
예를 들어 사진을 촬영하는 기능이 앱에 있을 경우 해당 권한(Manifest.permission.CAMERA)을 사용자가 수락해야 촬영할 수 있습니다. 그런데 여기서 문제는 해당 권한을 수락하지 않는 상태에서 사진을 촬영하는 기능을 실행하면 크래시가 발생하여 앱이 종료 될 수 있다는 것입니다.
구글 가이드에서는 권한이 없을 경우 기능이 요청되었을때 대한 상황을 다음과 같이 설명합니다. “앱이 해당 권한을 필요로 하는 작업을 수행하려 시도한다고 해도 그 작업이 반드시 예외를 발생시키는 것은 아닙니다. 그 대신에 빈 데이터 세트를 반환하거나 오류를 신호하거나, 기타 예기치 못한 동작을 선보일 수 있습니다.”
즉, 앱이 권한이 없어도 안정적으로 구동되도록 (크래시를 통한 종료가 되지 않도록) 처리해 준다는 보장이 없다는 것입니다. 실제로 몇몇 앱들에서는 권한 없음으로 인한 앱 종료 현상이 발생함을 확인하였습니다.
* Intent 를 통해 수행할 수 있는 대체가능 기능들은 Intent 를 통해 수행될 경우 별도의 권한 허용이 없어도 사용 가능합니다. 예를 들어 사진을 촬영하거나, 주소록을 선택하거나 전화를 걸거나 메시지시를 쓰는 등과 같은 기능들이 대표적입니다.
구글은 이 부분의 해결을 위해 새로 발표한 권한 관련 메서드를 통해 권한 부족을 안정적으로 처리하라 가이드하고 있지만 이 말은 다시 생각해보면 앱 내 모든 권한을 사용하는 부분을 일일이 확인하고 분기 처리를 해야 된다는 말과 같습니다. 그리고 이전에 사용자 권한요청이 필요한 기능 호출시 먼저 해당 기능에 대한 권한을 사용자가 승락했는지에 대한 쿼리작업도 필요합니다. 이에 대한 플로우는 다음과 같습니다.
- 사용자가 앱내 사진 촬영하기 버튼 클릭
- 단말에 설치된 OS가 마시멜로 인지 판단
- Build.VERSION.SDK_INT < 23 — 마시멜로는 API LEVEL 이 23이므로 위 조건이 false 일 경우 다음 단계로 넘어가도록 합니다.
3. checkSelfPermission 을 통해 사진을 촬영하는 권한이 PERMISSION_GRANTED (허용) 상태인지 판단
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
!= PackageManager.PERMISSION_GRANTED)
4. 허용 상태가 아닌경우(false) 왜 해당 권한이 필요한지를 설명
if (ActivityCompat.shouldShowRequestPermissionRationale(this,
Manifest.permission.CAMERA)){
// 권한 필요 설명 UI 노출
}
5. requestPermissions 을 통해 권한 요청
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA},
REQUEST_CAMERA);
6. 권한 허용 팝업노출
7. onRequestPermissionsResult 을 통해 사용자 허용여부 판단
8. 허용여부에 따른 코드 분기 처리
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
if (requestCode == REQUEST_CAMERA) {
if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// 허용
} else {
// 비허용
}
} else {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
그나마 위 분기처리는 2번 항목을 보시면 아시겠지만 마시멜로 기준으로 빌드된 앱들에 대해서만 적용됩니다. 즉, 마시멜로 이전 버전으로 빌드된 프로젝트는 마시멜로에서 안정적으로 처리가 불가능하다는 말입니다. 아래 CASE를 통해 좀 더 자세히 살펴보도록 하겠습니다.
CASE1. 현재 앱이 마시멜로 버전 이하로 빌드되어 있는경우
아직 컴파일 타겟을 마시멜로 설정해 마켓에 배포된 앱은 아직 없겠지요. 현재 기준으로 서비스하는 모든 앱을 통칭한다고 생각하시면 됩니다. 이 상태에서 디바이스의 OS 가 마시멜로로 업데이트 되었다고 가정해 봅니다.
이럴경우 기존에 OS에서 사용하고 있는 앱들은 권한을 모두 사용자가 승낙한 것으로 인지, 정상적으로 동작합니다. 그러나 마시멜로에서는 아래와 같이 앱 별로 해당 앱의 권한을 사용자가 수동으로 on/OFF 할 수 있는 메뉴가 존재합니다. (시스템의 설정 화면으로 이동하여 앱 >app_name > 권한) 여기에서 사용자가 권한을 OFF 하면 해당 권한에 해당되는 기능을 사용자가 수행하면 권한이 없기에 앱이 종료 될 수 있습니다.
또한 사용자가 앱을 삭제하고 다시 설치한 경우에는 권한이 모두 OFF 된 상태로 실행되기 때문에 문제는 심각해집니다. — 해당 부분은 구글에서 전달받은 정책이나 preview3 에서 테스트한 결과 targetSdkVersion이 23보다 낮은경우 재설치 시 권한이 모두 on 되어있으며 23인 경우 OFF 되었다. “targetSdkVersion 에 상관없이 마시멜로 버전의 단말에 새로 설치되는 모든앱은 default OFF 이다”라는 정책과 맞지 않기때문에 해당부분은 정식 이미지가 발표될때가지 모니터링 할 필요가 있다.
현재로서는 디바이스의 OS M 으로 업데이트 되기 전에 위에 설명한 분기처리를 모두 적용하여 M 타겟으로 배포하는 것이 최선의 방법입니다.
CASE2. 현재 앱이 마시멜로 버전으로 빌드되어 있는경우
마시멜로 버전을 타겟으로 앱을 생성한 경우 사용자가 앱을 실행하면 위에서 설명한 분기처리는 모두 적용했을 것입니다. 그러나 마시멜로 버전 기준 앱을 마켓에 배포하면 해당 앱은 마시멜로 버전의 디바이스에서만 사용하는 것이 아니기 때문에 해당 버전이 마시멜로 버전 이상인지를 확인하여 마시멜로 버전 이상일 경우에 대해서만 권한관련 분기처리하도록 코드를 관리해야 할 것 입니다.
Build.VERSION.SDK_INT >=23 // 마시멜로 버전 이상 확인
CASE3. 앱이 백그라운드에서만 동작하는 경우 (UI가 없는 경우)
현재 구글 문서상으로는 UI가 존재하는 앱에 대해서만 권한을 요청 할 수 있다고 가이드 되어 있습니다. 이에 해당 특성이 있는 앱들은 특정 레벨 이상의 권한을 요하는 기능을 사용할 수 없습니다.
런타임 권한에 대한 자체 FAQ
Q1. 권한 그룹에 관련 권한이 4개가 있다고 하자 그럼 관련 권한 1개에 대해 사용자가 승인을 하게 되면 해당 권한 그룹 전체가 승인되는 것인가 아니면 정확히 그룹 중 해당 권한만 승인되는 것인가?
- 테스트 결과 CONTACTS 그룹의 경우 READ_CONTACT 만 승인하였지만 이외의 WRITE_CONTACTS 관련 작업도 모두 수행 가능했다. 그러나 STORAGE 그룹의 경우는 WRITE_EXTERNAL_STORAGE는 제외하고 READ_EXTERNAL_STORAGE 만 승인했을 경우 파일을 생성하지 못했다. 즉, 그룹에 따라 관련 권한을 승인하면 해당 그룹에 대해 승인되는 것이 있고 안그러는 것이 있는것으로 보인다. 이에 부작용을 최소한하기 위해서라도 정확히 필요한 권한이 있다면 모두 요청하는것이 좋을 것으로 판단된다.
개별 권한이 아닌 그룹 권한으로 ex) Manifest.permission_group.CONTACTS 권한 요청할 경우에는 승인되지 않았다.
Q2. 이전버전의 프리뷰에서는 여러개의 다른 그룹의 권한을 요청할 경우 그중 하나의 권한만을 승인할 수 있었는데 지금은 어떤가?
- 다른 그룹의 경우 다음 이미지 처럼 순서대로 권한 요청을 할 수 있도록 제공한다. (거부 또는 허용을 선택하면 다음 권한으로 바뀐다.) 즉, 앱 구동시 메인화면에 진입시 사용자에게 해당 앱에서 사용하는 모든 권한에 대해 한번에 허용하도록 적용할 수 있다. — 그러나 구글가이드를 보면 모든 기능에 대해 앱 구동시 권한 요청을 하게 되면 사용자는 부담을 느끼게 되어 앱을 종료하는 결과를 초래할 수 있다라고 되어 있다. 즉, 정말 필수적인 권한들에서만 요청하고 이후 부가적인 기능에 대한 권한은 각각의 기능을 수행할 때 요청하도록 가이드 하고 있다.
Q3. 가이드를 보면 위에 보여지는 권한 팝업이 노출되기 전에 자체 서비스에서 별도의 권한 설명 뷰를 노출하고 이후 권한 팝업이 노출되어야 한다고 되어 있는데 꼭 만들어야 하는가?
- 코드상으로보면 권한 설명 뷰를 노출하는것에 대해 강제는 없다. 그러나 사용자에게 충분한 설명을 하지 않을 경우에 대한 부작용을 고려해야 한다. 예를들어 앱 구동시 위와 같은 권한관련 팝업이 노출된다고 하자 사용자는 이러한 권한이 정확이 어디에 쓰지는지 모르기 때문에 거부를 할 수 있다. 이후 해당 권한이 필요한 기능을 수행했거나 다시 앱을 구동했을때는 팝업에 다시 묻지 않기라는 체크박스가 생성되어서 노출된다. (동일한 권한에 대한 팝업이 2번째 노출되었을때 부터 활성화) 사용자는 다시 묻지 않기에 체크를 하고 거부를 누른다. 이 케이스가 발생하면 사용자는 OS 설정 - 해당 앱 관리에 진입하여 권한 메뉴에서 각각의 권한에 대해 직접 on 하지 않는 이상 권한관련 기능을 영원히 사용할 수 없게 된다. 설명 뷰를 만들지 안만들지는 해당 상황을 인지하고 판단하도록 하자.
Q4. 정확히 shouldShowRequestPermissionRationale() 의 용도가 무엇인가?
해당 권한이 왜 필요로 한지 설명이 필요한 상태(설명 뷰가 노출되어야 하는 상태)인지를 명확하게 하기 위해서 만든 API 로 판단된다. 구글의 설명은 다음과 같다.
이 메서드는 사용자가 이전에 권한 요청을 거부한 경우에 ‘true’ 값을 반환합니다. 이 경우, 권한 요청을 위한 대화창에는 다음과 같이 ‘다시 묻지 않기’ 체크박스가 함께 표시됩니다.
사용자가 이를 선택하면, 이 후에 앱이 requestPermissions 메서드를 호출하더라도, 권한 요청 대화창이 표시되지 않으며, 바로 사용자가 해당 권한을 거부 할 때와 동일하게 콜백 함수가 호출 됩니다. 따라서, 이 메서드가 ‘true’ 값을 반환하면, 여러분의 앱이 해당 권한을 요구하는 이유와 왜 그 권한이 필요한지 사용자에게 조금 더 자세한 설명을 할 필요가 있습니다.
실제 테스트 진행결과는 아래와 같다.
- 단말의 OS가 마시멜로보다 낮은버전이라면 false
2. 해당 권한이 on (승인이 되어있는 경우) 되어 있는 경우 false
3. 해당 권한에 대해 OFF (승인이 되어 있지 않는 경우) 되어 있는 경우 첫 호출시 false
4. 3번 케이스 이후 호출 ture (다시 묻지 않기를 체크하고 거부한적이 없음)
5. 해당 권한에 대해 다시 묻지 않기를 체크한 상태로 거부한경우 false
각각의 상황에 대한 리턴값을 참고하여 해당 메서드를 사용할지 안할지에 대한 여부를 판단하도록 하자. 예를 들어 사용자가 해당 권한이 필요한 기능을 첫 수행한 경우 (이전에 권한 거부하지 않은경우) 해당 메서드를 수행하면 false 가 반환된다. 이후부터는 해당 메서드를 호출하면 true 가 반환된다. 구글의 입장에서는 첫번째 권한요청에 대해서는 사용자에게 어떤 이유로 해당 권한을 사용해야 하는가에 대해 설명을 하는것보다 다시 해당 권한이 필요한 기능을 수행했을때 설명을 보여주는 것이 맞다고 판단하는 것 같다.
이러한 속성을 통해서 해당 권한에 대해 다시 묻지 않기를 체크한 상태를 판단할 수도 있을 것 같다. 첫번때 권한 요청시 OS 팝업의 거부 사항을 내부적으로 저장하고 있다가 해당 권한에 대해 승인이 되어 있지 않았지만 shouldShowRequestPermissionRationale() 리턴값이 false 인 경우 이전에 해당 권한에 대해 거부한 이력이 있는지를 판단. 거부한적이 있다면 다시 묻지 않기를 체크한 상태로 거부한 것임을 유추할 수 있다. 이를 통해 해당 앱의 메뉴(사용자가 권한을 on 시킬수 있는 메뉴가 있는)로 이동할 수 있는, 사용자가 이전에 거부한상태로 판단하여 설정에서 해당 권한을 켜달라고 요청하는 UI를 노출시킬수도 있을 것이다.
참고로 해당 권한에 대해 다시 묻지 않기를 체크한 상태로 거부한 경우 사용자가 설정에서 해당 권한의 상태를 변경하면 다시 묻지 않기에 대해 체크한 상태를 reset 한다.
복잡하시죠. 구글도 런타임 권한 적용해 따른 혼란을 인지하고 이러한 상황에 대한 가이드를 제시하고 있습니다.
Educate before asking : 앱 실행시 튜토리얼 모드를 통해 왜 해당 권한을 사용하는지 정의
Ask up-front : 앱의 핵심기능이 권한이 필요한 경우 예를 들어 푸딩 카메라앱을 사용자가 구동했을 경우 사용자는 너무나 당연히 카메라를 사용하고자 앱을 실행했기 때문에 별도의 설명이 없이 카메라 권한 팝업을 노출한다.
Ask in context : 사용자들이 사용하고자 하는 기능에 대해 권한을 노출한다.
Educate in context : 사용자가 어떤기능을 사용하고자 원하거나 도움이 될 것 같다 판단이 되면 설명을 통해 이러한 권한을 승인하면 해당 기능을 사용하거나 도움을 받을 수 있다 설명한다.
Only ask for relevant permissions : 하나 이상의 권한을 요청할때는 서로 관련이 있는 권한들에 대해 요청하도록 한다.