Android 6.0 RuntimePermission

Android 6.0 棉花糖,app将不会在安装的时候授予权限,取而代之的是,app不得不在运行时一个一个询问用户授予权限。

1
2
3
4
5
6
7
8
9
10
android {
compileSdkVersion 23
...
defaultConfig {
...
targetSdkVersion 23
...
}
}

如果 app/build.gradle 里的 targetSdkVersion 是23的话,在6.0上运行一些权限,没有做处理,会直接崩溃:

1
2
3
4
5
6
...
Caused by: java.lang.SecurityException: Permission Denial:
opening provider com.android.providers.contacts.ContactsProvider2 from ProcessRecord
{c3b3c66 16712:com.wuxiaolong.apksample/u0a62} (pid=16712, uid=10062)
requires android.permission.READ_CONTACTS or android.permission.WRITE_CONTACTS
...

如果 targetSdkVersion 22,就不会发生这样的错误,下面就来实践下这个 Android 6.0运行的权限:

单个权限

效果预览

这里以通讯录示例,按照惯例,先上效果图:

说明:第一次请求授权,是没有“不再询问”,拒绝后,再次请求授权,就会出现“不再询问”。

AndroidManifest.xml

1
<uses-permission android:name="android.permission.READ_CONTACTS" />

如果没有注册权限,授权时直接返回拒绝,并没有报错。

代码调用

在需要使用到权限之前调用以下方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static int REQUEST_READ_CONTACTS = 101;
/**
* 单独请求通讯录权限
*/
@TargetApi(M)
public void requestContacts() {
switch (checkSelfPermission(READ_CONTACTS)) {
case PackageManager.PERMISSION_GRANTED:
// 已有授权
Log.i("wxl", "已有授权");
break;
case PackageManager.PERMISSION_DENIED:
// 1、没有权限:尚未请求过权限;
// 2、或者请求授权被拒绝,用shouldShowRequestPermissionRationale判断用户是否拒绝过,如果返回true,表示用户拒绝过,
// 再次请求权限,将会出现“不再询问”,勾上“不再询问”,只能选择拒绝,再次进入,shouldShowRequestPermissionRationale始终false
// 3、或者曾经授权过,但用户在设置中禁用权限
Log.i("wxl", "是否拒绝过=" + shouldShowRequestPermissionRationale(READ_CONTACTS));
requestPermissions(new String[]{READ_CONTACTS}, REQUEST_READ_CONTACTS);
break;
default:
break;
}
}

checkSelfPermission:检查权限
requestPermissions:请求权限
shouldShowRequestPermissionRationale:判断用户是否拒绝过

授权回调

不管是允许或拒绝,都会回调:

1
2
3
4
5
6
7
8
9
10
11
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
if (requestCode == REQUEST_READ_CONTACTS) {
if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// 授权请求被通过,读取通讯录
Log.i("wxl", "onRequestPermissionsResult=授权请求被通过,读取通讯录");
} else {
Log.i("wxl", "onRequestPermissionsResult=授权请求不被通过");
}
}
}

多个权限

如果同时请求多个权限,那应该怎么写呢?这里以通讯录和定位权限示例:

效果预览

AndroidManifest.xml

1
2
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>

注册权限,不用多说了。

代码调用

在需要使用到权限之前调用requestContactsLocation方法:

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
public static int REQUEST_READ_CONTACTS_LOCATION = 102;
/**
* 同时请求通讯录定位权限
*/
@TargetApi(M)
public void requestContactsLocation() {
List<String> permissionsList = new ArrayList<>();
permissionsList.add(READ_CONTACTS);
permissionsList.add(ACCESS_FINE_LOCATION);
requestPermissions(permissionsList.toArray(new String[permissionsList.size()]), REQUEST_READ_CONTACTS_LOCATION);
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
if (requestCode == REQUEST_READ_CONTACTS_LOCATION) {
Map<String, Integer> perms = new HashMap<>();
// Initial
perms.put(ACCESS_FINE_LOCATION, PackageManager.PERMISSION_GRANTED);
perms.put(READ_CONTACTS, PackageManager.PERMISSION_GRANTED);
for (int i = 0; i < permissions.length; i++)
perms.put(permissions[i], grantResults[i]);
if (perms.get(ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED
&& perms.get(READ_CONTACTS) == PackageManager.PERMISSION_GRANTED) {
Log.i("wxl", "授权请求被通过");
} else {
// Permission Denied
Log.i("wxl", "授权请求不被通过");
}
}
}

权限分组

权限那么多,如果一个个判断,岂不是会疯掉,如图:

这里总共有九大组,同一组的任何一个权限被授权了,其他权限也自动被授权。例如,一旦WRITE_CONTACTS被授权了,app也有READ_CONTACTS和GET_ACCOUNTS了。

简单封装

写在基类:

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
public class BaseActivity extends AppCompatActivity {
public static int REQUEST_PERMISSION = 100;
public onPermissionCallbackListener onPermissionCallbackListener;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@TargetApi(M)
public void requestRuntimePermission(String permission, onPermissionCallbackListener onPermissionCallbackListener) {
this.onPermissionCallbackListener = onPermissionCallbackListener;
switch (checkSelfPermission(permission)) {
case PackageManager.PERMISSION_GRANTED:
// 已有授权
Log.i("wxl", "已有授权");
if (this.onPermissionCallbackListener != null)
onPermissionCallbackListener.onGranted();
break;
case PackageManager.PERMISSION_DENIED:
// 1、没有权限:尚未请求过权限;
// 2、或者请求授权被拒绝,用shouldShowRequestPermissionRationale判断用户是否拒绝过,如果返回true,表示用户拒绝过,
// 再次请求权限,将会出现“不再询问”,勾上“不再询问”,只能选择拒绝,再次进入,shouldShowRequestPermissionRationale始终false
// 3、或者曾经授权过,但用户在设置中禁用权限
Log.i("wxl", "是否拒绝过=" + shouldShowRequestPermissionRationale(permission));
requestPermissions(new String[]{permission}, REQUEST_PERMISSION);
break;
default:
break;
}
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
if (requestCode == REQUEST_PERMISSION) {
if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// 授权请求被通过,读取通讯录
Log.i("wxl", "onRequestPermissionsResult=授权请求被通过");
if (onPermissionCallbackListener != null)
onPermissionCallbackListener.onGranted();
} else {
Log.i("wxl", "onRequestPermissionsResult=授权请求不被通过");
if (onPermissionCallbackListener != null)
onPermissionCallbackListener.onDenied();
}
}
}
}

这里新建了接口onPermissionCallbackListener,为了回调方便:

1
2
3
4
5
public interface onPermissionCallbackListener {
void onGranted();
void onDenied();
}

在需要使用到权限之前调用requestRuntimePermission方法,如通讯录权限:

1
2
3
4
5
6
7
8
9
10
requestRuntimePermission(READ_CONTACTS, new onPermissionCallbackListener() {
@Override
public void onGranted() {
Log.i("wxl", "授权请求通过");
}
@Override
public void onDenied() {
Log.i("wxl", "授权请求拒绝");
}
});

其他权限调用示例:

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
public void onPermission(View v) {
switch (v.getId()) {
case R.id.calendar:
//日历
requestRuntimePermission(READ_CALENDAR, this);
break;
case R.id.camera:
//照相机
requestRuntimePermission(CAMERA, this);
break;
case R.id.contacts:
//通讯录
requestRuntimePermission(READ_CONTACTS, this);
break;
case R.id.location:
//定位
requestRuntimePermission(ACCESS_FINE_LOCATION, this);
break;
case R.id.microPhone:
//录音
requestRuntimePermission(RECORD_AUDIO, this);
break;
case R.id.readPhone:
//读取手机状态
requestRuntimePermission(READ_PHONE_STATE, this);
break;
case R.id.sensors:
//传感器
requestRuntimePermission(BODY_SENSORS, this);
break;
case R.id.sms:
//短信
requestRuntimePermission(SEND_SMS, this);
break;
case R.id.storage:
//文件管理
requestRuntimePermission(READ_EXTERNAL_STORAGE, this);
break;
}
}

源码

https://github.com/WuXiaolong/AndroidSamples/blob/master/app/src/main/java/com/wuxiaolong/androidsamples/runtimepermission/RuntimePermissionActivity.java

参考

Android M 新的运行时权限开发者需要知道的一切

更新日志

  • 2017.03.04 修改
  • 2016.02.04 撰写


联系作者

我的微信公众号:吴小龙同学,欢迎关注交流,公号回复关键字「1024」有惊喜哦。