1. 程式人生 > >Activity WMS ViewRootImpl三者關係(Activity建立視窗 按鍵分發等)

Activity WMS ViewRootImpl三者關係(Activity建立視窗 按鍵分發等)

今天我們來梳理下Activity ViewRootImpl和WMS三者的關係,這裡面看了網上的部落格,也看了一些書,加上自己的總結,寫了這篇部落格。

1. Activity

我們先來看Activity,在ActivityThread中的performLaunchActivity函式中, 先建立了Activity,然後呼叫了Activity的attach函式

        ......
        Activity activity = null;
        try {
            java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
	......

            if (activity != null) {
                Context appContext = createBaseContextForActivity(r, activity);
                CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
                Configuration config = new Configuration(mCompatConfiguration);
                if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity "
                        + r.activityInfo.name + " with config " + config);
                activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor);

下面我們再來看看Activity的attach函式,先建立了PhoneWindow物件給了mWindow,然後呼叫其setWindowManager設定其WindowManager,最後再呼叫mWindow的getWindowManager方法作為Activity的mWindowManager成員變數

	......
        mWindow = new PhoneWindow(this);//新建PhoneWindow物件
        mWindow.setCallback(this);//這window中設定回撥,在按鍵事件分發的時候中用到。如果有這個回撥,就呼叫Activity的dispatchKeyEvent
        mWindow.setOnWindowDismissedCallback(this);
	......
	mWindow.setWindowManager(
		(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
		mToken, mComponent.flattenToString(),
		(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
        if (mParent != null) {
            mWindow.setContainer(mParent.getWindow());
        }
        mWindowManager = mWindow.getWindowManager();

再來看看Window類的setWindowManager方法,也是最後呼叫了WindowManagerImpl的createLocalWindowManager

    public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
            boolean hardwareAccelerated) {
        mAppToken = appToken;
        mAppName = appName;
        mHardwareAccelerated = hardwareAccelerated
                || SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false);
        if (wm == null) {
            wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
        }
        mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
    }

而createLocalWindowManager函式就是建立了一個WindowManagerImpl物件,因此Activity的mWindowManager成員變數最後就是WindowManagerImpl物件。

    public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
        return new WindowManagerImpl(mDisplay, parentWindow);
    }

而在WindowManagerImpl中有一個mGlobal的變數,最後都是通過這個變數呼叫的方法,因此我們來看看這個類。

public final class WindowManagerImpl implements WindowManager {
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();

WindowManagerGlobal類中主要有3個非常重要的變數

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

mViews儲存的是View物件,DecorView

mRoots儲存和頂層View關聯的ViewRootImpl物件

mParams儲存的是建立頂層View的layout引數。

而WindowManagerGlobal類也負責和WMS通訊。

2. PhoneWindow

在上一節Activity中,我們在Activity的attach函式中新建了PhoneWindow物件

2.1 建立DecorView

在PhoneWindow的setContentView函式中會呼叫installDector來建立DecorView物件

    @Override
    public void setContentView(View view, ViewGroup.LayoutParams params) {
        // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
        // decor, when theme attributes and the like are crystalized. Do not check the feature
        // before this happens.
        if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            view.setLayoutParams(params);
            final Scene newScene = new Scene(mContentParent, view);
            transitionTo(newScene);
        } else {
            mContentParent.addView(view, params);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
    }

在installDecor函式中呼叫了generateDecor函式來建立按DecorView物件。

    private void installDecor() {
        if (mDecor == null) {
            mDecor = generateDecor();
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        }
......
    protected DecorView generateDecor() {
        return new DecorView(getContext(), -1);
    }

2.2 DecorView的按鍵處理

而DecorView是PhoneWindow類的一個嵌入類。我們來看下按鍵事件在DecorView的處理過程,先是在ViewRootImpl的processKeyEvent函式

    final class ViewPostImeInputStage extends InputStage {
        public ViewPostImeInputStage(InputStage next) {
            super(next);
        }

        @Override
        protected int onProcess(QueuedInputEvent q) {
            if (q.mEvent instanceof KeyEvent) {
                return processKeyEvent(q);
            } else {
                // If delivering a new non-key event, make sure the window is
                // now allowed to start updating.
                handleDispatchWindowAnimationStopped();
                final int source = q.mEvent.getSource();
                if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
                    return processPointerEvent(q);
                } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
                    return processTrackballEvent(q);
                } else {
                    return processGenericMotionEvent(q);
                }
            }
        }

然後在processKeyEvent呼叫了了mView的dispatchKeyEvent函式,就是呼叫了DecorView的dispatchKeyEvent函式

        private int processKeyEvent(QueuedInputEvent q) {
            final KeyEvent event = (KeyEvent)q.mEvent;

            if (event.getAction() != KeyEvent.ACTION_UP) {
                // If delivering a new key event, make sure the window is
                // now allowed to start updating.
                handleDispatchWindowAnimationStopped();
            }

            // Deliver the key to the view hierarchy.
            if (mView.dispatchKeyEvent(event)) {
                return FINISH_HANDLED;
            }

然後在DecorView的dispatchKeyEvent中,最後呼叫getCallBack,如果有回撥,就呼叫回撥的dispatchKeyEvent,這個就是在Activity的attach中將Activity設定為PhoneWindow的回撥,因此最後dispatchKeyEvent就會到Activity的dispatchKeyEvent函式中

        public boolean dispatchKeyEvent(KeyEvent event) {
            final int keyCode = event.getKeyCode();
            final int action = event.getAction();
            final boolean isDown = action == KeyEvent.ACTION_DOWN;

            if (isDown && (event.getRepeatCount() == 0)) {
                // First handle chording of panel key: if a panel key is held
                // but not released, try to execute a shortcut in it.
                if ((mPanelChordingKey > 0) && (mPanelChordingKey != keyCode)) {
                    boolean handled = dispatchKeyShortcutEvent(event);
                    if (handled) {
                        return true;
                    }
                }

                // If a panel is open, perform a shortcut on it without the
                // chorded panel key
                if ((mPreparedPanel != null) && mPreparedPanel.isOpen) {
                    if (performPanelShortcut(mPreparedPanel, keyCode, event, 0)) {
                        return true;
                    }
                }
            }

            if (!isDestroyed()) {
                final Callback cb = getCallback();
                final boolean handled = cb != null && mFeatureId < 0 ? cb.dispatchKeyEvent(event)
                        : super.dispatchKeyEvent(event);
                if (handled) {
                    return true;
                }
            }

            return isDown ? PhoneWindow.this.onKeyDown(mFeatureId, event.getKeyCode(), event)
                    : PhoneWindow.this.onKeyUp(mFeatureId, event.getKeyCode(), event);
        }
Activity的attach中呼叫了PhoneWindow的setCallBack函式將回調設定成Activity,最後dispatchKeyEvent就會呼叫到Activity的dispatchKeyEvent中。
    final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor) {
        attachBaseContext(context);

        mFragments.attachHost(null /*parent*/);

        mWindow = new PhoneWindow(this);
        mWindow.setCallback(this);


3. ViewRootImpl

在ActivityThread的handleResumeActivity函式中會呼叫WindowManager的addView函式,而這個WindowManager就是Activity的mWindowManager。

            if (r.window == null && !a.mFinished && willBeVisible) {
                r.window = r.activity.getWindow();
                View decor = r.window.getDecorView();
                decor.setVisibility(View.INVISIBLE);
                ViewManager wm = a.getWindowManager();
                WindowManager.LayoutParams l = r.window.getAttributes();
                a.mDecor = decor;
                l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
                l.softInputMode |= forwardBit;
                if (a.mVisibleFromClient) {
                    a.mWindowAdded = true;
                    wm.addView(decor, l);
                }

            // If the window has already been added, but during resume
            // we started another activity, then don't yet make the
            // window visible.
            }

這個我們在Activity一節中分析過了,因此最後addView函式在WindowManagerImpl中實現,我們再來看看WindowManagerImpl的addView函式。

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

而在WindowManagerImpl最後是呼叫了WindowManagerGlobal的addView函式, 在這個函式中我們新建ViewRootImpl物件,然後呼叫了ViewRootImpl的setView函式,這裡的view就是Activity的Window物件的DecorView。並且在這個函式中,把view root param這3個儲存在mViews mRoots mParams這3個成員變數中了。

        ......
        ViewRootImpl root;
        View panelParentView = null;

        synchronized (mLock) {
            // Start watching for system property changes.
            if (mSystemPropertyUpdater == null) {
                mSystemPropertyUpdater = new Runnable() {
                    @Override public void run() {
                        synchronized (mLock) {
                            for (int i = mRoots.size() - 1; i >= 0; --i) {
                                mRoots.get(i).loadSystemProperties();
                            }
                        }
                    }
                };
                SystemProperties.addChangeCallback(mSystemPropertyUpdater);
            }

            int index = findViewLocked(view, false);
            if (index >= 0) {
                if (mDyingViews.contains(view)) {
                    // Don't wait for MSG_DIE to make it's way through root's queue.
                    mRoots.get(index).doDie();
                } else {
                    throw new IllegalStateException("View " + view
                            + " has already been added to the window manager.");
                }
                // The previous removeView() had not completed executing. Now it has.
            }

            // If this is a panel window, then find the window it is being
            // attached to for future reference.
            if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
                    wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
                final int count = mViews.size();
                for (int i = 0; i < count; i++) {
                    if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
                        panelParentView = mViews.get(i);
                    }
                }
            }

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

            view.setLayoutParams(wparams);

            mViews.add(view);
            mRoots.add(root);//儲存各個成員變數
            mParams.add(wparams);
        }

        // do this last because it fires off messages to start doing things
        try {
            root.setView(view, wparams, panelParentView);//呼叫ViewRootImpl的setView函式
        } catch (RuntimeException e) {
            // BadTokenException or InvalidDisplayException, clean up.
            synchronized (mLock) {
                final int index = findViewLocked(view, false);
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
            }
            throw e;
        }
    }

在ViewRootImpl的setView函式中,先呼叫了requestLayout來繪製view,然後呼叫了mWindowSession的addToDisplay函式和WMS通訊

                ......
                requestLayout();//繪製View
                if ((mWindowAttributes.inputFeatures
                        & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                    mInputChannel = new InputChannel();
                }
                try {
                    mOrigWindowType = mWindowAttributes.type;
                    mAttachInfo.mRecomputeGlobalAttributes = true;
                    collectViewAttributes();
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mInputChannel);
                } 

我們先來看下mWindowSession,就是通過WMS獲取到WindowSession的Binder,並且自己設定了一個回撥在WMS中

    public static IWindowSession getWindowSession() {
        synchronized (WindowManagerGlobal.class) {
            if (sWindowSession == null) {
                try {
                    InputMethodManager imm = InputMethodManager.getInstance();
                    IWindowManager windowManager = getWindowManagerService();
                    sWindowSession = windowManager.openSession(
                            new IWindowSessionCallback.Stub() {
                                @Override
                                public void onAnimatorScaleChanged(float scale) {
                                    ValueAnimator.setDurationScale(scale);
                                }
                            },
                            imm.getClient(), imm.getInputContext());
                } catch (RemoteException e) {
                    Log.e(TAG, "Failed to open window session", e);
                }
            }
            return sWindowSession;
        }
    }

最後BInder呼叫到Session的addToDisplay,也會呼叫WMS的addWindow函式,這個我們稍後在WMS中分析。

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

我們再來看ViewRootImpl中呼叫mWindowSession.addToDisplay函式傳入的一個引數mWindow,

        mWindow = new W(this);

W是ViewRootImpl的一個嵌入類,也是一個Binder服務。通過mWindowSession.addToDisplay函式傳入WMS,用來在WMS中通過Binder回撥。

    static class W extends IWindow.Stub {
        private final WeakReference<ViewRootImpl> mViewAncestor;
        private final IWindowSession mWindowSession;

        W(ViewRootImpl viewAncestor) {
            mViewAncestor = new WeakReference<ViewRootImpl>(viewAncestor);
            mWindowSession = viewAncestor.mWindowSession;
        }

        @Override
        public void resized(Rect frame, Rect overscanInsets, Rect contentInsets,
                Rect visibleInsets, Rect stableInsets, Rect outsets, boolean reportDraw,
                Configuration newConfig) {
            final ViewRootImpl viewAncestor = mViewAncestor.get();
            if (viewAncestor != null) {
                viewAncestor.dispatchResized(frame, overscanInsets, contentInsets,
                        visibleInsets, stableInsets, outsets, reportDraw, newConfig);
            }
        }
......


4. WMS

WMS指的是WindowManagerService,下面我們來看下WMS

public class WindowManagerService extends IWindowManager.Stub
        implements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs {
		......
		final WindowManagerPolicy mPolicy = new PhoneWindowManager();
		......
		/**
		 * All currently active sessions with clients.
		 */
		final ArraySet<Session> mSessions = new ArraySet<>();

		/**
		 * Mapping from an IWindow IBinder to the server's Window object.
		 * This is also used as the lock for all of our state.
		 * NOTE: Never call into methods that lock ActivityManagerService while holding this object.
		 */
		final HashMap<IBinder, WindowState> mWindowMap = new HashMap<>();

		/**
		 * Mapping from a token IBinder to a WindowToken object.
		 */
		final HashMap<IBinder, WindowToken> mTokenMap = new HashMap<>();

mPolicy是WMS所執行的視窗管理策略類,現在android只有PhoneWindowManager一個策略類。

mSessions儲存的是Session服務類,每個應用都在WMS中有一個對應的Session物件儲存在mSessions中。

mTokenMap儲存的是所有視窗的WindowToken物件。

mWindowMap儲存的是所有視窗的WindowState物件。

4.1 WindowToken物件

在WMS中有兩種常見的Token,WindowToken和AppWindowToken。先來看WindowToken的定義:

class WindowToken {
    // The window manager!
    final WindowManagerService service;

    // The actual token.
    final IBinder token;

    // The type of window this token is for, as per WindowManager.LayoutParams.
    final int windowType;

    // Set if this token was explicitly added by a client, so should
    // not be removed when all windows are removed.
    final boolean explicit;

    // For printing.
    String stringName;

    // If this is an AppWindowToken, this is non-null.
    AppWindowToken appWindowToken;

    // All of the windows associated with this token.
    final WindowList windows = new WindowList();

WindowToken的成員變數token是IBinder物件,具有系統唯一性。因此,向WMS的mWindowMap或者mTokenMap插入物件時都是使用token值作為索引。

WindowToken用來表示一組視窗物件,windowType表示視窗型別。

explicit為false表示這個Token是在WMS中建立的,true表示在其他模組通過呼叫addAppToken或者addWindowToken方法顯示建立的。

windows型別是WindowList,包含一個WindowState物件的列表,所有擁有相同WindowToken的視窗都在這個列表中。一般而言視窗和依附於它的子視窗擁有相同的WindowToken物件。

APPWindowToken從WindowToken類派生,是一種比較特殊的WindowToken,代表應用視窗,主要是Activity中建立的頂層視窗。一個WindowToken物件的成員變數APPWindowToken如果為NULL,那麼它就不是APPWindowToken,否則就是APPWindowToken物件。

APPWindowToken中增加了幾個特有的成員變數,如下:

class AppWindowToken extends WindowToken {
    // Non-null only for application tokens.
    final IApplicationToken appToken;

    // All of the windows and child windows that are included in this
    // application token.  Note this list is NOT sorted!
    final WindowList allAppWindows = new WindowList();
    final AppWindowAnimator mAppAnimator;

    final WindowAnimator mAnimator;

其中appToken用來表示應用的Token,它是在AMS中建立的,代表一個應用。

allAppWindows也是一個WindowList,儲存了所有相同APPWindowToken的應用視窗及其子視窗。

在WMS中定義了addAppToken用來向WMS新增一個APPWindowToken型別的Token。

4.2 視窗型別

在WMS中定義了3中型別的視窗:應用視窗,子視窗,系統視窗。

應用視窗:

應用視窗是Activity使用的全屏視窗。

1.TYPE_BASE_APPLICATION 應用中所有視窗都位於其上層

2.TYPE_APPLICATION Activity的頂層視窗,也是最常見的視窗

3.TYPE_APPLICATION_STARTING 應用啟動時系統建立的視窗,顯示一些資訊,直到應用視窗顯示出來

子視窗:

子視窗是依附於應用視窗或者系統視窗的視窗型別,它們一般隨所依附的視窗一起出現或者切到後臺。

TYPE_APPLICATION_PANEL:應用面板視窗,顯示於所依附視窗上層

TYPE_APPLICATION_MEDIA:應用Media視窗,通常獨立的Surface,用於播放Video或顯示照相機預覽畫面。顯示於所依附視窗的下層

TYPE_APPLICATION_SUB_PANEL:子面板視窗,顯示在所有子視窗的上層

TYPE_APPLICATION_ATTACHED_DIALOG:和麵板視窗類似的視窗,但是layout的方式和Activity的頂層視窗類似

TYPE_APPLICATION_MEDIA_OVERLAY:一種顯示在Media視窗和所依附視窗之間的半透明視窗,用於顯示Video字幕或者相機操作提示介面

系統視窗:

系統視窗大都用於系統生成的UI中,比較獨立,種類較多。

狀態條,搜尋,通話,輸入法等。

import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;//第一個應用視窗
import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;//第一個子視窗
import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND;
import static android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;//最後一個應用視窗
import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW;//最後一個子視窗
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;//Activity頂層視窗,應用中最常見視窗
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;//應用啟動時由系統建立的視窗,顯示一些資訊,直到應用建立的視窗顯示出來
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;//基礎應用視窗,應用中所有視窗都位於其上層。只能又系統建立
import static android.view.WindowManager.LayoutParams.TYPE_BOOT_PROGRESS;
import static android.view.WindowManager.LayoutParams.TYPE_DREAM;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG;
import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_SCRIM;
import static android.view.WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION;
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;//狀態條視窗
import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG;//顯示狀態條彈出對話方塊
import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;//系統內部錯誤視窗
import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
import static com.android.server.am.ActivityStackSupervisor.HOME_STACK_ID;

還有一些在WindowManager中的LayoutParams中

        public static final int TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW;

        /**
         * Window type: window for showing media (such as video).  These windows
         * are displayed behind their attached window.
         */
        public static final int TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW + 1;

        /**
         * Window type: a sub-panel on top of an application window.  These
         * windows are displayed on top their attached window and any
         * {@link #TYPE_APPLICATION_PANEL} panels.
         */
        public static final int TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW + 2;

        /** Window type: like {@link #TYPE_APPLICATION_PANEL}, but layout
         * of the window happens as that of a top-level window, <em>not</em>
         * as a child of its container.
         */
        public static final int TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW + 3;

        /**
         * Window type: window for showing overlays on top of media windows.
         * These windows are displayed between TYPE_APPLICATION_MEDIA and the
         * application window.  They should be translucent to be useful.  This
         * is a big ugly hack so:
         * @hide
         */
        public static final int TYPE_APPLICATION_MEDIA_OVERLAY  = FIRST_SUB_WINDOW + 4;

4.3 新建視窗的過程

每次新建Activity會增加視窗,彈出選單同樣會在WMS增加視窗。而最終都是通過Session的addToDisplay向WMS增加一個視窗。WMS最終會呼叫addWindow來實現這個功能。

我們來回顧下之前Activity是如何呼叫增加一個視窗的。

新建Activity的時候先呼叫handleLaunchActivity,然後在這個函式中會呼叫handleResumeActivity函式然後在這個函式中會呼叫WindowManagerGlobal的addView函式,而在WindowManagerGlobal的addView函式中會新加ViewRootImpl物件,然後呼叫其setView函式,在setView中會呼叫mWindowSession.addToDisplay函式,最後到WMS的addWindow函式。


4.3.1 引數檢查
    public int addWindow(Session session, IWindow client, int seq,
            WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
            Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
            InputChannel outInputChannel) {
	    ......
	    //檢查mWindowMap是否有該視窗,如果有重複了,直接退出
            if (mWindowMap.containsKey(client.asBinder())) {
                Slog.w(TAG, "Window " + client + " is already added");
                return WindowManagerGlobal.ADD_DUPLICATE_ADD;
            }
	    //子視窗型別,父視窗型別必須存在且不能是子視窗型別,否則退出
            if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) {
                attachedWindow = windowForClientLocked(null, attrs.token, false);
                if (attachedWindow == null) {
                    Slog.w(TAG, "Attempted to add window with token that is not a window: "
                          + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
                }
                if (attachedWindow.mAttrs.type >= FIRST_SUB_WINDOW
                        && attachedWindow.mAttrs.type <= LAST_SUB_WINDOW) {
                    Slog.w(TAG, "Attempted to add window with token that is a sub-window: "
                            + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
                }
            }
			//私有視窗型別,顯示裝置不是私有型別,退出
            if (type == TYPE_PRIVATE_PRESENTATION && !displayContent.isPrivate()) {
                Slog.w(TAG, "Attempted to add private presentation window to a non-private display.  Aborting.");
                return WindowManagerGlobal.ADD_PERMISSION_DENIED;
            }

            boolean addToken = false;
            WindowToken token = mTokenMap.get(attrs.token);
			//如果mTokenMap中沒有,但是視窗型別是應用視窗,輸入法視窗,桌布,Dream則退出
            if (token == null) {
                if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) {
                    Slog.w(TAG, "Attempted to add application window with unknown token "
                          + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                if (type == TYPE_INPUT_METHOD) {
                    Slog.w(TAG, "Attempted to add input method window with unknown token "
                          + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                if (type == TYPE_VOICE_INTERACTION) {
                    Slog.w(TAG, "Attempted to add voice interaction window with unknown token "
                          + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                if (type == TYPE_WALLPAPER) {
                    Slog.w(TAG, "Attempted to add wallpaper window with unknown token "
                          + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                if (type == TYPE_DREAM) {
                    Slog.w(TAG, "Attempted to add Dream window with unknown token "
                          + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                if (type == TYPE_ACCESSIBILITY_OVERLAY) {
                    Slog.w(TAG, "Attempted to add Accessibility overlay window with unknown token "
                            + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
	        //其他型別的視窗新建token
                token = new WindowToken(this, attrs.token, -1, false);
                addToken = true;
            } else if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) {
	       //應用型別視窗
                AppWindowToken atoken = token.appWindowToken;
                if (atoken == null) {//應用型別視窗必須有appWindowToken
                    Slog.w(TAG, "Attempted to add window with non-application token "
                          + token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_NOT_APP_TOKEN;
                } else if (atoken.removed) {
                    Slog.w(TAG, "Attempted to add window with exiting application token "
                          + token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_APP_EXITING;
                }
                if (type == TYPE_APPLICATION_STARTING && atoken.firstWindowDrawn) {
	           //還在啟動中,不能新增視窗
                    // No need for this guy!
                    if (localLOGV) Slog.v(
                            TAG, "**** NO NEED TO START: " + attrs.getTitle());
                    return WindowManagerGlobal.ADD_STARTING_NOT_NEEDED;
                }
            } else if (type == TYPE_INPUT_METHOD) {//輸入法
                if (token.windowType != TYPE_INPUT_METHOD) {
                    Slog.w(TAG, "Attempted to add input method window with bad token "
                            + attrs.token + ".  Aborting.");
                      return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
            } else if (type == TYPE_VOICE_INTERACTION) {//
                if (token.windowType != TYPE_VOICE_INTERACTION) {
                    Slog.w(TAG, "Attempted to add voice interaction window with bad token "
                            + attrs.token + ".  Aborting.");
                      return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
            } else if (type == TYPE_WALLPAPER) {//桌布
                if (token.windowType != TYPE_WALLPAPER) {
                    Slog.w(TAG, "Attempted to add wallpaper window with bad token "
                            + attrs.token + ".  Aborting.");
                      return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
            } else if (type == TYPE_DREAM) {//Dream
                if (token.windowType != TYPE_DREAM) {
                    Slog.w(TAG, "Attempted to add Dream window with bad token "
                            + attrs.token + ".  Aborting.");
                      return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
            } else if (type == TYPE_ACCESSIBILITY_OVERLAY) {
                if (token.windowType != TYPE_ACCESSIBILITY_OVERLAY) {
                    Slog.w(TAG, "Attempted to add Accessibility overlay window with bad token "
                            + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
            } else if (token.appWindowToken != null) {
                Slog.w(TAG, "Non-null appWindowToken for system window of type=" + type);
                // It is not valid to use an app token with other system types; we will
                // instead make a new token for it (as if null had been passed in for the token).
                attrs.token = null;
                token = new WindowToken(this, null, -1, false);
                addToken = true;
            }

這段程式碼主要是進行引數檢查,大部分引數從客戶程序傳過來的。為了保證資料的一致性,重點檢查視窗的WindowToken物件中的視窗型別和之前在WMS的WindowToken的windowType是否一致。這種檢查主要針對應用視窗、輸入法、桌布、dream、TYPE_VOICE_INTERACTION、TYPE_ACCESSIBILITY_OVERLAY,因為這幾種視窗的WindowToken物件在addWindow前已經建立好並加入WMS的mTokenMap中,這裡需要檢查是否一致。

一些特殊的視窗,入輸入法視窗,必須由InputManagerService允許後才能建立。這些服務會預先呼叫WMS的addWindowToken方法插入WindowToken到WMS的mTokenMap中。如果應用不通過服務獲取相應的WindowToken將無法建立這些特殊視窗,方便控制。

對於其他型別的視窗,addWindow方法會為它們建立WindowToken物件。


4.3.2 建立視窗物件
            //建立WindowState物件
            WindowState win = new WindowState(this, session, client, token,
                    attachedWindow, appOp[0], seq, attrs, viewVisibility, displayContent);
            if (win.mDeathRecipient == null) {
                // Client has apparently died, so there is no reason to
                // continue.
                Slog.w(TAG, "Adding window client " + client.asBinder()
                        + " that is dead, aborting.");
                return WindowManagerGlobal.ADD_APP_EXITING;
            }

            if (win.getDisplayContent() == null) {
                Slog.w(TAG, "Adding window to Display that has been removed.");
                return WindowManagerGlobal.ADD_INVALID_DISPLAY;
            }
            mPolicy.adjustWindowParamsLw(win.mAttrs);
			//檢查和設定檢視是否能夠讓其他使用者看見
            win.setShowToOwnerOnlyLocked(mPolicy.checkShowToOwnerOnly(attrs));

            res = mPolicy.prepareAddWindowLw(win, attrs);
            if (res != WindowManagerGlobal.ADD_OKAY) {
                return res;
            }
            //為按鍵建立和應用的通訊通道
            if (outInputChannel != null && (attrs.inputFeatures
                    & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                String name = win.makeInputChannelName();
                InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);
                win.setInputChannel(inputChannels[0]);
                inputChannels[1].transferTo(outInputChannel);

                mInputManager.registerInputChannel(win.mInputChannel, win.mInputWindowHandle);
            }

            // From now on, no exceptions or errors allowed!

            res = WindowManagerGlobal.ADD_OKAY;

            origId = Binder.clearCallingIdentity();

            if (addToken) {//如果是新的Token,加入mTokenMap
                mTokenMap.put(attrs.token, token);
            }
            win.attach();
            mWindowMap.put(client.asBinder(), win);//加入WindowState物件列表中
            ......

這段程式碼主要是儲存WindowState物件,放入mWindowMap中。

mPolicy的adjustWindowParamsLw方法檢查視窗型別是否是TYPE_SYSTEM_OVERLAY或者TYPE_SERCURE_SYSTEM_OVERLAY,如果是將視窗標記上FLAG_NOT_FOCUSABLE FLAG_NOT_TOUCHABLE FLAG_WATCH_OUTSIDE_TOUCH.這些視窗不能獲取焦點。

4.3.3 將視窗加入Display列表
            boolean imMayMove = true;

            if (type == TYPE_INPUT_METHOD) {//如果視窗類是輸入法視窗
                win.mGivenInsetsPending = true;
                mInputMethodWindow = win;
                addInputMethodWindowToListLocked(win);//插入輸入法視窗到應用視窗上層
                imMayMove = false;
            } else if (type == TYPE_INPUT_METHOD_DIALOG) {//如果視窗是輸入法對話方塊
                mInputMethodDialogs.add(win);
                addWindowToListInOrderLocked(win, true);//插入到正常位置
                moveInputMethodDialogsLocked(findDesiredInputMethodWindowIndexLocked(true));//調整對話方塊位置
                imMayMove = false;
            } else {
                addWindowToListInOrderLocked(win, true);
                if (type == TYPE_WALLPAPER) {
                    mLastWallpaperTimeoutTime = 0;
                    displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
                } else if ((attrs.flags&FLAG_SHOW_WALLPAPER) != 0) {
                    displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
                } else if (mWallpaperTarget != null
                        && mWallpaperTarget.mLayer >= win.mBaseLayer) {
                    // If there is currently a wallpaper being shown, and
                    // the base layer of the new window is below the current
                    // layer of the target window, then adjust the wallpaper.
                    // This is to avoid a new window being placed between the
                    // wallpaper and its target.
                    displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
                }
            }

DisplayContent類的mWindows列表按Z序儲存了每個視窗,這段程式碼就是在根據視窗型別把視窗加入到DisplayContent合適位置。

addInputMethodWindowToListLocked方法作用就是一個輸入法視窗放子啊需要顯示輸入法視窗的上面。

addWindowToListInOrderLocked將一個視窗插入到視窗堆疊的當前位置。

4.3.4 調整視窗的次序
           winAnimator.mEnterAnimationPending = true;
            winAnimator.mEnteringAnimation = true;

            if (displayContent.isDefaultDisplay) {
                mPolicy.getInsetHintLw(win.mAttrs, mRotation, outContentInsets, outStableInsets,//計算視窗大小
                        outOutsets);
            } else {
                outContentInsets.setEmpty();
                outStableInsets.setEmpty();
            }

            if (mInTouchMode) {
                res |= WindowManagerGlobal.ADD_FLAG_IN_TOUCH_MODE;
            }
            if (win.mAppToken == null || !win.mAppToken.clientHidden) {
                res |= WindowManagerGlobal.ADD_FLAG_APP_VISIBLE;
            }

            mInputMonitor.setUpdateInputWindowsNeededLw();

            boolean focusChanged = false;
            if (win.canReceiveKeys()) {//如果視窗能接受輸入,計算是否引起焦點變化
                focusChanged = updateFocusedWindowLocked(UPDATE_FOCUS_WILL_ASSIGN_LAYERS,
                        false /*updateInputWindows*/);
                if (focusChanged) {
                    imMayMove = false;
                }
            }

            if (imMayMove) {
                moveInputMethodWindowsIfNeededLocked(false);
            }

            assignLayersLocked(displayContent.getWindowList());
            // Don't do layout here, the window must call
            // relayout to be displayed, so we'll do it there.

            if (focusChanged) {
                mInputMonitor.setInputFocusLw(mCurrentFocus, false /*updateInputWindows*/);
            }
            mInputMonitor.updateInputWindowsLw(false /*force*/);

            if (localLOGV || DEBUG_ADD_REMOVE) Slog.v(TAG, "addWindow: New client "
                    + client.asBinder() + ": window=" + win + " Callers=" + Debug.getCallers(5));

            if (win.isVisibleOrAdding() && updateOrientationFromAppTokensLocked(false)) {
                reportNewConfig = true;//如果視窗的配置發生變化
            }
        }

        if (reportNewConfig) {
            sendNewConfiguration();//發生新的配置
        }

        Binder.restoreCallingIdentity(origId);

        return res;

後續還有確定視窗Z軸位置,視窗尺寸,視窗動畫後續接觸到再分析吧。