为了保护系统的整体性和用户隐私,Android系统中的每个App都是运行在一个带限制的sandbox中,如果一个app想要从sandbox外获取信息或资源,app就需要请求permission,而具体请求什么permission则要视你的具体情况而定,请求之后系统会根据不同的请求来自动授权或者让用户来选择是否授权.
1. 声明Permission
如果你要请求某些permission来进行相关操作,你需要在Manifest中声明这些permission,系统会根据permission的敏感程度来决定授权方式,比如:
- 你的app请求permission来打开闪关灯,系统会自动授权.
- 你的app请求读取联系人的权限,系统就会询问用户是同意授权该请求.
还有,不同的系统版本系统的授权方式也会不同:
- Android 5.1及以下是安装的时候请求授权.
- Android 6.0及以上是app真正操作需要是才请求授权.
1.1 确定你的App需要的Permission
如何确定什么样的操作需要请求permission?请求哪一种permission?
通常,如果你要获取一些app外部的信息或资源,或者执行一些会影响到设备或其他app的行为,这就需要permission,比如连接网络,使用摄像头,操作wifi开关等.而要申请哪种permission则可以查看系统提供的permission列表,具体在Normal and Dangerous Permissions中.
要注意的是你只需要申请你直接操作的action所需要的permission即可,如果你调用其他的app去获取一些信息或资源再返回给你,你就不需要申请相应的permission.比如:
- 你直接去读取通讯录,就需要有READ_CONTACTS permission.
- 但是如果你使用Intent来启动通讯录app,然后返回给你结果,你就不需要READ_CONTACTS permission,而需要该permission的则是通讯录app.
1.2 在Manifest中添加permission
如下示例:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.snazzyapp">
<uses-permission android:name="android.permission.SEND_SMS"/>
<application ...>
...
</application>
</manifest>
2.运行时请求Permission
从Android 6.0(API 23)开始,系统的授权方式有变,从之前的安装时授权变成使用时授权.这会简化应用的安装流程,也会给用户在功能上更多的控制权.比如,很多相机需要打开摄像头的权限和获取位置信息的权限,现在当app请求这两个权限时,你可以只授权获取相机但是不授权获取位置信息的permission.系统的permission分为两类:normal和dangeroous:
- Normal Permission: 无获取用户隐私信息的风险,如果在manifest中声明了这些权限,系统会自动授权.
- Dangerous Permission: 需要获取用户隐私数据,在manifest中声明后,还需要在使用中让用户来决定同意是否授权.
对于任何系统版本,都需要在menifest中声明要使用的permission,但是normal和dangerous的permission还是有不同的影响:
- Android 5.1(API 22)及以下,在安装某个app的时候,如果你不同意列出的dangerous permissions,系统就不会安装该app.
- Android 6.0(API 23)及以上,app安装时不需要授权,在运行时需要授权时用户来决定是否同意授权,如果不同意app还是可以继续运行只是某些功能可能会不能用.
下面将介绍如何使用Android Support Library来检查和请求权限,与Android 6.0相似,但是这个support类库能够兼容之前的版本,所以无需判断系统版本,会更简单.
2.1 检查Permission
dangerous permission需要你每次使用时都检查是否有权限,可以调用 ContextCompat.checkSelfPermission()方法来检查,如下示例:
// Assume thisActivity is the current activity
int permissionCheck = ContextCompat.checkSelfPermission(thisActivity,
Manifest.permission.WRITE_CALENDAR);
返回的结果为PackageManager中的两个常量:PERMISSION_GRANTED和PERMISSION_DENIED.
2.2 请求Permission
如果你在manifest中声明了dangerous permission,就必须要询问用户是否同意授权该permission.Android系统中提供了几种请求permission的方法,使用这些方法会调起一个标准的Android dialog,里面有一些选项,这个dialog你是不能定制的.
2.2.1 解释为什么app需要这些permission
在一些情况下,你可能需要帮助用户理解为什么你的app需要这些权限.比如你的应用是一个摄影类app,很正常要请求摄像头相关的permission,但是用户可能不理解为什么你的app要获取地址或者通讯录信息.在你请求这些可能不好理解的权限时,你应该给用户提供一些解释说明,不要太复杂.
有一个方法可以帮助你确定合适需要提供一个解释说明,就是ActivityCompat类中的shouldShowRequestPermissionRationale()方法.如果你之前请求过某个请求并被拒绝,该方法会返回true.
- 注意: 当用户拒绝该请求时并勾选了"不再询问"时,shouldShowRequestPermissionRationale()会返回false,同时如果permission为设备的政策禁止app要请求的permission时,也会返回false.
2.2.2 只请求需要的permission
如果你的app没拿到需要的permission,你需要使用requestPermissions()来请求,是异步的,但是你取消不了dialog,除非选择,代码如下:
// Here, thisActivity is the current activity
if (ContextCompat.checkSelfPermission(thisActivity,
Manifest.permission.READ_CONTACTS)
!= PackageManager.PERMISSION_GRANTED) {
// Should we show an explanation?
if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity,
Manifest.permission.READ_CONTACTS)) {
// Show an expanation to the user *asynchronously* -- don't block
// this thread waiting for the user's response! After the user
// sees the explanation, try again to request the permission.
} else {
// No explanation needed, we can request the permission.
ActivityCompat.requestPermissions(thisActivity,
new String[]{Manifest.permission.READ_CONTACTS},
MY_PERMISSIONS_REQUEST_READ_CONTACTS);
// MY_PERMISSIONS_REQUEST_READ_CONTACTS is an
// app-defined int constant. The callback method gets the
// result of the request.
}
}
- 注意: requestPermissions()中的第三个参数是请求码,方便回调处理.并且请求码只能使用(0-255内的数值)(lower 8 bits).
2.2.3 处理回调
示例代码如下:
@Override
public void onRequestPermissionsResult(int requestCode,
String permissions[], int[] grantResults) {
switch (requestCode) {
case MY_PERMISSIONS_REQUEST_READ_CONTACTS: {
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// permission was granted, yay! Do the
// contacts-related task you need to do.
} else {
// permission denied, boo! Disable the
// functionality that depends on this permission.
}
return;
}
// other 'case' lines to check for other
// permissions this app might request
}
}
上面的处理结果是按组permission group来分的,也就是只要你这个组中有个permission被授权,这个组中的其他permission也会被授权.比如你请求READ_CONTACTS和WRITE_CONTACTS两个permission,如果系统授权了一个则另一个也会自动会被授权. 但是要注意的是permission的分组有可能在以后改变,所以不能依赖具体分组来做操作.
3. Permission的最佳实践
Permission的良好请求也是用户体验的一部分,用不好分分钟被卸载,还是要重视的.
3.1 考虑使用Intent
很多情况下你会有两个选择:
a. 直接请求permission来执行相关操作.
b. 使用Intent来调起其他的app来执行相关操作.
比如拍照:
a. 你可以请求你可以请求CAMERA权限,然后直接操作摄像头,利用相关API来拍照,这样你可以完全控制拍照过程和自定义拍照UI.
b. 你可以使用ACTION_IMAGE_CAPTURE Intent来调起相机,然后在onActivityResult()中处理返回结果,具体可以看下这个
相似的,在打电话,访问通讯录等你也有上述两种选择,现在说说这两种选择的优点和缺点:
a. 直接请求permission来执行相关操作:
* 你的App拥有完全的控制权,同时又带来复杂的工作量.
* 如果用户没有给你授权或者授权之后撤销了,你的相关功能就不能运行.
b. 使用Intent:
* 无需考虑操作的UI,工作量小,执行该intent的app会负责这些,也就是你对于这些UI没有控制权.
* 如果有多个处理该Intent的app,则用户需要选择一个来操作,如果用户没有将其设为默认,则每次处理该Intent用户都要选一次,可能会麻烦.
3.2 测试Permission模型
Android 6.0开始permission的授权方式改变了,下面有一些tips可以帮助你识别Android 6.0及以后的设备中permission相关的问题:
- 识别app当前的permission和相关代码的路径.
- Test user flows across permission-protected services and data.
- 测试授权或撤销各种组合.
- 使用adb工具类管理permission.
// List permissions and status by group:
$ adb shell pm list permissions -d -g
// Grant or revoke one or more permissions:
$ adb shell pm [grant|revoke] <permission-name> ...
- 分析你的app使用权限的services
网友评论
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED
&& ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
Log.i("Log", "no permission");
if (ActivityCompat.shouldShowRequestPermissionRationale(this,
Manifest.permission.ACCESS_FINE_LOCATION)) {
// Show an expanation to the user *asynchronously* -- don't block
// this thread waiting for the user's response! After the user
// sees the explanation, try again to request the permission.
Log.i("Log", "shouldShowRequestPermissionRationale: true");
} else {
// No explanation needed, we can request the permission.
Log.i("Log", "shouldShowRequestPermissionRationale: false");
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 111);
}
} else {
Log.i("Log", "got permission1");
lastKnownLocation = manager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
// Log.i("location", lastKnownLocation.getLatitude() + "||" + lastKnownLocation.getLongitude());
tv.setText(lastKnownLocation.getLatitude() + "||" + lastKnownLocation.getLongitude());
// do location request
//LocationProvider provider = manager.getProvider(LocationManager.NETWORK_PROVIDER);
manager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, new LocationListener() {
@Override
public void onLocationChanged(Location location) {
tv.setText(location.getLatitude() + "||" + location.getLongitude());
}
@Override
public void onStatusCha