WindowManager 窗口流程分析

WindowManager 是窗口,它是一个虚拟的概念,它并不是实际存在的,而是依托于 View ,它的实现类 WindowManagerImpl 更像是个代理人一样,管理着view,我们在 Activity 中可以通过 layout 来添加布局,同样,也可以通过 WindowManager 来实现,如下


class TestActivity extends Activity {

    private WindowManager mWindowManager;
    private Button mButton;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mWindowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
        WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
        layoutParams.type = WindowManager.LayoutParams.TYPE_TOAST;// 系统提示window
        layoutParams.format = PixelFormat.TRANSLUCENT;// 支持透明
        layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL |       //该flags描述的是窗口的模式,是否可以触摸,可以聚焦等
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
        layoutParams.width = 100;//窗口的宽和高
        layoutParams.height = 100;
        layoutParams.x = 500;//窗口位置的偏移量
        layoutParams.y = 500;
        mButton = new Button(this);
        mButton.setText("  button  ");
        mWindowManager.addView(mButton, layoutParams);

    }


    @Override
    protected void onDestroy() {
        super.onDestroy();
        mWindowManager.removeView(mButton);//移除窗口
    }
}

首先获取系统的窗口管理器,在 Activity 中调用 getSystemService() 方法,实际上调用的是 ContextImpl 中的方法,

ContextImpl:
    public Object getSystemService(String name) {
        return SystemServiceRegistry.getSystemService(this, name);
    }
    
SystemServiceRegistry:
    private static final HashMap<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
            new HashMap<String, ServiceFetcher<?>>();
    
    static {
        registerService(Context.WINDOW_SERVICE, WindowManager.class,
                    new CachedServiceFetcher<WindowManager>() {
                @Override
                public WindowManager createService(ContextImpl ctx) {
                    return new WindowManagerImpl(ctx.getDisplay());
                }});
        ...
    }
    public static Object getSystemService(ContextImpl ctx, String name) {
        ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
        return fetcher != null ? fetcher.getService(ctx) : null;
    }
    private static <T> void registerService(String serviceName, Class<T> serviceClass,
            ServiceFetcher<T> serviceFetcher) {
        SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
        SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
    }

通过上面的代码分析,最终获取的是 WindowManagerImpl 对象,可以理解为通过 getSystemService() 方法获取的对象,是单利的,全局只有它一个,这里用的是多态的格式,因为WindowManagerImpl 是隐藏的,它不需要对外暴露细节实现,我们就用 WindowManager 来表示获取了窗口管理器。

WindowLayoutParams.type 设置的是层级,每个 window 都有对应的层级,应用层例如 Activity 里的 window 在 1-99,子系列例如 Dialog 的 window 是在 1000-1999,系统 Toast 等的 window 在 2000-2999 ,层级高的会覆盖层级低的子 window 必须依赖于父 window 存在,例如 Dialog 必须在 Activity 中弹出,Dialog 中的 window 为子 window ,Activity 中的 window 为父 window,注意显示系统级别的 window 需要权限 <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> 在配置清单中配置。WindowLayoutparams 的 flags 是决定焦点及输入事件等, FLAG_NOT_FOUCSABLE  窗口不需要获取焦点,也不需要接收各种输入事件,这个比较细致,可以参考文档。width、height 是显示窗口显示内容区域的宽和高;x、y 是指窗口的偏移量,360手机助手悬浮窗,拖动时就是通过它来改变位置的;然后便是new一个view控件,然后把它添加进去,activity 生命周期结束时,把它remove掉。

上面是我们自己写的极端例子,如果是在 Activity 中,可以直接使用 Activity 的Window,通过 getWindowManager() 方法获取得到 WindowManager;Window 是个抽象类,它的实现类是PhoneWindow,它会通过 setWindowManager() 方法生成一个新的 WindowManager 即实现类 WindowManagerImpl,最终调用 addView() 添加控件,

WindowManagerImpl:

    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();

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

    public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.updateViewLayout(view, params);
    }
    
    public void removeView(View view) {
        mGlobal.removeView(view, false);
    }

通过代码可以知道,添加和移除控件最终还是通过 WindowManagerGlobal 来实现的,看它的方法名,它是单利模式,全局只有一个,看看它的 addView() 方法

    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) {
        ...

        ViewRootImpl root;
        View panelParentView = null;
        synchronized (mLock) {
            ...
            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) {
            synchronized (mLock) {
                final int index = findViewLocked(view, false);
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
            }
            throw e;
        }
    }

方法可以简化成这个样子,注意看, 创建了 ViewRootImpl 对象,把 view 和 ViewRootImpl 及 WindowManager.LayoutParams 对象添加到对应的集合中,然后是调用 setView() 方法,这个方法是重点

ViewRootImpl:

    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
                ...
                requestLayout();
                try {
                    mOrigWindowType = mWindowAttributes.type;
                    mAttachInfo.mRecomputeGlobalAttributes = true;
                    collectViewAttributes();
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                    getHostVisibility(), mDisplay.getDisplayId(),
                    mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                    mAttachInfo.mOutsets, mInputChannel);
                } catch (RemoteException e) {
                     ...
                } finally {
                    if (restore) {
                        attrs.restore();
                    }
                }
            ...
        }
    }

重点看这几行代码即可,requestLayout() 是进行 View 的测量绘制, mWindowSession.addToDisplay() 是通过跨进程 IPC 来添加布局,mWindowSession 是 Session 类型,

    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);
    }

mService 是 WindowManagerService 类型,这里切换到了 WMS 中, 看了看 WindowManagerService 类,也是 Binder 机制,不熟悉它的朋友,可以看看前几篇关于跨进程的文章,重新切回到 requestLayout() 方法中,看看它里面有什么花样

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

    void checkThread() {
        if (mThread != Thread.currentThread()) {
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
    }
看看 checkThread() 方法,这是个校验线程的方法,原来子线程不能更新UI的限制是从这来的,继续看 scheduleTraversals() 方法

    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            ...
        }
    }
mChoreographer.postCallback() 方法中,传递进去的 mTraversalRunnable 是 Runnable 类型,mChoreographer 中有 Handler,会执行 mTraversalRunnable

    final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
    final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }

    void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
            performTraversals();
            ...
        }
    }

最终会执行到 performTraversals() 方法中


    private void performTraversals() {

        final View host = mView;

        host.dispatchAttachedToWindow(mAttachInfo, 0);
        mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);

        getRunQueue().executeActions(mAttachInfo.mHandler);

        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
        mAttachInfo.mTreeObserver.dispatchOnGlobalLayout();

        performLayout(lp, desiredWindowWidth, desiredWindowHeight);

        performDraw();

    }


    private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
        try {
            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }

    private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
            int desiredWindowHeight) {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout");
        try {
            host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }

    private void performDraw() {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw");
        try {
            draw(fullRedrawNeeded);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }

performTraversals() 中重点看这些方法就行了,尤其是 performMeasure()、performLayout()、performDraw() 这三个方法,分别对应根节点 View 的 measure()、layout()、draw()方法,这分别是view的 测量、布局、绘制 的入口,与前面 https://blog.csdn.net/Deaht_Huimie/article/details/88560620 文章相连接。
mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);mAttachInfo.mTreeObserver.dispatchOnGlobalLayout();
这些方法,看起名字,mTreeObserver 与view的监听有关,view.getViewTreeObserver() 里面可以添加获取焦点、view是否依附于窗口、view测量完成等等监听,就是在performTraversals()里调用的,这里只是举了两个例子。 getRunQueue().executeActions(mAttachInfo.mHandler);  对应的是 view.post() 方法,有兴趣的可以看看源码流程。

同理,mWindowManager.removeView(mButton) 时,会调用到 WindowManagerGlobal 中 removeViewLocked() 方法,

    public void removeView(View view, boolean immediate) {
       ...
        synchronized (mLock) {
            int index = findViewLocked(view, true);
            View curView = mRoots.get(index).getView();
            removeViewLocked(index, immediate);
            ...
        }
    }
    
    private void removeViewLocked(int index, boolean immediate) {
        ViewRootImpl root = mRoots.get(index);
        ...
        boolean deferred = root.die(immediate);
        if (view != null) {
            view.assignParent(null);
            if (deferred) {
                mDyingViews.add(view);
            }
        }
    }
    
最终还是在 ViewRootImpl 中进行操作
    boolean die(boolean immediate) {
        ...
        mHandler.sendEmptyMessage(MSG_DIE);
        return true;
    }
mHandler 中 MSG_DIE 对应的方法是 

    void doDie() {
        checkThread();
        synchronized (this) {
            if (mRemoved) {
                return;
            }
            mRemoved = true;
            if (mAdded) {
                dispatchDetachedFromWindow();
            }
            ...
            try {
                if ((relayoutWindow(mWindowAttributes, viewVisibility, false)
                        & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
                    mWindowSession.finishDrawing(mWindow);
                }
            } catch (RemoteException e) {
            }
            ...
        }
        WindowManagerGlobal.getInstance().doRemoveView(this);
    }

老套路,先检查线程,然后是 dispatchDetachedFromWindow() 会触发 view 离开窗口的回调 mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(false),  mWindowSession是Session 类型,在它内部会触发 WindowManagerService 的 finishDrawingWindow(this, window) 方法,通过 Binder 机制去移除view,最后是在 WindowManagerGlobal 中集合,移除它的相关内容。

再看看 updateViewLayout() 更新位置的方法,

WindowManagerImpl:
    public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
        ...
        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);
        }
    }

ViewRootImpl:
    void setLayoutParams(WindowManager.LayoutParams attrs, boolean newView) {
        synchronized (this) {
            ...
            scheduleTraversals();
        }
    }

updateViewLayout() 方法中,会遍历找到该 View 在集合中对应的 ViewRootImpl,然后把它给移除掉,然后重新添加进去新的 ViewRootImpl 值,最后调用 ViewRootImpl 的setLayoutParams() 方法来进行新的绘制 scheduleTraversals() 方法。
 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值