Android 悬浮窗及权限

引言:需要实现一个视频悬浮播放的功能,功能实现后发现悬浮权限的检测与申请并没有想象中那样简单。

时间:2017年04月10日23:44:46

作者:JustDo23

01. 前言

看到悬浮窗最先想到的就是360悬浮小球和视频播放的悬浮小窗,悬浮功能通过WindowManager来实现,另外悬浮功能需要使用到相关的悬浮窗(SYSTEM_ALERT_WINDOW)权限,在 Android 6.0 之前 Google 并没有对这个权限进行单独处理,国内各个手机厂商订制 ROOM 进行后授权界面各不相同,悬浮权限的检测与申请的适配问题还需注意一下。

02. 悬浮窗口

利用WindowManager来实现悬浮小球等需求

  1. android悬浮窗口的实现
  2. Android桌面悬浮窗效果实现,仿360手机卫士悬浮窗效果
  3. Android桌面悬浮窗进阶,QQ手机管家小火箭效果实现
  4. 像360悬浮窗那样,用WindowManager实现炫酷的悬浮迷你音乐盒(上)
  5. 像360悬浮窗那样,用WindowManager实现炫酷的悬浮迷你音乐盒(下)

03. 悬浮权限

实现悬浮需要在功能清单中添加权限

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>

这在 Android 中是一个特殊权限,Android 中的权限有一般权限、危险权限和两个特殊权限。

04. 权限适配

Android 悬浮窗权限各机型各系统适配大全

https://github.com/zhaozepeng/FloatWindowPermission

https://github.com/czy1121/settingscompat

看过大量文章之后找到这篇对悬浮权限的检测及跳转授权界面适配比较全面的文章,看过文章和源码之后简单的绘制了一张表格

api<19 api>=19 && api<23 api>=23
检测 默认拥有 小米华为魅族360需要检测其他默认拥有 通用的检测
请求 不用跳转 小米华为魅族360各自跳转其他不用跳转 通用的跳转

05. 权限检测

权限检测的总体方法

/**
 * 悬浮窗权限判断
 *
 * @param context 上下文
 * @return [ true, 有权限 ][ false, 无权限 ]
 */
private boolean checkPermission(Context context) {
  Boolean hasPermission = false;
  if (Build.VERSION.SDK_INT < 19) {
    hasPermission = true;
  } else if (Build.VERSION.SDK_INT >= 19 && Build.VERSION.SDK_INT < 23) {
    if (RomUtils.checkIsMiuiRom() || RomUtils.checkIsMeizuRom() || RomUtils.checkIsHuaweiRom() || RomUtils.checkIs360Rom()) {// 特殊机型
      hasPermission = opPermissionCheck(context, 24);
    } else {// 其他机型
      hasPermission = true;
    }
  } else if (Build.VERSION.SDK_INT >= 23) {// 6.0 版本之后由于 google 增加了对悬浮窗权限的管理,所以方式就统一了
    hasPermission = highVersionPermissionCheck(context);
  }
  return hasPermission;
}

中间版本的特殊机型权限检测

/**
 * [19-23]之间版本通过[AppOpsManager]的权限判断
 *
 * @param context 上下文
 * @param op
 * @return [ true, 有权限 ][ false, 无权限 ]
 */
private boolean opPermissionCheck(Context context, int op) {
  try {
    AppOpsManager manager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
    Class clazz = AppOpsManager.class;
    Method method = clazz.getDeclaredMethod("checkOp", int.class, int.class, String.class);
    return AppOpsManager.MODE_ALLOWED == (int) method.invoke(manager, op, Binder.getCallingUid(), context.getPackageName());
  } catch (Exception e) {
    LogUtil.e(Log.getStackTraceString(e));
  }
  return false;
}

高版本的通用权限检测

/**
 * Android 6.0 版本及之后的权限判断
 *
 * @param context 上下文
 * @return [ true, 有权限 ][ false, 无权限 ]
 */
private boolean highVersionPermissionCheck(Context context) {
  if (RomUtils.checkIsMeizuRom()) {// 魅族6.0的系统单独适配
    return opPermissionCheck(context, 24);
  }
  try {
    Class clazz = Settings.class;
    Method canDrawOverlays = clazz.getDeclaredMethod("canDrawOverlays", Context.class);
    return (Boolean) canDrawOverlays.invoke(null, context);
  } catch (Exception e) {
    LogUtil.e(Log.getStackTraceString(e));
  }
  return false;
}

源码中有提到魅族手机Android 6.0版本并不能使用通用的检测方式,需要使用低版本的方式。

06. 权限请求

总体的请求权限方法的伪代码

private void requestPermission() {
  if (Build.VERSION.SDK_INT < 19) {
    // 不用跳转
  } else if (Build.VERSION.SDK_INT >= 19 && Build.VERSION.SDK_INT < 23) {
    if (RomUtils.checkIsMiuiRom() || RomUtils.checkIsMeizuRom() || RomUtils.checkIsHuaweiRom() || RomUtils.checkIs360Rom()) {
      // 分机型跳转
    } else {
      // 不用跳转
    }
  } else if (Build.VERSION.SDK_INT >= 23) {
    // 通用跳转
  }
}

总体的请求权限的方法

/**
 * 请求悬浮窗权限
 *
 * @param context 上下文
 */
private void requestPermission(Context context) {
  if (Build.VERSION.SDK_INT >= 19 && Build.VERSION.SDK_INT < 23) {
    if (RomUtils.checkIsMiuiRom()) {
      miuiROMPermissionApply(context);
    } else if (RomUtils.checkIsMeizuRom()) {
      meizuROMPermissionApply(context);
    } else if (RomUtils.checkIsHuaweiRom()) {
      huaweiROMPermissionApply(context);
    } else if (RomUtils.checkIs360Rom()) {
      ROM360PermissionApply(context);
    }
  } else if (Build.VERSION.SDK_INT >= 23) {
    highVersionPermissionRequest(context);
  }
}

注意:中间版本权限的检测都是一样的,都是使用AppOpsManager类的方式并且参数op的值都是24,这里重点适配的是不同的手机跳转到不同的授权界面,而且小米手机各个版本的授权界面并不完全相同。

高版本的跳转通用授权界面

/**
 * Android 6.0 版本及之后的跳转权限申请界面
 *
 * @param context 上下文
 */
private void highVersionJump2PermissionActivity(Context context) {
  try {
    Class clazz = Settings.class;
    Field field = clazz.getDeclaredField("ACTION_MANAGE_OVERLAY_PERMISSION");
    Intent intent = new Intent(field.get(null).toString());
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    intent.setData(Uri.parse("package:" + context.getPackageName()));
    context.startActivity(intent);
  } catch (Exception e) {
    Log.e(TAG, Log.getStackTraceString(e));
  }
}

这里同样需要注意魅族手机Android 6.0版本仍旧使用中间版本的方式。

07. 应用详情

在使用即刻APP的时候仔细观察了一下,发现即刻的视频悬浮同样需要使用权限,在没有授权限的时候会弹框提示用户去应用详情界面去设置权限,提示路径设置 > 应用 > 即刻,同时点击按钮直接跳转应用详情界面。这种方式虽然简单但其实并不是所有的机型都可以达到完美适配的情况,仍有部分机型在此界面是无法跳转到相关的手机位置,用户可能会一脸茫然。

/**
 * 跳转应用详情界面
 *
 * @param context
 */
private void jump2DetailActivity(Context context) {
  Intent intent = new Intent();
  intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
  intent.setData(Uri.fromParts("package", context.getPackageName(), null));
  context.startActivity(intent);
}

这样便可以尝试简单的授权请求

/**
 * 请求悬浮窗权限
 *
 * @param context 上下文
 */
private void requestPermission(Context context) {
  if (Build.VERSION.SDK_INT >= 19 && Build.VERSION.SDK_INT < 23) {
    jump2DetailActivity(context);
  } else if (Build.VERSION.SDK_INT >= 23) {
    highVersionPermissionRequest(context);
  }
}

08. 权限规避

  1. Android无需权限显示悬浮窗, 兼谈逆向分析app
  2. 突破小米悬浮窗权限控制--不需要权限的悬浮窗

其实这些文章中提到的规避方案都是在type类型的指定上:

  • WindowManager.LayoutParams.TYPE_TOAST
  • WindowManager.LayoutParams.TYPE_PHONE

09. 第三方库

强调一下找到的比较好的适配库

10. 有趣的事

16年国庆买个了魅蓝 3s手机5.x的系统,用了一段时间后在某次写Demo中发现Toast吐不出来,不明原因,然而并没有在意。年底在自如上租了房子,电子门锁需要从手机上获取动态密码,照样是Toast吐不出来,尴尬了好几天,结果在一次倒腾中发现了手机管家中的权限管理,通知管理-悬浮窗。好些国产手机都有手机管家之类的系统软件,有相关权限管理,魅蓝 3s手机5.x的系统中的手机管理 > 权限管理 > 通知管理 > 悬浮窗就可以管理一个应用的悬浮窗权限,就在昨天我升级手机系统到Flyme 6.0.2.0A并且是Android 5.1结果相同路径的悬浮窗就变成了悬浮通知并能管理悬浮窗权限,另外应用详情中的悬浮窗变成了桌面悬浮窗真正管理悬浮窗权限。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容