안드로이드 6.0(마시멜로우) 부터는 기존과 다르게 어플에서 사용자에게 필요한 권한을 획득해야 할 때마다 사용자에게 권한을 사용하겠다는 허락을 받아야 한다. 처음 어플을 설치할 때 한번에 나열되었던 때에 비해서 개발자 입장에서 매우 귀찮아 진 것은 사실이지만 무분별한 어플의 권한 획득으로 인한 피해를 막겠다는 의도는 좋아 보인다.
아무튼 퍼미션 권한을 획득하지 못하면 해당 기능을 사용할 수 없으니 퍼미션 권한 획득 방법에 대해 알아보자.
현재 이 정책은 마시멜로우 이후 부터 적용되는 것이기 때문에 그 이전에 나온 버전들에 대해서는 권한을 일일히 얻어야 할 필요는 없다. 그렇기 때문에 SDK버전이 마시멜로우 이하. 그러니까 targetSDKversion을 23미만으로 설정하면 기존과 같이 manifest에 퍼미션 설정을 한번씩만 해주면 된다.
그러나 앞으로 6.0이하 버전은 점점 줄어들고 그 이상 버전이 늘어날 것이 확실하기 때문에 퍼미션 관련 문제에 대해 미리 대응을 해두는 것이 바람직하다.어차피 언젠가는 알아 두어야 할 과정이므로 숙지하도록 하자.
얻을 수 있는 권한. 그러니까 퍼미션 종류에는 수십가지가 있고, 그 모든 퍼미션 권한 획득에 대해서 해당 이슈를 해결해야 하는가? 라는 의문이 들 수 있는데 꼭 그렇지는 않다. 구글에서 제공하는 안드로이드 개발자 페이지에서 퍼미션 관련 문서를 읽어보면 알겠지만 구글은 퍼미션 권한에 대해 중요도를 부여햐여 심각한 문제를 야기할 수 있는 권한에 대해서만 사용자에게 사용 여부를 묻도록 하고 있다.
Permission Group | Permissions |
---|---|
CALENDAR | |
CAMERA | |
CONTACTS | |
LOCATION | |
MICROPHONE | |
PHONE | |
SENSORS | |
SMS | |
STORAGE |
이 표에 나온 퍼미션 권한들은 사용하려면 꼭 사용자에게 권한 획득 여부를 물어봐야 하는 권한들이다. 이 표에 없는 권한들에 대해서는 꼭 사용자에게 여부를 묻지 않아도 무방하다.
- 권한 획득 방법
public class MainActivity extends AppCompatActivity {
private Button callingButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
callingButton = (Button) findViewById(R.id.button);
callingButton.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
Intent intent = new Intent(Intent.ACTION_CALL, Uri.parse("tel:010-1234-5678"));
startActivity(intent); <- 퍼미션 에러
}
});
}
}
위에 있는 코드는 버튼 클릭을 통해 전화를 거는 예제이다. 마시멜로우 이전 버전에서는 이렇게만 해도 권한 취득과 함께 기능 사용을 할 수 있었다.
<uses-permission android:name="android.permission.CALL_PHONE"/>
해당 퍼미션을 기존과 같이 메니페스트에 추가하면 startActivity에 빨간줄이 생기면서 퍼미션 권한을 사용하려면 사용자에게 여부를 물어보는 코드를 작성하라는 메세지를 볼 수 있다. 물론 targetSDKversion이 22 이하라면 아직은 괜찮다.
자 그럼 이제 이 이슈를 어떻게 해결해야 할까. 일단 사용자의 핸드폰 버전이 마시멜로우 이전인지 이후인지 부터 판별을 해 보아야 한다. 마시멜로우 이전 버전에서는 확인을 받지 않기 때문이다. 이 이슈는 마시멜로우 이상 버전에서만 통용되는 것이기에 그 이후에만 이 코드가 돌아야 한다.
public class MainActivity extends AppCompatActivity {
private Button callingButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
callingButton = (Button) findViewById(R.id.button);
callingButton.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // 사용자의 안드로이드 OS버전이 마시멜로우 이상인지 체크. 맞다면 IF문 내부의 소스코드 작동.
}else{ // 마시멜로우 미만 버전. 즉시 실행.
}
}
});
}
}
- 안드로이드 버전 체크.
위와 같이 안드로이드 버전을 체크한 다음으로 해야 할 것은 현재 퍼미션 권한이 취득이 되어 있는가. 아닌가 하는 것이다. 이미 권한 취득이 되어 있는 상태라면 해당 권한을 사용하는 기능을 동작시키면 되고, 그게 아니라면 다시 권한 취득을 요청해야 한다.
@Override
public void onClick(View v) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // 사용자의 안드로이드 OS버전이 마시멜로우 이상인지 체크. 맞다면 IF문 내부의 소스코드 작동.
// 사용자의 단말기에 "전화 걸기" 기능이 허용되어 있는지 확인.
int permissionResult = checkSelfPermission(Manifest.permission.CALL_PHONE); // 해당 퍼미션 체크.
if(permissionResult == PackageManager.PERMISSION_DENIED){ // 해당 퍼미션 권한여부 체크.
}else{ // 사용자가 권한을 승락함. 바로 실행.
}
}else{ // 마시멜로우 미만 버전. 즉시 실행.
}
}
checkSelfPermission(Manifest.permission.CALL_PHONE) - 이 부분이 해당 퍼미션에 대해서 승인/미승인 여부를 리턴시켜주는 메소드이다. 이 메소드를 이용해 현재 퍼미션의 권한 취득 여부를 알 수 있다.
이제 이 부분에서 두 가지로 나뉘게 되는데, 현재 퍼미션 권한 취득 방법은 크게 두 가지로 나뉜다. 구글 개발자 문서를 보면 처음 권한 요청시 권한을 요청하는 이유에 대해 설명하지 않고, 두번째 요청부터 권한을 취득해야 하는 이유를 물어보라고 나와있기 때문에 대부분 이 방식을 따르고 있으나 그 방식에 대해서 의문을 가지고 맨 처음 요청시 부터 요청하는 이유를 물어봐야 한다는 사람들은 맨 처음부터 요청 이유를 설명하게끔 코딩을 하고 있다.
@Override
public void onClick(View v) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // 사용자의 안드로이드 OS버전이 마시멜로우 이상인지 체크. 맞다면 IF문 내부의 소스코드 작동.
// 사용자의 단말기에 "전화 걸기" 기능이 허용되어 있는지 확인.
int permissionResult = checkSelfPermission(Manifest.permission.CALL_PHONE); // 해당 퍼미션 체크.
if(permissionResult == PackageManager.PERMISSION_DENIED){ // 해당 퍼미션 권한여부 체크.
/*
* 해당 권한이 거부된 적이 있는지 유무 판별 해야함.
* 거부된 적이 있으면 true, 거부된 적이 없으면 false 리턴
*/
if(shouldShowRequestPermissionRationale(Manifest.permission.CALL_PHONE)){ // 거부된 적이 있으면 해당 권한을 사용할 때 상세 내용을 설명. 거부한 적 있으면 true 리턴.
AlertDialog.Builder dialog = new AlertDialog.Builder(MainActivity.this);
dialog.setTitle("권한이 필요합니다.")
.setMessage("이 기능을 사용하기 위해서는 단말기의 \"전화걸기\"권한이 필요합니다. 계속 하시겠습니까?")
.setPositiveButton("네", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){
requestPermissions(new String[]{Manifest.permission.CALL_PHONE}, 1000);
}
}
})
.setNegativeButton("아니오", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Toast.makeText(MainActivity.this, "기능을 취소했습니다", Toast.LENGTH_SHORT).show();
}
})
.create()
.show();
}else{ // 거부된 적이 없으면 설명 없이 해당 권한을 요청.
}
}else{ // 사용자가 권한을 승락함. 바로 실행.
}
}else{ // 마시멜로우 미만 버전. 즉시 실행.
}
}
위 코드에서 shouldShowRequerstPermissionRationale(Manifest.permission.CALL_PHONE) 부분이 이전에 거부된 이력이 있는지 알아보는 메소드이다.
거부된 적이 있으면 TRUE값을 반환하고 거부된 적이 없다면 FALSE를 반환하므로 이를 이용하여 이력을 판단하고 추후 설명이 나오도록 한다. 이 부분에서 개발자들의 의견이 갈리게 되는것은 최초값이 무조건 FALSE로 지정되어 있기 때문에 처음에 권한 요청에 대해서 설명을 해야한다는 필요성을 느끼는 사람들이 이 메소드를 사용하면 최초에 설명을 할 수 없게 된다는 것이다.
그러므로 이 메소드를 사용하지 않고 다이얼로그를 직접 띄우면 최초 요청부터 사유를 설명할 수 있다.
@Override
public void onClick(View v) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // 사용자의 안드로이드 OS버전이 마시멜로우 이상인지 체크. 맞다면 IF문 내부의 소스코드 작동.
// 사용자의 단말기에 "전화 걸기" 기능이 허용되어 있는지 확인.
int permissionResult = checkSelfPermission(Manifest.permission.CALL_PHONE); // 해당 퍼미션 체크.
if (permissionResult == PackageManager.PERMISSION_DENIED) { // 해당 퍼미션 권한여부 체크.
/*
* 해당 권한이 거부된 적이 있는지 유무 판별 해야함.
* 거부된 적이 있으면 true, 거부된 적이 없으면 false 리턴
*/
if (shouldShowRequestPermissionRationale(Manifest.permission.CALL_PHONE)) { // 거부된 적이 있으면 해당 권한을 사용할 때 상세 내용을 설명. 거부한 적 있으면 true 리턴.
AlertDialog.Builder dialog = new AlertDialog.Builder(MainActivity.this);
dialog.setTitle("권한이 필요합니다.")
.setMessage("이 기능을 사용하기 위해서는 단말기의 \"전화걸기\"권한이 필요합니다. 계속 하시겠습니까?")
.setPositiveButton("네", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
requestPermissions(new String[]{Manifest.permission.CALL_PHONE}, 1000);
}
}
})
.setNegativeButton("아니오", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Toast.makeText(MainActivity.this, "기능을 취소했습니다", Toast.LENGTH_SHORT).show();
}
})
.create()
.show();
} else { // 최초 요청시. 설명 없이 해당 권한을 요청.
requestPermissions(new String[]{Manifest.permission.CALL_PHONE}, 1000);
}
} else { // 사용자가 권한을 승락함. 바로 실행.
Intent intent = new Intent(Intent.ACTION_CALL, Uri.parse("tel:010-1234-5678"));
startActivity(intent);
}
} else { // 마시멜로우 미만 버전. 즉시 실행.
Intent intent = new Intent(Intent.ACTION_CALL, Uri.parse("tel:010-1234-5678"));
startActivity(intent);
}
}
이렇게 퍼미션 권한을 획득할 수 있으나 매번 퍼미션 권한 획득을 요청하기 위해 이 모든 코드를 사용할 수는 없으므로, 중복되는 부분을 하나의 메소드로 따로 구성해 놓는것이 현명하다고 생각한다.
'Programming > Android' 카테고리의 다른 글
Service 사용 (0) | 2017.02.22 |
---|---|
Custom ListView (0) | 2017.02.17 |
Fragment 6. 실 사용예 (0) | 2017.02.15 |
Fragment - 5. Fragment 수명 주기 처리+ (0) | 2017.02.15 |
Fragment - 4. 액티비티와의 통신 (0) | 2017.02.15 |