天天看点

Android 源码解析之WindowManager删除窗口

一,写在前面  

        在博客Android 源码解析之WindowManager添加窗口 中,先是介绍了如何通过WindowManager添加一个系统窗口,并从源码角度分析了PhoneWindow的创建时机,Window中创建WindowManagerImpl对象,以及创建ViewRootImpl对象,并调用setView方法完成添加窗口的操作。在ViewRootImpl类内部,通过一次IPC调用(基于Binder机制)和系统服务WindowManagerService进行通信,获取到Session对象。finally,在Session类的内部,将添加窗口的操作交给WindowManagerService处理。

二,进入主题

       本篇文章是基于窗口的添加基础上进行介绍,强烈建议阅读本篇文章的哥们,先了解WindowManager是如何添加窗口,见 Android 源码解析之WindowManager添加窗口 。因此,直接从WindomManagerImpl开始分析,WindomManagerImpl有这样两个方法都可以删除窗口。        源码如下:

@Override
    public void removeView(View view) {
        mGlobal.removeView(view, false);
    }

    @Override
    public void removeViewImmediate(View view) {
        mGlobal.removeView(view, true);
    }
           

       可以看出两个方法都调用了WindomManagerGlobal的removeView方法,只是第二个参数的boolean值不同,调用removeViewImmediate时传入true,调用removeView传入false。                查看WindomManagerGlobal$removeView源码:

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

       removeView最开始对参数view进行判空检查,为空则抛出异常。为避免多线程同时删除一个View,在同步代码块中处理删除窗口的操作,继续分析代码块中内容。        其中,WindomManagerGlobal$findViewLocked源码如下:

private int findViewLocked(View view, boolean required) {
        final int index = mViews.indexOf(view);
        if (required && index < 0) {
            throw new IllegalArgumentException("View=" + view + " not attached to window manager");
        }
        return index;
    }
           

       在文章 Android 源码解析之WindowManager添加窗口介绍过,mViews是一个存放View的List集合,添加窗口时,会将View添加到mViews中。首先调用list.indexOf获取该View的索引位置,若没有找到该View则抛出异常。在实际开发中,如果该View没有添加到窗口中,那么删除该View会报错。可以在执行删除操作前进行判断,避免抛出异常。也即是说,findViewLocked方法是获取到被删除的View对象在集合中的索引。

       回到WindomManagerGlobal$removeView方法,内部的调用了removeViewLocked方法。        查看WindomManagerGlobal$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);
            }
        }
    }
           

       第2行,通过索引位置index,获取list集合中ViewRootImpl对象。前篇文章中有讲过,在添加窗口的流程中,每添加一次窗口,就会创建一个ViewRootImpl方法,并添加到集合mRoots中。        第3行,调用ViewRootImpl类的方法,获取被删除的View对象。        第11行,真正删除窗口的操作是交给ViewRootImpl$die方法。        ViewRootImpl$die源码如下:

/**
     * @param immediate True, do now if not in traversal. False, put on queue and do later.
     * @return True, request has been queued. False, request has been completed.
     */
    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(mTag, "Attempting to destroy the window while drawing!\n" +
                    "  window=" + this + ", title=" + mWindowAttributes.getTitle());
        }
        mHandler.sendEmptyMessage(MSG_DIE);
        return true;
    }
           

       第8行,若immediate为true,也就是调用removeViewImmediate删除窗口,直接调用doDie(),并返回false;        第19行,若immediate为false,也就是调用removeView删除窗口,则使用Handler发送一个消息,并返回true;消息的处理也是调用doDie(),只是多了一步,通过消息机制来处理。        消息处理源码如下:

final class ViewRootHandler extends Handler {
	//...

	@Override
        public void handleMessage(Message msg) {
	//...

	    case MSG_DIE:
		doDie();
		break;
	
	//...
	}

	//...
    
    }
           

       回到WindomManagerGlobal$removeViewLocked方法,第14行,deferred为true时,也就是调用removeView删除窗口时,将该View对象添加到集合mDyingViews中。注意此时,只是执行了mHandler.sendEmptyMessage(MSG_DIE),发送了一个消息,还并未调用doDie()。因此,mDyingViews存放的是需要删除的View,但仍未真正删除的View。

        通过前面的分析可知:不管使用哪种方式删除窗口,区别在于是否通过消息机制来删除,但最终都是调用了doDie()来处理。        ViewRootImpl$doDie源码如下:

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

	//...code

            mAdded = false;
        }
        WindowManagerGlobal.getInstance().doRemoveView(this);
    }
           

       第10行,删除窗口操作放在 dispatchDetachedFromWindow方法中;        第17行,在完成删除窗口操作后,更新WindowManagerGlobal中维护的四个集合的数据。        查看WindowManagerGlobal$doRemoveView源码:

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 (ThreadedRenderer.sTrimForeground && ThreadedRenderer.isAvailable()) {
            doTrimForeground();
        }
    }
           

       回到ViewRootImpl$doDie方法,内部调用dispatchDetachedFromWindow删除窗口。        查看ViewRootImpl$dispatchDetachedFromWindow源码如下:

void dispatchDetachedFromWindow() {
        if (mView != null && mView.mAttachInfo != null) {
            mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(false);
            mView.dispatchDetachedFromWindow();
        }

        mAccessibilityInteractionConnectionManager.ensureNoConnection();
        mAccessibilityManager.removeAccessibilityStateChangeListener(
                mAccessibilityInteractionConnectionManager);
        mAccessibilityManager.removeHighTextContrastStateChangeListener(
                mHighContrastTextManager);
        removeSendWindowContentChangedCallback();

        destroyHardwareRenderer();

        setAccessibilityFocus(null, null);

        mView.assignParent(null);
        mView = null;
        mAttachInfo.mRootView = null;

        mSurface.release();

        if (mInputQueueCallback != null && mInputQueue != null) {
            mInputQueueCallback.onInputQueueDestroyed(mInputQueue);
            mInputQueue.dispose();
            mInputQueueCallback = null;
            mInputQueue = null;
        }
        if (mInputEventReceiver != null) {
            mInputEventReceiver.dispose();
            mInputEventReceiver = null;
        }
        try {
            mWindowSession.remove(mWindow);
        } catch (RemoteException e) {
        }

        // Dispose the input channel after removing the window so the Window Manager
        // doesn't interpret the input channel being closed as an abnormal termination.
        if (mInputChannel != null) {
            mInputChannel.dispose();
            mInputChannel = null;
        }

        mDisplayManager.unregisterDisplayListener(mDisplayListener);

        unscheduleTraversals();
    }
           

       首先是做一些垃圾回收的工作,执行View的一些回调方法,注销了一些接口。        重点注意第35行,内部执行mWindowSession.remove(mWindow)删除窗口,这里的变量mWindowSession就是Session对象。变量mWindowSession的实现类的获取:内部基于Binder机制,与系统服务WindowManagerService进行通信,获取到Session对象。详情可以查看文章 Android 源码解析之WindowManager添加窗口,这里不再阐述。

       查看Session$remove源码如下:

@Override
    public void remove(IWindow window) {
        mService.removeWindow(this, window);
    }
           

       mService就是WindowManagerService对象,因此,删除窗口的操作最终是交给WindowManagerService处理了。本篇文章主要介绍WindowManager删除窗口的内部实现原理,分析到这里就结束了。暂不对WindowManagerService删除窗口的具体流程进行介绍,有需要的哥们可以查阅老罗的博客解惑吧~

三,总结

        值得一提的是,WindowManager删除窗口这种说法,内部其实是删除View。每一个View都需要依赖于Window,View不能独立的存在的去添加,删除,更新。窗口Window是一种抽象的概念,抽取出了一些方法,例如,可以创建以及获取WindowManagerImpl的实例。因此,通过WindowManager增加,删除,更新窗口,内部其实是对View的增加,删除,更新。从WindowManagerImpl方法的命名就可以看出,均跟view相关。

       本篇文章介绍了WindowManager删除窗口的的内部实现原理,WindowManagerImpl有removeView,removeViewImmediate两种方式删除窗口。区别在于removeView通过消息机制,在处理的消息时才调用doDie(),removeViewImmediate则是直接调用doDie()。diDie()内部调用dispatchDetachedFromWindow方法,在dispatchDetachedFromWindow方法中调用Session对象的remove方法,内部是将删除窗口的操作交给WindowManagerService处理。

        另外,在文章 Android 源码解析之WindowManager添加窗口中介绍了窗口得添加,调用了Session对象的addToDisplay方法,内部也是将添加窗口的操作交给WindowManagerService来处理。由此可见,通过WindowManager删除,添加窗口的操作,最终都是交给系统服务WindowManagerService来处理了。可以猜测更新窗口的操作也是这样的,当然确实如此啦~