在Android开发中经常需要各种各样的权限,通常我们只需要在
AndroidManifest.xml
中声明一下需要的权限即可。但是自从Android6.0(即 API Level 23)开始,Android系统将权限的授予从安装时,变成了运行时。这样可以提高APP的安装速度和提高用户对于权限的控制。
Normal And Dangerous
现在我们来看一下这段话:
System permissions are divided into two categories, normal and dangerous:
- Normal permissions do not directly risk the user’s privacy. If your app lists a normal permission in its manifest, the system grants the permission automatically.
- Dangerous permissions can give the app access to the user’s confidential data. If your app lists a normal permission in its manifest, the system grants the permission automatically. If you list a dangerous permission, the user has to explicitly give approval to your app.
简而言之,就是
normal
的就是不会涉及用户隐私的权限,它是在你需要用的时候,系统自动授权的,
dangerous
的是会设计用户隐私的权限,他需要用户显示的授权给你的APP。
那接下来让我们来看一下
dangerous
的权限都是哪些:
Permission Group | Permissions |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
这些分别对应着日历、相机、通讯录、位置、麦克风、电话、传感器、短信和外部存储。
由于这些信息都是可以直接或者间接的去获取到用户的隐私所以google将它设置为dangerous不是没有道理的,相信这个可以在一定程度上改善国内应用获取权限过多的情况。
获取dangerous权限
我们讲了那么多关于dangerous权限,现在就让我们来看看如何去获取dangerous权限。
现在我们来尝试获取一下Google Galaxy Nexus Android 4.4.2 API Level 17的设备的通讯录。

里面只有一个联系人,现在我们先要来获取一下该联系人,然后插入另一个联系人
private void readContacts() {
Cursor cursor = getContentResolver().query(ContactsContract.Data.CONTENT_URI, new String[]{ContactsContract.Contacts.DISPLAY_NAME_PRIMARY,
ContactsContract.Data.DATA1}, null, null, null);
cursor.moveToFirst();
Log.d(TAG, "readContacts: name: " + cursor.getString() + " ,number: " + cursor.getString());
}
private void writeContacts() {
ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
int rawContactInsertIndex = ops.size();
ops.add(ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
.withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, null)
.withValue(ContactsContract.RawContacts.ACCOUNT_NAME, null).build());
//Phone Number
ops.add(ContentProviderOperation
.newInsert(ContactsContract.Data.CONTENT_URI)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID,
rawContactInsertIndex)
.withValue(ContactsContract.Data.MIMETYPE,
ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, "987654321")
.withValue(ContactsContract.Data.MIMETYPE,
ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.Phone.TYPE, "1").build());
//Display name/Contact name
ops.add(ContentProviderOperation
.newInsert(ContactsContract.Data.CONTENT_URI)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID,
rawContactInsertIndex)
.withValue(ContactsContract.Data.MIMETYPE,
ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, "Jack")
.build());
try {
ContentProviderResult[] res = getContentResolver().applyBatch(
ContactsContract.AUTHORITY, ops);
} catch (RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (OperationApplicationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
现在我们来看一下运行的结果
MainActivity: readContacts: name: charles ,number: 1 234-567-8
从控制台打印的
log
和这两张图片我们可以看出来,我们可以成功的获取和插入通讯录。
现在我们来看看 Google Nexus 5X Android 7.0 API level 24 的设备的运行结果。
看一下控制台发现原来是抛出了一个异常
java.lang.SecurityException: Permission Denial: opening provider com.android.providers.contacts.ContactsProvider2
requires android.permission.READ_CONTACTS or android.permission.WRITE_CONTACTS
这个是一行,只是我把它截取成两行,方便大家看。这边说权限被拒绝,访问ContactsProvider 需要这两个权限
android.permission.READ_CONTACTS
android.permission.WRITE_CONTACTS
。可是我明明在
AndroidManifest.xml
中注册了
<uses-permission android:name="android.permission.READ_CONTACTS"></uses-permission>
<uses-permission android:name="android.permission.WRITE_CONTACTS"></uses-permission>
为什么还会这样呢?我们来看看文档是怎么说的:
If the device is running Android 6.0 or higher, and your app’s target SDK is 23 or higher: The app has to list the permissions in the manifest, and it must request each dangerous permission it needs while the app is running. The user can grant or deny each permission, and the app can continue to run with limited capabilities even if the user denies a permission request.
这样看一下就明了了,在Android 6.0 以上的版本,我们除了要在
AndroidManifest.xml
中罗列出需要的权限以外还需要在运行的时候显示的申请每一个危险的权限。
我们先来看一下,在这台手机当中只有这个号码
现在我们运行一下程序,
在程序的第一个页面是这样的:
我们来看看onCreate()的方法
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.read).setOnClickListener(this);
findViewById(R.id.write).setOnClickListener(this);
if (checkPermission(READ_CONTACTS,CONTACTS_READ_CODE))
readContacts();
if (checkPermission(WRITE_CONTACTS,CONTACTS_WRITE_CODE))
writeContacts();
}
我们可以看到简单的这个改进,我们的程序没有crash了,在运行时跳出了权限的请求。原因在于`if`语句中的那个`checkPermission()`方法,现在我们来看看这个方法:
@TargetApi(Build.VERSION_CODES.M) //因为requestPermission()这个方法要求的api level是23,所以需要这行的annotation来标注
public boolean checkPermission(final String permission,final int requestCode){
//判断,如果SDK版本小于23就直接返回TRUE,因为在低于23的版本是不需要运行时的申请权限的
if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.M){
//如果权限已经得到批准就返回TRUE
if (this.checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
//这个if语句必须要是第二次才会运行的,就是被DENY一次以后
if (this.shouldShowRequestPermissionRationale(permission)) {
Log.i(TAG,
"Displaying permission rationale to provide additional context.");
Snackbar.make(findViewById(R.id.activity_main),"we need to manage your contacts" ,
Snackbar.LENGTH_INDEFINITE)
.setAction("OK", new View.OnClickListener() {
@Override
public void onClick(View view) {
MainActivity.this.requestPermissions(new String[]{permission}, requestCode);
}
})
.show();
} else {
this.requestPermissions(new String[]{permission}, requestCode);
}
return false;
}
return true;
}
return true;
}
现在我们来看一下控制台的
log
打印情况:什么也没有。所以在这个时候,我们还访问不到
Contacts Provider
。
现在我们选择
DENY
选项,我们再选择
READ CONTACT
这个button,可以看到在屏幕的底部弹出了这样的一个snackbar
现在我们点
ALLOW
,可以看到在控制台打印出了这样的一条
log
:
MainActivity: readContacts: name: charles ,number: 1 234-567-89
说明我们成功的获取到了
Contact Provider
,我们再来点一下
WRITE CONTACTS
这个
button
,会发现我们不再需要申请权限了。而通讯录中也多出了一个
Jack
。
你可能有疑问,为什么点击
WRITE CONTACTS
这个
button
不需要申请权限呢?让我们接着来看看Android的文档:
If an app requests a dangerous permission listed in its manifest, and the app already has another dangerous permission in the same permission group, the system immediately grants the permission without any interaction with the user. For example, if an app had previously requested and been granted the READ_CONTACTS permission, and it then requests WRITE_CONTACTS, the system immediately grants that permission.
这段话的意思就是,只要你取得了一个dangerous group中的一个permission,那么你再申请同一个group的其他permission就可以立即被granted这个权限。
但是如果你点了
Don't ask again
这个选项,那么你后面的申请都是无效的了。
其他注意事项:
如果你使用运行时权限,你还需要实现这个接口
ActivityCompat.OnRequestPermissionsResultCallback
,然后实现它的方法
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (requestCode == CONTACTS_READ_CODE)
if (grantResults.length > && grantResults[]==PackageManager.PERMISSION_GRANTED)
readContacts();
if (grantResults.length > && requestCode == CONTACTS_WRITE_CODE)
if (grantResults[] == PackageManager.PERMISSION_GRANTED)
writeContacts();
}
因为在
checkPermission()
中的
requestPermission()
会去回调
ActivityCompat.OnRequestPermissionsResultCallback
的方法。