引言:需要實(shí)現(xiàn)一個(gè)視頻懸浮播放的功能,功能實(shí)現(xiàn)后發(fā)現(xiàn)懸浮權(quán)限的檢測(cè)與申請(qǐng)并沒(méi)有想象中那樣簡(jiǎn)單恒削。
時(shí)間:2017年04月10日23:44:46
作者:JustDo23
01. 前言
看到懸浮窗最先想到的就是360懸浮小球和視頻播放的懸浮小窗,懸浮功能通過(guò)WindowManager
來(lái)實(shí)現(xiàn)驱入,另外懸浮功能需要使用到相關(guān)的懸浮窗(SYSTEM_ALERT_WINDOW)權(quán)限俐载,在 Android 6.0 之前 Google 并沒(méi)有對(duì)這個(gè)權(quán)限進(jìn)行單獨(dú)處理,國(guó)內(nèi)各個(gè)手機(jī)廠商訂制 ROOM 進(jìn)行后授權(quán)界面各不相同缓淹,懸浮權(quán)限的檢測(cè)與申請(qǐng)的適配問(wèn)題還需注意一下哈打。
02. 懸浮窗口
利用WindowManager
來(lái)實(shí)現(xiàn)懸浮小球等需求
- android懸浮窗口的實(shí)現(xiàn)
- Android桌面懸浮窗效果實(shí)現(xiàn),仿360手機(jī)衛(wèi)士懸浮窗效果
- Android桌面懸浮窗進(jìn)階讯壶,QQ手機(jī)管家小火箭效果實(shí)現(xiàn)
- 像360懸浮窗那樣料仗,用WindowManager實(shí)現(xiàn)炫酷的懸浮迷你音樂(lè)盒(上)
- 像360懸浮窗那樣,用WindowManager實(shí)現(xiàn)炫酷的懸浮迷你音樂(lè)盒(下)
03. 懸浮權(quán)限
實(shí)現(xiàn)懸浮需要在功能清單中添加權(quán)限
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
這在 Android 中是一個(gè)特殊權(quán)限伏蚊,Android 中的權(quán)限有一般權(quán)限立轧、危險(xiǎn)權(quán)限和兩個(gè)特殊權(quán)限。
04. 權(quán)限適配
Android 懸浮窗權(quán)限各機(jī)型各系統(tǒng)適配大全
https://github.com/zhaozepeng/FloatWindowPermission
https://github.com/czy1121/settingscompat
看過(guò)大量文章之后找到這篇對(duì)懸浮權(quán)限的檢測(cè)及跳轉(zhuǎn)授權(quán)界面適配比較全面的文章,看過(guò)文章和源碼之后簡(jiǎn)單的繪制了一張表格
api<19 | api>=19 && api<23 | api>=23 | |
---|---|---|---|
檢測(cè) | 默認(rèn)擁有 | 小米華為魅族360需要檢測(cè)其他默認(rèn)擁有 | 通用的檢測(cè) |
請(qǐng)求 | 不用跳轉(zhuǎn) | 小米華為魅族360各自跳轉(zhuǎn)其他不用跳轉(zhuǎn) | 通用的跳轉(zhuǎn) |
05. 權(quán)限檢測(cè)
權(quán)限檢測(cè)的總體方法
/**
* 懸浮窗權(quán)限判斷
*
* @param context 上下文
* @return [ true, 有權(quán)限 ][ false, 無(wú)權(quán)限 ]
*/
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()) {// 特殊機(jī)型
hasPermission = opPermissionCheck(context, 24);
} else {// 其他機(jī)型
hasPermission = true;
}
} else if (Build.VERSION.SDK_INT >= 23) {// 6.0 版本之后由于 google 增加了對(duì)懸浮窗權(quán)限的管理氛改,所以方式就統(tǒng)一了
hasPermission = highVersionPermissionCheck(context);
}
return hasPermission;
}
中間版本的特殊機(jī)型權(quán)限檢測(cè)
/**
* [19-23]之間版本通過(guò)[AppOpsManager]的權(quán)限判斷
*
* @param context 上下文
* @param op
* @return [ true, 有權(quán)限 ][ false, 無(wú)權(quán)限 ]
*/
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;
}
高版本的通用權(quán)限檢測(cè)
/**
* Android 6.0 版本及之后的權(quán)限判斷
*
* @param context 上下文
* @return [ true, 有權(quán)限 ][ false, 無(wú)權(quán)限 ]
*/
private boolean highVersionPermissionCheck(Context context) {
if (RomUtils.checkIsMeizuRom()) {// 魅族6.0的系統(tǒng)單獨(dú)適配
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;
}
源碼中有提到魅族手機(jī)Android 6.0
版本并不能使用通用的檢測(cè)方式帐萎,需要使用低版本的方式。
06. 權(quán)限請(qǐng)求
總體的請(qǐng)求權(quán)限方法的偽代碼
private void requestPermission() {
if (Build.VERSION.SDK_INT < 19) {
// 不用跳轉(zhuǎn)
} else if (Build.VERSION.SDK_INT >= 19 && Build.VERSION.SDK_INT < 23) {
if (RomUtils.checkIsMiuiRom() || RomUtils.checkIsMeizuRom() || RomUtils.checkIsHuaweiRom() || RomUtils.checkIs360Rom()) {
// 分機(jī)型跳轉(zhuǎn)
} else {
// 不用跳轉(zhuǎn)
}
} else if (Build.VERSION.SDK_INT >= 23) {
// 通用跳轉(zhuǎn)
}
}
總體的請(qǐng)求權(quán)限的方法
/**
* 請(qǐng)求懸浮窗權(quán)限
*
* @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);
}
}
注意:中間版本權(quán)限的檢測(cè)都是一樣的胜卤,都是使用AppOpsManager
類的方式并且參數(shù)op
的值都是24
疆导,這里重點(diǎn)適配的是不同的手機(jī)跳轉(zhuǎn)到不同的授權(quán)界面,而且小米手機(jī)各個(gè)版本的授權(quán)界面并不完全相同瑰艘。
高版本的跳轉(zhuǎn)通用授權(quán)界面
/**
* Android 6.0 版本及之后的跳轉(zhuǎn)權(quán)限申請(qǐng)界面
*
* @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));
}
}
這里同樣需要注意魅族手機(jī)Android 6.0
版本仍舊使用中間版本的方式是鬼。
07. 應(yīng)用詳情
在使用即刻
APP的時(shí)候仔細(xì)觀察了一下,發(fā)現(xiàn)即刻的視頻懸浮同樣需要使用權(quán)限紫新,在沒(méi)有授權(quán)限的時(shí)候會(huì)彈框提示用戶去應(yīng)用詳情界面去設(shè)置權(quán)限均蜜,提示路徑設(shè)置 > 應(yīng)用 > 即刻
,同時(shí)點(diǎn)擊按鈕直接跳轉(zhuǎn)應(yīng)用詳情界面芒率。這種方式雖然簡(jiǎn)單但其實(shí)并不是所有的機(jī)型都可以達(dá)到完美適配的情況囤耳,仍有部分機(jī)型在此界面是無(wú)法跳轉(zhuǎn)到相關(guān)的手機(jī)位置,用戶可能會(huì)一臉茫然偶芍。
/**
* 跳轉(zhuǎn)應(yīng)用詳情界面
*
* @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);
}
這樣便可以嘗試簡(jiǎn)單的授權(quán)請(qǐng)求
/**
* 請(qǐng)求懸浮窗權(quán)限
*
* @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. 權(quán)限規(guī)避
其實(shí)這些文章中提到的規(guī)避方案都是在type
類型的指定上:
- WindowManager.LayoutParams.TYPE_TOAST
- WindowManager.LayoutParams.TYPE_PHONE
09. 第三方庫(kù)
強(qiáng)調(diào)一下找到的比較好的適配庫(kù)
10. 有趣的事
16年國(guó)慶買個(gè)了魅藍(lán) 3s
手機(jī)5.x的系統(tǒng)充择,用了一段時(shí)間后在某次寫(xiě)Demo中發(fā)現(xiàn)Toast
吐不出來(lái),不明原因匪蟀,然而并沒(méi)有在意椎麦。年底在自如上租了房子,電子門鎖需要從手機(jī)上獲取動(dòng)態(tài)密碼材彪,照樣是Toast
吐不出來(lái)观挎,尷尬了好幾天,結(jié)果在一次倒騰中發(fā)現(xiàn)了手機(jī)管家中的權(quán)限管理段化,通知管理-懸浮窗嘁捷。好些國(guó)產(chǎn)手機(jī)都有手機(jī)管家之類的系統(tǒng)軟件,有相關(guān)權(quán)限管理显熏,魅藍(lán) 3s
手機(jī)5.x的系統(tǒng)中的手機(jī)管理 > 權(quán)限管理 > 通知管理 > 懸浮窗
就可以管理一個(gè)應(yīng)用的懸浮窗權(quán)限雄嚣,就在昨天我升級(jí)手機(jī)系統(tǒng)到Flyme 6.0.2.0A
并且是Android 5.1
結(jié)果相同路徑的懸浮窗就變成了懸浮通知
并能管理懸浮窗權(quán)限,另外應(yīng)用詳情中的懸浮窗
變成了桌面懸浮窗
真正管理懸浮窗權(quán)限喘蟆。