Window和WindowManager

bugly也写了一篇更详细的
浅析Android的窗口
windowmanager 使用 demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
WindowManager mWindowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
Button mFloatingButton = new Button(this);
mFloatingButton.setText("click me");
WindowManager.LayoutParams mLayoutParams = new WindowManager.LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, 0, 0,
PixelFormat.TRANSPARENT);
mLayoutParams.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL
| LayoutParams.FLAG_NOT_FOCUSABLE
| LayoutParams.FLAG_SHOW_WHEN_LOCKED;
mLayoutParams.type = LayoutParams.TYPE_SYSTEM_ERROR;
mLayoutParams.gravity = Gravity.LEFT | Gravity.TOP;
mLayoutParams.x = 100;
mLayoutParams.y = 300;
mWindowManager.addView(mFloatingButton, mLayoutParams);

注意:小米等手机进行了framework的修改,所以不能正常显示悬浮窗,需要自己去设置中心里面设置允许弹窗。

一、WindowManager.LayoutParams的flag

Flags参数表示window的属性,通过这些选项可以控制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
35
/** Window flag: this window won't ever get key input focus, so the
* user can not send key or other button events to it. Those will
* instead go to whatever focusable window is behind it. This flag
* will also enable {@link #FLAG_NOT_TOUCH_MODAL} whether or not that
* is explicitly set.
*
* <p>Setting this flag also implies that the window will not need to
* interact with
* a soft input method, so it will be Z-ordered and positioned
* independently of any active input method (typically this means it
* gets Z-ordered on top of the input method, so it can use the full
* screen for its content and cover the input method if needed. You
* can use {@link #FLAG_ALT_FOCUSABLE_IM} to modify this behavior. */
public static final int FLAG_NOT_FOCUSABLE = 0x00000008;

/** Window flag: even when this window is focusable (its
* {@link #FLAG_NOT_FOCUSABLE} is not set), allow any pointer events
* outside of the window to be sent to the windows behind it. Otherwise
* it will consume all pointer events itself, regardless of whether they
* are inside of the window. */

public static final int FLAG_NOT_TOUCH_MODAL = 0x00000020;


/** Window flag: special flag to let windows be shown when the screen
* is locked. This will let application windows take precedence over
* key guard or any other lock screens. Can be used with
* {@link #FLAG_KEEP_SCREEN_ON} to turn screen on and display windows
* directly before showing the key guard window. Can be used with
* {@link #FLAG_DISMISS_KEYGUARD} to automatically fully dismisss
* non-secure keyguards. This flag only applies to the top-most
* full-screen window.
*/
public static final int FLAG_SHOW_WHEN_LOCKED = 0x00080000;
//window会显示在锁屏界面(但测试发现,并没有)

二、WindowManager.LayoutParams的type

Type 表示Window的类型。有3种类型。

  • 应用Window。z-index 1-99
  • 子Window。不能单独存在,需要附属在父Window上。比如PopupWindow(TYPE_APPLICATION_PANEL),Dialog(查看源代码)。z-index 1000-1999
  • 系统Window。Toast和状态栏等。z-index 2000-2999
    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
    /**
    * Start of types of sub-windows. The {@link #token} of these windows
    * must be set to the window they are attached to. These types of
    * windows are kept next to their attached window in Z-order, and their
    * coordinate space is relative to their attached window.
    */

    public static final int FIRST_SUB_WINDOW = 1000;
    /**
    * Window type: a panel on top of an application window. These windows
    * appear on top of their attached window.
    */

    public static final int TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW;
    /**
    * Start of system-specific window types. These are not normally
    * created by applications.
    */

    public static final int FIRST_SYSTEM_WINDOW = 2000;
    /**
    * Window type: the status bar. There can be only one status bar
    * window; it is placed at the top of the screen, and all other
    * windows are shifted down so they are below it.
    * In multiuser systems shows on all users' windows.
    */

    public static final int TYPE_STATUS_BAR = FIRST_SYSTEM_WINDOW;
    /**
    * Window type: transient notifications.
    * In multiuser systems shows only on the owning user's window.
    */

    public static final int TYPE_TOAST = FIRST_SYSTEM_WINDOW+5;

z-index越大,显示越在前面。有些type需要声明权限后使用。
利用type可以做悬浮窗,之前UC 浏览器出来的弹窗被广大安卓爱好者反编译研究。

秋百万对悬浮窗的总结
Android无需权限显示悬浮窗, 兼谈逆向分析app

三、WindowManager类关系图


WindowManage继承自ViewManager,分别处理添加、删除、更新view的操作。WindowManager操作的都是View,而实际上Window是一个抽象的概念,是以View的形式存在的。View变化,与之对应的Window也会随之变化。如下面的代码,通过给View设置OnClickListener,改变LayoutParams.x,y就可以改变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
@Override
public boolean onTouch(View v, MotionEvent event) {

int rawX = (int) event.getRawX();
int rawY = (int) event.getRawY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
break;
}
case MotionEvent.ACTION_MOVE: {
int x = (int) event.getX();
int y = (int) event.getY();
mLayoutParams.x = rawX;
mLayoutParams.y = rawY;
mWindowManager.updateViewLayout(mFloatingButton, mLayoutParams);
break;
}
case MotionEvent.ACTION_UP: {
break;
}
default:
break;
}

return false;
}

四、详解Window

Window是抽象类,从[Android是怎么加载布局的?]中我们发现PhoneWindow是Window的实例,而这个PhoneWindow是在com.android.internal.policy.impl包中,我们无法直接使用,只能是通过WindowManager来访问。

window的添加

需要使用WindowManager的addView来实现,WindowManagerImpl实现了WindowManager.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
Provides low-level communication with the system window manager for operations that are bound to a particular context, display or parent window. Instances of this object are sensitive to the compatibility info associated with the running application. This object implements the ViewManager interface, allowing you to add any View subclass as a top-level window on the screen. Additional window manager specific layout parameters are defined for control over how windows are displayed. It also implements the WindowManager interface, allowing you to control the displays attached to the device.
Applications will not normally use WindowManager directly, instead relying on the higher-level facilities in android.app.Activity and android.app.Dialog.

Even for low-level window manager access, it is almost never correct to use this class. For example, android.app.Activity.getWindowManager() provides a window manager for adding windows that are associated with that activity -- the window manager will not normally allow you to add arbitrary windows that are not associated with an activity.
*/*

public final class WindowManagerImpl implements WindowManager {

private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
private final Display mDisplay;
private final Window mParentWindow;
//省略n行代码。。
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mDisplay, mParentWindow);
}

WindowManagerGlobal.java

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
private final ArrayList<View> mViews = new ArrayList<View>();
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
private final ArrayList<WindowManager.LayoutParams> mParams =
new ArrayList<WindowManager.LayoutParams>();

public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
if (display == null) {
throw new IllegalArgumentException("display must not be null");
}
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}

final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
if (parentWindow != null) {
parentWindow.adjustLayoutParamsForSubWindow(wparams);
} else {
// If there's no parent and we're running on L or above (or in the
// system context), assume we want hardware acceleration.
final Context context = view.getContext();
if (context != null
&& context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP) {
wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
}
}

ViewRootImpl root;
View panelParentView = null;

synchronized (mLock) {
// Start watching for system property changes.
if (mSystemPropertyUpdater == null) {
mSystemPropertyUpdater = new Runnable() {
@Override public void run() {
synchronized (mLock) {
for (int i = mRoots.size() - 1; i >= 0; --i) {
mRoots.get(i).loadSystemProperties();
}
}
}
};
SystemProperties.addChangeCallback(mSystemPropertyUpdater);
}

int index = findViewLocked(view, false);
if (index >= 0) {
if (mDyingViews.contains(view)) {
// Don't wait for MSG_DIE to make it's way through root's queue.
mRoots.get(index).doDie();
} else {
throw new IllegalStateException("View " + view
+ " has already been added to the window manager.");
}
// The previous removeView() had not completed executing. Now it has.
}

// If this is a panel window, then find the window it is being
// attached to for future reference.
if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
final int count = mViews.size();
for (int i = 0; i < count; i++) {
if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
panelParentView = mViews.get(i);
}
}
}

root = new ViewRootImpl(view.getContext(), display);

view.setLayoutParams(wparams);

mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
}

// do this last because it fires off messages to start doing things
try {
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
synchronized (mLock) {
final int index = findViewLocked(view, false);
if (index >= 0) {
removeViewLocked(index, true);
}
}
throw e;
}
}

根据代码,WindowManagerGlobal.addView()主要分以下几步:
1、检查参数是否合法
2、创建ViewRootImpl,并将View添加到列表mRoots中;view添加到mViews中;param添加到mParams中。
3、通过root.setView来完成Window的添加过程。ViewRootImpl的setView方法会触发requestLayout,然后就是喜闻乐见的view绘制过程了。接下来通过windowSession完成window的添加过程。最终交给WMS来处理。
ViewRootImpl.java

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
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
//省略n多代码
// Schedule the first layout -before- adding to the window
// manager, to make sure we do the relayout before receiving
// any other events from the system.
requestLayout();
if ((mWindowAttributes.inputFeatures
& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
mInputChannel = new InputChannel();
}

try {
mOrigWindowType = mWindowAttributes.type;
mAttachInfo.mRecomputeGlobalAttributes = true;
collectViewAttributes();
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mInputChannel);
} catch (RemoteException e) {
mAdded = false;
mView = null;
mAttachInfo.mRootView = null;
mInputChannel = null;
mFallbackEventHandler.setView(null);
unscheduleTraversals();
setAccessibilityFocus(null, null);
throw new RuntimeException("Adding window failed", e);
} finally {
if (restore) {
attrs.restore();
}
}

}
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}

window的删除和更新

这个就没啥好说的了。跟上面太类似了。删除通过VIewRootImpl来完成删除操作,进行垃圾回收等等。通过WMS删除window;回调Window的onDetachedFromWindow等等。更新就是重新设置LayoutParam,ViewRootImpl通过schedualTraversals对View进行重绘。结合Android源码和开发艺术探索第8章去看吧。

五、Window的创建过程

View是Android中视图的呈现方式,但View是附着在Window这个抽象上面,因此有视图的地方就有window,比如Activity、Dialog、Toast、Popupwindow等。
其实在Android是怎么加载布局的?中已经描述了window的创建过程,就是那个PhoneWindow。。
简单描述Activity启动过程:

  • 1、ActivityThread.performLaunchActivity
    ActivityThread.java

    1
    2
    3
    4
    5
    6
    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    ...
    activity.attach(...)

    ...
    }
  • 2、创建Window,设置回调
    PhoneWindow

    1
    2
    3
    4
    5
    6
    7
    8
    9
    final void attach(Context context, ActivityThread aThread,
    Instrumentation instr, IBinder token, int ident,
    Application application, Intent intent, ActivityInfo info,
    CharSequence title, Activity parent, String id,
    NonConfigurationInstances lastNonConfigurationInstances,
    Configuration config, String referrer, IVoiceInteractor voiceInteractor) {
    attachBaseContext(context);
    mFragments.attachActivity(this, mContainer, null);
    mWindow = PolicyManager.makeNewWindow(this);
  • 3、当Activity的setContentView被调用时,就会创建Decor等。

  • 最后Activity组件创建完成之后,通过调用ActivityThread类的handleResumeActivity将它激活。进而调用Activity的makeVisible方法显示控件。
    1
    2
    3
    4
    5
    6
    7
    8
    void makeVisible() {
    if (!mWindowAdded) {
    ViewManager wm = getWindowManager();
    wm.addView(mDecor, getWindow().getAttributes());
    mWindowAdded = true;
    }
    mDecor.setVisibility(View.VISIBLE);
    }

参考资料:
开发艺术探索:第8章
Android应用Activity、Dialog、PopWindow、Toast窗口添加机制及源码分析