Window、WindowManager

282 阅读17分钟

Window,表示一个窗口的抽象的概念;同时也是一个抽象类,唯一的实现是PhoneWindow。在PhoneWindow中有一个顶级View—DecorView,继承自FrameLayout,我们可以通过getDecorView()获得它,当我们调用Activity的setContentView时,其实最终会调用Window的setContentView,当我们调用Activity的findViewById时,其实最终调用的是Window的findViewById,这也间接的说明了Window是View的直接管理者。 但是Window并不是真实存在的 ,它更多的表示一种抽象的功能集合,View才是Android中的视图呈现形式,绘制到屏幕上的是View不是Window,但是View不能单独存在,它必需依附在Window这个抽象的概念上面,Android中需要依赖Window提供视图的有Activity,Dialog,Toast,PopupWindow,StatusBarWindow(系统状态栏),输入法窗口等,因此Activity,Dialog等视图都对应着一个Window。 创建Window,通过WindowManager即可完成。WindowManager是操作Window的入口,Window的具体实现是在WindowManagerService中。WindowManager和WindowManagerService交互是IPC(跨进程通信)过程。

Window是View的管理者,当我们说创建Window时,一方面指实例化这个管理者,一方面指 用WindowManager.addView()添加view,以view的形式来呈现Window这个概念。

一、Window和WindowManager

1.1 window

先看创建window的代码

 1	  WindowManager windowManager = getWindowManager();
 2        Button view = new Button(this);
 3        view.setText("添加到window中的button");
 4
 5        WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
 6
 7        layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
 8                | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
 9                | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
10        layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION;
11
12        layoutParams.format = PixelFormat.TRANSPARENT;
13        layoutParams.gravity = Gravity.TOP | Gravity.LEFT;
14        layoutParams.x = 100;
15        layoutParams.y = 100;
16        layoutParams.width = ViewGroup.LayoutParams.WRAP_CONTENT;
17        layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
18
19        windowManager.addView(view, layoutParams);

实际就只有一句windowManager.addView(view, layoutParams),这样就添加了一个Window,这个window只有一个button。看下LayoutParams的两个不太认识的属性,flags、type。

flags,决定window的显示特性,有很多值,看下常用的:

FLAG_NOT_FOCUSABLE,不需要获取焦点、不需要 输入事件,同时会自定开启 FLAG_NOT_TOUCH_MODAL,最终事件会传递给下层具有焦点的window。

FLAG_NOT_TOUCH_MODAL,window区域以外的单击事件会传递给下层window,window范围内的事件自己处理。一般需要开启此标记,否则其他window不能收到事件。

FLAG_SHOW_WHEN_LOCKED,开启后 可以让window显示在锁屏的界面上。

type参数表示window的类型。window有三种类型,应用window、子window、系统window。应用window对应activity;子window要依附在父window上,如dialog;系统window需要申明权限才能创建,比如toast、系统状态栏。

window是分层的,每个window都有对应的z-ordered,层级大的在层级小的上层。应用window的层级范围是1-99,子window是1000-19999=,系统window是2000-2999,即type的值。

如果想window位于所有window顶层,那就用系统window。可以设置layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,并且,要申明使用权限,且6.0以后要让用户手动打开权限。

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>

1.2 WindowManager

WindowManager是个接口,继承自ViewManager:

1public interface ViewManager{
2    public void addView(View view, ViewGroup.LayoutParams params);
3    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
4    public void removeView(View view);
5}

所以,windowManager就是 添加、更新、删除 view,实际使用的就是这三个方法,上面创建window的例子用的就是addView方法。所以,操作window就是操作view。

二、window的内部机制

window是抽象的概念,在视图中不是实际存在,它以view的形式呈现。一个window就对应一个view,window操作view实际是通过ViewRootImpl实现。使用中是通过WindowManager 对的操作,无法直接访问window。下面就看看WindowManager的三个方法。

 1@Override
 2    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
 3        applyDefaultToken(params);
 4        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
 5    }
 6
 7    @Override
 8    public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
 9        applyDefaultToken(params);
10        mGlobal.updateViewLayout(view, params);
11    }
12
13    @Override
14    public void removeView(View view) {
15        mGlobal.removeView(view, false);
16    }

可以看到,全都交给mGlobal处理了,那看下mGlobal,是个单例对象:

1private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
1    public static WindowManagerGlobal getInstance() {
2        synchronized (WindowManagerGlobal.class) {
3            if (sDefaultWindowManager == null) {
4                sDefaultWindowManager = new WindowManagerGlobal();
5            }
6            return sDefaultWindowManager;
7        }
8    }

那么来看下mGlobal.addView,具体简要概括为3个步骤:

数据检查

更新各种参数列表

RootViewImpl添加view(含window的添加)

 1public void addView(View view, ViewGroup.LayoutParams params,
 2            Display display, Window parentWindow) {
 3        //1、数据检查
 4        if (view == null) {
 5            throw new IllegalArgumentException("view must not be null");
 6        }
 7        if (display == null) {
 8            throw new IllegalArgumentException("display must not be null");
 9        }
10        if (!(params instanceof WindowManager.LayoutParams)) {
11            throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
12        }
13
14        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
15        if (parentWindow != null) {
16            parentWindow.adjustLayoutParamsForSubWindow(wparams);
17        } else {
18            // If there's no parent, then hardware acceleration for this view is
19            // set from the application's hardware acceleration setting.
20            final Context context = view.getContext();
21            if (context != null
22                    && (context.getApplicationInfo().flags
23                            & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
24                wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
25            }
26        }
27
28        ViewRootImpl root;
29        View panelParentView = null;
30        ...
31            //创建viewRoot(一个window对应一个viewRoot)
32            root = new ViewRootImpl(view.getContext(), display);
33            view.setLayoutParams(wparams);
34
35            //2、更新各种参数列:所有window的--view的列表、rootView的列表、view参数的列表
36            mViews.add(view);
37            mRoots.add(root);
38            mParams.add(wparams);
39
40            // do this last because it fires off messages to start doing things
41            try {
42                   // 3、RootViewImpl添加view(含window的添加)
43                root.setView(view, wparams, panelParentView);
44            } catch (RuntimeException e) {
45                // BadTokenException or InvalidDisplayException, clean up.
46                if (index >= 0) {
47                    removeViewLocked(index, true);
48                }
49                throw e;
50            }
51        }
52    }

接着看ViewRootImpl的setView:

 1public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
 2                ...
 3                //1.绘制view
 4                requestLayout();
 5
 6                if ((mWindowAttributes.inputFeatures
 7                        & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
 8                    mInputChannel = new InputChannel();
 9                }
10                mForceDecorViewVisibility = (mWindowAttributes.privateFlags
11                        & PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY) != 0;
12                try {
13                    mOrigWindowType = mWindowAttributes.type;
14                    mAttachInfo.mRecomputeGlobalAttributes = true;
15                    collectViewAttributes();
16                    //2.通过session与WMS建立通信:完成window的添加
17                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
18                            getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,
19                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
20                            mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);
21                } catch (RemoteException e) {
22                    mAdded = false;
23                    mView = null;
24                    mAttachInfo.mRootView = null;
25                    mInputChannel = null;
26                    mFallbackEventHandler.setView(null);
27                    unscheduleTraversals();
28                    setAccessibilityFocus(null, null);
29                    throw new RuntimeException("Adding window failed", e);
30                }
31                ...
32}

两个步骤:1、调用requestLayout()异步刷新view,2、mWindowSession.addToDisplay()完成window的添加。 requestLayout()内部最后走到performTraversals(),我们知道这是view绘制流程入口。如下所示:

1@Override
2    public void requestLayout() {
3        if (!mHandlingLayoutInLayoutRequest) {
4            checkThread();
5            mLayoutRequested = true;
6            scheduleTraversals();
7        }
8    }
 1void scheduleTraversals() {
 2        if (!mTraversalScheduled) {
 3            mTraversalScheduled = true;
 4            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
 5            mChoreographer.postCallback(
 6                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
 7            if (!mUnbufferedInputDispatch) {
 8                scheduleConsumeBatchedInput();
 9            }
10            notifyRendererOfFramePending();
11            pokeDrawLockIfNeeded();
12        }
13    }
1final class TraversalRunnable implements Runnable {
2        @Override
3        public void run() {
4            doTraversal();
5        }
6    }
 1void doTraversal() {
 2        if (mTraversalScheduled) {
 3            mTraversalScheduled = false;
 4            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
 5
 6            if (mProfile) {
 7                Debug.startMethodTracing("ViewAncestor");
 8            }
 9            // 绘制流程
10            performTraversals();
11
12            if (mProfile) {
13                Debug.stopMethodTracing();
14                mProfile = false;
15            }
16        }
17    }

至于mWindowSession.addToDisplay(),先看mWindowSession,类型是IWindowSession,是个Binder对象,具体是com.android.server.wm.Session,所以window的添加是一个IPC过程。

mWindowSessionde 是在ViewRootImpl创建时获取,由WindowManagerGlobal通过获取WindowManagerService来为 每个应用创建一个单独的session。

1public ViewRootImpl(Context context, Display display) {
2        mContext = context;
3        mWindowSession = WindowManagerGlobal.getWindowSession();
4    ...
5}
 1    public static IWindowSession getWindowSession() {
 2        synchronized (WindowManagerGlobal.class) {
 3            if (sWindowSession == null) {
 4                try {
 5                    InputMethodManager imm = InputMethodManager.getInstance();
 6                    IWindowManager windowManager = getWindowManagerService();
 7                    sWindowSession = windowManager.openSession(
 8                            new IWindowSessionCallback.Stub() {
 9                                @Override
10                                public void onAnimatorScaleChanged(float scale) {
11                                    ValueAnimator.setDurationScale(scale);
12                                }
13                            },
14                            imm.getClient(), imm.getInputContext());
15                } catch (RemoteException e) {
16                    throw e.rethrowFromSystemServer();
17                }
18            }
19            return sWindowSession;
20        }
21    }
 1    public static IWindowManager getWindowManagerService() {
 2        synchronized (WindowManagerGlobal.class) {
 3            if (sWindowManagerService == null) {
 4                sWindowManagerService = IWindowManager.Stub.asInterface(
 5                        ServiceManager.getService("window"));
 6                try {
 7                    if (sWindowManagerService != null) {
 8                        ValueAnimator.setDurationScale(
 9                                sWindowManagerService.getCurrentAnimatorScale());
10                    }
11                } catch (RemoteException e) {
12                    throw e.rethrowFromSystemServer();
13                }
14            }
15            return sWindowManagerService;
16        }
17    }

然后是WindowManagerService的openSession:

1    @Override
2    public IWindowSession openSession(IWindowSessionCallback callback, IInputMethodClient client,
3            IInputContext inputContext) {
4        if (client == null) throw new IllegalArgumentException("null client");
5        if (inputContext == null) throw new IllegalArgumentException("null inputContext");
6        Session session = new Session(this, callback, client, inputContext);
7        return session;
8    }

接着看Session的addToDisplay

1@Override
2    public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
3            int viewVisibility, int displayId, Rect outFrame, Rect outContentInsets,
4            Rect outStableInsets, Rect outOutsets,
5            DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel) {
6        return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame,
7                outContentInsets, outStableInsets, outOutsets, outDisplayCutout, outInputChannel);
8    }

window的添加就交给WindowManagerService了。

WindowManagerService主要作用: 窗口管理:是先进行窗口的权限检查,因为系统窗口需要声明权限,然后根据相关的Display信息以及窗口信息对窗口进行校对,再然后获取对应的WindowToken,再根据不同的窗口类型检查窗口的有效性,如果上面一系列步骤都通过了,就会为该窗口创建一个WindowState对象,以维护窗口的状态和根据适当的时机调整窗口状态,最后就会通过WindowState的attach方法与SurfaceFlinger通信。因此SurfaceFlinger能使用这些Window信息来合成surfaces,并渲染输出到显示设备。

输入事件的中转站:当我们的触摸屏幕时就会产生输入事件,在Android中负责管理事件的输入是InputManagerService,它里面有一个InputManager,在启动IMS的同时会创建InputManager,在创建InputManager同时创建InputReader和InputDispatcher,InputReader会不断的从设备节点中读取输入事件,InputReader将这些原始输入事件加工后就交给InputDispatcher,而InputDispatcher它会寻找一个最合适的窗口来处理输入事件,WMS是窗口的管理者,WMS会把所有窗口的信息更新到InputDispatcher中,这样InputDispatcher就可以将输入事件派发给合适的Window,Window就会把这个输入事件传给顶级View,然后就会涉及我们熟悉的事件分发机制。

2.2 window的更新

直接看mGlobal.updateViewLayout(view, params):

 1    public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
 2        //1、参数检查
 3        if (view == null) {
 4            throw new IllegalArgumentException("view must not be null");
 5        }
 6        if (!(params instanceof WindowManager.LayoutParams)) {
 7            throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
 8        }
 9
10        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
11        //2、更新layoutParams及参数列表列表
12        view.setLayoutParams(wparams);
13        synchronized (mLock) {
14            int index = findViewLocked(view, true);
15            ViewRootImpl root = mRoots.get(index);
16            mParams.remove(index);
17            mParams.add(index, wparams);
18            //3、RootViewImpl更新布局
19            root.setLayoutParams(wparams, false);
20        }
21    }

再看ViewRootIml.setLayoutParams()中会调用scheduleTraversals() 重新绘制布局,其中也会调用mWindowSession.relayout来更新window ,也是IPC过程。

2.3 window 删除

直接看mGlobal.removeView(view, false):

 1    public void removeView(View view, boolean immediate) {
 2        if (view == null) {
 3            throw new IllegalArgumentException("view must not be null");
 4        }
 5
 6        synchronized (mLock) {
 7            //找到要移除view在列表中的index
 8            int index = findViewLocked(view, true);
 9            View curView = mRoots.get(index).getView();
10            //移除
11            removeViewLocked(index, immediate);
12            if (curView == view) {
13                return;
14            }
15
16            throw new IllegalStateException("Calling with view " + view
17                    + " but the ViewAncestor is attached to " + curView);
18        }
19    }

再看removeViewLocked(index, immediate):

 1    private void removeViewLocked(int index, boolean immediate) {
 2        //找到对应的ViewRoot
 3        ViewRootImpl root = mRoots.get(index);
 4        View view = root.getView();
 5
 6        if (view != null) {
 7            InputMethodManager imm = InputMethodManager.getInstance();
 8            if (imm != null) {
 9                imm.windowDismissed(mViews.get(index).getWindowToken());
10            }
11        }
12        //ViewRoot用die来删除
13        boolean deferred = root.die(immediate);
14        if (view != null) {
15            view.assignParent(null);
16            if (deferred) {
17                //记录要删除的view
18                mDyingViews.add(view);
19            }
20        }
21    }

继续看root.die(immediate):

 1    boolean die(boolean immediate) {
 2        // 如果是立刻删除,直接调doDie()
 3        if (immediate && !mIsInTraversal) {
 4            doDie();
 5            return false;
 6        }
 7
 8        if (!mIsDrawing) {
 9            destroyHardwareRenderer();
10        } else {
11            Log.e(mTag, "Attempting to destroy the window while drawing!\n" +
12                    "  window=" + this + ", title=" + mWindowAttributes.getTitle());
13        }
14        //不是立刻删,就放入队列
15        mHandler.sendEmptyMessage(MSG_DIE);
16        return true;
17    }

继续看doeDie():

 1    void doDie() {
 2        checkThread();
 3        if (LOCAL_LOGV) Log.v(mTag, "DIE in " + this + " of " + mSurface);
 4        synchronized (this) {
 5            if (mRemoved) 
 6                return;
 7            }
 8            mRemoved = true;
 9            if (mAdded) {
10                //删除操作
11                dispatchDetachedFromWindow();
12            }
13
14            ...
15         //移除对应列表中的root、view、param、dyingView
16        WindowManagerGlobal.getInstance().doRemoveView(this);
17    }

看下dispatchDetachedFromWindow():

 1    void dispatchDetachedFromWindow() {
 2        mFirstInputStage.onDetachedFromWindow();
 3        if (mView != null && mView.mAttachInfo != null) {
 4            mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(false);
 5            //回调view的dispatchDetachedFromWindow方法,意思是view要从window中移除了。一般可在其中做一些资源回收工作,如 停止动画等。
 6            mView.dispatchDetachedFromWindow();
 7        }
 8        //移除各种回调
 9        mAccessibilityInteractionConnectionManager.ensureNoConnection();
10        mAccessibilityManager.removeAccessibilityStateChangeListener(
11                mAccessibilityInteractionConnectionManager);
12        mAccessibilityManager.removeHighTextContrastStateChangeListener(
13                mHighContrastTextManager);
14        removeSendWindowContentChangedCallback();
15
16        destroyHardwareRenderer();
17
18        setAccessibilityFocus(null, null);
19
20        mView.assignParent(null);
21        mView = null;
22        mAttachInfo.mRootView = null;
23
24        mSurface.release();
25
26        if (mInputQueueCallback != null && mInputQueue != null) {
27            mInputQueueCallback.onInputQueueDestroyed(mInputQueue);
28            mInputQueue.dispose();
29            mInputQueueCallback = null;
30            mInputQueue = null;
31        }
32        if (mInputEventReceiver != null) {
33            mInputEventReceiver.dispose();
34            mInputEventReceiver = null;
35        }
36        try {
37            //删除window
38            mWindowSession.remove(mWindow);
39        } catch (RemoteException e) {
40        }
41
42        // Dispose the input channel after removing the window so the Window Manager
43        // doesn't interpret the input channel being closed as an abnormal termination.
44        if (mInputChannel != null) {
45            mInputChannel.dispose();
46            mInputChannel = null;
47        }
48
49        mDisplayManager.unregisterDisplayListener(mDisplayListener);
50
51        unscheduleTraversals();
52    }

三、常见Window的创建过程

View依附于Window这个抽象概念,有Activity、Dialog、Toast、PopupWindow等。

3.1 Activity的Window创建

Activity的启动略复杂,这里先看ActivityThread里的performLaunchActivity():

 1    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
 2    ...
 3    //创建activity实例:通过类加载器创建
 4    java.lang.ClassLoader cl = appContext.getClassLoader();
 5    activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
 6    ...
 7    //调用Activity的attach方法--关联上下文环境变量
 8      activity.attach(appContext, this, getInstrumentation(), r.token,
 9                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
10                        r.embeddedID, r.lastNonConfigurationInstances, config,
11                        r.referrer, r.voiceInteractor, window, r.configCallback);    
12    ...
13}

接着看activity.attach方法:

 1        //实例化window,就是Window的唯一实现PhoneWindow
 2        mWindow = new PhoneWindow(this, window, activityConfigCallback);
 3        ...
 4        //把activity作为回调接口传入window,这样window从外界接受的状态变化都会交给activity
 5        //例如:dispatchTouchEvent、onAttachedToWindow、onDetachedFromWindow
 6        mWindow.setCallback(this);
 7        ...
 8        //设置windowManager,实际就是WindowManagerImpl的实例,在activity中getWindowManager()获取的就是这个实例
 9        mWindow.setWindowManager(
10                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
11                mToken, mComponent.flattenToString(),
12                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
13        ...
14        mWindowManager = mWindow.getWindowManager();

activity视图的管理者window已创建,那么什么时候用windowManager.addView() 来把activity的视图依附在window上呢?

先看Activity的setContentView方法,我们activity的视图由此方法设置:

1    public void setContentView(@LayoutRes int layoutResID) {
2        getWindow().setContentView(layoutResID);
3        initWindowDecorActionBar();
4    }

接着看PhonrWindow的setContentView:

 1    public void setContentView(int layoutResID) {
 2        // mContentParent为空,就调installDecor(),猜想installDecor()里面创建了mContentParent。且从名字看出mContentParent就是内容视图的容器
 3        if (mContentParent == null) {
 4            installDecor();
 5        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
 6            mContentParent.removeAllViews();
 7        }
 8
 9        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
10            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
11                    getContext());
12            transitionTo(newScene);
13        } else {
14            //这里看到,确实把我们的视图加载到mContentParent了
15            mLayoutInflater.inflate(layoutResID, mContentParent);
16        }
17        ...
18    }

那就看installDecor():

 1private void installDecor() {
 2    if (mDecor == null) {
 3            //创建mDecor
 4            mDecor = generateDecor(-1);
 5            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
 6            mDecor.setIsRootNamespace(true);
 7            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
 8                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
 9            }
10        } else {
11            mDecor.setWindow(this);
12        }
13        if (mContentParent == null) {
14            //创建mContentParent
15            mContentParent = generateLayout(mDecor);
16   ...
17}

看下generateDecor(-1),就是new了个DecorView:

 1    protected DecorView generateDecor(int featureId) {
 2        Context context;
 3        if (mUseDecorContext) {
 4            Context applicationContext = getContext().getApplicationContext();
 5            if (applicationContext == null) {
 6                context = getContext();
 7            } else {
 8                context = new DecorContext(applicationContext, getContext());
 9                if (mTheme != -1) {
10                    context.setTheme(mTheme);
11                }
12            }
13        } else {
14            context = getContext();
15        }
16        return new DecorView(context, featureId, this, getAttributes());
17    }

继续看generateLayout(mDecor):

 1        // Apply data from current theme.
 2        TypedArray a = getWindowStyle();
 3        ...
 4        // 这里下面一堆代码是 根据主题,获取DecorView的布局资源
 5        int layoutResource;
 6        int features = getLocalFeatures();
 7        if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
 8            layoutResource = R.layout.screen_swipe_dismiss;
 9            setCloseOnSwipeEnabled(true);
10        } else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) 
11        ...
12        //把布局给到mDecor,这样mDecor就有视图了。
13        mDecor.onResourceLoaded(mLayoutInflater, layoutResource)
14    //findViewById就是getDecorView().findViewById(id);
15    //所以从DecorView中找到id为ID_ANDROID_CONTENT = com.android.internal.R.id.content 的容器,就用用来存放我们activity中设置的视图的。
16    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
17    ...
18    return contentParent;
19}

好了,通过以上流程,就清楚了activity中通过setContentView设置的布局实际是加载到DecorView的id为com.android.internal.R.id.content容器中。我们查看DecorView所有的主题的布局,发现都有这个id的容器,且是FrameLayout。

 1<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 2    android:orientation="vertical"
 3    android:fitsSystemWindows="true">
 4    <!-- Popout bar for action modes -->
 5    <ViewStub android:id="@+id/action_mode_bar_stub"
 6              android:inflatedId="@+id/action_mode_bar"
 7              android:layout="@layout/action_mode_bar"
 8              android:layout_width="match_parent"
 9              android:layout_height="wrap_content"
10              android:theme="?attr/actionBarTheme" />
11
12    <FrameLayout android:id="@android:id/title_container" 
13        android:layout_width="match_parent" 
14        android:layout_height="?android:attr/windowTitleSize"
15        android:transitionName="android:title"
16        style="?android:attr/windowTitleBackgroundStyle">
17    </FrameLayout>
18    //这个容器
19    <FrameLayout android:id="@android:id/content"
20        android:layout_width="match_parent" 
21        android:layout_height="0dip"
22        android:layout_weight="1"
23        android:foregroundGravity="fill_horizontal|top"
24        android:foreground="?android:attr/windowContentOverlay" />
25</LinearLayout>

最后一步,就是windowManager.addView了,在哪呢? 在ActivityThred的handleResumeActivity()中:

1r.activity.makeVisible();

再看activity.makeVisible():

 1    void makeVisible() {
 2        if (!mWindowAdded) {
 3            ViewManager wm = getWindowManager();
 4            //1、windowManager.addView
 5            wm.addView(mDecor, getWindow().getAttributes());
 6            mWindowAdded = true;
 7        }
 8        //2、Decor可见
 9        mDecor.setVisibility(View.VISIBLE);
10    }

3.2 Dialog的window创建

先看Dialog的构造方法:

 1    Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
 2        ...
 3        //获取windowManager
 4        mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
 5        //实例化PhoneWindow
 6        final Window w = new PhoneWindow(mContext);
 7        mWindow = w;
 8        //设置回调
 9        w.setCallback(this);
10        w.setOnWindowDismissedCallback(this);
11        w.setOnWindowSwipeDismissedCallback(() -> {
12            if (mCancelable) {
13                cancel();
14            }
15        });
16        w.setWindowManager(mWindowManager, null, null);
17        ...
18    }

接着看setContentView,和activity类似,把内容视图放入DecorView:

1public void setContentView(@LayoutRes int layoutResID) {
2        mWindow.setContentView(layoutResID);
3    }

再看下show方法:

 1    public void show() {
 2        ...
 3        mDecor = mWindow.getDecorView();
 4        ...
 5        WindowManager.LayoutParams l = mWindow.getAttributes();
 6        boolean restoreSoftInputMode = false;
 7        if ((l.softInputMode
 8                & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) == 0) {
 9            l.softInputMode |=
10                    WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
11            restoreSoftInputMode = true;
12        }
13        //使用WindowManager.addView
14        mWindowManager.addView(mDecor, l);
15        ...
16    }

注意,一般创建dialog时 传入的context必须是Activity。如果要传Application,那么要dialog.getWindow().setType(),设置系统window的type。

3.3 Toast的window创建

使用Toast方式:

1        Toast.makeText(this, "hehe", Toast.LENGTH_SHORT).show();

看makeText(),就是new一个Toast,设置mNextView为TextView、mDuration:

1    public static Toast makeText(Context context, CharSequence text, @Duration int duration) {
2        return makeText(context, null, text, duration);
3    }
 1    public static Toast makeText(@NonNull Context context, @Nullable Looper looper,
 2            @NonNull CharSequence text, @Duration int duration) {
 3         //实例化
 4        Toast result = new Toast(context, looper);
 5
 6        LayoutInflater inflate = (LayoutInflater)
 7                context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
 8        View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null);
 9        TextView tv = (TextView)v.findViewById(com.android.internal.R.id.message);
10        tv.setText(text);
11        //设置视图、时间
12        result.mNextView = v;
13        result.mDuration = duration;
14
15        return result;
16    }

Toast构造方法:

1    public Toast(@NonNull Context context, @Nullable Looper looper) {
2        mContext = context;
3        //有个TN,是个Binder对象
4        mTN = new TN(context.getPackageName(), looper);
5        mTN.mY = context.getResources().getDimensionPixelSize(
6                com.android.internal.R.dimen.toast_y_offset);
7        mTN.mGravity = context.getResources().getInteger(
8                com.android.internal.R.integer.config_toastDefaultGravity);
9    }

实际也可以用setView()自定义视图:

1    public void setView(View view) {
2        mNextView = view;
3    }

再看show():

 1    public void show() {
 2        //没有视图不行
 3        if (mNextView == null) {
 4            throw new RuntimeException("setView must have been called");
 5        }
 6
 7        INotificationManager service = getService();
 8        String pkg = mContext.getOpPackageName();
 9        TN tn = mTN;
10        tn.mNextView = mNextView;
11
12        try {
13            //IPC过程:NotificationManagerServcice.enqueueToast(),为啥要IPC过程呢?(注意这里的tn就是Toast构造方法里的new的TN)
14            service.enqueueToast(pkg, tn, mDuration);
15        } catch (RemoteException e) {
16            // Empty
17        }
18    }

看下NotificationManagerServcice.enqueueToast():

1//创建ToastRecord,callback就是传进来的TN
2record = new ToastRecord(callingPid, pkg, callback, duration, token);
3                        mToastQueue.add(record);
4...
5if (index == 0) {
6    //这里看起来是show方法
7     showNextToastLocked();
8 }

看不showNextToastLocked():

 1    void showNextToastLocked() {
 2        //取出第一个record,这里为啥第0个?
 3        ToastRecord record = mToastQueue.get(0);
 4        while (record != null) {
 5            if (DBG) Slog.d(TAG, "Show pkg=" + record.pkg + " callback=" + record.callback);
 6            try {
 7                //这里跑到TN的show方法了,显然是系统服务NotificationManagerServcice向我们的APP发起IPC过程,完成最终的show。这个保留疑问后面再看~
 8                record.callback.show(record.token);
 9                //这个就是 定时 调TN的hide方法,时间就是我们的toast的设置的show时间?为啥这么说,往下看~
10                scheduleDurationReachedLocked(record);
11                return;
12            } 
13            ...
14        }
15    }

看下scheduleDurationReachedLocked(record):

1    private void scheduleDurationReachedLocked(ToastRecord r)
2    {
3        mHandler.removeCallbacksAndMessages(r);
4        Message m = Message.obtain(mHandler, MESSAGE_DURATION_REACHED, r);
5        long delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY;
6        //handler发送定时任务MESSAGE_DURATION_REACHED,看名字就是隐藏toast,时间就是我们的long或者short
7        mHandler.sendMessageDelayed(m, delay);
8    }

这个mHandler就是NMS中的handler,找到上面任务的处理方法:

1    private void handleDurationReached(ToastRecord record)
2    {
3        synchronized (mToastQueue) {
4            int index = indexOfToastLocked(record.pkg, record.callback);
5            if (index >= 0) {
6                cancelToastLocked(index);
7            }
8        }
9    }
 1    void cancelToastLocked(int index) {
 2        ToastRecord record = mToastQueue.get(index);
 3        try {
 4            //果然,是TN的hide方法,哈哈
 5            record.callback.hide();
 6        } catch (RemoteException e) 
 7        ...
 8        ToastRecord lastToast = mToastQueue.remove(index);
 9        if (mToastQueue.size() > 0) {
10            // 开始下一个~~~
11            showNextToastLocked();
12        }
13    }

总结下NotificationManagerServcice.enqueueToast()这个IPC的作用:使用NMS中的mHandler 处理队列中的ToastRecord,具体就是通过IPC调用Toast中的TN的show(),然后在定时调用TN的hide()。就是说,系统来保证toast的循序排队,及展示时间。

另外还一点,对非系统应用,队列中最多同时又50个ToastRecord:

1                // limit the number of outstanding notificationrecords an app can have
2               //MAX_PACKAGE_NOTIFICATIONS = 50
3                int count = getNotificationCountLocked(pkg, userId, id, tag);
4                if (count >= MAX_PACKAGE_NOTIFICATIONS) {
5                    mUsageStats.registerOverCountQuota(pkg);
6                    Slog.e(TAG, "Package has already posted or enqueued " + count
7                            + " notifications.  Not showing more.  package=" + pkg);
8                    return false;
9                }

好了,系统进程看完了。接着看实例化Toast时的创建的TN,我们在上面分析,猜测 这里才是我们想要的WIndow的创建过程,那么往下看吧:

  1    private static class TN extends ITransientNotification.Stub {
  2        private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();
  3
  4        private static final int SHOW = 0;
  5        private static final int HIDE = 1;
  6        private static final int CANCEL = 2;
  7        final Handler mHandler;
  8        ...
  9
 10        static final long SHORT_DURATION_TIMEOUT = 4000;
 11        static final long LONG_DURATION_TIMEOUT = 7000;
 12
 13        TN(String packageName, @Nullable Looper looper) {
 14            final WindowManager.LayoutParams params = mParams;
 15            ...
 16            //window的type:TYPE_TOAST = FIRST_SYSTEM_WINDOW+5,是个系统window
 17            params.type = WindowManager.LayoutParams.TYPE_TOAST;
 18            params.setTitle("Toast");
 19            //window的flags
 20            params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
 21                    | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
 22                    | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
 23
 24            mPackageName = packageName;
 25
 26            //这里可知,必须在有looper的线程才能new Toast,为啥呢?因为前面分析NMS中调用TN的show、Hide,因为是IPC过程,实际在App这边执行是在Bind线程池中进行的,所以需要切换到当前发Toast的线程
 27            if (looper == null) {
 28                // Use Looper.myLooper() if looper is not specified.
 29                looper = Looper.myLooper();
 30                if (looper == null) {
 31                    throw new RuntimeException(
 32                            "Can't toast on a thread that has not called Looper.prepare()");
 33                }
 34            }
 35            mHandler = new Handler(looper, null) {
 36                @Override
 37                public void handleMessage(Message msg) {
 38                    switch (msg.what) {
 39                        case SHOW: {
 40                            IBinder token = (IBinder) msg.obj;
 41                            handleShow(token);
 42                            break;
 43                        }
 44                        case HIDE: {
 45                            handleHide();
 46                            // Don't do this in handleHide() because it is also invoked by
 47                            // handleShow()
 48                            mNextView = null;
 49                            break;
 50                        }
 51                        case CANCEL: {
 52                            handleHide();
 53                            // Don't do this in handleHide() because it is also invoked by
 54                            // handleShow()
 55                            mNextView = null;
 56                            try {
 57                                getService().cancelToast(mPackageName, TN.this);
 58                            } catch (RemoteException e) {
 59                            }
 60                            break;
 61                        }
 62                    }
 63                }
 64            };
 65        }
 66
 67        /**
 68         * schedule handleShow into the right thread
 69         */
 70        @Override
 71        public void show(IBinder windowToken) {
 72            if (localLOGV) Log.v(TAG, "SHOW: " + this);
 73            mHandler.obtainMessage(SHOW, windowToken).sendToTarget();
 74        }
 75
 76        /**
 77         * schedule handleHide into the right thread
 78         */
 79        @Override
 80        public void hide() {
 81            if (localLOGV) Log.v(TAG, "HIDE: " + this);
 82            mHandler.obtainMessage(HIDE).sendToTarget();
 83        }
 84
 85        public void cancel() {
 86            if (localLOGV) Log.v(TAG, "CANCEL: " + this);
 87            mHandler.obtainMessage(CANCEL).sendToTarget();
 88        }
 89
 90        public void handleShow(IBinder windowToken) {
 91            ...
 92            if (mView != mNextView) {
 93                // remove the old view if necessary
 94                handleHide();
 95                //mNextView赋值给mView
 96                mView = mNextView;
 97                ...
 98                //1.获取WindowManager
 99                mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
100                // the layout direction
101                final Configuration config = mView.getContext().getResources().getConfiguration();
102                final int gravity = Gravity.getAbsoluteGravity(mGravity, config.getLayoutDirection());
103                ...
104                ...
105                try {
106                    //2.windowManager的addView
107                    mWM.addView(mView, mParams);
108                    trySendAccessibilityEvent();
109                } catch (WindowManager.BadTokenException e) {
110                    /* ignore */
111                }
112            }
113        }
114
115        public void handleHide() {
116            if (localLOGV) Log.v(TAG, "HANDLE HIDE: " + this + " mView=" + mView);
117            if (mView != null) {
118                if (mView.getParent() != null) {
119                    if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
120                    //windowManager的removeView
121                    mWM.removeViewImmediate(mView);
122                }
123            ...
124                mView = null;
125            }
126        }
127    }

所以,TN才是Toast中真正处理Window创建的地方。