如何处理 SYSTEM_ALERT_WINDOW 权限未在某些 Marshmallow 之前的设备上自动授予
How to handle SYSTEM_ALERT_WINDOW permission not being auto-granted on some pre-Marshmallow devices
我收到一些小米设备(例如 Mi 2,运行 API 21 级)没有显示叠加层的报告。我的应用程序目标 API 23.
有 several posts 与此相关。 MIUI 设备似乎在安装时没有启用此权限(与其他 Marshmallow 之前的设备不同)。
遗憾的是,Settings.canDrawOverlays()
仅适用于 Android 23+。
- 在 Marshmallow 之前检查此权限是否尚未启用的正确方法是什么?
- 是否有将用户带到相关 MUIU 设置页面的 Intent?也许:
new Intent("android.settings.action.MANAGE_OVERLAY_PERMISSION", packageName)
但我没有办法测试这个。
1) 在 API 23 之前,已经授予权限,因为用户在安装时授予了权限。
编辑:Android 6 上似乎有一个错误(会 get fixed on 6.0.1),如果用户拒绝此权限,应用程序将因 SecurityException 而崩溃。不过不知道 Google 是如何修复它的。
2) 这样:
public static void requestSystemAlertPermission(Activity context, Fragment fragment, int requestCode) {
if (VERSION.SDK_INT < VERSION_CODES.M)
return;
final String packageName = context == null ? fragment.getActivity().getPackageName() : context.getPackageName();
final Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + packageName));
if (fragment != null)
fragment.startActivityForResult(intent, requestCode);
else
context.startActivityForResult(intent, requestCode);
}
然后,在onActivityResult中,可以检查是否给了权限,如:
@TargetApi(VERSION_CODES.M)
public static boolean isSystemAlertPermissionGranted(Context context) {
final boolean result = VERSION.SDK_INT < VERSION_CODES.M || Settings.canDrawOverlays(context);
return result;
}
编辑:目前,如果您将应用发布到 Play 商店,您的应用将自动获得此权限。你可以读到 here. When I asked about it, I thought it was a part of Android itself, as I thought all we need is to target a high enough value for targetSdkVersion. What Google wrote to me (here) 是因为他们想避免在流行的应用程序上出现问题。
我建议正确处理此权限,即使您会自动授予它。
在小米、魅族中搜索了一段时间,找到了这个。它运行完美..
public static boolean isMiuiFloatWindowOpAllowed(@NonNull Context context) {
final int version = Build.VERSION.SDK_INT;
if (version >= 19) {
return checkOp(context, OP_SYSTEM_ALERT_WINDOW); //See AppOpsManager.OP_SYSTEM_ALERT_WINDOW=24 /*@hide/
} else {
return (context.getApplicationInfo().flags & 1<<27) == 1;
}
}
public static boolean checkOp(Context context, int op, String packageName, int uid) {
final int version = Build.VERSION.SDK_INT;
if (version >= 19) {
AppOpsManager manager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
try {
return (AppOpsManager.MODE_ALLOWED == (Integer) ReflectUtils.invokeMethod(manager, "checkOp", op, uid, packageName));
} catch (Exception e) {
e.printStackTrace();
}
} else {
Flog.e("Below API 19 cannot invoke!");
}
return false;
}
ReflectUtils.java
public static Object invokeMethod(@NonNull Object receiver, String methodName, Object... methodArgs) throws Exception {
Class<?>[] argsClass = null;
if (methodArgs != null && methodArgs.length != 0) {
int length = methodArgs.length;
argsClass = new Class[length];
for (int i=0; i<length; i++) {
argsClass[i] = getBaseTypeClass(methodArgs[i].getClass());
}
}
Method method = receiver.getClass().getMethod(methodName, argsClass);
return method.invoke(receiver, methodArgs);
}
检查您是否拥有 drawOverlays 权限使用此方法更安全:
@SuppressLint("NewApi")
public static boolean canDrawOverlayViews(Context con){
if(Build.VERSION.SDK_INT< Build.VERSION_CODES.LOLLIPOP){return true;}
try {
return Settings.canDrawOverlays(con);
}
catch(NoSuchMethodError e){
return canDrawOverlaysUsingReflection(con);
}
}
public static boolean canDrawOverlaysUsingReflection(Context context) {
try {
AppOpsManager manager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
Class clazz = AppOpsManager.class;
Method dispatchMethod = clazz.getMethod("checkOp", new Class[] { int.class, int.class, String.class });
//AppOpsManager.OP_SYSTEM_ALERT_WINDOW = 24
int mode = (Integer) dispatchMethod.invoke(manager, new Object[] { 24, Binder.getCallingUid(), context.getApplicationContext().getPackageName() });
return AppOpsManager.MODE_ALLOWED == mode;
} catch (Exception e) { return false; }
}
自定义 ROM 可以更改 OS,因此 Settings.canDrawOverlays() 不可用。我在小米设备上遇到过这种情况,应用程序崩溃了。
请求权限:
@SuppressLint("InlinedApi")
public static void requestOverlayDrawPermission(Activity act, int requestCode){
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + act.getPackageName()));
act.startActivityForResult(intent, requestCode);
}
以下是处理此问题的分步说明:
首先在清单文件中给予以下权限:
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
或
<uses-permission-sdk-23 android:name="android.permission.SYSTEM_ALERT_WINDOW" />
然后使用以下代码处理其余的事情:
public final static int REQUEST_CODE = 65635;
public void checkDrawOverlayPermission() {
/** check if we already have permission to draw over other apps */
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (!Settings.canDrawOverlays(this)) {
/** if not construct intent to request permission */
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
Uri.parse("package:" + getPackageName()));
/** request permission via start activity for result */
startActivityForResult(intent, REQUEST_CODE);
}
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
/** check if received result code
is equal our requested code for draw permission */
if (requestCode == REQUEST_CODE) {
// ** if so check once again if we have permission */
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (Settings.canDrawOverlays(this)) {
// continue here - permission was granted
goYourActivity();
}
}
}
}
只需从您的 LauncherActivity 或您需要的任何地方调用 checkDrawOverlayPermission()
。
当您执行该项目时,您将看到 window 并要求启用权限。获得许可后,您将可以对此做任何事情。
您可以这样检查权限:
boolean granted = activity.checkSelfPermission("android.permission.SYSTEM_ALERT_WINDOW") == PackageManager.PERMISSION_GRANTED;
我收到一些小米设备(例如 Mi 2,运行 API 21 级)没有显示叠加层的报告。我的应用程序目标 API 23.
有 several posts 与此相关。 MIUI 设备似乎在安装时没有启用此权限(与其他 Marshmallow 之前的设备不同)。
遗憾的是,Settings.canDrawOverlays()
仅适用于 Android 23+。
- 在 Marshmallow 之前检查此权限是否尚未启用的正确方法是什么?
- 是否有将用户带到相关 MUIU 设置页面的 Intent?也许:
new Intent("android.settings.action.MANAGE_OVERLAY_PERMISSION", packageName)
但我没有办法测试这个。
1) 在 API 23 之前,已经授予权限,因为用户在安装时授予了权限。
编辑:Android 6 上似乎有一个错误(会 get fixed on 6.0.1),如果用户拒绝此权限,应用程序将因 SecurityException 而崩溃。不过不知道 Google 是如何修复它的。
2) 这样:
public static void requestSystemAlertPermission(Activity context, Fragment fragment, int requestCode) {
if (VERSION.SDK_INT < VERSION_CODES.M)
return;
final String packageName = context == null ? fragment.getActivity().getPackageName() : context.getPackageName();
final Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + packageName));
if (fragment != null)
fragment.startActivityForResult(intent, requestCode);
else
context.startActivityForResult(intent, requestCode);
}
然后,在onActivityResult中,可以检查是否给了权限,如:
@TargetApi(VERSION_CODES.M)
public static boolean isSystemAlertPermissionGranted(Context context) {
final boolean result = VERSION.SDK_INT < VERSION_CODES.M || Settings.canDrawOverlays(context);
return result;
}
编辑:目前,如果您将应用发布到 Play 商店,您的应用将自动获得此权限。你可以读到 here. When I asked about it, I thought it was a part of Android itself, as I thought all we need is to target a high enough value for targetSdkVersion. What Google wrote to me (here) 是因为他们想避免在流行的应用程序上出现问题。
我建议正确处理此权限,即使您会自动授予它。
在小米、魅族中搜索了一段时间,找到了这个。它运行完美..
public static boolean isMiuiFloatWindowOpAllowed(@NonNull Context context) {
final int version = Build.VERSION.SDK_INT;
if (version >= 19) {
return checkOp(context, OP_SYSTEM_ALERT_WINDOW); //See AppOpsManager.OP_SYSTEM_ALERT_WINDOW=24 /*@hide/
} else {
return (context.getApplicationInfo().flags & 1<<27) == 1;
}
}
public static boolean checkOp(Context context, int op, String packageName, int uid) {
final int version = Build.VERSION.SDK_INT;
if (version >= 19) {
AppOpsManager manager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
try {
return (AppOpsManager.MODE_ALLOWED == (Integer) ReflectUtils.invokeMethod(manager, "checkOp", op, uid, packageName));
} catch (Exception e) {
e.printStackTrace();
}
} else {
Flog.e("Below API 19 cannot invoke!");
}
return false;
}
ReflectUtils.java
public static Object invokeMethod(@NonNull Object receiver, String methodName, Object... methodArgs) throws Exception {
Class<?>[] argsClass = null;
if (methodArgs != null && methodArgs.length != 0) {
int length = methodArgs.length;
argsClass = new Class[length];
for (int i=0; i<length; i++) {
argsClass[i] = getBaseTypeClass(methodArgs[i].getClass());
}
}
Method method = receiver.getClass().getMethod(methodName, argsClass);
return method.invoke(receiver, methodArgs);
}
检查您是否拥有 drawOverlays 权限使用此方法更安全:
@SuppressLint("NewApi")
public static boolean canDrawOverlayViews(Context con){
if(Build.VERSION.SDK_INT< Build.VERSION_CODES.LOLLIPOP){return true;}
try {
return Settings.canDrawOverlays(con);
}
catch(NoSuchMethodError e){
return canDrawOverlaysUsingReflection(con);
}
}
public static boolean canDrawOverlaysUsingReflection(Context context) {
try {
AppOpsManager manager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
Class clazz = AppOpsManager.class;
Method dispatchMethod = clazz.getMethod("checkOp", new Class[] { int.class, int.class, String.class });
//AppOpsManager.OP_SYSTEM_ALERT_WINDOW = 24
int mode = (Integer) dispatchMethod.invoke(manager, new Object[] { 24, Binder.getCallingUid(), context.getApplicationContext().getPackageName() });
return AppOpsManager.MODE_ALLOWED == mode;
} catch (Exception e) { return false; }
}
自定义 ROM 可以更改 OS,因此 Settings.canDrawOverlays() 不可用。我在小米设备上遇到过这种情况,应用程序崩溃了。
请求权限:
@SuppressLint("InlinedApi")
public static void requestOverlayDrawPermission(Activity act, int requestCode){
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + act.getPackageName()));
act.startActivityForResult(intent, requestCode);
}
以下是处理此问题的分步说明:
首先在清单文件中给予以下权限:
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
或
<uses-permission-sdk-23 android:name="android.permission.SYSTEM_ALERT_WINDOW" />
然后使用以下代码处理其余的事情:
public final static int REQUEST_CODE = 65635;
public void checkDrawOverlayPermission() {
/** check if we already have permission to draw over other apps */
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (!Settings.canDrawOverlays(this)) {
/** if not construct intent to request permission */
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
Uri.parse("package:" + getPackageName()));
/** request permission via start activity for result */
startActivityForResult(intent, REQUEST_CODE);
}
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
/** check if received result code
is equal our requested code for draw permission */
if (requestCode == REQUEST_CODE) {
// ** if so check once again if we have permission */
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (Settings.canDrawOverlays(this)) {
// continue here - permission was granted
goYourActivity();
}
}
}
}
只需从您的 LauncherActivity 或您需要的任何地方调用 checkDrawOverlayPermission()
。
当您执行该项目时,您将看到 window 并要求启用权限。获得许可后,您将可以对此做任何事情。
您可以这样检查权限:
boolean granted = activity.checkSelfPermission("android.permission.SYSTEM_ALERT_WINDOW") == PackageManager.PERMISSION_GRANTED;