Window内部机制浅谈

来源:互联网 发布:java 异步服务器 编辑:程序博客网 时间:2024/05/17 00:17

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<View> mViews = new ArrayList<View>();    private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();    private final ArrayList<WindowManager.LayoutParams> mParams =            new ArrayList<WindowManager.LayoutParams>();    private final ArraySet<View> mDyingViews = new ArraySet<View>();    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<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();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中具体实现的。

0 0
原创粉丝点击