关于Android WindowManager行为变化

本文是关于针对Android WindowManager悬浮窗行为变化的总结。

官方参考

WindowManager
public interface WindowManager
implements ViewManager

android.view.WindowManager

示例

首先创建一个简单的WindowManager悬浮窗,具体代码如下:

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
package com.shoewann.windowmanager;
import android.graphics.PixelFormat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.Gravity;
import android.view.View;
import android.view.WindowManager;
import com.shoewann.windowmanager.view.FloatView;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
(findViewById(R.id.btn_show)).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
setUpFloatView();
}
});
}
public void setUpFloatView() {
WindowManager windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
FloatView floatView = new FloatView(getApplicationContext());
WindowManager.LayoutParams params = new WindowManager.LayoutParams();
params.type = WindowManager.LayoutParams.TYPE_PHONE;
params.format = PixelFormat.RGBA_8888;
params.gravity = Gravity.START | Gravity.TOP;
params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
params.width = 200;
params.height = 200;
params.x = 0;
params.y = 0;
floatView.setImageResource(R.mipmap.ic_launcher_round);
windowManager.addView(floatView, params);
}
}

上面的代码简单易懂,这里就不再进行解释了。

最后不要忘记了,在AndroidManifest.xml添加所需要的权限。

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

行为变化

SDK < Android M

以上的示例代码,在这种情况下(比如Kitkat 4.4)的设备上现在就已经可以成功创建出一个系统悬浮窗了。

运行,效果图如下

windowmanager-k

SDK >= Android M

到了Android M 之后的设备,也就是targetSdkVersion要指定23(及其以上),程序就会崩溃,抛出如下异常信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.shoewann.windowmanager, PID: 3120
android.view.WindowManager$BadTokenException: Unable to add window android.view.ViewRootImpl$W@c1fda7 -- permission denied for this window type
at android.view.ViewRootImpl.setView(ViewRootImpl.java:591)
at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:310)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:85)
at com.shoewann.windowmanager.MainActivity.setUpFloatView(MainActivity.java:39)
at com.shoewann.windowmanager.MainActivity$1.onClick(MainActivity.java:21)
at android.view.View.performClick(View.java:5198)
at android.view.View$PerformClick.run(View.java:21147)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5417)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)

参考:SYSTEM_ALERT_WINDOW

Note: If the app targets API level 23 or higher, the app user must explicitly grant this permission to the app through a permission management screen. The app requests the user’s approval by sending an intent with action ACTION_MANAGE_OVERLAY_PERMISSION. The app can check whether it has this authorization by calling Settings.canDrawOverlays().

官方的解释是,如果你的应用程序目标API等级为23或者更高版本,在使用SYSTEM_ALERT_WINDOW权限时,需要先调用Settings.canDrawOverlays()来判断一下是否允许创建悬浮窗,如果允许的话就可以创建了,不允许的话还要发送一个action值为ACTION_MANAGE_OVERLAY_PERMISSION的Intent来让用户同意创建悬浮窗。

针对该行为变化,对上面的示例代码进行了修改,添加权限请求,以兼容Android M:

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
package com.shoewann.windowmanager;
import android.content.Context;
import android.content.Intent;
import android.graphics.PixelFormat;
import android.net.Uri;
import android.os.Build;
import android.provider.Settings;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.Gravity;
import android.view.View;
import android.view.WindowManager;
import com.shoewann.windowmanager.view.FloatView;
import java.lang.reflect.Method;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
(findViewById(R.id.btn_show)).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (!commonROMPermissionCheck(MainActivity.this)) {
requestAlertWindowPermission();
} else {
setUpFloatView();
}
}
});
}
public void setUpFloatView() {
Log.d(TAG, "setUpFloatView() called");
WindowManager windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
FloatView floatView = new FloatView(getApplicationContext());
WindowManager.LayoutParams params = new WindowManager.LayoutParams();
params.type = WindowManager.LayoutParams.TYPE_PHONE;
params.format = PixelFormat.RGBA_8888;
params.gravity = Gravity.START | Gravity.TOP;
params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
params.width = 200;
params.height = 200;
params.x = 0;
params.y = 0;
floatView.setImageResource(R.mipmap.ic_launcher_round);
windowManager.addView(floatView, params);
}
private static final int REQUEST_CODE = 1;
//判断权限
private boolean commonROMPermissionCheck(Context context) {
Log.d(TAG, "commonROMPermissionCheck() called with: 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() {
Log.d(TAG, "requestAlertWindowPermission() called");
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
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 (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (Settings.canDrawOverlays(this)) {
Log.i(TAG, "onActivityResult granted");
setUpFloatView();
}else {
Log.i(TAG, "onActivityResult denied");
}
}
}
}
}

运行,效果图如下

windowmanager-m

SDK >= Android O

到了Android O 的设备运行上面在M上二次修改的动态请求权限的代码,程序抛出运行时异常,随后崩溃,这又是为什么呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.shoewann.windowmanager, PID: 17929
android.view.WindowManager$BadTokenException: Unable to add window android.view.ViewRootImpl$W@d422e7c -- permission denied for window type 2002
at android.view.ViewRootImpl.setView(ViewRootImpl.java:789)
at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:356)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:93)
at com.shoewann.windowmanager.MainActivity.setUpFloatView(MainActivity.java:54)
at com.shoewann.windowmanager.MainActivity$1.onClick(MainActivity.java:34)
at android.view.View.performClick(View.java:6294)
at android.view.View$PerformClick.run(View.java:24770)
at android.os.Handler.handleCallback(Handler.java:790)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:164)
at android.app.ActivityThread.main(ActivityThread.java:6494)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)

查阅Android Oreo官方文档,Alert windows行为变化

应用程序使用SYSTEM_ALERT_WINDOW权限时,不再支持如下窗口类型在其他应用和系统窗口之上显示警告窗口了:

应用必须使用一个被叫做TYPE_APPLICATION_OVERLAY类型的新窗口类型替代。

当使用TYPE_APPLICATION_OVERLAY类型的窗口为你的应用程序去显示警告窗口,请记住新窗口的如下特征:

  • 一个应用程序的警告窗口总是显示在系统窗口之下,比如状态栏和输入法。
  • 系统能够移除和重置窗口大小,使用TYPE_APPLICATION_OVERLAY类型的窗口去提高屏幕演示
  • 通过打开通知栏窗帘,用户能够访问设置去屏蔽一个应用通过TYPE_APPLICATION_OVERLAY窗口类型去显示警告窗口的显示

针对该行为变化,对上面的示例代码进行了修改,以兼容Android O:

更改TYPE_PHONETYPE_APPLICATION_OVERLAY

1
2
- params.type = WindowManager.LayoutParams.TYPE_PHONE;
+ params.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;

运行,效果图如下

windowmanager-o

以上就是简单的对Android WindowManager悬浮窗行为变化的简单总结。如有不足,欢迎指正,谢谢。

Shoewann wechat
欢迎订阅公众号——“谷愛”
如果觉得本文对您有用,请随意 ¥打赏支持 !