Android6.0运行时权限,化繁为简

前言

权限申请效果

GIF.gif

GitHub地址PermissionUtils

Android8.0昨天已经发布,但是关于Android版本的最新统计,来看看图

image.png

很明显,6.0版本目前比重最高,从6.0开始的运行时权限,是每个Android开发者绕不过的问题

正文

虽然Google大佬允许我们将targetSdkVersion 设置为22及以下,但是向来紧跟Google大佬步伐的我们,怎么会用这种投机取消的方式呢。

关于Android权限,Google分为两类,一类为普通权限,默认在AndroidManifest中注册即可,主要为以下,这类权限默认不需要用户授权,比如震动、访问网络权限:

ACCESS_LOCATION_EXTRA_COMMANDS
ACCESS_NETWORK_STATE
ACCESS_NOTIFICATION_POLICY
ACCESS_WIFI_STATE
BLUETOOTH
BLUETOOTH_ADMIN
BROADCAST_STICKY
CHANGE_NETWORK_STATE
CHANGE_WIFI_MULTICAST_STATE
CHANGE_WIFI_STATE
DISABLE_KEYGUARD
EXPAND_STATUS_BAR
GET_PACKAGE_SIZE
INSTALL_SHORTCUT
INTERNET
KILL_BACKGROUND_PROCESSES
MODIFY_AUDIO_SETTINGS
NFC
READ_SYNC_SETTINGS
READ_SYNC_STATS
RECEIVE_BOOT_COMPLETED
REORDER_TASKS
REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
REQUEST_INSTALL_PACKAGES
SET_ALARM
SET_TIME_ZONE
SET_WALLPAPER
SET_WALLPAPER_HINTS
TRANSMIT_IR
UNINSTALL_SHORTCUT
USE_FINGERPRINT
VIBRATE
WAKE_LOCK
WRITE_SYNC_SETTINGS

而另一类就是危险权限,涉及用户隐私或影响设备安全
根据表中可以看出,危险权限都是成组出现的,那么在授权时,如果申请一组中某个权限,权限申请弹窗会提示整组权限的说明,而且如果一组权限中的某个权限已经被授权,那么在申请同组其他权限时,系统立即授权,不需要通过用户允许。
注意: 运行时权限依然要在AndroidManifest中注册

Permission Group Permissions
CALENDAR READ_CALENDARWRITE_CALENDAR
CAMERA CAMERA
CONTACTS READ_CONTACTSWRITE_CONTACTSGET_ACCOUNTS
LOCATION ACCESS_FINE_LOCATIONACCESS_COARSE_LOCATION
MICROPHONE RECORD_AUDIO
PHONE READ_PHONE_STATECALL_PHONEREAD_CALL_LOGWRITE_CALL_LOGADD_VOICEMAILUSE_SIPPROCESS_OUTGOING_CALLS
SENSORS BODY_SENSORS
SMS SEND_SMSRECEIVE_SMSREAD_SMSRECEIVE_WAP_PUSHRECEIVE_MMS
STORAGE READ_EXTERNAL_STORAGEWRITE_EXTERNAL_STORAGE

开始适配
这里举例适配Manifest.permission.ACCESS_FINE_LOCATION位置权限

  • 1、在AndroidManifest中注册权限
  • 2、检查权限

    1
    2
    3
    ContextCompat.checkSelfPermission(thisActivity,
    Manifest.permission.ACCESS_FINE_LOCATION)
    != PackageManager.PERMISSION_GRANTED
  • 3、申请权限

    1
    2
    3
    ActivityCompat.requestPermissions(thisActivity,
    new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
    myRequestCode);
  • 4、申请回调的处理

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    @Override
    public void onRequestPermissionsResult(int requestCode,
    String permissions[], int[] grantResults) {
    switch (requestCode) {
    case myRequestCode: {
    if (grantResults.length > 0
    && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
    //申请通过
    } else {
    //申请失败
    //是否需要向用户解释申请的原因,在用户点击拒绝后,弹出dialog,给用户解释
    if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity,
    Manifest.permission.ACCESS_FINE_LOCATION))
    }
    }
    }
    }
    }

封装
运行时权限的流程很清晰,那么开发中每次写这么多固定代码,为何不进行封装一下呢?那就来分析申请的流程和特点,进行处理

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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
package com.ddz.lifestyle.utils;
import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.provider.Settings;
import android.support.annotation.IntRange;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.Fragment;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AlertDialog;
import android.util.SparseArray;
import com.ddz.lifestyle.LifeStyle;
import com.ddz.lifestyle.http.GankApiStores;
import com.ddz.lifestyle.http.bean.BookBean;
import com.tencent.smtt.sdk.QbSdk;
import org.jetbrains.annotations.NotNull;
/**
* @Author: ddz
* Creation time: 17.8.11 17:11
* describe:{Android权限申请的封装,并在内部实现申请结果的处理}
*/
public class PermissionUtils {
private static PermissionUtils permission;
private String[] permissions;
private Activity mActivity;
private RequestPermissionListener PermissionListener;
public static int requestCode = 100; //requestCode传值为100
public static PermissionUtils getInstance() {
if (null == permission) {
synchronized (PermissionUtils.class) {
if (null == permission) {
permission = new PermissionUtils();
}
}
}
return permission;
}
/**
* 权限检查
*
* @param permission
* @return
*/
public boolean checkPermission(@NonNull String permission) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return true;
} else {
return ContextCompat.checkSelfPermission(LifeStyle.getContext(), permission) == PackageManager.PERMISSION_GRANTED;
}
}
/**
* Activity 页面申请权限
*
* @param activity
* @param permissions
* @param requestCode
* @param requestPermissionListener
*/
public void requestPermissiion(final @NonNull Activity activity,
final @NonNull String[] permissions, final @IntRange(from = 0) int requestCode, @NonNull RequestPermissionListener requestPermissionListener) {
this.mActivity = activity;
PermissionListener = requestPermissionListener;
this.permissions = permissions;
ActivityCompat.requestPermissions(activity, permissions, requestCode);
}
/**
* Fragment页面申请权限
*
* @param fragment
* @param permissions
* @param requestCode
* @param requestPermissionListener
*/
public void requestFragmentPermission(final @NonNull Fragment fragment,
final @NonNull String[] permissions, final @IntRange(from = 0) int requestCode, @NonNull RequestPermissionListener requestPermissionListener) {
PermissionListener = requestPermissionListener;
this.permissions = permissions;
fragment.requestPermissions(permissions, requestCode);
}
/**
* 权限申请结果的回调
*
* @param activity
* @param requestCode
* @param permissions
* @param grantResults
*/
public void onRequestPermissionResult(final @NonNull Activity activity, int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
this.mActivity = activity;
if (null != PermissionListener) {
if (requestCode == PermissionUtils.requestCode && grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
PermissionListener.requestConfirm();
} else {
if (ActivityCompat.shouldShowRequestPermissionRationale(activity, permissions[0])) {
PermissionListener.requestCancel();
} else {
PermissionListener.requestCancelAgain();
}
}
}
}
/**
* 用户点击拒绝,弹出申请权限的说明弹窗,也可以自定义实现
*
* @param context Context
* @param title 弹窗标题
* @param message 申请权限解释说明
* @param confirm 确认按钮的文字,默认OK
* @param cancel 取消按钮呢的文字,默认不显示取消按钮
*/
public void requestDialog(Context context, @NonNull String title, @NonNull String message, String confirm, String cancel) {
try {
AlertDialog.Builder builder = new AlertDialog.Builder(context).setTitle(title).setMessage(message);
builder.setPositiveButton(confirm == null ? "OK" : confirm, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
requestPermissiion(mActivity, permissions, requestCode, PermissionListener);
dialog.dismiss();
}
});
if (null != cancel) {
builder.setNegativeButton(cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
}
builder.setCancelable(false);
builder.show();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 用户勾选不再显示并点击拒绝,弹出打开设置页面申请权限,也可以自定义实现
*
* @param context Context
* @param title 弹窗标题
* @param message 申请权限解释说明
* @param confirm 确认按钮的文字,默认OK
* @param cancel 取消按钮呢的文字,默认不显示取消按钮
*/
public void requestDialogAgain(Context context, @NonNull String title, @NonNull String message, String confirm, String cancel) {
try {
AlertDialog.Builder builder = new AlertDialog.Builder(context).setTitle(title).setMessage(message);
builder.setPositiveButton(confirm == null ? "OK" : confirm, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
startSettingActivity(mActivity);
dialog.dismiss();
}
});
if (null != cancel) {
builder.setNegativeButton(cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
}
builder.setCancelable(false);
builder.show();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 打开设置页面打开权限
*
* @param context
*/
public void startSettingActivity(@NonNull Activity context) {
try {
Intent intent =
new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.parse("package:" +
context.getPackageName()));
intent.addCategory(Intent.CATEGORY_DEFAULT);
context.startActivityForResult(intent, 10); //这里的requestCode和onActivityResult中requestCode要一致
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 打开设置页面的回调
*
* @param requestCode
* @param resultCode
* @param data
*/
public void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case 10: //这里值是打开设置页面申请权限的RequestCode,默认为10
try {
if (null != PermissionListener) {
if (null != permissions && permissions.length > 0) {
for (String permission : permissions) {
if (checkPermission(permission)) {
PermissionListener.requestConfirm();
} else {
PermissionListener.requestFailed();
}
}
} else {
PermissionListener.requestFailed();
}
} else {
PermissionListener.requestFailed();
}
} catch (Exception e) {
e.printStackTrace();
}
break;
}
}
/**
* 权限申请回调
*/
public interface RequestPermissionListener {
void requestConfirm(); //申请成功
void requestCancel(); //拒绝
void requestCancelAgain(); //勾选不再提示并拒绝
void requestFailed(); //在设置页面申请权限失败
}
}

封装的工具类使用 (分为四步)

检查权限

1
PermissionUtils.getInstance().checkPermission(Manifest.permission.ACCESS_FINE_LOCATION)

申请权限

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
PermissionUtils.getInstance().requestPermissiion(this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, PermissionUtils.requestCode, new PermissionUtils.RequestPermissionListener() {
@Override
public void requestConfirm() {
//申请成功
toast(null);
}
@Override
public void requestCancel() {
//用户拒绝,对用户解释申请理由
//如果想使用封装好的弹窗提示
PermissionUtils.getInstance().requestDialog(TestActivity.this, "申请权限", "需要位置权限", null, null);
}
@Override
public void requestCancelAgain() {
//用户勾选不再提示并拒绝,申请打开应用设置页面申请权限,具体逻辑自己写
//使用默认封装好的提示,并打开设置页面
PermissionUtils.getInstance().requestDialogAgain(TestActivity.this, "申请权限", "去设置页面打开位置限才能正常使用", null, null);
}
@Override
public void requestFailed() {
//申请失败
toast("对不起,没有权限,退出");
}
});

权限回调的处理

1
2
PermissionUtils.getInstance().onRequestPermissionResult(TestActivity.this, requestCode, permissions, grantResults);
super.onRequestPermissionsResult(requestCode, permissions, grantResults);

打开设置页面申请权限的处理

1
PermissionUtils.getInstance().onActivityResult(requestCode, resultCode, data);

根据封装好的工具类,写个简单例子

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
package com.ddz.lifestyle.view.activity;
import android.Manifest;
import android.content.Intent;
import android.os.Bundle;
import android.os.PersistableBundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.widget.Toast;
import com.ddz.lifestyle.R;
import com.ddz.lifestyle.utils.PermissionUtils;
/**
* @Author: ddz
* Creation time: 17.8.11 16:07
* describe:()
*/
public class TestActivity extends AppCompatActivity {
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
initPremission();
}
private void initPremission() {
if (PermissionUtils.getInstance().checkPermission(Manifest.permission.ACCESS_FINE_LOCATION)) {
toast(null);
} else {
PermissionUtils.getInstance().requestPermissiion(this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, PermissionUtils.requestCode, new PermissionUtils.RequestPermissionListener() {
@Override
public void requestConfirm() {
//申请成功
toast(null);
}
@Override
public void requestCancel() {
//用户拒绝,对用户解释申请理由
//如果想使用封装好的弹窗提示
PermissionUtils.getInstance().requestDialog(TestActivity.this, "申请权限", "需要位置权限", null, null);
}
@Override
public void requestCancelAgain() {
//用户勾选不再提示并拒绝,申请打开应用设置页面申请权限,具体逻辑自己写
//使用默认封装好的提示,并打开设置页面
PermissionUtils.getInstance().requestDialogAgain(TestActivity.this, "申请权限", "去设置页面打开位置限才能正常使用", null, null);
}
@Override
public void requestFailed() {
//申请失败
toast("对不起,没有权限,退出");
}
});
}
}
//一定要对权限回调进行处理
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
PermissionUtils.getInstance().onRequestPermissionResult(TestActivity.this, requestCode, permissions, grantResults);
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
//如果打开了设置页面申请权限,一定要对回调进行处理
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
PermissionUtils.getInstance().onActivityResult(requestCode, resultCode, data);
super.onActivityResult(requestCode, resultCode, data);
}
private void toast(String message) {
Toast.makeText(TestActivity.this, message == null ? "权限申请成功" : message, Toast.LENGTH_SHORT).show();
}
}

结束

封装后的权限申请,结构更加清晰,根据回调结果的不同可以做出相应处理,也封装了权限申请的说明弹窗,如果没有特殊的设计要求,即可满足日常开发的权限申请工作。
GitHub地址:PermissionUtils

文章目录
  1. 1. 前言
  2. 2. 正文
    1. 2.0.1. 结束
|