Window 内部机制浅谈

916 阅读8分钟

Window 基本概念:

什么是Window,顾名思义 Window 表示一个窗口的概念。在日常开发中我们直接接触 Window 的机会并不多,但是在某些特殊情况下我们需要使用Window 来实现。

Android中所有视图都是由 Window 展示的,无论是Activity、Dialog、Toast 它们的视图实际上都是附属在Window上的 ,因此Window实际是View 的直接管理者。

源码中Window是一个抽象类;

public abstract class Window{}

而它的具体实现是通过PhoneWindow 来实现的;

public class PhoneWindow extends Window implements MenuBuilder.Callback {}

如何创建一个Window 窗口

创建一个Window窗口是很简单的,只需要通过WindowManager 即可完成创建,WindowManager是外部访问Window 的入口,Window创建的具体实现是在WindowManagerService中, 而 WindowManager 与WindowManagerService 的交互是一个IPC过程。

以下是 通过WindowManager 添加Window的示例代码:

WindowManager windowManager = (WindowManager)getSystemService(Context.WINDOW_SERVICE);
        Button button = new Button(this);
        button.setText("Hello Window");
        //参数说明:  LayoutParams(int w, int h, int _type, int _flags, int _format);
        //1:layoutParams w
        //2:layoutParams h
        //3:窗口类型
        //4:行为选项、旗标
        //5:设置Window的背景支持半透明
        WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.WRAP_CONTENT,0,0, PixelFormat.TRANSPARENT);
        layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL|WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE|WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
        layoutParams.gravity = Gravity.TOP| Gravity.LEFT;

        layoutParams.x = 100;
        layoutParams.y = 300;

       //添加
        windowManager.addView(button,layoutParams);
        //更新
        windowManager.updateViewLayout(button,layoutParams);
        //删除
        windowManager.removeView(button);

WindowManager.LayoutParams 中flags 与type 这两个参数是比较重要的,
下面对其常用的选项进行简单的介绍,如果读者想了解更所,可以参考官网api

Flags参数 表示Window的属性,通过这些属性 可以控制Window的显示特性。

FLAG_NOT_TOUCH_MODAL:表示Window 不需要获取焦点,也不需要接受各种输入事件,此标记会同时启用FLAG_NOT_TOUCH_MODAL,最终事件会直接传递给下层具有焦点的Window
FLAG_NOT_FOCUSABLE: 此模式下,系统会将当前Window区域以外的单击事件传递给底层的Window,当前Window区域以内的单击事件则自己处理,这个标记很重要,一般来说都需要开启此标记,否则其他Window将无法接受到单击事件。
FLAG_SHOW_WHEN_LOCKED:开启该模式 表示可以让Window显示在锁屏界面上。

Type :

在Android中大致可以将Window 分为三层,每个Window都也有自己的z-ordered ,层次大的会覆盖在层次小的Window 上面。在这三层中:

应用Window的层级范围 0~99,
子Window的层级范围 1000~1999;
系统Window的层级范围 2000~ 2999;

很显然系统Window的层级是最大的,如果我们选用系统Window ,一般情况下可以选用TYPE_SYSTEM_OVERLAY 或者 TYPE_SYSTEM_ERROR 如果采用 TYPE_SYSTEM_ERROR 我们只需要 :
layoutParams.type = LayoutParams.TYPE_SYSTEM_ERROR

并同时 添加权限


注:
android版本大于6.0之后 针对 SYSTEM_ALERT_WINDOW 该权限需要通过代码去打开启动授权界面来玩成

        if (Settings.canDrawOverlays(this)) {
            ....
        } else {
            Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
            startActivity(intent);
        }

WindowManager 内部方法解析

WindowManager中常用的只有三个方法:

 public void addView(View view, ViewGroup.LayoutParams params);
 public void updateViewLayout(View view, ViewGroup.LayoutParams params);
 public void removeView(View view);

而在源码中我们可以看到这三个方法主要是定义在 ViewManager 接口中;

public interface ViewManager
{

    public void addView(View view, ViewGroup.LayoutParams params);
    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
    public void removeView(View view);
}

WindowManager 同样也是一个接口,只不过是继承了ViewManager 该接口;

public interface WindowManager extends ViewManager {}

WindowManager的具体实现类是;

public final class WindowManagerImpl implements WindowManager{}

由此可见当调用WindowManager中的添加,删除,修改等方法时,其实是调用了 WindowManagerImpl
中的 各个方法。

下面我们针对WindowManagerImpl中的 addView、updateViewLayout、removeView

这三个方法来进一步分析 Window 的添加,更新,删除。

1:WindowManager.addView(….)
 private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();

@Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mDisplay, mParentWindow);
    }

通过addView 源码我们可以看出 Window的添加实际是通过 WindowManagerGlobal. addVeiw方法完成的。
以下我们只需要看 WindowManagerGlobal. addVeiw 方法即可

    private final ArrayList mViews = new ArrayList();
    private final ArrayList mRoots = new ArrayList();
    private final ArrayList mParams =
            new ArrayList();
    private final ArraySet mDyingViews = new ArraySet();

    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;
        .....

        ViewRootImpl root;
        View panelParentView = null;

       .....

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

        view.setLayoutParams(wparams);

        mViews.add(view);
        mRoots.add(root);
        mParams.add(wparams);
        }
        ....
     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;
        }
    }

该方法比较长,我们只需要关注我们想要关注的内容即可,
注:通过源码可以看到,内部会将View、roots、wparams 存储到不同的 ArrayList 内, 可以发现mView 存储的是所有Window所对应的View,mRoots 存储的是所有Window 所对应的ViewRootImpl,mParams存储的是所有Window所对应的布局参数,而mDyingViews 则存储了那些正在被删除的view 对象,或者说是那些已经调用removeView方法 但是删除操作还没有完成的Window对象。

在addView 中我们可以看出 最终 是调用了 ViewRootImpl.setView(….)方法来实现Window的添加

  final IWindowSession mWindowSession;

  public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
                .....
                // 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();
              .....
                    mOrigWindowType = mWindowAttributes.type;
                    mAttachInfo.mRecomputeGlobalAttributes = true;
                    collectViewAttributes();
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mInputChannel);
        }
    }

改方法中我们只需要关注 reqestLayout() 该方法即可,该方法主要是来完成异步刷新请求。
接下来我们看一下 requestLayout()方法内部是如何实现的。

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

内部调用了scheduleTraversals 方法,根据View的绘制原理,我们知道scheduleTraversals就是View的绘制入口。
当View绘制完成之后,接着通过WindowSession 来完成Window的添加 ,在setView中 我们可以看到内部调用了 mWindowSession.addToDisplay(…..)

而mWIndowSession的类型是IWindowSession ,真正的实现类是Session 而通过Session的源码

final class Session extends IWindowSession.Stub
        implements IBinder.DeathRecipient 

我们可以看到Window的添加过程其实就是一个IPC过程。

而当mWindowSession.addToDisplay(….)方法我们可以看出


 final WindowManagerService mService;
 @Override
    public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
            int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets,
            Rect outOutsets, InputChannel outInputChannel) {
        return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
                outContentInsets, outStableInsets, outOutsets, outInputChannel);
    }

在session内部我们看到内部调用了mService.addWindow,而mService对象 是 WindowManagerService 如此一来,Window的添加请求,就交给了WindowManagerService 去处理了,到现在我们对Window的添加流程大致了解清除了,而对于实际通过WindowManagerService 添加一个WIndow的具体实现,读者可以 通过查看源码进行进一步的分析,

内部源码 有兴趣的读者 可以自行查看。

2:WindowManager.removeView(….)

接下来我们看以下Window的删除,在WindowManagerImpl中我们可以看到

private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
@Override
    public void removeView(View view) {
        mGlobal.removeView(view, false);
    }

内部删除时调用了 WindowManagerGlobal.removeView(….) 方法,

而在WindowManagerGlobal.removeView(….) 方法内部,我们可以看到


private final ArrayList mRoots = new ArrayList();
public void removeView(View view, boolean immediate) {
        if (view == null) {
            throw new IllegalArgumentException("view must not be null");
        }

        synchronized (mLock) {
            int index = findViewLocked(view, true);
            View curView = mRoots.get(index).getView();
            removeViewLocked(index, immediate);
            if (curView == view) {
                return;
            }

            throw new IllegalStateException("Calling with view " + view
                    + " but the ViewAncestor is attached to " + curView);
        }
    }

内部先找到带删除的索引 index 然后通过该索引得到当前要删除的View,之后调用 removeViewLocked(…)来进一步删除,

private void removeViewLocked(int index, boolean immediate) {
        ViewRootImpl root = mRoots.get(index);
        View view = root.getView();

        if (view != null) {
            InputMethodManager imm = InputMethodManager.getInstance();
            if (imm != null) {
                imm.windowDismissed(mViews.get(index).getWindowToken());
            }
        }
        boolean deferred = root.die(immediate);
        if (view != null) {
            view.assignParent(null);
            if (deferred) {
                mDyingViews.add(view);
            }
        }
    }

可以看到 removeViewLocked 方法内部是通过 ViewRootImpl 来实现删除操作的。在代码内部我们可以看到具体的删除操作时通过 root.die(immediate) 来完成的。
该 root.die(immediate)的源码 我们可以看出

boolean die(boolean immediate) {
        // Make sure we do execute immediately if we are in the middle of a traversal or the damage
        // done by dispatchDetachedFromWindow will cause havoc on return.
        if (immediate && !mIsInTraversal) {
            doDie();
            return false;
        }

        if (!mIsDrawing) {
            destroyHardwareRenderer();
        } else {
            Log.e(TAG, "Attempting to destroy the window while drawing!\n" +
                    "  window=" + this + ", title=" + mWindowAttributes.getTitle());
        }
        mHandler.sendEmptyMessage(MSG_DIE);
        return true;
    }
*****************************
     case MSG_DIE:
                doDie();
                break;

die内部有两种形式的删除,一种是同步删除 则直接调用了doDie方法,另外一种是异步删除 首先发送了一条Message 接着Handler在接受到该Message之后,再调用doDie方法。因此无论是同步还说异步都会调用到doDie方法,我们只需看该方法内是如何实现Window是如何删除的。

 void doDie() {
        checkThread();
        if (LOCAL_LOGV) Log.v(TAG, "DIE in " + this + " of " + mSurface);
        synchronized (this) {
            if (mRemoved) {
                return;
            }
            mRemoved = true;
            if (mAdded) {
                dispatchDetachedFromWindow();
            }
            .....
        }
        WindowManagerGlobal.getInstance().doRemoveView(this);
    }

可以看出该方法内部会调用dispatchDetachedFromWindow方法,而真正删除Window的逻辑是在dispatchDetachedFromWindow方法内实现的。

    final IWindowSession mWindowSession
    void dispatchDetachedFromWindow() {
       .....
        try {
            mWindowSession.remove(mWindow);
        } catch (RemoteException e) {
        }

       .....
    }

而在改方法内部我们可以看出 内部实际上是调用了mWindowSession.remove(….)方法,根Window的添加一样,remove方法最终会交给 WindowManagerService 中去实现。

以下是WindowManagerService 中removeWindowToken的实现细节;

 public void removeWindow(Session session, IWindow client) {
        synchronized(mWindowMap) {
            WindowState win = windowForClientLocked(session, client, false);
            if (win == null) {
                return;
            }
            removeWindowLocked(win);
        }
    }

内部的详细代码 感兴趣的童鞋可以仔细研读。

之后紧接着调用 WindowManagerGlobal.getInstance().doRemoveView(this) 该方法 刷新数据。

  void doRemoveView(ViewRootImpl root) {
        synchronized (mLock) {
            final int index = mRoots.indexOf(root);
            if (index >= 0) {
                mRoots.remove(index);
                mParams.remove(index);
                final View view = mViews.remove(index);
                mDyingViews.remove(view);
            }
        }
        if (HardwareRenderer.sTrimForeground && HardwareRenderer.isAvailable()) {
            doTrimForeground();
        }
    }

该方法内部我们可以看出(包括mRoots、mParams、以及mDYingView) 从当前Window所关联的这三类对象从列表中删除。
至此 Window的删除流程我们大致已经了解了。

3:WindowManager.updateViewLayout(….)

接下来我们看WIndow的更新过程,我们还是从WindowManagerImpl方法开始

  private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
 @Override
    public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.updateViewLayout(view, params);
    }

内部调用到 WindowManagerGlobal.updateViewLayout

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

        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;

        view.setLayoutParams(wparams);

        synchronized (mLock) {
            int index = findViewLocked(view, true);
            ViewRootImpl root = mRoots.get(index);
            mParams.remove(index);
            mParams.add(index, wparams);
            root.setLayoutParams(wparams, false);
        }
    }

而在 WindowManagerGlobal.updateViewLayout 内部我们可以看出哪部更新实现是 首先更新View的LayoutParams并替换掉老掉LayoutParams 接着再更新ViewRootImpl中LayoutParams。

在ViewRootImpl 中会通过 scheduleTraversals方法来对View 重新布局,同时还会通过WindowSession来更新Window视图。

    void setLayoutParams(WindowManager.LayoutParams attrs, boolean newView) {
        synchronized (this) {
          .......

            if (newView) {
                mSoftInputMode = attrs.softInputMode;
                requestLayout();
            }

           .....
            scheduleTraversals();
        }
    }

这个过程最终会调用到WindowManagerService.relayoutWindow 来更新窗口,这同样是一个IPC过程;

总结: 针对Window的添加、删除、更新,我们可以看出其实都是一个IPC过程,最终无论是添加、删除、更新 都是在WindowManagerService中具体实现的。