原文鏈接:點擊打開鏈接
=============================================================================================
這篇博客主要介紹的是 Android 主流各種機型和各種版本的懸浮窗權限適配,但是由於碎片化的問題,所以在適配方面也無法做到完全的主流機型適配,這個需要大家的一起努力,這個博客的名字永遠都是一個將來時。
懸浮窗適配
懸浮窗適配有兩種方法:第一種是按照正規的流程,如果系統沒有賦予 APP 彈出懸浮窗的權限,就先跳轉到權限授權界面,等用戶打開該權限之後,再去彈出懸浮窗,比如 QQ 等一些主流應用就是這麼做得;第二種就是利用系統的漏洞,繞過權限的申請,簡單粗暴,這種方法我不是特別建議,但是現在貌似有些應用就是這樣,比如 UC 和有道詞典,這樣適配在大多數手機上都是 OK 的,但是在一些特殊的機型不行,比如某米的 miui8。
正常適配流程
在 4.4~5.1.1 版本之間,和 6.0~最新版本之間的適配方法是不一樣的,之前的版本由於 google 並沒有對這個權限進行單獨處理,所以是各家手機廠商根據需要定製的,所以每個權限的授權界面都各不一樣,適配起來難度較大,6.0 之後適配起來就相對簡單很多了。
Android 4.4 ~ Android 5.1.1
由於判斷權限的類 AppOpsManager 是 API19 版本添加,所以Android 4.4 之前的版本(不包括4.4)就不用去判斷了,直接調用 WindowManager 的 addView 方法彈出即可,但是貌似有些特殊的手機廠商在 API19 版本之前就已經自定義了懸浮窗權限,如果有發現的,請聯繫我。
衆所周知,國產手機的種類實在是過於豐富,而且一個品牌的不同版本還有不一樣的適配方法,比如某米(嫌棄臉),所以我在實際適配的過程中總結了幾種通用的方法, 大家可以參考一下:
可以清楚看到授權 activity 頁面的包名和 activity 名,而且可以清楚地知道跳轉的 intent 是否帶了 extra,如果沒有 extra 就可以直接跳入,如果帶上了 extra,百度一下該 activity 的名字,看能否找到有用信息,比如適配方案或者源碼 APK 之類的;
依舊利用上面的方法,找到 activity 的名字,然後 root 準備適配的手機,直接在相關目錄 /system/app 下把源碼 APK 拷貝出來,反編譯,根據 activity 的名字找到相關代碼,之後的事情就簡單了;
還有一個方法就是發動人力資源去找,看看已經適配該手機機型的 app 公司是否有自己認識的人,或者乾脆點,直接找這個手機公司裏面是否有自己認識的手機開發朋友,直接詢問,方便快捷。
常規手機
由於 6.0 之前的版本常規手機並沒有把懸浮窗權限單獨拿出來,所以正常情況下是可以直接使用 WindowManager.addView 方法直接彈出懸浮窗。
如何判斷手機的機型,辦法很多,在這裏我就不貼代碼了,一般情況下在 terminal 中執行 getprop 命令,然後在打印出來的信息中找到相關的機型信息即可,這裏貼出國產幾款常見機型的判斷:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
|
/**
* 獲取 emui 版本號
* @return
*/
public
static
double
getEmuiVersion() {
try
{
String emuiVersion = getSystemProperty(
"ro.build.version.emui"
);
String version = emuiVersion.substring(emuiVersion.indexOf(
"_"
) +
1
);
return
Double.parseDouble(version);
}
catch
(Exception e) {
e.printStackTrace();
}
return
4.0
;
}
/**
* 獲取小米 rom 版本號,獲取失敗返回 -1
*
* @return miui rom version code, if fail , return -1
*/
public
static
int
getMiuiVersion() {
String version = getSystemProperty(
"ro.miui.ui.version.name"
);
if
(version !=
null
) {
try
{
return
Integer.parseInt(version.substring(
1
));
}
catch
(Exception e) {
Log.e(TAG,
"get miui version code error, version : "
+ version);
}
}
return
-
1
;
}
public
static
String getSystemProperty(String propName) {
String line;
BufferedReader input =
null
;
try
{
Process p = Runtime.getRuntime().exec(
"getprop "
+ propName);
input =
new
BufferedReader(
new
InputStreamReader(p.getInputStream()),
1024
);
line = input.readLine();
input.close();
}
catch
(IOException ex) {
Log.e(TAG,
"Unable to read sysprop "
+ propName, ex);
return
null
;
}
finally
{
if
(input !=
null
) {
try
{
input.close();
}
catch
(IOException e) {
Log.e(TAG,
"Exception while closing InputStream"
, e);
}
}
}
return
line;
}
public
static
boolean
checkIsHuaweiRom() {
return
Build.MANUFACTURER.contains(
"HUAWEI"
);
}
/**
* check if is miui ROM
*/
public
static
boolean
checkIsMiuiRom() {
return
!TextUtils.isEmpty(getSystemProperty(
"ro.miui.ui.version.name"
));
}
public
static
boolean
checkIsMeizuRom() {
//return Build.MANUFACTURER.contains("Meizu");
String meizuFlymeOSFlag = getSystemProperty(
"ro.build.display.id"
);
if
(TextUtils.isEmpty(meizuFlymeOSFlag)){
return
false
;
}
else
if
(meizuFlymeOSFlag.contains(
"flyme"
) || meizuFlymeOSFlag.toLowerCase().contains(
"flyme"
)){
return
true
;
}
else
{
return
false
;
}
}
/**
* check if is 360 ROM
*/
public
static
boolean
checkIs360Rom() {
return
Build.MANUFACTURER.contains(
"QiKU"
);
}
|
小米
首先需要適配的就應該是小米了,而且比較麻煩的事情是,miui 的每個版本適配方法都是不一樣的,所以只能每個版本去單獨適配,不過還好由於使用的人數多,網上的資料也比較全。首先第一步當然是判斷是否賦予了懸浮窗權限,這個時候就需要使用到 AppOpsManager 這個類了,它裏面有一個 checkop 方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
/**
* Do a quick check for whether an application might be able to perform an operation.
* This is <em>not</em> a security check; you must use {@link #noteOp(int, int, String)}
* or {@link #startOp(int, int, String)} for your actual security checks, which also
* ensure that the given uid and package name are consistent. This function can just be
* used for a quick check to see if an operation has been disabled for the application,
* as an early reject of some work. This does not modify the time stamp or other data
* about the operation.
* @param op The operation to check. One of the OP_* constants.
* @param uid The user id of the application attempting to perform the operation.
* @param packageName The name of the application attempting to perform the operation.
* @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
* {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without
* causing the app to crash).
* @throws SecurityException If the app has been configured to crash on this op.
* @hide
*/
public
int
checkOp(
int
op,
int
uid, String packageName) {
try
{
int
mode = mService.checkOperation(op, uid, packageName);
if
(mode == MODE_ERRORED) {
throw
new
SecurityException(buildSecurityExceptionMsg(op, uid, packageName));
}
return
mode;
}
catch
(RemoteException e) {
}
return
MODE_IGNORED;
}
|
找到懸浮窗權限的 op 值是:
1
2
|
/** @hide */
public
static
final
int
OP_SYSTEM_ALERT_WINDOW =
24
;
|
注意到這個函數和這個值其實都是 hide 的,所以沒辦法,你懂的,只能用反射:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
/**
* 檢測 miui 懸浮窗權限
*/
public
static
boolean
checkFloatWindowPermission(Context context) {
final
int
version = Build.VERSION.SDK_INT;
if
(version >=
19
) {
return
checkOp(context,
24
);
//OP_SYSTEM_ALERT_WINDOW = 24;
}
else
{
// if ((context.getApplicationInfo().flags & 1 << 27) == 1) {
// return true;
// } else {
// return false;
// }
return
true
;
}
}
@TargetApi
(Build.VERSION_CODES.KITKAT)
private
static
boolean
checkOp(Context context,
int
op) {
final
int
version = Build.VERSION.SDK_INT;
if
(version >=
19
) {
AppOpsManager manager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
try
{
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) {
Log.e(TAG, Log.getStackTraceString(e));
}
}
else
{
Log.e(TAG,
"Below API 19 cannot invoke!"
);
}
return
false
;
}
|
檢測完成之後就是跳轉到授權頁面去開啓權限了,但是由於 miui 不同版本的權限授權頁面不一樣,所以需要根據不同版本進行不同處理:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
|
/**
* 獲取小米 rom 版本號,獲取失敗返回 -1
*
* @return miui rom version code, if fail , return -1
*/
public
static
int
getMiuiVersion() {
String version = RomUtils.getSystemProperty(
"ro.miui.ui.version.name"
);
if
(version !=
null
) {
try
{
return
Integer.parseInt(version.substring(
1
));
}
catch
(Exception e) {
Log.e(TAG,
"get miui version code error, version : "
+ version);
Log.e(TAG, Log.getStackTraceString(e));
}
}
return
-
1
;
}
/**
* 小米 ROM 權限申請
*/
public
static
void
applyMiuiPermission(Context context) {
int
versionCode = getMiuiVersion();
if
(versionCode ==
5
) {
goToMiuiPermissionActivity_V5(context);
}
else
if
(versionCode ==
6
) {
goToMiuiPermissionActivity_V6(context);
}
else
if
(versionCode ==
7
) {
goToMiuiPermissionActivity_V7(context);
}
else
if
(versionCode ==
8
) {
goToMiuiPermissionActivity_V8(context);
}
else
{
Log.e(TAG,
"this is a special MIUI rom version, its version code "
+ versionCode);
}
}
private
static
boolean
isIntentAvailable(Intent intent, Context context) {
if
(intent ==
null
) {
return
false
;
}
return
context.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY).size() >
0
;
}
/**
* 小米 V5 版本 ROM權限申請
*/
public
static
void
goToMiuiPermissionActivity_V5(Context context) {
Intent intent =
null
;
String packageName = context.getPackageName();
intent =
new
Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
Uri uri = Uri.fromParts(
"package"
, packageName,
null
);
intent.setData(uri);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if
(isIntentAvailable(intent, context)) {
context.startActivity(intent);
}
else
{
Log.e(TAG,
"intent is not available!"
);
}
//設置頁面在應用詳情頁面
// Intent intent = new Intent("miui.intent.action.APP_PERM_EDITOR");
// PackageInfo pInfo = null;
// try {
// pInfo = context.getPackageManager().getPackageInfo
// (HostInterfaceManager.getHostInterface().getApp().getPackageName(), 0);
// } catch (PackageManager.NameNotFoundException e) {
// AVLogUtils.e(TAG, e.getMessage());
// }
// intent.setClassName("com.android.settings", "com.miui.securitycenter.permission.AppPermissionsEditor");
// intent.putExtra("extra_package_uid", pInfo.applicationInfo.uid);
// intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
// if (isIntentAvailable(intent, context)) {
// context.startActivity(intent);
// } else {
// AVLogUtils.e(TAG, "Intent is not available!");
// }
}
/**
* 小米 V6 版本 ROM權限申請
*/
public
static
void
goToMiuiPermissionActivity_V6(Context context) {
Intent intent =
new
Intent(
"miui.intent.action.APP_PERM_EDITOR"
);
intent.setClassName(
"com.miui.securitycenter"
,
"com.miui.permcenter.permissions.AppPermissionsEditorActivity"
);
intent.putExtra(
"extra_pkgname"
, context.getPackageName());
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if
(isIntentAvailable(intent, context)) {
context.startActivity(intent);
}
else
{
Log.e(TAG,
"Intent is not available!"
);
}
}
/**
* 小米 V7 版本 ROM權限申請
*/
public
static
void
goToMiuiPermissionActivity_V7(Context context) {
Intent intent =
new
Intent(
"miui.intent.action.APP_PERM_EDITOR"
);
intent.setClassName(
"com.miui.securitycenter"
,
"com.miui.permcenter.permissions.AppPermissionsEditorActivity"
);
intent.putExtra(
"extra_pkgname"
, context.getPackageName());
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if
(isIntentAvailable(intent, context)) {
context.startActivity(intent);
}
else
{
Log.e(TAG,
"Intent is not available!"
);
}
}
/**
* 小米 V8 版本 ROM權限申請
*/
public
static
void
goToMiuiPermissionActivity_V8(Context context) {
Intent intent =
new
Intent(
"miui.intent.action.APP_PERM_EDITOR"
);
intent.setClassName(
"com.miui.securitycenter"
,
"com.miui.permcenter.permissions.PermissionsEditorActivity"
);
intent.putExtra(
"extra_pkgname"
, context.getPackageName());
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if
(isIntentAvailable(intent, context)) {
context.startActivity(intent);
}
else
{
Log.e(TAG,
"Intent is not available!"
);
}
}
|
getSystemProperty 方法是直接調用 getprop 方法來獲取系統信息:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
public
static
String getSystemProperty(String propName) {
String line;
BufferedReader input =
null
;
try
{
Process p = Runtime.getRuntime().exec(
"getprop "
+ propName);
input =
new
BufferedReader(
new
InputStreamReader(p.getInputStream()),
1024
);
line = input.readLine();
input.close();
}
catch
(IOException ex) {
Log.e(TAG,
"Unable to read sysprop "
+ propName, ex);
return
null
;
}
finally
{
if
(input !=
null
) {
try
{
input.close();
}
catch
(IOException e) {
Log.e(TAG,
"Exception while closing InputStream"
, e);
}
}
}
return
line;
}
|
最新的 V8 版本有些機型已經是 6.0 ,所以就是下面介紹到 6.0 的適配方法了,感謝 @pinocchio2mx 的反饋,有些機型的 miui8 版本還是5.1.1,所以 miui8 依舊需要做適配,非常感謝,希望大家一起多多反饋問題,謝謝~~。
魅族
魅族的適配,由於我司魅族的機器相對較少,所以只適配了 flyme5.1.1/android 5.1.1 版本 mx4 pro 的系統。和小米一樣,首先也要通過 API19 版本添加的 AppOpsManager 類判斷是否授予了權限:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
/**
* 檢測 meizu 懸浮窗權限
*/
public
static
boolean
checkFloatWindowPermission(Context context) {
final
int
version = Build.VERSION.SDK_INT;
if
(version >=
19
) {
return
checkOp(context,
24
);
//OP_SYSTEM_ALERT_WINDOW = 24;
}
return
true
;
}
@TargetApi
(Build.VERSION_CODES.KITKAT)
private
static
boolean
checkOp(Context context,
int
op) {
final
int
version = Build.VERSION.SDK_INT;
if
(version >=
19
) {
AppOpsManager manager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
try
{
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) {
Log.e(TAG, Log.getStackTraceString(e));
}
}
else
{
Log.e(TAG,
"Below API 19 cannot invoke!"
);
}
return
false
;
}
|
然後是跳轉去懸浮窗權限授予界面:
1
2
3
4
5
6
7
8
9
10
|
/**
* 去魅族權限申請頁面
*/
public
static
void
applyPermission(Context context){
Intent intent =
new
Intent(
"com.meizu.safe.security.SHOW_APPSEC"
);
intent.setClassName(
"com.meizu.safe"
,
"com.meizu.safe.security.AppSecActivity"
);
intent.putExtra(
"packageName"
, context.getPackageName());
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
|
如果有魅族其他版本的適配方案,請聯繫我。
華爲
華爲的適配是根據網上找的方案,外加自己的一些優化而成,但是由於華爲手機的衆多機型,所以覆蓋的機型和系統版本還不是那麼全面,如果有其他機型和版本的適配方案,請聯繫我,我更新到 github 上。和小米,魅族一樣,首先通過 AppOpsManager 來判斷權限是否已經授權:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
/**
* 檢測 Huawei 懸浮窗權限
*/
public
static
boolean
checkFloatWindowPermission(Context context) {
final
int
version = Build.VERSION.SDK_INT;
if
(version >=
19
) {
return
checkOp(context,
24
);
//OP_SYSTEM_ALERT_WINDOW = 24;
}
return
true
;
}
@TargetApi
(Build.VERSION_CODES.KITKAT)
private
static
boolean
checkOp(Context context,
int
op) {
final
int
version = Build.VERSION.SDK_INT;
if
(version >=
19
) {
AppOpsManager manager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
try
{
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) {
Log.e(TAG, Log.getStackTraceString(e));
}
}
else
{
Log.e(TAG,
"Below API 19 cannot invoke!"
);
}
return
false
;
}
|
然後根據不同的機型和版本跳轉到不同的頁面:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
/**
* 去華爲權限申請頁面
*/
public
static
void
applyPermission(Context context) {
try
{
Intent intent =
new
Intent();
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
// ComponentName comp = new ComponentName("com.huawei.systemmanager","com.huawei.permissionmanager.ui.MainActivity");//華爲權限管理
// ComponentName comp = new ComponentName("com.huawei.systemmanager",
// "com.huawei.permissionmanager.ui.SingleAppActivity");//華爲權限管理,跳轉到指定app的權限管理位置需要華爲接口權限,未解決
ComponentName comp =
new
ComponentName(
"com.huawei.systemmanager"
,
"com.huawei.systemmanager.addviewmonitor.AddViewMonitorActivity"
);
//懸浮窗管理頁面
intent.setComponent(comp);
if
(RomUtils.getEmuiVersion() ==
3.1
) {
//emui 3.1 的適配
context.startActivity(intent);
}
else
{
//emui 3.0 的適配
comp =
new
ComponentName(
"com.huawei.systemmanager"
,
"com.huawei.notificationmanager.ui.NotificationManagmentActivity"
);
//懸浮窗管理頁面
intent.setComponent(comp);
context.startActivity(intent);
}
}
catch
(SecurityException e) {
Intent intent =
new
Intent();
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
// ComponentName comp = new ComponentName("com.huawei.systemmanager","com.huawei.permissionmanager.ui.MainActivity");//華爲權限管理
ComponentName comp =
new
ComponentName(
"com.huawei.systemmanager"
,
"com.huawei.permissionmanager.ui.MainActivity"
);
//華爲權限管理,跳轉到本app的權限管理頁面,這個需要華爲接口權限,未解決
// ComponentName comp = new ComponentName("com.huawei.systemmanager","com.huawei.systemmanager.addviewmonitor.AddViewMonitorActivity");//懸浮窗管理頁面
intent.setComponent(comp);
context.startActivity(intent);
Log.e(TAG, Log.getStackTraceString(e));
}
catch
(ActivityNotFoundException e) {
/**
* 手機管家版本較低 HUAWEI SC-UL10
*/
// Toast.makeText(MainActivity.this, "act找不到", Toast.LENGTH_LONG).show();
Intent intent =
new
Intent();
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
ComponentName comp =
new
ComponentName(
"com.Android.settings"
,
"com.android.settings.permission.TabItem"
);
//權限管理頁面 android4.4
// ComponentName comp = new ComponentName("com.android.settings","com.android.settings.permission.single_app_activity");//此處可跳轉到指定app對應的權限管理頁面,但是需要相關權限,未解決
intent.setComponent(comp);
context.startActivity(intent);
e.printStackTrace();
Log.e(TAG, Log.getStackTraceString(e));
}
catch
(Exception e) {
//拋出異常時提示信息
Toast.makeText(context,
"進入設置頁面失敗,請手動設置"
, Toast.LENGTH_LONG).show();
Log.e(TAG, Log.getStackTraceString(e));
}
}
|
emui4 之後就是 6.0 版本了,按照下面介紹的 6.0 適配方案即可。
360
360手機的適配方案在網上可以找到的資料很少,也沒有給出最後的適配方案,不過最後居然直接用最簡單的辦法就能跳進去了,首先是權限的檢測:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
/**
* 檢測 360 懸浮窗權限
*/
public
static
boolean
checkFloatWindowPermission(Context context) {
final
int
version = Build.VERSION.SDK_INT;
if
(version >=
19
) {
return
checkOp(context,
24
);
//OP_SYSTEM_ALERT_WINDOW = 24;
}
return
true
;
}
@TargetApi
(Build.VERSION_CODES.KITKAT)
private
static
boolean
checkOp(Context context,
int
op) {
final
int
version = Build.VERSION.SDK_INT;
if
(version >=
19
) {
AppOpsManager manager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
try
{
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) {
Log.e(TAG, Log.getStackTraceString(e));
}
}
else
{
Log.e(
""
,
"Below API 19 cannot invoke!"
);
}
return
false
;
}
|
如果沒有授予懸浮窗權限,就跳轉去權限授予界面:
1
2
3
4
5
6
|
public
static
void
applyPermission(Context context) {
Intent intent =
new
Intent();
intent.setClassName(
"com.android.settings"
,
"com.android.settings.Settings$OverlaySettingsActivity"
);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
|
哈哈哈,是不是很簡單,有時候真相往往一點也不復雜,OK,適配完成。
Android 6.0 及之後版本
懸浮窗權限在 6.0 之後就被 google 單獨拿出來管理了,好處就是對我們來說適配就非常方便了,在所有手機和 6.0 以及之後的版本上適配的方法都是一樣的,首先要在 Manifest 中靜態申請<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
權限,然後在使用時先判斷該權限是否已經被授權,如果沒有授權使用下面這段代碼進行動態申請:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
private
static
final
int
REQUEST_CODE =
1
;
//判斷權限
private
boolean
commonROMPermissionCheck(Context context) {
Boolean result =
true
;
if
(Build.VERSION.SDK_INT >=
23
) {
try
{
Class clazz = Settings.
class
;
Method canDrawOverlays = clazz.getDeclaredMethod(
"canDrawOverlays"
, Context.
class
);
result = (Boolean) canDrawOverlays.invoke(
null
, context);
}
catch
(Exception e) {
Log.e(TAG, Log.getStackTraceString(e));
}
}
return
result;
}
//申請權限
private
void
requestAlertWindowPermission() {
Intent intent =
new
Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
intent.setData(Uri.parse(
"package:"
+ getPackageName()));
startActivityForResult(intent, REQUEST_CODE);
}
@Override
//處理回調
protected
void
onActivityResult(
int
requestCode,
int
resultCode, Intent data) {
super
.onActivityResult(requestCode, resultCode, data);
if
(requestCode == REQUEST_CODE) {
if
(Settings.canDrawOverlays(
this
)) {
Log.i(LOGTAG,
"onActivityResult granted"
);
}
}
}
|
上述代碼需要注意的是:
在用戶開啓相關權限之後才能使用 WindowManager.LayoutParams.TYPE_SYSTEM_ERROR ,要不然是會直接崩潰的哦。
特殊適配流程
如何繞過系統的權限檢查,直接彈出懸浮窗?需要使用mParams.type = WindowManager.LayoutParams.TYPE_TOAST;
來取代 mParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
,這樣就可以達到不申請權限,而直接彈出懸浮窗,至於原因嘛,我們看看 PhoneWindowManager 源碼的關鍵處:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
|
@Override
public
int
checkAddPermission(WindowManager.LayoutParams attrs,
int
[] outAppOp) {
....
switch
(type) {
case
TYPE_TOAST:
// XXX right now the app process has complete control over
// this... should introduce a token to let the system
// monitor/control what they are doing.
outAppOp[
0
] = AppOpsManager.OP_TOAST_WINDOW;
break
;
case
TYPE_DREAM:
case
TYPE_INPUT_METHOD:
case
TYPE_WALLPAPER:
case
TYPE_PRIVATE_PRESENTATION:
case
TYPE_VOICE_INTERACTION:
case
TYPE_ACCESSIBILITY_OVERLAY:
// The window manager will check these.
break
;
case
|