android 6.0 Marshmallow版本之后,系统不会在软件安装的时候就赋予该app所有其申请的权限,对于一些危险级别的权限,app需要在运行时一个一个询问用户授予权限。
From android M permissions will be granted at runtime. User consent is not required for Normal permissions but for Dangerous permissions user is required to grant the permission to application.
Normal permissions:
https://developer.android.com/guide/topics/security/normal-permissions.htmlDangerous permissions:
Dangerous permissions cover areas where the app wants data or resources that involve the user’s private information https://developer.android.com/guide/topics/security/permissions.html#normal-dangerous
Normal permissions
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(覆盖面是APP想要的那些用户的隐私信息)
- android.permission-group.CALENDAR
- android.permission.READ_CALENDAR
- android.permission.WRITE_CALENDAR
- android.permission-group.CAMERA
- android.permission.CAMERA
- android.permission-group.CONTACTS
- android.permission.READ_CONTACTS
- android.permission.WRITE_CONTACTS
- android.permission.GET_ACCOUNTS
- android.permission-group.LOCATION
- android.permission.ACCESS_FINE_LOCATION
- android.permission.ACCESS_COARSE_LOCATION
- android.permission-group.MICROPHONE
- android.permission.RECORD_AUDIO
- android.permission-group.PHONE
- android.permission.READ_PHONE_STATE
- android.permission.CALL_PHONE
- android.permission.READ_CALL_LOG
- android.permission.ADD_VOICEMAIL
- android.permission.WRITE_CALL_LOG
- android.permission.USE_SIP
- android.permission.PROCESS_OUTGOING_CALLS
- android.permission-group.SENSORS
- android.permission.BODY_SENSORS
- android.permission-group.SMS
- android.permission.SEND_SMS
- android.permission.RECEIVE_SMS
- android.permission.READ_SMS
- android.permission.RECEIVE_WAP_PUSH
- android.permission.RECEIVE_MMS
- android.permission-group.STORAGE
- android.permission.READ_EXTERNAL_STORAGE
- android.permission.WRITE_EXTERNAL_STORAGE
授权操作说明
只有那些targetSdkVersion 设置为23及以上的应用才会出现异常,在使用危险权限的时候系统必须要获得用户的同意才能使用,要不然应用就会崩溃,出现这个错误java.lang.SecurityException: Permission Denial...
所以targetSdkVersion如果没有设置为23版本或者以上,系统还是会使用旧规则:在安装的时候赋予该app所申请的所有权限。
如果以前的老应用申请的权限被用户手动关闭了,不会抛出异常,不会崩溃,只不过调用那些被用户禁止权限的api接口返回值都为null或者0。
在Android M的api中,我们可以通过ContextCompat.checkSelfPermission检测软件是否有某一项权限。如果没有该权限,可以使用ActivityCompat.requestPermissions去请求一组权限。请求完成,会有相对应的回调方法,通知软件用户是否授予了权限。通过在Activity或者Fragment中重写onRequestPermissionsResult方法。如果用户一旦拒绝过某权限的授权。下一次弹框时,用户会有一个“不再提醒(Never ask again)”的选项的来防止app以后继续请求授权。如果这个选项在拒绝授权前被用户勾选了。下次为这个权限请求requestPermissions时,对话框就不弹出来了,系统会直接回调onRequestPermissionsResult函数,回调结果为最后一次用户的选择。所以为了应对这种情况,系统提供了一个shouldShowRequestPermissionRationale()函数,这个函数的作用是帮助开发者找到需要向用户额外解释权限的情况。
应用安装后第一次访问,直接返回false;
第一次请求权限时,用户拒绝了,下一次shouldShowRequestPermissionRationale()返回 true,这时候可以显示一些为什么需要这个权限的说明;
第二次请求权限时,用户拒绝了,并选择了“不再提醒”的选项时:shouldShowRequestPermissionRationale()返回 false;
设备的系统设置中禁止当前应用获取这个权限的授权,shouldShowRequestPermissionRationale()返回false,此时可以弹出dialog,提醒用户该权限的重要性;
ActivityCompat.shouldShowRequestPermissionRationale()在6.0之前版本调用,永远返回false。
项目实战
AndroidManifest.xml添加如下代码
<uses-sdk
android:minSdkVersion="23"
android:targetSdkVersion="25" />
<!-- Dangerous Permissions Start -->
<!-- android.permission-group.CALENDAR -->
<uses-permission android:name="android.permission.READ_CALENDAR" />
<uses-permission android:name="android.permission.WRITE_CALENDAR" />
<!-- android.permission-group.CAMERA -->
<uses-permission android:name="android.permission.CAMERA" />
<!-- android.permission-group.CONTACTS -->
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<!-- android.permission-group.LOCATION -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!-- android.permission-group.MICROPHONE -->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<!-- android.permission-group.PHONE -->
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.CALL_PHONE" />
<uses-permission android:name="android.permission.READ_CALL_LOG" />
<uses-permission android:name="android.permission.ADD_VOICEMAIL" />
<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" />
<!-- android.permission-group.SENSORS -->
<uses-permission android:name="android.permission.BODY_SENSORS" />
<!-- android.permission-group.SMS -->
<uses-permission android:name="android.permission.SEND_SMS" />
<uses-permission android:name="android.permission.RECEIVE_SMS" />
<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" />
<!-- android.permission-group.STORAGE -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!-- Dangerous Permissions End -->
DangerousPermissions.java
package com.example.permissionhelperdemo;
import android.Manifest;
/**
* Dangerous permissions
* <ol>
* <li>android.permission-group.CALENDAR
* <ul>
* <li>android.permission.READ_CALENDAR</li>
* <li>android.permission.WRITE_CALENDAR</li>
* </ul>
* </li>
*
* <li>android.permission-group.CAMERA
* <ul>
* <li>android.permission.CAMERA</li>
* </ul>
* </li>
*
* <li>android.permission-group.CONTACTS
* <ul>
* <li>android.permission.READ_CONTACTS</li>
* <li>android.permission.WRITE_CONTACTS</li>
* <li>android.permission.GET_ACCOUNTS</li>
* </ul>
* </li>
*
* <li>android.permission-group.LOCATION
* <ul>
* <li>android.permission.ACCESS_FINE_LOCATION</li>
* <li>android.permission.ACCESS_COARSE_LOCATION</li>
* </ul>
* </li>
*
* <li>android.permission-group.MICROPHONE
* <ul>
* <li>android.permission.RECORD_AUDIO</li>
* </ul>
* </li>
*
* <li>android.permission-group.PHONE
* <ul>
* <li>android.permission.READ_PHONE_STATE</li>
* <li>android.permission.CALL_PHONE</li>
* <li>android.permission.READ_CALL_LOG</li>
* <li>android.permission.WRITE_CALL_LOG</li>
* <li>android.permission.ADO_VOICEMAIL</li>
* <li>android.permission.USE_SIP</li>
* <li>android.permission.PROCESS_OUTGOING_CALLS</li>
* </ul>
* </li>
*
* <li>android.permission-group.SENSORS
* <ul>
* <li>android.permission.BODY_SENSORS</li>
* </ul>
* </li>
*
* <li>android.permission-group.SMS
* <ul>
* <li>android.permission.SEND_SMS</li>
* <li>android.permission.RECEIVE_SMS</li>
* <li>android.permission.READ_SMS</li>
* <li>android.permission.RECEIVE_WAP_PUSH</li>
* <li>android.permission.RECEIVE_MMS</li>
* <li>android.permission.READ_CELL_BROADCASTS</li>
* </ul>
* </li>
*
* <li>android.permission-group.STORAGE
* <ul>
* <li>android.permission.READ_EXTERNAL_STORAGE</li>
* <li>android.permission.WRITE_EXTERNAL_STORAGE</li>
* </ul>
* </li>
*
* <ol>
*/
public class DangerousPermissions {
private static final String CALENDAR = Manifest.permission.READ_CALENDAR;
private static final String CAMERA = Manifest.permission.CAMERA;
private static final String CONTACTS = Manifest.permission.READ_CONTACTS;
private static final String LOCATION = Manifest.permission.ACCESS_FINE_LOCATION;
private static final String MICROPHONE = Manifest.permission.RECORD_AUDIO;
private static final String PHONE = Manifest.permission.READ_PHONE_STATE;
private static final String SENSORS = Manifest.permission.BODY_SENSORS;
private static final String SMS = Manifest.permission.SEND_SMS;
private static final String STORAGE = Manifest.permission.READ_EXTERNAL_STORAGE;
public static final String[] PERMISSIONS_IN_DIFFERENT_GROUP = new String[] {
CALENDAR,
CAMERA,
CONTACTS,
LOCATION,
MICROPHONE,
PHONE,
SENSORS,
SMS,
STORAGE,
};
public static final String[] PERMISSIONS_IN_SAME_GROUP = new String[] {
Manifest.permission.READ_CALENDAR,
Manifest.permission.WRITE_CALENDAR
};
}
PermissionHelper.java
package com.example.permissionhelperdemo;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Build;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
public class PermissionHelper {
public static final int PERMISSION_REQUEST_CODE = 1;
/**
* 如果API小于23就不用检查权限了,获取不到就返回0或null<br>
* 但如果API大于23,用户未同意便会出现下面错误: java.lang.SecurityException: Permission Denial...
*/
public boolean needCheckPermissions() {
boolean result = Build.VERSION.SDK_INT < 23;
System.out.println("Build.VERSION.SDK_INT < 23 = " + result);
if (result) {
return false;
}
return true;
}
/**
* checkSelfPermissions函数是否有效仅跟Build.VERSION.SDK_INT有关,与targetVersion无关
*/
public boolean needCheckPermissions(Context context) {
boolean result = context.getApplicationInfo().targetSdkVersion < 23;
System.out.println("context.getApplicationInfo().targetSdkVersion < 23 = " + result);
if(result) {
return false;
}
return true;
}
/**
* 校验权限<br>
* 如果API小于23,ContextCompat.checkSelfPermission和ActivityCompat.requestPermissions并不会正常工作,并总是返回0(PERMISSION_GRANTED),即使你是在Android 6.0(API23)上运行
*/
public boolean checkSelfPermissions(Context context, String... permissions) {
if (permissions == null || permissions.length == 0) {
return true;
}
for (String permission : permissions) {
boolean result = ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED;
System.out.println("ContextCompat.checkSelfPermission " + permission + " -> " + result);
if (!result) {
return false;
}
}
return true;
}
public void requestPermissions(Activity activity, String... permissions) {
ActivityCompat.requestPermissions(activity, permissions, PERMISSION_REQUEST_CODE);
}
}
MainActivity.java
package com.example.permissionhelperdemo;
import android.app.Activity;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
public class MainActivity extends Activity implements OnClickListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button permissionInSameGroupBtn = (Button) findViewById(R.id.permission_in_same_group_btn);
Button permissionInDifferentGroupBtn = (Button) findViewById(R.id.permission_in_different_group_btn);
permissionInSameGroupBtn.setOnClickListener(this);
permissionInDifferentGroupBtn.setOnClickListener(this);
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
for(int i=0; i<permissions.length; i++) {
boolean result = grantResults[i] == PackageManager.PERMISSION_GRANTED;
System.out.println("onRequestPermissionsResult " + permissions[i] + " -> " + result);
boolean shouldShow = shouldShowRequestPermissionRationale(permissions[i]);
System.out.println("shouldShowRequestPermissionRationale " + permissions[i] + " -> " + shouldShow);
}
}
private void checkPermission(String... permissions) {
PermissionHelper permissionHelper = new PermissionHelper();
permissionHelper.needCheckPermissions();
permissionHelper.needCheckPermissions(this);
boolean result = permissionHelper.checkSelfPermissions(this, permissions);
System.out.println("permissionHelper.checkSelfPermissions -> " + result);
permissionHelper.requestPermissions(this, permissions);
}
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.permission_in_same_group_btn:
checkPermission(DangerousPermissions.PERMISSIONS_IN_SAME_GROUP);
break;
case R.id.permission_in_different_group_btn:
checkPermission(DangerousPermissions.PERMISSIONS_IN_DIFFERENT_GROUP);
break;
}
}
}
activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.example.permissionhelperdemo.MainActivity" >
<Button
android:id="@+id/permission_in_same_group_btn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="PERMISSIONS_IN_SAME_GROUP" />
<Button
android:id="@+id/permission_in_different_group_btn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="PERMISSIONS_IN_DIFFERENT_GROUP" />
</LinearLayout>
可能会遇到的问题
如果大家使用eclipse编译项目有可能会报错,这是因为libs/android-support-v4.jar包太旧,可以将appcompat_v7项目的libs/android-support-v4.jar复制到本项目相应目录。
appcompat_v7的目录为 sdk\extras\android\support\v7\appcompat
如果还是不行,请更新SDK。
运行截图
# 测试手机三星SM-A9000,Android 6.01, API 23
总结
Android在赋予权限的时候采取了一个非常取巧的办法——将相似的权限分为一组,用户只能对组进行授权。一旦授权则意味着允许该组的所有权限行为。
- checkSelfPermission用户赋予权限后返回true,否则返回false
该方法只能check单个permission(Manifest.permission.*),不能check权限组(Manifest.permission_group.*)。并且仅在手机API >= 23时有效,与target API 或 Build target API均无关,当手机API < 23时总是返回true。 - ActivityCompat.requestPermissions与onRequestPermissionsResult(Project Build target API >= 23)配合使用,用来获取用户是否授权的返回结果。并且当手机API < 23时,这2个方法无效。
- shouldShowRequestPermissionRationale(min API >= 23)在第一次拒绝时返回true;再次拒绝或用户确认赋予权限时,均返回false。
参考链接
- http://www.th7.cn/Program/Android/201603/780587.shtml
- http://stackoverflow.com/questions/7339743/android-permissions-how-can-i-learn-which-are-dangerous-vs-normal
补充说明
看了这么多,可能有些人还是不懂这个到底有什么用。
举个实际的例子:
有的手机,比如乐1S,升级到Android 6.0 后,默认安装应用之后是不会赋予任何权限的。这时候我打开该款蓝牙应用就会搜不到任何设备。当然你可以提示用户去“系统设置”打开“位置权限”(Android 6.0之后蓝牙需要位置权限,但蓝牙不属于危险权限,默认会帮你开启)。但是更好的做法是直接在代码中判断是否具有该权限,如果没有弹窗提示用户开启。
另外,还需要注意的一点是,该授权机制虽然可以判断是否具有没有在AndroidManifest.xml中声明的权限(结果肯定是否啦。。。),但是即使调用相关函数,也不能让系统弹窗提示用户授予该权限。