본문 바로가기

개발 코딩 정보 공유/안드로이드 자바 코틀린

안드로이드 런타임 퍼미션 깨부수기

 

 

 

 

 

 

 

 

안드로이드 지식 공유

 

 

 

런타임 퍼미션 관리하기

 

 

 

 

안드로이드는 6.0(API 23) 마시멜로우를 기점으로 런타임 퍼미션이 적용되었습니다. 기존에 사용하던 설치시점에서의 권한 부여 방식은 잊으셔야 합니다. 런타임 퍼미션 적용으로 인해 사용자가 직접 권한을 허용/거부할수 있으며 설정으로 들어가면 언제든 허용/거부를 다시 컨트롤 할 수 있습니다.

 

* 주의 : 런타임 퍼미션 적용은 상당히 귀찮고 피곤함을 야기시킵니다.

 

안드로이드 시스템의 권한의 경우 두가지로 나눠 볼 수 있는데요.

 

 

1. normal

2. dangerous

 

 

1은 그야말로 권한 설정을 해도 아무 위험이 없는 경우, 2는 개인정보 등의 이유로 위험이 되는경우 입니다. 

1의 경우 메니페스트 설정을 통해 나열하면 시스템에 권한이 부여됩니다. 문제는 2 인데요. 

이놈은 우리가 따로 런타임 퍼미션 로직을 추가 해줘야 합니다.

 

 

권한 확인

 

권한이 필요한 작업을 요하는 경우 마다 권한을 체크해야 합니다. 유저는 언제든 권한을 취소할 수 있으므로 

처음 허용을 했다고 계속 허용이 아니라는 말입니다. 권한 체크는 ContextCompat.checkSelfPermission()

를 통해 알수 있습니다. 

 

 

 

1
2
3
int permissionCheck = ContextCompat.checkSelfPermission(thisActivity, 
Manifest.permission.WRITE_CALENDAR);
cs

 

 

 

앱에 권한이 있는 경우 이 메서드는 PackageManager.PERMISSION_GRANTED를 반환하고, 

앱이 작업을 계속 진행할 수 있습니다. 앱에 권한이 없는 경우 이 메서드는 PERMISSION_DENIED를 반환하고, 

앱이 사용자에게 명시적으로 권한을 요청해야 합니다.

 

 

 

권한 요청

 

안드로이드 6.0이하가 아니라면 메니페스트에 정의된 위험 권한의 경우 반드시 런타임 퍼미션으로 ... 사용자에게 요청해야합니다. 권한확인후 없다는 걸 알았다면. 권한을 요청해야죠. requestPermissions() 를 통해 사용자에게 권한 체크 다이얼로그를 띄울수 있습니다. 또한 결과값에 따른 콜백으로 onRequestPermissionsResult() 에서 확인 할 수 있습니다.

 

 

 

 

 

기타 

 

만약 requestPermissions() 를 통해 요청했는데 사용자가 거부를 했다고 하면?

그럴때를 대비해 shouldShowRequestPermissionRationale() 메서드가 존재합니다.

거부후에는 값이 true로 변경된 걸 확인 할 수 있습니다. (다시 묻지 않기를 선택하게되면 false로 나옵니다.)

이 부분에서 왜 권한을 겟 해야하는지를 설명을 해주면 되겠습니다. (다이얼로그같은걸 띄워서 설명하면되겠죠?)

 

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.test.mynewapplication">
 
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.CAMERA" />
 
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
 
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
 
        <activity android:name=".RuntimePermActivity"></activity>
 
    </application>
 
</manifest>
 
 
 
 
cs

 

 

 

메니페스트의 user-permission 을 통해 원하는 권한을 나열해 줍니다. 이중에는 노멀그룹에 속하는 권한도 있고 위험그룹에 속하는 권한도 있겠죠. 노멀그룹은 나열하는 것만으로 권한을 획득 합니다.

 

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
 
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
 
 
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".RuntimePermActivity">
 
    <Button
        android:id="@+id/btnExe"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="172dp"
        android:text="실행하기"
        app:layout_constraintBottom_toTopOf="@+id/btnRequestPerm"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.912" />
 
    <Button
        android:id="@+id/btnRequestPerm"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:text="퍼미션체크"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.61" />
 
</android.support.constraint.ConstraintLayout>
 
 
cs

 

 

 

레이아웃에 버튼을 만들고 그에 따른 기능을 정의해 보겠습니다.

 

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
import static android.Manifest.permission.CAMERA;
 
public class RuntimePermActivity extends AppCompatActivity {
 
    private Button btnExe;
    private Button btnPermCheck;
    //퍼미션 권한 관련
    private String[] perms = {ACCESS_FINE_LOCATION, CAMERA};
    private static final int PERM_REQUEST_CODE = 200;
 
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_runtime_perm);
 
        btnExe = findViewById(R.id.btnExe);
        btnPermCheck = findViewById(R.id.btnRequestPerm);
 
        //상태를 체크 합니다.
        btnExe.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View v) {
                if (checkPermission()) {
 
                    Snackbar.make(v, "권한을 획득하셨네요.", Snackbar.LENGTH_LONG).show();
 
                } else {
 
                    Snackbar.make(v, "권한이 없습니다. 요청하세요.", Snackbar.LENGTH_LONG).show();
                }
            }
        });
]
        //권한을 요청하는 버튼
        btnPermCheck.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View v) {
                if (!checkPermission()) {    
                    //권한 요청 로직
                    requestPermission();
 
                } else {
 
                    Snackbar.make(v, "권한을 이미 획득 하셨네요.", Snackbar.LENGTH_LONG).show();
 
                }
            }
        });
 
    }
 
    //권한을 체크하고 둘줄 하나라도 권한이 없다면 false
    private boolean checkPermission() {
        int result = ContextCompat.checkSelfPermission(getApplicationContext(), perms[0]);
        int result1 = ContextCompat.checkSelfPermission(getApplicationContext(), perms[1]);
 
        return result == PackageManager.PERMISSION_GRANTED &&
                result1 == PackageManager.PERMISSION_GRANTED;
    }
 
    //권한을 요청한다
    private void requestPermission() {
        ActivityCompat.requestPermissions(this, perms, PERM_REQUEST_CODE);
    }
 
 
    /**
    * 권한을 요청하고 그에 따른 결과를 콜백 받는다.
    * 허용/거부에 따른 해당 로직 구현
    */
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        switch (requestCode) {
            case PERM_REQUEST_CODE:
                if (grantResults.length > 0) {
 
                    //권한결과 확인
                    boolean locationAccepted = grantResults[0== PackageManager.PERMISSION_GRANTED;
                    boolean cameraAccepted = grantResults[1== PackageManager.PERMISSION_GRANTED;
 
                    View view = getWindow().getDecorView().findViewById(android.R.id.content);
 
                    //ok
                    if (locationAccepted && cameraAccepted) {
                        Snackbar.make(view,
                                "권한을 획득했습니다. 위치정보에 접근할 수 있습니다.",
                                Snackbar.LENGTH_LONG).show();
                    }else { // denied
 
                        Snackbar.make(view,
                                "권한 요청을 거부하셨습니다. 위치정보를 사용할 수 없습니다.",
                                Snackbar.LENGTH_LONG).show();
 
                        //마쉬멜로우 이상에서는 퍼미션 설명후 재시도
                        //shouldShowRequestPermissionRationale 을 통해 권한 거부 설정 체크
                        /*if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                            if (shouldShowRequestPermissionRationale(ACCESS_FINE_LOCATION) ||
                                    shouldShowRequestPermissionRationale(CAMERA)) {
 
                                showOKCancel("You need to allow access to both the permissions",
                                        new DialogInterface.OnClickListener() {
                                            @Override
                                            public void onClick(DialogInterface dialog, int which) {
                                                requestPermissions(perms, 300);
                                            }
                                        });
 
                                return;
                            }
                        }*/
 
                    }
                }//case if end
                break;
        } //switch end
    }
 
 
    private void showOKCancel(String message, DialogInterface.OnClickListener okListener) {
        new AlertDialog.Builder(this)
                .setMessage(message)
                .setPositiveButton("OK", okListener)
                .setNegativeButton("Cancel"null)
                .create()
                .show();
    }
 
 
}
cs

 

  

 

 

1. ContextCompat.checkSelfPermission 을 통해서 권한을 확인 합니다.

2. ActivityCompat.requestPermissions 을 통해서 권한을 요청합니다. 이때 넘겨준 PERM_REQUEST_CODE 는 onRequestPermissionsResult 에서 확인 할 수 있습니다.

3. onRequestPermissionsResult 에서 결과를 확인하고 그에 따른 동작을 정의 합니다.

 

 

 

이것이 전부 입니다. ^^ 쉽지만 귀찮은 작업임이 분명합니다. 

 

* 조금 다른 방식으로 처리 하자면 이런식으로 설정화면으로 넘겨주고 사용자가 직접 변경 하게 처리하는것도 한가지 방법이겠죠.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
final AlertDialog dialog = new AlertDialog(this);
    dialog.setMessage("권한요청");
    dialog.setPositiveButton("OK"new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, 
         startActivityForResult(intent, 200);
            dialog.dismiss();
        }
    }).setNegativeButton("Cancel"new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            dialog.dismiss();
        }
    });
   cs