安卓动态权限详解
随着安卓的不断升级,手机的权限也逐渐被google收回,在6.0时出现了动态权限。知识图由来介绍安卓6.0之前应用的权限在安装时就可以全部授予(清单文件声明的),然而这可能造成店大欺客的情况,用户为了安装app,必须同意所有的权限。在 Android 6.0 或更高版本对权限进行了分类,对某些涉及到用户隐私的权限可在运行时根据用户的需要动态授予。这样就不需要在安装时被强迫同意某些权限...
随着安卓的不断升级,手机的权限也逐渐被google收回,在6.0时出现了动态权限的概念。
知识图
一、由来介绍
安卓6.0之前应用的权限在安装时就可以全部授予(清单文件声明的),然而这可能造成店大欺客的情况,用户为了安装app,必须同意所有的权限。在 Android 6.0 或更高版本对权限进行了分类,对某些涉及到用户隐私的权限可在运行时根据用户的需要动态授予。这样就不需要在安装时被强迫同意某些权限。
二、权限分类
- 正常权限
manifest文件声明即可使用,安装apk时授予,app运行时不在提示。
- 危险权限
涉及到用户隐私、用户数据相关的权限。manifest声明,代码中还要动态申请。
三、权限组
安卓系统对所有的危险权限进行了分组,功能相似的权限方放为一组内。
1、android 6.0时,用户只要同意权限组的任意一个权限,用户则会获得此权限组内的所有权限。
2、android 9.0开始,用户申请某个组内的一个权限,系统不会给同组内的其他权限。用户申请哪个,系统给哪个。
1、危险权限和普通权限的区分
除了20多个危险权限,其他都是普通权限(危险权限如下图)
注意:app申请使用的危险权限,用户都可以在手机-设置-应用程序信息-权限里面手动打开或者关闭。
2、安卓对危险权限组与权限的封装
封装在Manifest类中,可以使我们快速得到权限或者权限组字符串,而不用自己手写双引号引的字符串,避免了出错(源码如下)
public final class Manifest {
//危险权限组
public static final class permission_group {
public static final String ACTIVITY_RECOGNITION = "android.permission-group.ACTIVITY_RECOGNITION";
public static final String CALENDAR = "android.permission-group.CALENDAR";
public static final String CALL_LOG = "android.permission-group.CALL_LOG";
public static final String CAMERA = "android.permission-group.CAMERA";
public static final String CONTACTS = "android.permission-group.CONTACTS";
public static final String LOCATION = "android.permission-group.LOCATION";
public static final String MICROPHONE = "android.permission-group.MICROPHONE";
public static final String PHONE = "android.permission-group.PHONE";
public static final String SENSORS = "android.permission-group.SENSORS";
public static final String SMS = "android.permission-group.SMS";
public static final String STORAGE = "android.permission-group.STORAGE";
}
// 危险权限
public static final class permission {
public static final String ACCEPT_HANDOVER = "android.permission.ACCEPT_HANDOVER";
public static final String ACCESS_BACKGROUND_LOCATION = "android.permission.ACCESS_BACKGROUND_LOCATION";
public static final String ACCESS_CHECKIN_PROPERTIES = "android.permission.ACCESS_CHECKIN_PROPERTIES";
public static final String ACCESS_COARSE_LOCATION = "android.permission.ACCESS_COARSE_LOCATION";
public static final String ACCESS_FINE_LOCATION = "android.permission.ACCESS_FINE_LOCATION";
public static final String ACCESS_LOCATION_EXTRA_COMMANDS = "android.permission.ACCESS_LOCATION_EXTRA_COMMANDS";
public static final String ACCESS_MEDIA_LOCATION = "android.permission.ACCESS_MEDIA_LOCATION";
public static final String ACCESS_NETWORK_STATE = "android.permission.ACCESS_NETWORK_STATE";
public static final String ACCESS_NOTIFICATION_POLICY = "android.permission.ACCESS_NOTIFICATION_POLICY";
public static final String ACCESS_WIFI_STATE = "android.permission.ACCESS_WIFI_STATE";
public static final String ACCOUNT_MANAGER = "android.permission.ACCOUNT_MANAGER";
public static final String ACTIVITY_RECOGNITION = "android.permission.ACTIVITY_RECOGNITION";
public static final String ADD_VOICEMAIL = "com.android.voicemail.permission.ADD_VOICEMAIL";
public static final String ANSWER_PHONE_CALLS = "android.permission.ANSWER_PHONE_CALLS";
public static final String BATTERY_STATS = "android.permission.BATTERY_STATS";
public static final String BIND_ACCESSIBILITY_SERVICE = "android.permission.BIND_ACCESSIBILITY_SERVICE";
public static final String BIND_APPWIDGET = "android.permission.BIND_APPWIDGET";
public static final String BIND_AUTOFILL_SERVICE = "android.permission.BIND_AUTOFILL_SERVICE";
public static final String BIND_CALL_REDIRECTION_SERVICE = "android.permission.BIND_CALL_REDIRECTION_SERVICE";
public static final String BIND_CARRIER_MESSAGING_CLIENT_SERVICE = "android.permission.BIND_CARRIER_MESSAGING_CLIENT_SERVICE";
}
四、运行时请求权限
1、权限申请常用步骤
2、检查权限
ContextCompat 类中的方法:
/**
* Determine whether <em>you</em> have been granted a particular permission.
* 确定是否已经授权了一个特殊的权限
*
* @param permission The name of the permission being checked.
* 参数:permission ,将要被检测的权限
*
* @return {@link android.content.pm.PackageManager#PERMISSION_GRANTED} if you have the
* permission, or {@link android.content.pm.PackageManager#PERMISSION_DENIED} if not.
*返回值两种:
*1、PackageManager.PERMISSION_GRANTED 表示用户请求的权限已经授权
*2、PackageManager.PERMISSION_DENIED 表示用户请求的权限未授权
*
* @see android.content.pm.PackageManager#checkPermission(String, String)
*/
public static int checkSelfPermission(@NonNull Context context, @NonNull String permission)
收获:
1、使用ContextCompat 类的checkSelfPermission来检测是否具有某项权限
2、checkSelfPermission的返回值为两种int值,这两个值封装在PackageManager类中
3、请求权限
ActivityCompat的方法:
/*
* @param activity The target activity.
* 参数:activity
* @param permissions The requested permissions. Must me non-null and not empty.
* 参数:要申请的权限字符串数组(非null,非empty)
* @param requestCode Application specific request code to match with a result
* 参数:请求码,请求码必须为整数,且大于0
* reported to {@link OnRequestPermissionsResultCallback#onRequestPermissionsResult(int, String[], int[])}.
* Should be >= 0.
*/
public static void requestPermissions(final @NonNull Activity activity,
final @NonNull String[] permissions, final @IntRange(from = 0) int requestCode) {
收获:
1、此方法为ActivityCompat类的方法
2、requestPermissions执行时会弹系统对话框申请相关的权限。用户可以选择同意授权,或者拒绝授权。对话框关闭之后就走Activity的onRequestPermissionsResult回调了。
3、请求危险权限,之前需要在清单文件声明,否则对话框都不谈,直接就是用户拒绝了这个权限。
4、总是一句话:只要使用权限就必须清单文件先声明,只是危险权限还要动态申请下。
4、请求回调
FragmentActivity的方法:
/**
* Callback for the result from requesting permissions. This method
* is invoked for every call on {@link #requestPermissions(String[], int)}.
* 请求权限方法回调
*
* @param requestCode The request code passed in {@link #requestPermissions(String[], int)}.
* 参数:请求码(用户申请权限时传递的int值)
*
* @param permissions The requested permissions. Never null.
* 参数:请求的权限
*
* @param grantResults The grant results for the corresponding permissions
* which is either {@link android.content.pm.PackageManager#PERMISSION_GRANTED}
* or {@link android.content.pm.PackageManager#PERMISSION_DENIED}. Never null.
* 参数:授权结果数组
*/
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults)
收获:
1、直接在你的activity中重写onRequestPermissionsResult方法即可。因为我们现在一般使用AppCompatActivity,兼容包下的。这个activity是继承FragmentActivity的。
5、简单实战
/**
* 权限申请步骤:
* 1、检查有无权限
* 有权限-> doSomething
* 无权限->申请权限
*
* 2、申请权限(走权限回调)
*
* 用户同意->doSomething
* 用户拒绝->展示跳转设置界面对话框
*
* 3、跳转设置对话框
*
* 同意跳转->跳转特定的权限打开界面
* 用户拒绝->Toast提示没权限,功能不能正常使用
*/
public class MainActivity extends AppCompatActivity {
private static final int REQUEST_CODE = 0x11; // 请求权限请求码
private String[] permissions;
private boolean isAllGrant;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 开发是需要的权限
permissions = new String[]{
Manifest.permission.READ_CONTACTS,
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
};
}
public void normal(View view) {
isAllGrant = checkIsAllPermissionGranted(permissions, this);
if (isAllGrant) {
// 已有权限 ->做事情
Toast.makeText(this, "检测到已有权限!", Toast.LENGTH_SHORT).show();
doWork();
} else {
// 没有权限-> 申请权限
ActivityCompat.requestPermissions(this, permissions, REQUEST_CODE);
}
}
public void frame(View view) {
startActivity(new Intent(this, FrameActivity.class));
}
private void doWork() {
// todo 有权限时,用户想要做的逻辑
}
/**
* @param permissions 权限数组
* @return 权限都授权时返回true,否则false
* @function 检查是否所有的权限都授权
*/
private boolean checkIsAllPermissionGranted(String[] permissions, Context context) {
// 遍历检测权限
for (String permission : permissions) {
if (ContextCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED) {
return false;
}
}
return true;
}
/**
* 权限回调处理,此时用户还拒绝,则提示他去手机的设置-权限管理界面打开权限
*/
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == REQUEST_CODE) {
isAllGrant = true;
for (int grant : grantResults) {
if (grant == PackageManager.PERMISSION_DENIED) {
isAllGrant = false;
break;
}
}
}
if (isAllGrant) {// 同意授权->做事情
Toast.makeText(MainActivity.this, "授权成功", Toast.LENGTH_SHORT).show();
doWork();
} else {
// 拒绝授权->开弹窗跳询问是否跳设置-权限管理界面
AlertDialog.Builder builder = new AlertDialog.Builder(this)
.setMessage("应用需要您的通讯录和存储权限,请到设置-权限管理中授权。")
.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Intent intent = new Intent();
intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.addCategory(Intent.CATEGORY_DEFAULT);
intent.setData(Uri.parse("package:" + getPackageName()));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
startActivity(intent);
}
}).setCancelable(false)
.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Toast.makeText(MainActivity.this, "您没有获得权限,此功能不能正常使用", Toast.LENGTH_SHORT).show();
}
});
builder.create().show();
}
}
}
五、动态权限框架
这里主要推荐下几个star高的权限请求框架嘿嘿。。。
- easypermissions google亲生,建议使用。毕竟安卓都是google的。
- PermissionsDispatcher 基于注解,很好用
六 shouldShowRequestPermissionRationale 用途
1、测试代码
<uses-permission android:name="android.permission.CAMERA"/>
/**
* Create by SunnyDay /11/01 11:58:56
*/
object CameraPermissionHelper {
private const val CAMERA_PERMISSION_CODE = 0
private const val CAMERA_PERMISSION = Manifest.permission.CAMERA
fun hasCameraPermission(activity: Activity) = ContextCompat.checkSelfPermission(
activity,
CAMERA_PERMISSION
) == PackageManager.PERMISSION_GRANTED
fun requestCameraPermission(activity: Activity) {
ActivityCompat.requestPermissions(
activity,
arrayOf(CAMERA_PERMISSION),
CAMERA_PERMISSION_CODE
)
}
fun shouldShowRequestPermissionRationale(activity: Activity): Boolean =
ActivityCompat.shouldShowRequestPermissionRationale(
activity,
CAMERA_PERMISSION
)
fun launchPermissionSetting(activity: Activity) {
activity.startActivity(Intent().apply {
action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
data = Uri.fromParts("package", activity.packageName, null)
})
}
}
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 检测权限 无权限时申请。
Log.d("MainActivity", "onCreate#shouldShowRequestPermissionRationale:${CameraPermissionHelper.shouldShowRequestPermissionRationale(this)}")
if (!CameraPermissionHelper.hasCameraPermission(this)) {
CameraPermissionHelper.requestCameraPermission(this)
}
}
/**
* 权限回调
* */
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
Log.d("MainActivity", "onRequestPermissionsResult#shouldShowRequestPermissionRationale:${CameraPermissionHelper.shouldShowRequestPermissionRationale(this)}")
if (!CameraPermissionHelper.hasCameraPermission(this)) {
Log.d(
"MainActivity",
"onRequestPermissionsResult: Camera permission is needed to run this application"
)
if (!CameraPermissionHelper.shouldShowRequestPermissionRationale(this)) {
CameraPermissionHelper.launchPermissionSetting(this)
}
finish()
} else {
Log.d("MainActivity", "onRequestPermissionsResult: has Permission!")
}
}
}
2、系统默认的权限弹窗
如下是android 12 red mi K30 pro 手机测试现象。
(1) 点击"本次运行允许":app获得权限,只要不清除数据、不手动关闭权限则app后续一直具有权限。
(2)点击"仅在使用中允许":app获得权限,只要不清除数据、不手动关闭权限则app后续一直具有权限。
(3) 点击"拒绝":app未获取到权限,下次再打开app申请权限时会展示带"不再提示"标语的弹框。
(1) 点击"本次运行允许":app获得权限,只要不清除数据、不手动关闭权限则app后续一直具有权限。
(2)点击"仅在使用中允许":app获得权限,只要不清除数据、不手动关闭权限则app后续一直具有权限。
(3)点击"拒绝切不再询问":app未获取到权限,且后续触发权限申请也不会弹窗(除非手动清除数据、手动去设置里面更改权限)
代码内部表现是权限回调方法直接返回"无权限"结果
3、log观测&结论
发现只有点击"拒绝" 这一流程获取到的shouldShowRequestPermissionRationale值为true,其他情况都为false。大家也可自行验证下。
结论:单纯的使用shouldShowRequestPermissionRationale去做什么判断,是没用的,因为其值受用户选择影响,其值可能为true,可能为false。建议在权限回调后使用。
当用户拒绝了权限&shouldShowRequestPermissionRationale返回了false 这时用户点击了"拒绝切不再询问",此时直接跳设置。
在这篇博客的评论中看到过一位哥们评论的挺nice的:
1,没有申请过权限,申请就是了,所以返回false;
2,申请了用户拒绝了,那你就要提示用户了,所以返回true;
3,用户选择了拒绝并且不再提示,那你也不要申请了,也不要提示用户了,所以返回false;
4,已经允许了,不需要申请也不需要提示,所以返回false;
code 总结
参考文献:
https://blog.csdn.net/totond/article/details/73648103
https://www.cnblogs.com/ldq2016/p/7090872.html
更多推荐
所有评论(0)