运行时权限(Runtime permission)
android的权限系统一直是首要的安全概念,因为这些权限只在安装的时候被询问一次。一旦安装了,app可以在用户毫不知晓的情况下访问权限内的所有东西。 这是极其危险的事情 所以,在Android M 权限请求设计改版了,有点类似iOS的权限请求
1461651981945.jpg
在android6.0棉花糖,app将不会在安装的时候授予权限。取而代之的是,app不得不在运行时一个一个询问用户授予权限。
注意权限询问对话框不会自己弹出来。开发者不得不自己调用。如果开发者要调用的一些函数需要某权限而用户又拒绝授权的话,函数将抛出异常甚至导致程序崩溃.
旧版兼容
为了与旧版本兼容,比如你的 build.gradle 中的 targetSdkVersion 设置为 23 之前,比如22. 也能在Android6.0 的手机上面跑,并且权限请求机制使用6.0之前的 安装时请求 的模式. 吐槽一下: Excuse Me? 这到底是兼容还是漏洞... targetSdkVersion 设置为23以前,不让跑6.0不是更合理? 可能处于市场应用的API版本考虑,不兼容估计大部分应用都不能跑6.0 所以,如果你觉得运行时弹出权限框让用户勾选很不友好,那么就取巧使用targetSdkVersion <23 吧,但这绝对不是长久之计...(丑陋...)
android {
compileSdkVersion
23
buildToolsVersion
"23.0.2"
defaultConfig {
minSdkVersion
8
targetSdkVersion 22
versionCode
1
versionName
"1.0"
}
buildTypes {
release {
minifyEnabled
false
proguardFiles getDefaultProguardFile(
'proguard-android.txt'),
'proguard-rules.pro'
}
}
}
1461662965441.jpg
6.0权限弹框的两种模式
1.初次请求,弹出对话框叫你勾选
1461654947945.jpg
2,第二次请求之后,弹出对话框,出现不再提醒字样
1461655014689.jpg
当用户点击了不再提醒,你再次请求权限的时候,就不会弹出对话框,所以这时,你需要根据需求另做处理 下面会如何处理
6.0之后的权限分类
分为两类 Normal permissions 和 Dangerous permissions
Normal permissions(普通权限)
只需要在xml中申请就可以了,与6.0之前没什么区别 包括的权限有
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
其实不需要记,记住哪些是危险权限就是了
Dangerous permissions(危险权限)
危险权限,需要在运行时请求. 注意: 危险权限是按组来分的,所以,当你申请了多个同组的危险权限时,运行时只需要申请一个就行 例如:
<uses-permission android:name="android.permission.READ_CALL_LOG" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.CALL_PHONE" />
<uses-permission android:name="android.permission.WRITE_CALL_LOG" />
<uses-permission android:name="android.permission.USE_SIP" />
<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS" />
<uses-permission android:name="com.android.voicemail.permission.ADD_VOICEMAIL" />
你申请了关于电话的那么多权限,在动态申请的时候,它只会弹出
1461654947945.jpg
这一个权限框 所以,这是一个权限组的概念,运行时选择你申请的同组权限的一个就行
目前所有的危险权限组集合
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.READ_CALL_LOG" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.CALL_PHONE" />
<uses-permission android:name="android.permission.WRITE_CALL_LOG" />
<uses-permission android:name="android.permission.USE_SIP" />
<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS" />
<uses-permission android:name="com.android.voicemail.permission.ADD_VOICEMAIL" />
<uses-permission android:name="android.permission.READ_CALENDAR" />
<uses-permission android:name="android.permission.WRITE_CALENDAR" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.BODY_SENSORS" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_SMS" />
<uses-permission android:name="android.permission.RECEIVE_WAP_PUSH" />
<uses-permission android:name="android.permission.RECEIVE_MMS" />
<uses-permission android:name="android.permission.RECEIVE_SMS" />
<uses-permission android:name="android.permission.SEND_SMS" />
对应的java code
Manifest.permission.WRITE_CONTACTS,
Manifest.permission.GET_ACCOUNTS,
Manifest.permission.READ_CONTACTS,
Manifest.permission.READ_CALL_LOG,
Manifest.permission.READ_PHONE_STATE,
Manifest.permission.CALL_PHONE,
Manifest.permission.WRITE_CALL_LOG,
Manifest.permission.USE_SIP,
Manifest.permission.PROCESS_OUTGOING_CALLS,
Manifest.permission.ADD_VOICEMAIL,
Manifest.permission.READ_CALENDAR,
Manifest.permission.WRITE_CALENDAR,
Manifest.permission.CAMERA,
Manifest.permission.BODY_SENSORS,
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.RECORD_AUDIO,
Manifest.permission.READ_SMS,
Manifest.permission.RECEIVE_WAP_PUSH,
Manifest.permission.RECEIVE_MMS,
Manifest.permission.RECEIVE_SMS,
Manifest.permission.SEND_SMS,
运行时权限请求的基本步骤
1.在xml中注册 2. 运行时请求权限
以下是权限检查的帮助类
/**
* 检查权限是否已请求到 (6.0)
*/
public void checkPermissions(String... permissions) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
&& lacksPermissions(permissions)) {
requestPermissions(permissions);
}
}
/**
* 判断是否缺失权限集合中的权限
*/
private boolean lacksPermissions(String... permissions) {
for (String permission : permissions) {
if (lacksPermission(permission)) {
return true;
}
}
return false;
}
/**
* 判断是否缺少某个权限
*/
private boolean lacksPermission(String permission) {
return ContextCompat.checkSelfPermission(context, permission) ==
PackageManager.PERMISSION_DENIED;
}
/**
* 请求权限
*/
private void requestPermissions(String... permissions) {
ActivityCompat.requestPermissions(context, permissions, PERMISSION_REQUEST_CODE);
}
/**
* 启动应用的设置,进入手动配置权限页面
*/
private void startAppSettings() {
Intent intent =
new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
Uri uri = Uri.fromParts(
"package", context.getPackageName(),
null);
intent.setData(uri);
context.startActivity(intent);
}
注意: 其中的 requestPermissions 方法,它会弹出权限提示框( 没有点击不再提醒的话 ),然后调用 public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) 方法,该方法在Activity或者Fragment中回调
3.在onRequestPermissionsResult回调中处理
在 public void onRequestPermissionsResult() 方法中,你可以捕获到用于是点击了 不再提醒 还是 拒绝 ,然后做出不同的操作..
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.M ||
requestCode != PermissionsChecker.PERMISSION_REQUEST_CODE)
return;
for (
int i =
0, len = permissions.length; i < len; i++) {
String permission = permissions[i];
if (grantResults[i] == PackageManager.PERMISSION_DENIED) {
boolean showRationale = shouldShowRequestPermissionRationale(permission);
if (!showRationale) {
break;
}
else {
}
}
}
}
权限请求策略
下面提供一种我认为还不错的策略 在需要某权限的Activity的 onStrart() 中去请求权限 在 onRequestPermissionsResult 回调中,如果用户点击了拒绝,则继续请求权限 如果用户点击了不再提醒,则弹出自定义对话框,引导用户手动去开启权限,如果用户不授权,则退出当前页面 注意:适用于没有权限就无法使用该功能的情况
1461661615410.jpg
Activity代码
public class BaseActivity extends AppCompatActivity {
private static final String TAG =
"BaseActivity";
private PermissionsChecker checker;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
checker =
new PermissionsChecker(
this);
}
public PermissionsChecker getChecker() {
return checker;
}
/**
* 在该声明周期,检查权限申请情况
*/
@Override
protected void onStart() {
super.onStart();
checker.checkPermissions(PermissionsChecker.PERMISSIONS);
}
/**
* 请求权限检查完后回调的结果
*
* @param requestCode .
* @param permissions 所请求的权限
* @param grantResults .
*/
@TargetApi(Build.VERSION_CODES.M)
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M ||
requestCode != PermissionsChecker.PERMISSION_REQUEST_CODE)
return;
for (
int i =
0, len = permissions.length; i < len; i++) {
String permission = permissions[i];
if (grantResults[i] == PackageManager.PERMISSION_DENIED) {
boolean showRationale = shouldShowRequestPermissionRationale(permission);
if (!showRationale) {
checker.showMissingPermissionDialog();
break;
}
else {
checker.checkPermissions(PermissionsChecker.PERMISSIONS);
break;
}
}
}
}
}