Google早在Android6.0的时候就全面修改了应用程序的权限申请机制,除了需要在AndroidManifest.xml配置文件中事先做好权限配置之外,部分被认为是危险的系统权限还需要在代码中专门去单独申请。Google将这一改变称为“运行时权限(RunTimePermissions)”
由于之前一直有在用百度地图API实现一些地图定位的小功能,而自己手边并迟迟没有得到Android6.0以上版本的手机或是移动设备,所以这个运行时权限的问题始终没有被我重视。直到最近周围有人的手机升级到了Android6.x后,才发现了这一问题:百度地图API无法正常进行定位了。
在网上搜索了一下,解决方案其实非常简单,自己整理如下:
第一步:确定哪些系统权限是需要在运行时进行动态申请的
首先,以下这位博主整理出了所有的系统权限及分组:
http://www.tuicool.com/articles/rIjEniV
在他的文章中,将所有的系统权限及分组罗列了出来,并且说明了在代码中是可以按组直接申请运行时权限的,且每组只要有一个权限申请成功了,就默认整组权限都可以使用了:
group:android.permission-group.CONTACTS
permission:android.permission.WRITE_CONTACTS
permission:android.permission.GET_ACCOUNTS
permission:android.permission.READ_CONTACTS
group:android.permission-group.PHONE
permission:android.permission.READ_CALL_LOG
permission:android.permission.READ_PHONE_STATE
permission:android.permission.CALL_PHONE
permission:android.permission.WRITE_CALL_LOG
permission:android.permission.USE_SIP
permission:android.permission.PROCESS_OUTGOING_CALLS
permission:com.android.voicemail.permission.ADD_VOICEMAIL
group:android.permission-group.CALENDAR
permission:android.permission.READ_CALENDAR
permission:android.permission.WRITE_CALENDAR
group:android.permission-group.CAMERA
permission:android.permission.CAMERA
group:android.permission-group.SENSORS
permission:android.permission.BODY_SENSORS
group:android.permission-group.LOCATION
permission:android.permission.ACCESS_FINE_LOCATION
permission:android.permission.ACCESS_COARSE_LOCATION
group:android.permission-group.STORAGE
permission:android.permission.READ_EXTERNAL_STORAGE
permission:android.permission.WRITE_EXTERNAL_STORAGE
group:android.permission-group.MICROPHONE
permission:android.permission.RECORD_AUDIO
group:android.permission-group.SMS
permission:android.permission.READ_SMS
permission:android.permission.RECEIVE_WAP_PUSH
permission:android.permission.RECEIVE_MMS
permission:android.permission.RECEIVE_SMS
permission:android.permission.SEND_SMS
permission:android.permission.READ_CELL_BROADCASTS
除此以外,这位博主还罗列出了一些不需要在运行时申请的普通权限,这些权限还像Android6.0之前那样在AndroidManifest.xml文件中进行配置就可以了:
android.permission.ACCESS_LOCATION_EXTRA_COMMANDS
android.permission.ACCESS_NETWORK_STATE
android.permission.ACCESS_NOTIFICATION_POLICY
android.permission.ACCESS_WIFI_STATE
android.permission.ACCESS_WIMAX_STATE
android.permission.BLUETOOTH
android.permission.BLUETOOTH_ADMIN
android.permission.BROADCAST_STICKY
android.permission.CHANGE_NETWORK_STATE
android.permission.CHANGE_WIFI_MULTICAST_STATE
android.permission.CHANGE_WIFI_STATE
android.permission.CHANGE_WIMAX_STATE
android.permission.DISABLE_KEYGUARD
android.permission.EXPAND_STATUS_BAR
android.permission.FLASHLIGHT
android.permission.GET_ACCOUNTS
android.permission.GET_PACKAGE_SIZE
android.permission.INTERNET
android.permission.KILL_BACKGROUND_PROCESSES
android.permission.MODIFY_AUDIO_SETTINGS
android.permission.NFC
android.permission.READ_SYNC_SETTINGS
android.permission.READ_SYNC_STATS
android.permission.RECEIVE_BOOT_COMPLETED
android.permission.REORDER_TASKS
android.permission.REQUEST_INSTALL_PACKAGES
android.permission.SET_TIME_ZONE
android.permission.SET_WALLPAPER
android.permission.SET_WALLPAPER_HINTS
android.permission.SUBSCRIBED_FEEDS_READ
android.permission.TRANSMIT_IR
android.permission.USE_FINGERPRINT
android.permission.VIBRATE
android.permission.WAKE_LOCK
android.permission.WRITE_SYNC_SETTINGS
com.android.alarm.permission.SET_ALARM
com.android.launcher.permission.INSTALL_SHORTCUT
com.android.launcher.permission.UNINSTALL_SHORTCUT
在我上网翻查资料的时候,找到了另外一位博主,在他的文章中也提到了哪些是普通权限,仅需AndroidManifest.xml文件配置即可,而哪些是危险权限,需要运行时动态申请:
http://www.cnblogs.com/cr330326/p/5181283.html
他在文章中列出的权限说明如下:
普通权限:
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
INTERNET
KILL_BACKGROUND_PROCESSES
MODIFY_AUDIO_SETTINGS
NFC
READ_SYNC_SETTINGS
READ_SYNC_STATS
RECEIVE_BOOT_COMPLETED
REORDER_TASKS
REQUEST_INSTALL_PACKAGES
SET_TIME_ZONE
SET_WALLPAPER
SET_WALLPAPER_HINTS
TRANSMIT_IR
USE_FINGERPRINT
VIBRATE
WAKE_LOCK
WRITE_SYNC_SETTINGS
SET_ALARM
INSTALL_SHORTCUT
UNINSTALL_SHORTCUT
危险权限:
Permission Group | Permissions |
---|---|
CALENDAR | |
CAMERA | |
CONTACTS | |
LOCATION | |
MICROPHONE | |
PHONE | |
SENSORS | |
SMS | |
STORAGE |
所有这些权限及其分组的声明,我相信在Google官方网站上一定会及时更新。只是我比较懒,不想为这事儿去爬梯子。而以上两位博主在自己文章中的说明我认为已经足够细致了。
第二步:在代码中确认应用是否获得到了权限
在我的案例里,我需要为百度地图API动态申请运行时权限。这里我参考了以下这位博主的文章:
http://blog.csdn.net/qq_25817651/article/details/50913055
这位博主在自己的文章中列出了百度地图API所涉及到的系统权限中,以下这些是需要在运行时动态获取的:
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_PHONE_STATE
接下来就是首先判断是否获得到了这些权限,如果没有获得到的话,则进行申请。
2.1、判断Android的版本号是否在6.0之上
if(Build.VERSION.SDK_INT >= 23) {
//Android6.0以上,需要动态申请运行时权限
}
else{
//Android6.0以下,直接进行地图定位操作
}
2.2、检查是否获得到了想要的权限
ActivityCompat.checkSelfPermission(YourActivity.class, Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED
简单说明一下:
/**
* 这个方法在Android6.0以上的版本中,动态申请运行时权限,从而能够保证在百度地图上正常定位
*/
private void checkRunTimePermissionAndLocateOnTheMap(MapView mapView,String address) {
if(Build.VERSION.SDK_INT >= 23) {
/*
* 判断一下,在6.0以上版本的Android系统中需要申请权限
* 以下这些权限都是百度地图Api当中所用到的,且必须动态申请的权限
*/
if (ActivityCompat.checkSelfPermission(this.currentActivity, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED
|| ActivityCompat.checkSelfPermission(this.currentActivity, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED
|| ActivityCompat.checkSelfPermission(this.currentActivity, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED
|| ActivityCompat.checkSelfPermission(this.currentActivity, Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) {
/*
* 系统并没有授权,则申请权限
*/
requestRunTimePermission();
} else {
/*
* 已经授予了权限,则在地图上定位地址
*/
Log.i(TAG,"CAMERA permission has already been granted. Displaying camera preview.");
this.locateOnMap(mapView,address);
}
}
else{
/*
* 在6.0以下版本的Android系统中无需在运行时申请权限,可以直接在地图上定位地址
*/
this.locateOnMap(mapView,address);
}
}
对上述代码片段说明一下:
2.3、动态申请运行时权限
ActivityCompat.shouldShowRequestPermissionRationale(YourActivity.class,Manifest.permission.READ_PHONE_STATE)
简单说明一下:
ActivityCompat.requestPermissions(YourActivity,
new String[]{
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_PHONE_STATE
},REQUEST_CODE);
简单说明一下:
private static final int REQUEST_CODE = 1;
private void requestRunTimePermission() {
if (ActivityCompat.shouldShowRequestPermissionRationale(this.currentActivity,Manifest.permission.ACCESS_COARSE_LOCATION)
|| ActivityCompat.shouldShowRequestPermissionRationale(this.currentActivity,Manifest.permission.ACCESS_FINE_LOCATION)
|| ActivityCompat.shouldShowRequestPermissionRationale(this.currentActivity,Manifest.permission.WRITE_EXTERNAL_STORAGE)
|| ActivityCompat.shouldShowRequestPermissionRationale(this.currentActivity,Manifest.permission.READ_PHONE_STATE)) {
/*
* 如果没有获得过用户的权限许可,则向用户申请
*/
Snackbar.make(this.currentActivity.findViewById(R.id.LAYOUT_SHOW_ASSET_INFO), R.string.permission_location_rationale,
Snackbar.LENGTH_INDEFINITE).setAction(R.string.str_confirm, new View.OnClickListener() {
@Override
public void onClick(View view) {
ActivityCompat.requestPermissions(currentActivity,
new String[]{
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_PHONE_STATE
},REQUEST_CODE);
}
}).show();
} else {
/*
* 如果已经获得过用户的权限许可了,则直接申请运行时权限即可
*/
ActivityCompat.requestPermissions(currentActivity,
new String[]{
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_PHONE_STATE
},REQUEST_CODE);
}
}
简单说明一下:
compile 'com.android.support:design:23.1.1'
其中的版本号需要与你的targetSdkVersion相同
2.4、处理用户的反馈
在我的这个案例中,当用户点击了Snackbar上的按钮后,应用程序就会自动调用一个名为onRequestPermissionResult()的和回调方法。这个方法是来自接口ActivityCompat.OnRequestPermissionsResultCallback的,所以你的类必须实现这个接口:
public class YourClass implements ActivityCompat.OnRequestPermissionsResultCallback {
//...
}
在我的案例中,整个回调方法是这样的:
/**
* 处理运行时权限的申请结果
*/
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (requestCode == REQUEST_CODE) {
if (PermissionUtil.verifyPermissions(grantResults)) {
Snackbar.make(this.currentActivity.findViewById(R.id.LAYOUT_SHOW_ASSET_INFO), R.string.permision_available,Snackbar.LENGTH_SHORT).show();
} else {
Snackbar.make(this.currentActivity.findViewById(R.id.LAYOUT_SHOW_ASSET_INFO), R.string.permissions_not_granted,Snackbar.LENGTH_SHORT).show();
}
} else {
//
}
}
简单说明一下:
还记得在步骤2.3中所提到的那个由程序员自己声明的int常量吗,它又在这里出现了。通过它我们能够判断经过用户同意的授权是用在哪个具体的业务上的,从而可以针对性地去编写业务代码。如果你的Activity上十分复杂,需要分开多次申请不同的运行时权限的话,这个int常量会变得十分有用。
方法PermissionUtil.verfyPermissions()来自Google官方示例,这个方法用来处理一组多个权限,查看这些权限是否都被用户所允许了。这个类的完整代码如下:
package com.yumi.mibao.util;
import android.app.Activity;
import android.content.pm.PackageManager;
/**
* Utility class that wraps access to the runtime permissions API in M and provides basic helper
* methods.
*/
public abstract class PermissionUtil {
/**
* Check that all given permissions have been granted by verifying that each entry in the
* given array is of the value {@link PackageManager#PERMISSION_GRANTED}.
*
* @see Activity#onRequestPermissionsResult(int, String[], int[])
*/
public static boolean verifyPermissions(int[] grantResults) {
// At least one result must be checked.
if(grantResults.length < 1){
return false;
}
// Verify that each required permission has been granted, otherwise return false.
for (int result : grantResults) {
if (result != PackageManager.PERMISSION_GRANTED) {
return false;
}
}
return true;
}
}