主頁 > 移動端開發 > Android應用程式視窗設計之視窗的添加

Android應用程式視窗設計之視窗的添加

2020-12-13 10:20:51 移動端開發

???????Android應用程式視窗設計之視窗的添加


Android應用程式視窗設計系列博客:

Android應用程式視窗設計之Window及WindowManager的創建
Android應用程式視窗設計之setContentView布局加載的實作
普法Android的Token前世今生以及在APP,AMS,WMS之間傳遞
Android應用程式視窗設計之視窗的添加


本篇博客撰寫思路總結和關鍵點說明:

在這里插入圖片描述

為了更加方便的讀者閱讀博客,通過導讀思維圖的形式將本博客的關鍵點列舉出來,從而方便讀者取舍和閱讀!



引言

??通過前面博客Android應用程式視窗設計之Window及WindowManager的創建和Android應用程式視窗設計之setContentView布局加載的實作的原始碼分析以及結合實際開發的相關操作,我們完成了對Android應用程式視窗設計的如下積累:

  • 掌握了Android應用程式視窗以及視窗管理器的創建
  • 掌握了Android應用程式視窗中的布局檔案是怎么被加載實作的(即Activity怎么通過setContentView加載布局 )

那么Android應用程式視窗的實作就此完了嗎,當然不是!此時距離視窗展示到我們的終端界面還有很長的路要走(其中主要遺留的是Android系統是怎么對視窗進行管理和添加以及繪制等),但是不要灰心當然也不要喪氣,路是一步步走的,飯是一口口吃的不是,今天的博客我將帶領讀者攻破Android應用程式視窗添加機制,從而為全盤掌握Android應用程式視窗的實作又下一城!

注意:本篇的介紹是基于Android 7.xx平臺為基礎的,且視窗的添加是以Activity對應的視窗為例來說明的!其中涉及的代碼路徑如下:

frameworks/base/core/java/android/view/
	--- WindowManager.java
 	--- View.java
	--- ViewManager.java
	--- ViewRootImpl.java
	--- Window.java
	--- Display.java
	--- WindowManagerImpl.java
	--- WindowManager.java
	--- WindowManagerGlobal.java
	--- IWindowManager.aidl
	--- IWindow.aidl
	--- IWindowSession.aidl

frameworks/base/services/core/java/com/android/server/wm/
	---WindowManagerService.java
	---AppWindowToken.java
	---WindowState.java
	---Session.java
	---WindowToken.java

在正式開始本篇博客Android應用程式視窗添加機制及原始碼分析前,這里有必要先將涉及關于Android應用程式視窗相關類以及類圖圖關系先放出來,以便能夠方便后續的相關閱讀,使讀者能夠更加清楚的知道這幾者之間在原始碼中的關系,

在這里插入圖片描述




一.前期知識準備

??老話說的好,磨刀不誤砍柴工!所以我們也得先磨磨刀,有幾個相關知識點我們在正式開始相關的原始碼分析前,必須先亮出來!

關于下面要亮出的幾個知識點,沒有一定知識積累的可能一時不一定能理解,沒有關系!我在這里提前印出來只是為了讓讀者心里有一個底,提前熟悉下,后面在原始碼分析中會一一講解到的,


1.1 Android視窗的分類

Android的視窗被大致分為三類,分別是:

  • Android應用程式視窗,這個是最常見的(擁有自己的WindowToken)譬如:Activity與Dialog
  • Android應用程式子視窗(必須依附到其他非子視窗才能存在,通常這個被依附的視窗型別Activity視窗) 例如:PopupWindow
  • Android系統視窗,其中我們最最常見的就是Toast視窗了

這里Dialog比較特殊,從表現上來說偏向于子視窗,必須依附到Activity才能存在,而從性質上來說,仍然是應用視窗,有自己的AppWindowToken,


1.2 視窗管理中涉及的幾個重要概念

在接下來要分析的視窗添加以及視窗的管理中,有幾個非常只要的知識點有必要提前點名一下來一個眼前熟,它們分別是:

  • IWindow: APP端視窗暴露給WMS的抽象實體,同時也是WMS向APP端發送訊息的Binder通道,它在APP端的實作為W
  • IWindowSession:WMS端暴露給視窗端的用于和WMS服務端通信的Binder通道
  • WindowState:WMS端視窗的令牌,與IWindow視窗一一對應,是WMS管理視窗的重要依據
  • WindowToken:是視窗的令牌,也是視窗分組的依據,在WMS端,和分組對應的資料結構是WindowToken
  • Token:是在AMS構建Activity對應的ActivityRecord時里面的IApplicationToken的實體,會在Activity創建程序中傳遞到AMS中,并且Token會在Activity從創建到顯示的程序中會在App行程和AMS,WMS之間進行傳遞,關于Token可以詳見博客普法Android的Token前世今生以及在APP,AMS,WMS之間傳遞,此處很重要!

在這里插入圖片描述

  • IWindowSession:WMS服務用于提供給ViewRootImpl來和其進行跨Binder通信的介面

好了前面的這幾個知識點,讀者先心里大概有個了解即可了,通過后面的原始碼了解翻過頭來看你就會有一種大徹大悟的感覺了,



二.APP應用程式端開始處理視窗的添加流程

??通過前面的博客Android應用程式視窗設計之setContentView布局加載的實作分析我們可知在將視窗布局加載OK以后,會呼叫wm.addView()方法添加,在該方法中的decor引數為Acitity對應的Window中的視圖DecorView,wm為在創建PhoneWindow時創建的WindowManagerImpl物件(對于此處還有疑問的讀者,可以回看博客),該物件的addView方法實際最終呼叫到到是單例物件WindowManagerGlobal的addView方法(前文有提到),其關系如下:

在這里插入圖片描述
所以本章節我們會從此處繼續續寫前緣,接著分析!

//[WindowManagerImpl.java]
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        android.util.SeempLog.record_vg_layout(383,params);
        applyDefaultToken(params);//設定默認Token
        //這里我們需要注意的是此時的mParentWindow指向了前面創建的Activity對應的視窗PhoneWIndow
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);//詳見章節2.1
    }

2.1 WindowManagerGlobal.addView(…)添加視窗處理

在看addView代碼前,我先來看看WindowManagerGlobal物件成員變數(前面類圖關系中也可以看到),如下:

//[WindowManagerGlobal.java]
	private static WindowManagerGlobal sDefaultWindowManager;
    private static IWindowManager sWindowManagerService;
    private static IWindowSession sWindowSession;

    private final Object mLock = new Object();

    private final ArrayList<View> mViews = new ArrayList<View>();
    private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
    private final ArrayList<WindowManager.LayoutParams> mParams =
            new ArrayList<WindowManager.LayoutParams>();
    private final ArraySet<View> mDyingViews = new ArraySet<View>();

這里我們可以看到它有非常重要的三個成員變數mViews、mRoots和mParams分別是型別為View、ViewRootImpl和WindowManager.LayoutParams的陣列(它們之間是什么關系呢,這個后續我們會重點分析),此外還有一個成員變數mDyView,保存的則是已經不需要但還未被系統會收到View,

這里的View與LayoutParams比較好理解(這個是最最基本的Android概念),那么這里的突然冒出的ViewRootImpl物件的作用是什么呢?我們知道WindowManagerGlobal被稱作為視窗管理器,我們也知道管理者嗎一般是管理并不會直接得參與行動,它通常是根據Acitity和Window的呼叫請求,找到合適的做事的人,而真正執行WindowManagerGlobal相關功能的就是ViewRootImpl類了,它可以說是WindowManagerGlobal最得力的干將沒有之一,

這里我們先代為介紹下ViewRootImpl的基本實戰技能有如下幾點(這個在后續的ViewRootImpl中會有所體現):
1.完成了繪制程序,在ViewRootImpl類中,實作了perfromMeasure()、performDraw()、performLayout()等繪制相關的方法,
2.與系統服務進行互動,例如與AMS,WMS,DisplayService、AudioService等進行通信,保證了Acitity相關功能等正常運轉,
3.處理觸屏事件等分發邏輯的實作

在這里插入圖片描述

好了,前面介紹了這么多是時候直接開擼原始碼了,翠花上原始碼addView如下:

//[WindowManagerGlobal.java]
    public void addView(View view, //此處的view指向DecorView
    					ViewGroup.LayoutParams params,//這里的params為Window對應的默認WindowManager.LayoutParams實體物件mWindowAttributes
            			Display display, //這里的Display具體指向表示物理顯示設備有關的邏輯顯示的大小(譬如尺寸解析度)和密度的資訊
            			Window parentWindow) //這里的parentWindow指向了Activity對應的視窗實體物件PhoneWindow
   	{
        //引數有效性的檢查
        if (view == null) {
            throw new IllegalArgumentException("view must not be null");
        }
        if (display == null) {
            throw new IllegalArgumentException("display must not be null");
        }
        if (!(params instanceof WindowManager.LayoutParams)) {
            throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
        }

        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
		/*
			注意此時的parentWindow為不為null,
			指向的是Activity對應的PhoneWindow視窗,這個在視窗的創建程序中已經有說明

			根據視窗型別調整LayoutParams的相關引數
			
		*/
		if (parentWindow != null) {
            parentWindow.adjustLayoutParamsForSubWindow(wparams);
        } else {
			...
        }

        ViewRootImpl root;
        View panelParentView = null;        
        synchronized (mLock) {
            if (mSystemPropertyUpdater == null) {//監聽Property的變化
				...
            }

			/*
				從mViews中查找是否已經有添加過同樣的View
			*/
            int index = findViewLocked(view, false);
            if (index >= 0) {
				...
            }

			...
			/*
				構建ViewRootImpl
				它很重要,它很重要,它很重要,它是WindowManagerGlobal最最得力的干將沒有之一
				基本包攬了WindowManagerGlobal絕大部分作業
			*/
            root = new ViewRootImpl(view.getContext(), display);//詳見章節2.2

            view.setLayoutParams(wparams);

            mViews.add(view);//這三者之間是一一對應的情況
            mRoots.add(root);
            mParams.add(wparams);
        }

        try {
        	/*
        		將DecorView添加到ViewRootImpl中,最后添加到WMS中
        		注意這里傳遞的引數
        	*/
            root.setView(view, wparams, panelParentView);//詳見章節2.3
        } catch (RuntimeException e) {
            synchronized (mLock) {//發生例外處理情況
                final int index = findViewLocked(view, false);
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
            }
            throw e;
        }
    }                 

addView()方法涉及的代碼不是很多,但是它是簡約而不簡單啊,這里我們先來看看它的入參引數,如下:

引數型別引數名稱引數功能和含義
Viewview此處的view指向DecorView的實體物件
ViewGroup.LayoutParamsparams這里的params為Window對應的默認WindowManager.LayoutParams
實體物件mWindowAttributes,并且它的成員變數type默認值為TYPE_APPLICATION(這個很關鍵)
Displaydisplay這里的Display具體指向表示物理顯示設備有關的邏輯顯示的大小(譬如尺寸解析度)和密度的資訊
WindowparentWindow這里的parentWindow指向了Activity對應的視窗實體物件PhoneWindow

引數分析完畢,正式開始原始碼的分析,可以看到這里會對入參的引數進行相關的合法性檢測(這個不是重點),當我們傳入的引數parentWindow不為null時,呼叫它的方法adjustLayoutParamsForSubWindow()對傳入的引數params進行調整,這個方法我們簡單看下:

//[Window.java]
    void adjustLayoutParamsForSubWindow(WindowManager.LayoutParams wp) {
		...
		/*
			判斷視窗型別是不是應用程式子視窗型別
		*/
		if (wp.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
                wp.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
            if (wp.token == null) {
                View decor = peekDecorView();
                if (decor != null) {
					//LayoutParams的token設定為W本地Binder物件
                    wp.token = decor.getWindowToken();
                }
            }
			...
        } else if (wp.type >= WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW &&
                wp.type <= WindowManager.LayoutParams.LAST_SYSTEM_WINDOW) {
			...
        } else {
        	/*
        		如果不是子視窗和系統視窗,同時當前視窗沒有指定容器
        		則設定token為當前視窗的mAppToken代理物件
        		否則設定為指定容器的mAppToken代理物件
        		此時的mAppToken是從AMS傳遞過來的
        	*/
            if (wp.token == null) {
                wp.token = mContainer == null ? mAppToken : mContainer.mAppToken;
            }
			...
    }

還記得我們本博客一開始就重點說明了Android的視窗分為三種型別嗎,上述的原始碼就是對不同型別視窗的布局引數進行相應的設定,比如布局引數中的token設定!這里我們先不分析WindowManager.LayoutParams的原始碼,對于它我們現在暫且只需知道如下三點:

  • 應用程式視窗:對應的type取值范圍為FIRST_APPLICATION_WINDOW到LAST_APPLICATION_WINDOW之間,譬如Activity和Dialog

  • 子視窗:對應的type取值范圍為FIRST_SUB_WINDOW到LAST_SUB_WINDOW之間,譬如PopupWindow

  • 系統視窗:對應的type取值范圍為FIRST_SYSTEM_WINDOW到LAST_SYSTEM_WINDOW之間,譬如Toast

好了這里讓我們看看該方法中是怎么對布局引數進行調整的,它遵循了三個原則:

  • 如果是應用程式子視窗型別,則設定token為W本地Binder物件(這里的W本地物件也在開始有簡單介紹過了,這里先不指明后續會有分析)
  • 對于系統型別視窗token沒有調整
  • 如果不是應用程式子視窗和系統視窗,且同時當前視窗沒有指定視窗容器,則設定token為當前視窗的IApplicationToken.Proxy代理物件,否則設定為指定視窗的IApplicationToken.Proxy代理物件

由于我們這里是以Activity的視窗添加為例的,所以會走前面的第三條原則,設定為當前Activity對應的視窗的IApplicationToken.Proxy代理物件,而它通過前面我們博客普法Android的Token前世今生以及在APP,AMS,WMS之間傳遞知道它的物體端是ActivityRecord持有的Token物件,

分析至此我們知道,當應用程式向視窗管理器中添加一個視窗視圖物件時,首先會為該視圖物件創建一個ViewRootImpl物件,并且將視圖物件、ViewRootImpl物件、視圖布局引數分別保存到視窗管理器WindowManagerGlobal的mViews、mRoots、mParams陣列中(并且他們三者在對應的陣列中的索引是一一對應的),如下圖所示:

在這里插入圖片描述
最后通過ViewRootImpl物件來完成視窗視圖的添加和顯示程序!


2.2 ViewRootImpl得力干將構建程序

在這里插入圖片描述

還記得在前面章節2.1的一開始我們就將ViewRootImpl的功能夸上了天,是時候展示下真正的實力了,我們先來看看它的構造方法如下:

//[ViewRootImpl.java]

	final IWindowSession mWindowSession;
	final ViewRootHandler mHandler = new ViewRootHandler();
	// These can be accessed by any thread, must be protected with a lock.
    // Surface can never be reassigned or cleared (use Surface.clear()).
    final Surface mSurface = new Surface();
    
    public ViewRootImpl(Context context, Display display) {
        mContext = context;

		/*
			得到IWindowSession的代理物件,該物件用于和WMS通信
			看到這里讀者會不會心里有一個疑問就是為什么不是WMS的Binder代理端,而是
			來了一個IWindowSession呢,咋和Activity啟動程序中直接使用AMP和AMS有點不同呢
		*/
        mWindowSession = WindowManagerGlobal.getWindowSession();//詳見章節2.3
        mDisplay = display;
        mBasePackageName = context.getBasePackageName();
        mThread = Thread.currentThread();
        mLocation = new WindowLeaked(null);
        mLocation.fillInStackTrace();
        mWidth = -1;
        mHeight = -1;
        mDirty = new Rect();
        mTempRect = new Rect();
        mVisRect = new Rect();
        mWinFrame = new Rect();
		/*
			創建了一個W本地Binder物件,用于WMS通知應用程式行程,
			有點像ActiviytThread中的IApplicationThread實體物件

			關于Activity和WMS的互動完全可以參考Activity啟動程序中和AMS的互動
			但是ActivityThread中的IApplicationThread是單一實體,而這里的W卻是每一個
			ViewRootImpl都擁有一個			
		*/
        mWindow = new W(this);
		
        mTargetSdkVersion = context.getApplicationInfo().targetSdkVersion;
        mViewVisibility = View.GONE;
        mTransparentRegion = new Region();
        mPreviousTransparentRegion = new Region();
        mFirst = true; // true for the first time the view is added
        mAdded = false;

		/*
			構造一個AttachInfo物件
			注意傳入的一個引數為Handler型別ViewRootHandler實體mHandler,
			用于處理Android應用程式的視窗資訊
		*/
        mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this);
		
        mAccessibilityManager = AccessibilityManager.getInstance(context);
        mAccessibilityInteractionConnectionManager =
            new AccessibilityInteractionConnectionManager();
        mAccessibilityManager.addAccessibilityStateChangeListener(
                mAccessibilityInteractionConnectionManager);
        mHighContrastTextManager = new HighContrastTextManager();
        mAccessibilityManager.addHighTextContrastStateChangeListener(
                mHighContrastTextManager);
        mViewConfiguration = ViewConfiguration.get(context);
        mDensity = context.getResources().getDisplayMetrics().densityDpi;
        mNoncompatDensity = context.getResources().getDisplayMetrics().noncompatDensityDpi;
        mFallbackEventHandler = new PhoneFallbackEventHandler(context);

		/*
			獲取Choreographer實體物件mChoreographer(注意它是單例模式),所以一個Android應用程式只會擁有一個
			Choreographer將會被用于統一調度視窗繪圖
			江湖外號"編舞者"
		*/
        mChoreographer = Choreographer.getInstance();
        mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
        loadSystemProperties();
    }        

可以看到在ViewRootImpl的構造方法以及它的方法區中創建并初始化了一些關鍵的成員變數(大哥也要吃飯不是,必須整幾個小弟干活的),我們來看看ViewRootImpl中關鍵的成員變數以及意義:

  • 通過WindowManagerGlobal.getWindowSession()獲取IWindowSession的代理物件,用于后續和WMS服務通信,這個后續詳細掰持掰持

  • 創建IWindow的Binder物體端類W的實體物件mWindow(注意它是一個匿名Binder類),用于WMS通知APP端進行跨行程通信

  • 采用單例模式創建了一個Choreographer物件,用于統一調度視窗繪圖(這個本篇博客不重點關注,這個涉及到視窗繪制的內容)

  • 創建ViewRootHandler物件,用于處理當前視圖訊息的處理

  • 構造一個AttachInfo物件;

  • 創建Surface物件(注意此時它還只是一個空殼子),用于繪制當前視圖,當然該Surface物件的真正創建是由WMS來完成的,只不過是WMS傳遞給應用程式行程的(這個涉及到Binder跨行程傳遞資料了,Binder很重要啊!)

如果對ViewRootImpl以及它的小弟之間的關系,錯了和它成員類之間的關系還是有點迷糊的話,沒有關系來個終極大殺器來張結構圖,這樣應該會清晰明了些!

在這里插入圖片描述


2.3 getWindowSession()獲取和WMS通信的Binder代理端IWindowSession

在開始分析getWindowSession()的原始碼前,我們先來認識一下IWindowSession,正所謂知己知彼方能百戰百勝嗎!它是一個aidl介面類(肯定和Binder通信有關了),Android原始碼對它的定義如下:

//[IWindowSession.aidl]
/**
	中文翻譯一下就是用于每個應用程式和WMS進行通信的介面
 * System private per-application interface to the window manager.
 *
 * {@hide}
 */
interface IWindowSession {
}

其用類圖關系表示如下:
在這里插入圖片描述
好了,是時候開始我們真正的表演了,不真真的分析了!

//[WindowManagerGlobal.java]
    public static IWindowSession getWindowSession() {
        synchronized (WindowManagerGlobal.class) {
            if (sWindowSession == null) {
                try {
					//獲取輸入法管理器服務Binder代理端
                    InputMethodManager imm = InputMethodManager.getInstance();
					//獲取視窗管理器服務Binder代理端
                    IWindowManager windowManager = getWindowManagerService();
					//得到IWindowSession代理物件
					//這里需要注意的是傳遞的兩個引數都是匿名Binder,所以匿名Binder讀者最好能掌握掌握
                    sWindowSession = windowManager.openSession(
                            new IWindowSessionCallback.Stub() {
                                @Override
                                public void onAnimatorScaleChanged(float scale) {
                                    ValueAnimator.setDurationScale(scale);
                                }
                            },
                            imm.getClient(), imm.getInputContext());
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
            }
            return sWindowSession;
        }
    }

這里需要注意地是注意這里的IWindowManager不要和WindowManager搞混淆了,IWindowManager也是一個aidl介面類,被WMS服務實作了,我們先來看看它的類圖:
在這里插入圖片描述

有了以上支持的加持,分析上述的原始碼就水到渠成了,以上通過WMS服務的代理端跨Binder呼叫到的WMS服務的openSession()方法創建應用程式與WMS之間的連接通道,即獲取IWindowSession代理物件,并將該代理物件保存到WindowManagerGlobal的靜態成員變數sWindowSession中,因此在整個應用程式行程中IWindowSession代理物件是唯一的!

這里我們簡單來看看WMS服務是怎么處理openSession()請求的,如下:

//[windowManagerService.java]
    @Override
    public IWindowSession openSession(IWindowSessionCallback callback, IInputMethodClient client,
            IInputContext inputContext) {
        //例外檢測
        if (client == null) throw new IllegalArgumentException("null client");
        if (inputContext == null) throw new IllegalArgumentException("null inputContext");
        //直接明了,構建一個Session實體
        Session session = new Session(this, callback, client, inputContext);
        return session;
    }

WMS端對openSession()的處理簡單明了,直接通過傳遞過來的引數構建了Session的Binder物體端,然后跨Binder回傳回去,至此構建了如下的一條Binder通信通道:
在這里插入圖片描述


2.4 IWindow的Binder物體端類W的構造程序

在開始分析W的構造之間,我們先來認識一下IWindow,正所謂知己知彼方能百戰百勝嗎!它是一個aidl介面類(肯定和Binder通信有關了),Android原始碼對它的定義如下:

//[IWindow.aidl]
/**
	這里我用我蹩腳的英文簡單翻譯一下就是,WMS服務用于通知客戶端視窗一些事情的通道
 * API back to a client window that the Window Manager uses to inform it of
 * interesting things happening.
 *
 * {@hide}
 */
oneway interface IWindow {
}

好了有了上面Android的官方解釋我們就很清楚了,此處的W本地物件用于WMS通知應用程式行程,有點像ActiviytThread中的IApplicationThread實體物件用于AMS和APP行程通信的方法,都是使用匿名Binder!

關于Activity和WMS的互動完全可以參考Activity啟動程序中和AMS的互動但是ActivityThread中的IApplicationThread是單一實體,而這里的W卻是每一個ViewRootImpl都擁有一個

W的構造方法比較簡單,沒有什么過多看的,我們只要了解清楚了它的類圖關系以及被設計的初衷即可了!

在這里插入圖片描述


2.5 AttachInfo構造程序

AttachInfo的構造程序就比較簡單了,我們簡單過下!

//[View.java]
        AttachInfo(IWindowSession session, IWindow window, Display display,
                ViewRootImpl viewRootImpl, Handler handler, Callbacks effectPlayer) {
            mSession = session;//IWindowSession代理物件,用于和WMS通信
            mWindow = window;//W物件
            mWindowToken = window.asBinder();//W本地Binder物件
            mDisplay = display;
            mViewRootImpl = viewRootImpl;//ViewRootImpl實體
            mHandler = handler;//ViewRootHandler物件
            mRootCallbacks = effectPlayer;//回呼實體物件
        }

2.6 ViewRootImpl.setView()開始視窗視圖的添加

前面將ViewRootImpl的得力小弟一一介紹完畢了,氣勢搞起來了,是時候來看看ViewRootImpl是怎么處理視窗視圖的添加了!

    /**
		 這里我們分析的是Activity的DecorView視窗視圖添加的邏輯,所以此時不存在父視圖的概念,
     	 不會走到這里,此時的panelParentView為null
     */
    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
				/*
					初始化成員變數mView、mWindowAttraibutes
					mAttachInfo是View類的一個內部類AttachInfo類的實體物件
					該類的主要作用就是儲存一組當View attach給它的父Window的時候Activity各種屬性的資訊		
					將DecorView保存到ViewRootImpl的成員變數mView中
				*/
                mView = view;

                mAttachInfo.mDisplayState = mDisplay.getState();
                mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);

                mViewLayoutDirectionInitial = mView.getRawLayoutDirection();
                mFallbackEventHandler.setView(view);
                mWindowAttributes.copyFrom(attrs);//拷貝引數
                if (mWindowAttributes.packageName == null) {
                    mWindowAttributes.packageName = mBasePackageName;
                }
                attrs = mWindowAttributes;
				...

				/*
					我們的DecorView實作了RootViewSurfaceTaker介面,所以會進入此分支
				*/
				if (view instanceof RootViewSurfaceTaker) {
                    mSurfaceHolderCallback =
                            ((RootViewSurfaceTaker)view).willYouTakeTheSurface();
                    if (mSurfaceHolderCallback != null) {
                        mSurfaceHolder = new TakenSurfaceHolder();
                        mSurfaceHolder.setFormat(PixelFormat.UNKNOWN);
                    }
                }
				...

				/*
					繼續相關的初始化
				*/

                mSoftInputMode = attrs.softInputMode;
                mWindowAttributesChanged = true;
                mWindowAttributesChangesFlag = WindowManager.LayoutParams.EVERYTHING_CHANGED;
                mAttachInfo.mRootView = view;
                mAttachInfo.mScalingRequired = mTranslator != null;
                mAttachInfo.mApplicationScale =
                        mTranslator == null ? 1.0f : mTranslator.applicationScale;
                if (panelParentView != null) {
                    mAttachInfo.mPanelParentWindowToken
                            = panelParentView.getApplicationWindowToken();
                }
				//標記DecorView是否已經添加
                mAdded = true;
                int res; /* = WindowManagerImpl.ADD_OKAY; */

                
                /*
                 	這里呼叫異步重繪請求,最侄訓呼叫performTraversals方法來完成View的繪制
                 	在向WMS添加視窗前進行UI布局,這個本篇博客將不會重點分析,我們先重點關注視窗的添加
                */
                requestLayout();
				
                if ((mWindowAttributes.inputFeatures
                        & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                    mInputChannel = new InputChannel();
                }
				...
                try {
		
					...
					/*
						將視窗添加到WMS服務中,mWindow為W本地Binder物件,
						通過Binder傳輸到WMS服務端后,變為IWindow代理物件
					*/
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mInputChannel);
                } catch (RemoteException e) {
					...
                    throw new RuntimeException("Adding window failed", e);
                } finally {
                    if (restore) {
                        attrs.restore();
                    }
                }

                if (mTranslator != null) {
                    mTranslator.translateRectInScreenToAppWindow(mAttachInfo.mContentInsets);
                }
				...
                if (res < WindowManagerGlobal.ADD_OKAY) {
 					...
 					//檢查例外情況,并拋出例外給上層處理
                }

				//建立視窗訊息通道
				if (view instanceof RootViewSurfaceTaker) {
                    mInputQueueCallback =
                        ((RootViewSurfaceTaker)view).willYouTakeTheInputQueue();
                }

				//建立輸入事件通道
                if (mInputChannel != null) {
                    if (mInputQueueCallback != null) {
                        mInputQueue = new InputQueue();
                        mInputQueueCallback.onInputQueueCreated(mInputQueue);
                    }
                    mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
                            Looper.myLooper());
                }

    			...
            }
        }
    }

此處的代碼有點多,我這里采取四舍五入的方法將一些細節處理的代碼暫時省略了,在對該原始碼分析前我們先來回憶回憶腦補一下,我們是如何走到現在的階段的:

  • 首先我們在Activity啟動的最開始階段為它創建了視窗和視窗管理器
  • 然后用戶自定義的UI作為一個子View被添加到DecorView中,然后將頂級視圖DecorView添加到應用程式行程的視窗管理器中,視窗管理器首先為當前添加的View創建一個ViewRootImpl物件、一個布局引數物件ViewGroup.LayoutParams,然后將這三個物件分別保存到當前應用程式行程的視窗管理器WindowManagerImpl中
  • 最后就是呼叫ViewRootImpl的setView進行下一步的處理
    在這里插入圖片描述

好了,來龍我們整清楚了,我們得看看去脈,看看ViewRootImpl是怎么通過setView方法向WMS服務添加一個視窗的:

  • 首先呼叫ViewRootImpl內部方法requestLayout,對視窗視圖中的UI進行視窗布局等操作(三件套操作,測量,布局,繪圖),這個先不放在這個博客中分析,后續專門博客來講

  • 然后呼叫前面的獲取的代理端IWindowSession的addToDisplay向WMS添加一個視窗物件

  • 最后為ViewRootImpl注冊一些列的視窗和輸入事件通道,用于處理一系列事件


2.7 APP應用程式端開始處理視窗的添加流程小結

注意:上述原始碼分析我將關于Android應用程式視窗UI布局繪制的流程沒有分析,因為那個牽涉的東西非常多,不是一下子能分析清楚的,總之UI布局三個老套路測量,布局,繪圖!上述關于視窗UI布局牽涉的知識點也比較多,我們會在后續的專門的博客中進行分析,我們本篇博客的主體是將Android應用程式視窗的添加流程分析清楚!

至此APP應用程式端處理視窗添加的流程就到此結束了,我們先不乘勝追擊先來復盤一下我們前面的整體流程:

  • 首先我們在Activity啟動的最開始階段為它創建了視窗和視窗管理器
  • 然后用戶自定義的UI作為一個子View被添加到DecorView中,然后將頂級視圖DecorView添加到應用程式行程的視窗管理器中,視窗管理器首先為當前添加的View創建一個ViewRootImpl物件、一個布局引數物件ViewGroup.LayoutParams,然后將這三個物件分別保存到當前應用程式行程的視窗管理器WindowManagerImpl中-
  • 最后就是通過ViewRootImpl物件構造程序中和WIMS建立的IWindowSession匿名Binder通道將當前視圖物件注冊到WMS服務中

在前面2.6章節中已經有放出了簡單的流程圖,這里我就先偽代碼來簡單描述下:

WindowManagerImpl.addView(...)--->
WindowManagerGlobal.addView(...)--->
	parentWindow.adjustLayoutParamsForSubWindow(...)--->
	new ViewRootImpl(...)--->
		WindowManagerGlobal.getWindowSession()--->
		new W(this)--->
		Choreographer.getInstance()--->
	mViews.add(view)--->
    mRoots.add(root)--->
    mParams.add(wparams)--->
	ViewRootImpl.setView(...)--->
		mWindowSession.addToDisplay(...)--->


三.WMS端開始處理視窗的添加流程

不容易啊,視窗的添加我們Android應用程式端已經處理完畢,現在要正式開始進入WMS服務端進行處理了,激不激動,期不期待,廢話不多說直接開始干了,


3.1 Session.addToDisplay()處理視窗添加

還記得大明湖畔的夏雨荷嗎!錯了,還記得前面章節的我們的呼叫了mWindowSession.addToDisplay向WMS發起了添加視窗的請求,此時IWindowSession跨行程Binder的能力是時候體現出來了,我們原始碼展開分析一下:

//[Sesstion.java'
    @Override
    public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
            int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets,
            Rect outOutsets, InputChannel outInputChannel) {
        //注意這里的mService是在前面WMS呼叫openSession方法構造Session時傳遞過來的參考
        return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
                outContentInsets, outStableInsets, outOutsets, outInputChannel);//詳見3.2
    }

好嗎,其實Session就是一個中間人而已(掮客,不需要留下買路錢的那種),APP應用程式段通過它的跨行程Binder能力發送來的請求,它想都不想直接傳給WMS服務進行處理了,我是干飯人!


3.2 WMS開始真正開始視窗添加

前面的所有所有,都是為了此時的榮譽時刻!我們來看看視窗的真正管理者WMS是怎么處理視窗添加流程的(前方高能,讀者最好能先活動活動腦路,否則在接下來的原始碼分析中很難理清楚某些知識點,牛逼的人不算啊!),但是在這之前WMS的幾個成員變數我們有必要提前重點點名下:

//[WindowManagerService.java]
    /**
     * 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<>();

在這里插入圖片描述
好了,我們對前面的兩個哈希串列先有個了解,我們通過原始碼來分析深入它二者的設計意圖:

//[WindowManagerService.java]
    public int addWindow(Session session, IWindow client, int seq,
            WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
            Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
            InputChannel outInputChannel) {

		//這里的client為IWindow的代理物件,用于WMS和Activity進行通信
		
        ...
        WindowState attachedWindow = null;
		...

        synchronized(mWindowMap) {


			//判斷我們添加的視窗是否已經存在
            if (mWindowMap.containsKey(client.asBinder())) {
                Slog.w(TAG_WM, "Window " + client + " is already added");
                return WindowManagerGlobal.ADD_DUPLICATE_ADD;
            }

			//判斷添加的視窗型別是否為子視窗
            if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) {
				
				/*   
					根據attrs.token從mWindowMap中取出應用程式視窗在WMS服務中的描述符WindowState
					這里的前提是父視窗必須在WMS中已經被添加了
				*/
				
				attachedWindow = windowForClientLocked(null, attrs.token, false);
                if (attachedWindow == null) {
                    return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
                }
                if (attachedWindow.mAttrs.type >= FIRST_SUB_WINDOW
                        && attachedWindow.mAttrs.type <= LAST_SUB_WINDOW) {
                    return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
                }
            }

            if (type == TYPE_PRIVATE_PRESENTATION && !displayContent.isPrivate()) {
                Slog.w(TAG_WM, "Attempted to add private presentation window to a non-private display.  Aborting.");
                return WindowManagerGlobal.ADD_PERMISSION_DENIED;
            }

            boolean addToken = false;
			//根據attrs.token從mTokenMap中取出應用程式視窗在WMS服務中的描述符WindowToken
            WindowToken token = mTokenMap.get(attrs.token);
            AppWindowToken atoken = null;
            boolean addToastWindowRequiresToken = false;

            if (token == null) {//第一次add的情況下token怎么會有值呢,為什么呢
                if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) {
                    Slog.w(TAG_WM, "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_WM, "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_WM, "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_WM, "Attempted to add wallpaper window with unknown token "
                          + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                if (type == TYPE_DREAM) {
                    Slog.w(TAG_WM, "Attempted to add Dream window with unknown token "
                          + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                if (type == TYPE_QS_DIALOG) {
                    Slog.w(TAG_WM, "Attempted to add QS dialog window with unknown token "
                          + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                if (type == TYPE_ACCESSIBILITY_OVERLAY) {
                    Slog.w(TAG_WM, "Attempted to add Accessibility overlay window with unknown token "
                            + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                if (type == TYPE_TOAST) {
                    if (doesAddToastWindowRequireToken(attrs.packageName, callingUid,
                            attachedWindow)) {
                        Slog.w(TAG_WM, "Attempted to add a toast window with unknown token "
                                + attrs.token + ".  Aborting.");
                        return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                    }
                }
                //此處什么邏輯會走到這個地方呢
                token = new WindowToken(this, attrs.token, -1, false);
                addToken = true;
            } 
			//處理應用程式視窗邏輯
			else if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) {
                atoken = token.appWindowToken;
                if (atoken == null) {
                    Slog.w(TAG_WM, "Attempted to add window with non-application token "
                          + token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_NOT_APP_TOKEN;
                } else if (atoken.removed) {
                    Slog.w(TAG_WM, "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 (DEBUG_STARTING_WINDOW || localLOGV) Slog.v(
                            TAG_WM, "**** 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_WM, "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_WM, "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_WM, "Attempted to add wallpaper window with bad token "
                            + attrs.token + ".  Aborting.");
                      return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
            }//Dream視窗 
			else if (type == TYPE_DREAM) {
                if (token.windowType != TYPE_DREAM) {
                    Slog.w(TAG_WM, "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_WM, "Attempted to add Accessibility overlay window with bad token "
                            + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
            } else if (type == TYPE_TOAST) {
                addToastWindowRequiresToken = doesAddToastWindowRequireToken(attrs.packageName,
                        callingUid, attachedWindow);
                if (addToastWindowRequiresToken && token.windowType != TYPE_TOAST) {
                    Slog.w(TAG_WM, "Attempted to add a toast window with bad token "
                            + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
            } else if (type == TYPE_QS_DIALOG) {
                if (token.windowType != TYPE_QS_DIALOG) {
                    Slog.w(TAG_WM, "Attempted to add QS dialog window with bad token "
                            + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
            } else if (token.appWindowToken != null) {//系統視窗
                Slog.w(TAG_WM, "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;
            }

			//為Activity視窗(注意這里只是因為分析的是Activity視窗的添加而已)創建WindowState物件
            WindowState win = new WindowState(this, session, client, token,
                    attachedWindow, appOp[0], seq, attrs, viewVisibility, displayContent);
 
            res = WindowManagerGlobal.ADD_OKAY;

            if (excludeWindowTypeFromTapOutTask(type)) {
                displayContent.mTapExcludedWindows.add(win);
            }

            origId = Binder.clearCallingIdentity();

			//以鍵值對<IWindow.Proxy/Token,WindowToken>形式保存到mTokenMap表中
            if (addToken) {
                mTokenMap.put(attrs.token, token);
            }
            win.attach();//詳見章節3.2
			//以鍵值對<IWindow的代理物件,WindowState>形式保存到mWindowMap表中
            mWindowMap.put(client.asBinder(), win);
			
			...

            boolean imMayMove = true;

            if (type == TYPE_INPUT_METHOD) {
				...
            } else if (type == TYPE_INPUT_METHOD_DIALOG) {
				...
                addWindowToListInOrderLocked(win, true);
            } else {
                addWindowToListInOrderLocked(win, true);//將WindowState添加到關聯的AppWindowToken中,詳見章節3.3
				...
            }
		...
        return res;
    }

在我們正式開始上述的原始碼決議前,讀者一定要讀Token要有所了解,如果還不是很清楚強烈建議參見博客普法Android的Token前世今生以及在APP,AMS,WMS之間傳遞,

老規矩,我們先來看看該方法入參的幾個重要引數的含義,如下:

引數型別引數名稱引數功能和含義
Sessionsession為前面創建的Sesseion物件實體(Binder代理端經過跨行程傳輸后轉變成Binder物體端)
WindowManager.LayoutParams attrsattrs這里的attrs為Window對應的默認WindowManager.LayoutParams
實體物件mWindowAttributes經過一系列賦值之后的物件,
其中它的最最重要的兩個引數為token和type
IWindowclient這里的client為IWindow的代理物件,用于WMS和Activity進行通信

前面的引數我們簡單的總結了一下,這個時候得開始真正的表演了(下面的陳述可能有點拗口,不過沒有關系多理解多分析就好了,我們一定要引數理解清楚)!

  • 從我們前面的分析可知知道當應用程式行程添加一個DecorView到視窗管理器時,會為當前添加的視窗創建ViewRootImpl物件,同時構造了一個W本地Binder物件,無論是視窗視圖物件DecorView還是ViewRootImpl物件,都只是存在于應用程式行程中,在添加視窗程序中僅僅將該視窗的W物件傳遞給WMS服務,經過Binder傳輸后,到達WMS服務端行程后變為IWindow.Proxy代理物件,因此該函式的引數client的型別為IWindow.Proxy,這個就是我們client引數的終極由來

  • 引數attrs的型別為WindowManager.LayoutParams,這里的attrs為Window對應的默認WindowManager.LayoutParams實體物件mWindowAttributes經過一系列賦值之后的物件,其中它的最最重要的兩個引數為token和type,由于WindowManager.LayoutParams實作了Parcelable介面,因此WindowManager.LayoutParams物件可以跨行程傳輸,WMS服務的addWindow函式中的attrs引數就是應用程式行程發送過來的視窗布局引數,

  • 在WindowManagerGlobal的addView中我們呼叫了Window方法的adjustLayoutParamsForSubWindow()方法為視窗布局引數設定了相應的token,如果是應用程式視窗,則布局引數的token設為Token傳遞到APP端的代理物件,如果是應用程式子視窗則設定為token設為W本地Binder物件,由于應用程式和WMS分屬于兩個不同的行程空間,因此經過Binder傳輸后,布局引數的令牌attrs.token就轉變為IWindow.Proxy或者Token,

注意這里我們是以Android應用程式視窗為說明的

關于引數的入參的分析我真的盡力了,如果讀者還是沒有弄清楚的話,我只能說臣妾做不到啊!了解了引數的含義,該方法也就比較好理解了,其處理的流程如下:

  • 首先判斷視窗是否添加過以及是否合法,如果已經添加則直接回傳

  • 然后根據attrs.token從mWindowMap中取出應用程式視窗在WMS服務中的描述符WindowToken

  • 我們將上述得到的引數聚合在一起然后在構造一個WindowState物件,并將添加的視窗資訊記錄到mTokenMap和mWindowMap哈希表中
    在這里插入圖片描述

  • 接著呼叫WindowState的attach()來進一步完成視窗添加

  • 最后將上述的WindowState添加到關聯的WindowToken中的串列中,這個地方需要重點分析下


3.3 WindowState繼續處理視窗的添加

在正式開始WindowState繼續處理視窗的添加前,我們先來看看WindowState的構造方法如下:

//[WindowState.java]
    WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
           WindowState attachedWindow, int appOp, int seq, WindowManager.LayoutParams a,
           int viewVisibility, final DisplayContent displayContent) {
        mService = service;//WMS的參考
        mSession = s;//和APP端對應的Session端
        mClient = c;//APP端W對應的Binder代理端
        mAppOp = appOp;
        mToken = token;//此處的Token為AppWindowToken,Token從AMS傳遞到WMS程序中創建的

		...
		WindowState appWin = this;
        while (appWin.isChildWindow()) {
            appWin = appWin.mAttachedWindow;
        }
        WindowToken appToken = appWin.mToken;
        mRootToken = appToken;
        mAppToken = appToken.appWindowToken;
        ...
   }

它的構造方法主要是將傳入的初始化成員變數!我們接著繼續分析它的attach方法邏輯

//[WindowState.java]
    void attach() {
        if (WindowManagerService.localLOGV) Slog.v(
            TAG, "Attaching " + this + " token=" + mToken
            + ", list=" + mToken.windows);
        mSession.windowAddedLocked();
    }

無話,接著往下看

//[WindowState.java]
    void windowAddedLocked() {
        if (mSurfaceSession == null) {
            if (WindowManagerService.localLOGV) Slog.v(
                TAG_WM, "First window added to " + this + ", creating SurfaceSession");
            mSurfaceSession = new SurfaceSession();
            if (SHOW_TRANSACTIONS) Slog.i(
                    TAG_WM, "  NEW SURFACE SESSION " + mSurfaceSession);
            mService.mSessions.add(this);
            if (mLastReportedAnimatorScale != mService.getCurrentAnimatorScale()) {
                mService.dispatchNewAnimatorScaleLocked(this);
            }
        }
        mNumWindow++;//記錄對應的某個應用程式添加的視窗數量
    }

3.4 WMS呼叫addWindowToListInOrderLocked方法處理WindowState

//[WindowManagerService.java]
    private void addWindowToListInOrderLocked(final WindowState win, boolean addToToken) {
        if (DEBUG_FOCUS) Slog.d(TAG_WM, "addWindowToListInOrderLocked: win=" + win +
                " Callers=" + Debug.getCallers(4));
        if (win.mAttachedWindow == null) {
            final WindowToken token = win.mToken;
            int tokenWindowsPos = 0;
            if (token.appWindowToken != null) {
                tokenWindowsPos = addAppWindowToListLocked(win);
            } else {
                addFreeWindowToListLocked(win);
            }
            if (addToToken) {
                if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "Adding " + win + " to " + token);
                token.windows.add(tokenWindowsPos, win);
            }
        } else {
            addAttachedWindowToListLocked(win, addToToken);
        }

        final AppWindowToken appToken = win.mAppToken;
        if (appToken != null) {
            if (addToToken) {
                appToken.addWindow(win);//AppWindowToken進行管理
            }
        }
    }

在創建完視窗對應的WindowState之后,會接著addWindowToListInOrderLocked將WindowState放入AppWindowToken進行管理,(這個管理方式有點類似AMS中對Activity的堆疊管理),經過上述處理以后AppWindowToken和WindowState有如下的對應關系,

在這里插入圖片描述
對于上述關系我們也可以通過命令dumpsys來證明:

XXX:/ # dumpsys window displays
WINDOW MANAGER DISPLAY CONTENTS (dumpsys window displays)
  Display: mDisplayId=0
    init=720x1280 320dpi cur=720x1280 app=720x1184 rng=720x672-1184x1136
    deferred=false layoutNeeded=false

  Application tokens in top down Z order:
    mStackId=1
    mDeferDetach=false
    mFullscreen=true
    mBounds=[0,0][720,1280]
      taskId=120
        mFullscreen=true
        mBounds=[0,0][720,1280]
        mdr=false
        appTokens=[AppWindowToken{7e5b066 token=Token{977da8 ActivityRecord{6fd71cb u0 com.example.window/.MainActivity t120}}}]
        mTempInsetBounds=[0,0][0,0]
          Activity #0 AppWindowToken{7e5b066 token=Token{977da8 ActivityRecord{6fd71cb u0 com.example.window/.MainActivity t120}}}
          windows=[Window{9eb8f33 u0 com.example.window/com.example.window.MainActivity}, Window{cbaaa6d u0 hello}]
          windowType=2 hidden=false hasVisible=true
          app=true voiceInteraction=false
          allAppWindows=[Window{cbaaa6d u0 hello}, Window{9eb8f33 u0 com.example.window/com.example.window.MainActivity}]
          task={taskId=120 appTokens=[AppWindowToken{7e5b066 token=Token{977da8 ActivityRecord{6fd71cb u0 com.example.window/.MainActivity t120}}}] mdr=false}
           appFullscreen=true requestedOrientation=-1
          hiddenRequested=false clientHidden=false reportedDrawn=true reportedVisible=true
          numInterestingWindows=2 numDrawnWindows=2 inPendingTransaction=false allDrawn=true (animator=true)
          startingData=null removed=false firstWindowDrawn=true mIsExiting=false



四.小結

??至此Android應用程式視窗設計之視窗的添加就到此告一段落了,這里的告一段落也是對于博主來說而已,對于讀者來說還有很長的路要走,因為視窗添加的概念中涉及了太多的知識點和概念了,那么怎么把這些抽象的概念具體化呢,其實Android為我們提供了不錯的命令工具,dumpsys window,這個命令能夠很好的列印出我們視窗前面所說的一些概念的資訊,這個命令值得大家去使用,我們簡單看下:

WINDOW MANAGER LAST ANR (dumpsys window lastanr)
  <no ANR has occurred since boot>
  
WINDOW MANAGER POLICY STATE (dumpsys window policy)
    mSafeMode=false mSystemReady=true mSystemBooted=true

WINDOW MANAGER ANIMATOR STATE (dumpsys window animator)
    DisplayContentsAnimator #0:
      Window #0: WindowStateAnimator{2fa13aa com.android.systemui.ImageWallpaper}

WINDOW MANAGER SESSIONS (dumpsys window sessions)
  Session Session{7bf2bd 5690:u0a10023}:

WINDOW MANAGER DISPLAY CONTENTS (dumpsys window displays)
  Display: mDisplayId=0
    init=720x1280 320dpi cur=720x1280 app=720x1184 rng=720x672-1184x1136
    deferred=false layoutNeeded=false


WINDOW MANAGER TOKENS (dumpsys window tokens)
  All tokens:
  AppWindowToken{4513043 token=Token{65e80fd ActivityRecord{cfdc954 u0 com.android.launcher3/.Launcher t118}}}

WINDOW MANAGER WINDOWS (dumpsys window windows)
  Window #9 Window{4b8f2da u0 NavigationBar}:
    mDisplayId=0 stackId=0 mSession=Session{abf850b 4764:u0a10014} mClient=android.os.BinderProxy@be79485

好了這里限于篇幅就不一一展開了,總之經過前面的一系列分析我們應該知道了:

  • 應用程式端的每個視窗對應的ViewRootImpl對持有一個W物件,而WMS端持有它的代理端用于和APP的資訊回呼
  • 然后每個應用程式行程都持有一個與WMS服務會話通道IWindowSession,系統中創建的所有IWindowSession都被記錄到WMS服務的mSessions成員變數中,這樣WMS就可以知道自己正在處理那些應用程式的請求,到此我們來梳理一下在WMS服務端都創建了那些物件:
  • 然后為每個視窗在我們是WMS端創建一個WindowState物件,該物件是應用程式視窗在WMS服務端的描述符,有點類似于Activity在AMS端的描述符ActivityRecord

在這里插入圖片描述




四.寫在最后

??到這里本篇博客Android應用程式視窗設計之視窗的添加真的就宣告結束了,相信分析至此讀者應該對Android應用程式視窗的添加有了一定的理解了,但是想要說深入那還很遠,很遠,但是沒有關系萬事開頭難嗎,只有我們有信心放肆的干,我想關于Android應用程式視窗設計的整體流程一定會被我們攻破的,在接下來的博客中我們會接著分析Android系統視窗和子視窗的添加流程,歡迎讀者繼續關注!好了,青山不改綠水長流先到這里了,如果本博客對你有所幫助,麻煩關注或者點個贊,如果覺得很爛也可以踩一腳!謝謝各位了!!

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/233900.html

標籤:其他

上一篇:想進BAT一線互聯網大廠,該怎么準備技術面試?一位6年老Android的面經總結(附300+面試題)

下一篇:Android C++ STL最佳實踐

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more