引言:需要實現(xiàn)一個視頻懸浮播放的功能,功能實現(xiàn)后發(fā)現(xiàn)懸浮權限的檢測與申請并沒有想象中那樣簡單。
時間:2017年04月10日23:44:46
作者:JustDo23
01. 前言
看到懸浮窗最先想到的就是360懸浮小球和視頻播放的懸浮小窗,懸浮功能通過WindowManager來實現(xiàn),另外懸浮功能需要使用到相關的懸浮窗(SYSTEM_ALERT_WINDOW)權限,在 Android 6.0 之前 Google 并沒有對這個權限進行單獨處理,國內(nèi)各個手機廠商訂制 ROOM 進行后授權界面各不相同,懸浮權限的檢測與申請的適配問題還需注意一下。
02. 懸浮窗口
利用WindowManager來實現(xiàn)懸浮小球等需求
- android懸浮窗口的實現(xiàn)
- Android桌面懸浮窗效果實現(xiàn),仿360手機衛(wèi)士懸浮窗效果
- Android桌面懸浮窗進階,QQ手機管家小火箭效果實現(xiàn)
- 像360懸浮窗那樣,用WindowManager實現(xiàn)炫酷的懸浮迷你音樂盒(上)
- 像360懸浮窗那樣,用WindowManager實現(xiàn)炫酷的懸浮迷你音樂盒(下)
03. 懸浮權限
實現(xiàn)懸浮需要在功能清單中添加權限
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
這在 Android 中是一個特殊權限,Android 中的權限有一般權限、危險權限和兩個特殊權限。
04. 權限適配
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 增加了對懸浮窗權限的管理,所以方式就統(tǒng)一了
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的系統(tǒng)單獨適配
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類的方式并且參數(shù)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的時候仔細觀察了一下,發(fā)現(xiàn)即刻的視頻懸浮同樣需要使用權限,在沒有授權限的時候會彈框提示用戶去應用詳情界面去設置權限,提示路徑設置 > 應用 > 即刻,同時點擊按鈕直接跳轉應用詳情界面。這種方式雖然簡單但其實并不是所有的機型都可以達到完美適配的情況,仍有部分機型在此界面是無法跳轉到相關的手機位置,用戶可能會一臉茫然。
/**
* 跳轉應用詳情界面
*
* @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. 權限規(guī)避
其實這些文章中提到的規(guī)避方案都是在type類型的指定上:
- WindowManager.LayoutParams.TYPE_TOAST
- WindowManager.LayoutParams.TYPE_PHONE
09. 第三方庫
強調一下找到的比較好的適配庫
10. 有趣的事
16年國慶買個了魅藍 3s手機5.x的系統(tǒng),用了一段時間后在某次寫Demo中發(fā)現(xiàn)Toast吐不出來,不明原因,然而并沒有在意。年底在自如上租了房子,電子門鎖需要從手機上獲取動態(tài)密碼,照樣是Toast吐不出來,尷尬了好幾天,結果在一次倒騰中發(fā)現(xiàn)了手機管家中的權限管理,通知管理-懸浮窗。好些國產(chǎn)手機都有手機管家之類的系統(tǒng)軟件,有相關權限管理,魅藍 3s手機5.x的系統(tǒng)中的手機管理 > 權限管理 > 通知管理 > 懸浮窗就可以管理一個應用的懸浮窗權限,就在昨天我升級手機系統(tǒng)到Flyme 6.0.2.0A并且是Android 5.1結果相同路徑的懸浮窗就變成了懸浮通知并能管理懸浮窗權限,另外應用詳情中的懸浮窗變成了桌面懸浮窗真正管理懸浮窗權限。