如何处理在部分Android Marshmallow版本之前的设备上未自动授予SYSTEM_ALERT_WINDOW权限的问题

17
我收到了关于一些小米设备(如Mi 2,运行API级别21)未显示覆盖层的报告。我的应用程序目标为API 23。
有关此事,有几篇文章(例如这里这里)。看起来,MIUI 设备不会在安装时启用此权限(与其他先前的非Marshmallow设备不同)。
不幸的是,Settings.canDrawOverlays() 只适用于Android 23及以上版本。
两个问题:
1.检查尚未启用此权限的正确方法是什么? 2.是否有一个意图(Intent)将用户带到相关的MUIU设置页面?也许: new Intent("android.settings.action.MANAGE_OVERLAY_PERMISSION", packageName),但我没有测试的手段。

你找到解决方案了吗?我也遇到了类似的问题。一些设备(小米、魅族)默认情况下不允许“在其他应用上绘制”。 - TOP
我想我们永远不会知道。 - Denny
5个回答

22

1) 在API 23之前,权限已经被授予,因为用户在安装时授予了该权限。

编辑:似乎在Android 6上存在一个错误(将会在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商店,您的应用程序将自动获得此权限。您可以在这里阅读相关信息。当我问及此事时,我认为它是Android本身的一部分,因为我认为我们只需要针对足够高的targetSdkVersion进行定位即可。但Google写给我的回信(这里)是他们想要避免流行应用程序出现问题。
我建议即使您会自动获得该权限,也要正确处理此权限。

5
你的回答是针对一般情况的,而这个问题并不是关于那种情况的。部分小米和魅族设备并不是以这种方式工作的。 - Mark
@MarkCarter 嗯,这就是它应该工作的方式。你应该和他们交谈,询问他们如何允许它以其他方式工作。 - android developer
2
正确。这个问题是关于如何应用解决方法来处理在许多设备上出现的情况 - 其中许多设备可能永远不会收到更新,无论人们多么成功地说服小米和魅族修补问题。 - Mark
@MarkCarter 好的,抱歉。你是正确的。我不知道那里的工作原理。对于那些情况下尝试将内容放在顶部的东西,你可以尝试使用try-catch。此外,对于意图本身,你可以查看日志以查看需要哪个意图。如果该活动是公开的,你也可以自己找到它(也许尝试“设置”应用程序)。 - android developer
安卓6.0.1版本的SecurityException错误有任何更新吗?即使将targetSdk设置为25,该问题仍然存在。 - Jemshit Iskenderov

9

使用以下方法检查是否有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 可以修改操作系统,导致 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);

}

5

以下是处理此问题的逐步说明:

首先,在清单文件中授予以下权限:

<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()即可。

当您执行该项目时,您将看到一个窗口,并被要求启用权限。允许权限后,您将能够对其进行任何操作。


3
你没有理解问题,所以你的回答没有帮助。 - FD_

1

在搜索小米和魅族问题的过程中,我找到了这个。它完美地运作。

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);
}

参考文献


@Manifest,如何检测设备是否运行像小米、魅族等使用MIUI的操作系统? - tamtom
getBaseTypeClass不能编译,我做错了什么? - Tomer Petel

0
您可以这样检查权限:
boolean granted = activity.checkSelfPermission("android.permission.SYSTEM_ALERT_WINDOW") == PackageManager.PERMISSION_GRANTED;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接