Android 6.0(API 级别 23)除了提供诸多新特性和功能外,还对系统和 API行为做出了各种变更:运行时权限、低电耗模式和应用待机模式、通知、音频管理器变更等。
本文就运行时权限部分对自己在项目中的使用和遇到的问题做一些总结,希望对大家有所帮助。
- 传送门(Android6.0的一些改变):
https://developer.android.com/about/versions/marshmallow/android-6.0-changes.html?hl=zh-cn#behavior-runtime-permissions
1. Android6.0运行时权限的特点
Android6.0版本之前,我们只需要将用到的权限注册在AndroidManifest文件中,然后用户安装的时候系统就会默认授权,
这样可能出现的问题就是用户本不想授权某个权限,但开发者为了获取某个信息(当然用户不知道的)还是会申请这个权限并被系统默认授权,导致用户没有自主权。Android6.0权限模型:在需要使用某个权限的地方,才向用户申请,并接受用户的处理结果,
例如:我们要使用打电话的权限,并向用户以Dialog的形式发出请求,用户可以选择拒绝和允许,这样用户就会拥有很高的自主权,
来保护自己的一些隐私,当然你也可以在设置界面对每个app的权限进行查看,以及对单个权限进行授权或者解除授权。
Google将Android权限分为两类:Normal Permissions 和 Dangerous Permissions,Normal 正常权限:涵盖应用需要访问其沙盒外部数据或资源,但对用户隐私或其他应用操作风险很小的区域。例如,设置时区的权限就是正常权限。如果应用声明其需要正常权限,系统会自动向应用授予该权限。如需当前正常权限的完整列表。危险权限:涵盖应用需要涉及用户隐私信息的数据或资源,或者可能对用户存储的数据或其他应用的操作产生影响的区域。例如,能够读取用户的联系人属于危险权限。如果应用声明其需要危险权限,则用户必须明确向应用授予该权限
传送门:
Normal Permissions:https://developer.android.com/guide/topics/permissions/normal-permissions.html
Dangerous Permissions:https://developer.android.com/guide/topics/security/permissions.html#perm-groups
2. 老版本升级
- 新API的出现,自然让我们不得不考虑老版本的升级问题。首先来看官方文档的描述:
如果您的应用在其清单中列出正常权限(即,不会对用户隐私或设备操作造成很大风险的权限),系统会自动授予这些权限。如果您的应用在其清单中列出危险权限(即,可能影响用户隐私或设备正常操作的权限),系统会要求用户明确授予这些权限。Android 发出请求的方式取决于系统版本,而系统版本是应用的目标
如果设备运行的是 Android 6.0(API 级别 23)或更高版本,并且应用的 targetSdkVersion 是 23 或更高版本,则应用在运行时向用户请求权限。用户可随时调用权限,因此应用在每次运行时均需检查自身是否具备所需的权限。如需了解有关在应用中请求权限的详细信息,请参阅使用系统权限培训指南。
如果设备运行的是 Android 5.1(API 级别 22)或更低版本,并且应用的 targetSdkVersion 是 22 或更低版本,则系统会在用户安装应用时要求用户授予权限。如果将新权限添加到更新的应用版本,系统会在用户更新应用时要求授予该权限。用户一旦安装应用,他们撤销权限的唯一方式是卸载应用。
通常,权限失效会导致 SecurityException 被扔回应用。但不能保证每个地方都是这样。例如,sendBroadcast(Intent) 方法在数据传递到每个接收者时会检查权限,在方法调用返回后,即使权限失效,您也不会收到异常。但在几乎所有情况下,权限失效会记入系统日志。
targetSdkVersion是什么东西呢?它是Android 提供向前兼容的主要依据,在应用的 targetSdkVersion 没有更新之前系统不会应用最新的行为变化。这允许你在适应新的行为变化之前就可以使用新的 API。
很不幸,小白遇到的是文档之外的情况:设备运行的是Android6.0或更高版本,targetSDKVersion为22或更低版本。于是小白就兴致冲冲的将targetSDKVersion改为了23,然后去调用新的API处理权限,结果在设置页关闭某项权限后,checkSelfPermission返回的还是 PackageManager.PERMISSION_GRANTED即权限被授予,其实不难理解:新版本必然得兼容旧版本即如果你的targetSDKVersion为22或更低,那么将被认为app没有用23新权限测试过,将被使用旧有规则:即用户在安装时已经被默认授权了注册的所有权限。当然用户也可以选择关闭权限,这时候呢系统会弹出一个对话框,如下图所示:
- 如果用户选择了仍然拒绝,那么用户将补能正常使用某些相关功能(相信在警告框提醒下,很好有用户会选择拒绝,QQ目前就是这样的)当然要解决这个问题还是赶紧将targetSDKVersion升级为23或者更高吧。
3 Android 6.0运行时权限API
在AndroidManifest清单文件中添加需要的权限
要检查是否具有某项权限,请调用 ContextCompat.checkSelfPermission() 方法,例如,以下代码段显示了如何检查 Activity 是否具有在日历中进行写入的权限:
// Assume thisActivity is the current activity
int permissionCheck = ContextCompat.checkSelfPermission(thisActivity,
Manifest.permission.WRITE_CALENDAR);
if(permissionCheck==PackageManager.PERMISSION_GRANTED){
//权限被授予
}else{
//权限未被授予即permissionCheck==PERMISSION_DENIED,需要申请
}
申请权限:如果应用尚无所需的权限,则应用必须调用一个 requestPermissions() 方法,以请求适当的权限。应用将传递其所需的权限,以及指定用于识别此权限请求的整型请求代码(即REQUEST_CODE)。此方法异步运行:它会立即返回,并且在用户响应对话框之后,系统会使用结果调用应用的回调方法,将应用传递的相同请求代码传递到 requestPermissions(),以下代码可以检查应用是否具备读取用户联系人的权限,并根据需要请求该权限:
if (ContextCompat.checkSelfPermission(thisActivity,
Manifest.permission.READ_CONTACTS)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(thisActivity,
new String[]{Manifest.permission.READ_CONTACTS},
MY_PERMISSIONS_REQUEST_READ_CONTACTS);
//MY_PERMISSIONS_REQUEST_READ_CONTACTS为自定义的一个常量请求码,处理权限请求结果时会用到
}
}
- 结果处理:当应用请求权限时,系统将向用户显示一个对话框。当用户响应时,系统将调用应用的 onRequestPermissionsResult() 方法,向其传递用户响应。您的应用必须替换该方法,以了解是否已获得相应权限。回调会将您传递的相同请求代码传递给 requestPermissions()。例如,如果应用请求 READ_CONTACTS 访问权限,则它可能采用以下回调方法:
@Override
public void onRequestPermissionsResult(int requestCode,
String permissions[], int[] grantResults) {
switch (requestCode) {
case MY_PERMISSIONS_REQUEST_READ_CONTACTS: {
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
//权限申请成功
} else {
//权限被拒
}
return;
}
}
}
系统显示的对话框说明了您的应用需要访问的权限组;它不会列出具体权限。例如,如果您请求 READ_CONTACTS 权限,系统对话框只显示您的应用需要访问设备的联系人。用户只需要为每个权限组授予一次权限。如果您的应用请求该组中的任何其他权限(已在您的应用清单中列出),系统将自动授予应用这些权限。当您请求此权限时,系统会调用您的 onRequestPermissionsResult() 回调方法,并传递 PERMISSION_GRANTED,如果用户已通过系统对话框明确同意您的权限请求,系统将采用相同方式操作,如果用户拒绝某项权限后,下次再申请时,系统会回调为失败,此时我们可以通过某种方式告知用户:例如 以Dialog的形式告知用户使用某项功能必须拥有某项权限,让用户去应用详情页开启,跳转到某个应用详情页的代码:
/**
* 跳转到权限设置页设置权限
*/
public static void routeToAppSettings(Activity activity) {
try {
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(Uri.parse("package:" + activity.getPackageName()));
activity.startActivity(intent);
} catch (Exception e) {
e.printStackTrace();
}
}
4 简单使用
我们在一个Activity中申请相机权限(注意你的运行设备应为Android 6.0或更高,targetSDKVersion应为23或更高)
public class MainActivity extends AppCompatActivity {
private PermissionSettingDialog mPermissionSettingDialog;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
protected void onResume() {
super.onResume();
if (mPermissionSettingDialog == null || !mPermissionSettingDialog.isShowing()) {
boolean permissionCheck = ActivityCompat.checkSelfPermission(context,
android.Manifest.permission.CAMERA)
!= PackageManager.PERMISSION_GRANTED;
Log.i(TAG, String.valueOf(permissionCheck));
if (permissionCheck) {
ActivityCompat.requestPermissions(context,
new String[]{android.Manifest.permission.CAMERA},
MY_PERMISSIONS_REQUEST_READ_CONTACTS);
}
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
switch (requestCode) {
case MY_PERMISSIONS_REQUEST_READ_CONTACTS: {
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this, "权限申请成功", Toast.LENGTH_SHORT).show();
} else {
if (mPermissionSettingDialog == null) {
mPermissionSettingDialog = new PermissionSettingDialog(this);
}
mPermissionSettingDialog.show();
}
}
return;
}
}
}
首先系统会给我们一个弹窗,告诉我们需要申请哪个权限,如图:
列表内容
然后根据用户选择的结果会执行onRequestPermissionsResult()方法,我们可以在这里处理自己的结果,这里成功以后小白显示Toast提示,失败以后以Dialog的形式让用户去应用详情打开权限,如图:
点击设置:
5. 封装
运行时权限处理起来并不复杂,但我们没有必每次用到的地方都这样去申请,如果同个界面需要的权限很多,是不是感觉很懵逼?但是,不管什么时候,如果我们只需要将申请的权限传递过去,然后就知道了处理结果是不是感觉so easy?带着这个目的我们去动手。
- 1 链式调用添加权限然后申请,例如:
PermissionManager.newInstance()
.addPermission(Manifest.permission.CAMERA)
.addPermission(Manifest.permission.CALL_PHONE)
.addPermission(Manifest.permission.READ_CALENDAR)
.requestPermissions(this)
.setPermissionRequestListener(this);
- 2 处理权限回调结果:不需要关系内部实现,只需要在成功和失败的回调中处理自己的逻辑
@Override
public void onPermissionRequestGranted() {
Toast.makeText(this, "权限申请成功", Toast.LENGTH_SHORT).show();
}
@Override
public void onPermissionRequestDenied() {
Toast.makeText(this, "权限申请失败", Toast.LENGTH_SHORT).show();
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
Log.i(TAG, permissions.length + ":" + grantResults.length);
PermissionManager.newInstance().onRequestPermissionsResult(requestCode, permissions, grantResults);
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
3 一个完整的用法
- -
public class MainActivity extends AppCompatActivity implements PermissionManager.PermissionRequestListener {
private static final String TAG = MainActivity.class.getSimpleName();
private PermissionSettingDialog mPermissionSettingDialog;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
/**
* 设置提示Dialog
*/
private void showSettingDialog() {
if (mPermissionSettingDialog == null) {
mPermissionSettingDialog = new PermissionSettingDialog(this);
}
if (!mPermissionSettingDialog.isShowing()) {
mPermissionSettingDialog.show();
}
}
/**
* 申请权限
*/
private void requestNeedPermissions() {
if (mPermissionSettingDialog == null || !mPermissionSettingDialog.isShowing()) {
PermissionManager.newInstance()
.addPermission(Manifest.permission.CAMERA)
.addPermission(Manifest.permission.CALL_PHONE)
.addPermission(Manifest.permission.READ_CALENDAR)
.requestPermissions(this)
.setPermissionRequestListener(this);
}
}
@Override
protected void onResume() {
super.onResume();
requestNeedPermissions();
}
@Override
public void onPermissionRequestGranted() {
Toast.makeText(this, "权限申请成功", Toast.LENGTH_SHORT).show();
}
@Override
public void onPermissionRequestDenied() {
showSettingDialog();
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
Log.i(TAG, permissions.length + ":" + grantResults.length);
PermissionManager.newInstance().onRequestPermissionsResult(requestCode, permissions, grantResults);
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
至于为什么不用Github上的开源库,主要是因为出了问题难以修改,有些也没法满足自己的特殊需求,在者是自己动手丰衣足食嘛!最后,希望这篇文章对大家有所帮助!