Android悬浮窗权限“android.permission.SYSTEM_ALERT_WINDOW”判断是否开启问题

最近在android 8.0上遇到了一个判断悬浮窗权限是否开启的问题,当在一个界面弹出dialog提示用户开启悬浮窗权限,用户点击之后,跳转到设置界面开启悬浮窗权限,然后返回该页面,使用google提供的android 6.0以及以后可以使用的接口Settings.canDrawOverlays(context) 进行权限开启的判断,结果返回的是false;程序接收到的是权限没有开启,但是到设置里面查看确实是开启了的。而且当你对界面有刷新操作之后(去到其他界面、退出重新进入、点击按钮等等),检测到权限也是开启的。但是当你从界面跳转到设置并开启权限,然后返回界面直接调用Settings.canDrawOverlays(context)方法判断显示的是未开启。what?这是什么问题了?刚开始猜想会不会是延迟问题了,于是在开启界面多等了一会儿,结果返回还是不行!后面通过查看资料找到了问题的所在,所以在此记录一下。

###怎样解决?
首先来看看我原来的判断代码:

public static boolean checkFloatPermission(Context context) {
	if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT)
		return true;
	if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
		try {
			Class cls = Class.forName("android.content.Context");
			Field declaredField = cls.getDeclaredField("APP_OPS_SERVICE");
			declaredField.setAccessible(true);
			Object obj = declaredField.get(cls);
			if (!(obj instanceof String)) {
				return false;
			}
			String str2 = (String) obj;
			obj = cls.getMethod("getSystemService", String.class).invoke(context, str2);
			cls = Class.forName("android.app.AppOpsManager");
			Field declaredField2 = cls.getDeclaredField("MODE_ALLOWED");
			declaredField2.setAccessible(true);
			Method checkOp = cls.getMethod("checkOp", Integer.TYPE, Integer.TYPE, String.class);
			int result = (Integer) checkOp.invoke(obj, 24, Binder.getCallingUid(), context.getPackageName());
			return result == declaredField2.getInt(cls);
		} catch (Exception e) {
                return false;
		}
	} else {
		return Settings.canDrawOverlays(context);
	}
}

上面的代码区分了不同的版本悬浮窗权限的判断方法,在4.4以前是不用判断悬浮窗权限的直接使用就可以了,在4.4到6.0之前,google没有提供方法让我们用于判断悬浮窗权限,同时也没有跳转到设置界面进行开启的方法,因为此权限是默认开启的,但是有一些产商会修改它,所以在使用之前最好进行判断,以免使用时出现崩溃,判断方法是用反射的方式获取出是否开启了悬浮窗权限。在6.0以及以后的版本中,google为我们提供了判断方法和跳转界面的方法,直接使用Settings.canDrawOverlays(context) 就可以判断是否开启了悬浮窗权限,没有开启可以跳转到设置界面让用户开启,跳转方法就不做说明了。上面的代码看似没有什么问题,但是在使用的过程中,发现有的8.0手机会出现上面所说的问题。所以有了下面的新的判断方法,代码如下:

public static boolean checkFloatPermission(Context context) {
	if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT)
		return true;
	if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
		try {
			Class cls = Class.forName("android.content.Context");
			Field declaredField = cls.getDeclaredField("APP_OPS_SERVICE");
			declaredField.setAccessible(true);
			Object obj = declaredField.get(cls);
			if (!(obj instanceof String)) {
				return false;
			}
			String str2 = (String) obj;
			obj = cls.getMethod("getSystemService", String.class).invoke(context, str2);
			cls = Class.forName("android.app.AppOpsManager");
			Field declaredField2 = cls.getDeclaredField("MODE_ALLOWED");
			declaredField2.setAccessible(true);
			Method checkOp = cls.getMethod("checkOp", Integer.TYPE, Integer.TYPE, String.class);
			int result = (Integer) checkOp.invoke(obj, 24, Binder.getCallingUid(), context.getPackageName());
			return result == declaredField2.getInt(cls);
		} catch (Exception e) {
                return false;
		}
	} else {
		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
			AppOpsManager appOpsMgr = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
			if (appOpsMgr == null)
				return false;
			int mode = appOpsMgr.checkOpNoThrow("android:system_alert_window", android.os.Process.myUid(), context
                        .getPackageName());
			return Settings.canDrawOverlays(context) || mode == AppOpsManager.MODE_ALLOWED || mode == AppOpsManager.MODE_IGNORED;
		} else {
			return Settings.canDrawOverlays(context);
		}
	}
}

从新代码和老代码的对比可以发现,我对8.0的版本做了单独的处理,那就是通过判断mode来判断权限是否开启。为什么要这样了?因为在8.0上,有的手机是区分了mode的,导致使用Settings.canDrawOverlays(context) 方法判断的时候会出问题。下面我们先说说mode代表的意思:

  1. AppOpsManager.MODE_ALLOWED —— 表示授予了权限并且重新打开了应用程序
  2. AppOpsManager.MODE_IGNORED —— 表示授予权限并返回应用程序
  3. AppOpsManager.MODE_ERRORED —— 表示当前应用没有此权限
  4. AppOpsManager.MODE_DEFAULT —— 表示默认值,有些手机在没有开启权限时,mode的值就是这个

上面所说的区分mode的值,就是区分了AppOpsManager.MODE_ALLOWEDAppOpsManager.MODE_IGNORED ,而Settings.canDrawOverlays(context) 方法只有当mode等于AppOpsManager.MODE_ALLOWED 时才会返回true,所以当mode等于AppOpsManager.MODE_IGNORED 时会返回false,这样就会出现开启了权限,但是判断时却显示没有开启的问题。所以要解决这个问题,就要获取出当前的mode,对mode进行判断,当mode等于AppOpsManager.MODE_ALLOWED 或者mode等于AppOpsManager.MODE_IGNORED 时都是已经开启权限,代码如上。

  • 4
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 12
    评论
Android 6.0(API 级别 23)及以上版本中,应用程序需要在运行时请求权限,包括`android.permission.SYSTEM_ALERT_WINDOW`权限。下面是请求该权限的步骤: 1. 在 AndroidManifest.xml 文件中添加权限声明,如下所示: ``` <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> ``` 2. 在应用程序中检查该权限是否已经被授予。可以使用`checkSelfPermission()`方法来检查权限是否已经被授予,如下所示: ``` if (ContextCompat.checkSelfPermission(this, Manifest.permission.SYSTEM_ALERT_WINDOW) != PackageManager.PERMISSION_GRANTED) { // Permission is not granted } ``` 3. 如果权限未被授予,您可以使用`requestPermissions()`方法来请求权限。该方法将显示一个对话框,让用户选择是否授予权限。如果用户授予了权限,系统将调用应用程序的`onRequestPermissionsResult()`方法。如果用户拒绝了权限应用程序必须处理这种情况。 ``` ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.SYSTEM_ALERT_WINDOW}, MY_PERMISSIONS_REQUEST_SYSTEM_ALERT_WINDOW); ``` 在上面的代码中,`MY_PERMISSIONS_REQUEST_SYSTEM_ALERT_WINDOW`是应用程序定义的请求代码。它可以在`onRequestPermissionsResult()`方法中使用。 4. 处理权限请求结果。当用户选择是否授予权限时,系统将调用应用程序的`onRequestPermissionsResult()`方法。在该方法中,您可以检查用户是否授予了权限,并采取相应的措施。例如,如果用户授予了权限,您可以执行需要该权限的操作。如果用户拒绝了权限,您可以显示一个消息,告诉用户为什么需要该权限。以下是一个示例: ``` @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { switch (requestCode) { case MY_PERMISSIONS_REQUEST_SYSTEM_ALERT_WINDOW: { // If request is cancelled, the result arrays are empty. if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { // Permission granted } else { // Permission denied } return; } // Other cases... } } ``` 在上面的代码中,如果用户授予了权限应用程序将执行需要该权限的操作。如果用户拒绝了权限应用程序将显示一个消息,告诉用户为什么需要该权限

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值