理解Window和WindowManager
生活随笔
收集整理的這篇文章主要介紹了
理解Window和WindowManager
小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
一、Window簡介
(1)Window表示一個窗口的概念,一般用不到,當(dāng)在某些特殊的時候我們需要在桌面上顯示一個類似懸浮窗的東西就需要Window來實現(xiàn)。 (2)Window是一個抽象類,它的具體實現(xiàn)是PhoneWindow。 (3)創(chuàng)建一個Window只需要通過WindowManager即可完成。 (4)WindowManager是外界訪問Window的入口,Window的具體實現(xiàn)是WindowManagerService,WindowManager和WindowManagerService的交互是一個IPC過程。 (5)Android中所有的視圖都是通過Window呈現(xiàn)的,不管是Activity、Dialog還是Toast,他們的視圖實際上都是附加在Window上的,因此Window實際是View的直接管理者。
二、Window和WindowManager
1、使用WindowManager添加一個Window
為了分析Window的工作機制,我們需要先了解如何使用WindowManager添加一個Window: /** 下面的代碼演示了通過WindowManager添加Window的過程:* (1)創(chuàng)建Button按鈕。* (2)為Button設(shè)置文字內(nèi)容。* (3)創(chuàng)建一個WindowManager.LayoutParams的參數(shù),設(shè)置Button的寬高之類的。* (4)為WindowManager.LayoutParams設(shè)置Flags參數(shù)。* (5)為WindowManager.LayoutParams設(shè)置type參數(shù)。* (6、7、8)為WindowManager.LayoutParams設(shè)置中心點、坐標(biāo)位置。* (9)給Button設(shè)置觸摸事件。* (10)將Button按照WindowManager.LayoutParams參數(shù)用WindowManager添加View即可。* */mFloatingButton = new Button(this);mFloatingButton.setText("click me");mLayoutParams = new WindowManager.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, 0, 0,PixelFormat.TRANSPARENT);mLayoutParams.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL| LayoutParams.FLAG_NOT_FOCUSABLE| LayoutParams.FLAG_SHOW_WHEN_LOCKED;mLayoutParams.type = LayoutParams.TYPE_SYSTEM_ERROR;mLayoutParams.gravity = Gravity.LEFT | Gravity.TOP;mLayoutParams.x = 100;mLayoutParams.y = 300;mFloatingButton.setOnTouchListener(this);mWindowManager.addView(mFloatingButton, mLayoutParams); (1)Flags參數(shù)表示W(wǎng)indow的屬性,它有很多選項,通過這些選項可以控制Window的顯示特性,下面介紹幾個主要的:
FLAG_NOT_FOCUSABLE:表示W(wǎng)indow不需要獲取焦點,也不需要接收各種輸入事件,此標(biāo)記會同時啟用FLAG_NOT_TOUCH_MODAL,最終事件會直接傳遞給下層的具有焦點的Window。
FLAG_NOT_TOUCH_MODAL:在此模式下,系統(tǒng)會將當(dāng)前Window區(qū)域以外的單擊事件傳遞給底層的Window,當(dāng)前Window區(qū)域以內(nèi)的單擊事件則自己處理。?一般來說都需要開啟此標(biāo)記,否則其他Window將無法收到單擊事件。
?FLAG_SHOW_WHEN_LOCKED:此模式可以讓W(xué)indow顯示在鎖屏的界面上。 (2)Type參數(shù)表示W(wǎng)indow的類型,一共有三種類型:
應(yīng)用Window(層級范圍1~99):對應(yīng)著一個Activity。
子Window(層級范圍1000~1999):不能單獨存在,它需要附屬在特定的父Window之中,比如常見的Dialog就是一個子Window。
系統(tǒng)Window(層級范圍2000~2999):是需要聲明權(quán)限才能創(chuàng)建的Window,比如Toast和系統(tǒng)狀態(tài)欄都屬于系統(tǒng)Window。
?層級范圍對應(yīng)這WindowManager.LayoutParams的type參數(shù),層級大的會覆蓋在層級小的Window上面。所以系統(tǒng)層級是最大的,系統(tǒng)層級一般選用TYPE_SYSTEM_ERROR或者TYPE_SYSTEM_OVERLAY。?還有哦,系統(tǒng)層級需要聲明權(quán)限:不然會報錯:
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />(3)將Button按照WindowManager.LayoutParams參數(shù)用WindowManager添加View即可。?所以這個Button就作為一個新的Window了。
?!!!!!Window并不是實際存在的,它以View的形式存在。!!!!!!
WindowManager所提供的功能很簡單,常用的只有三個方法:即添加View、更新View和刪除View。這三個方法定義在ViewManager中,ViewManager是一個接口,而WindowManager正是繼承了ViewManager。
package android.view;/** Interface to let you add and remove child views to an Activity. To get an instance* of this class, call {@link android.content.Context#getSystemService(java.lang.String) Context.getSystemService()}.*/ public interface ViewManager {/*** Assign the passed LayoutParams to the passed View and add the view to the window.* <p>Throws {@link android.view.WindowManager.BadTokenException} for certain programming* errors, such as adding a second view to a window without removing the first view.* <p>Throws {@link android.view.WindowManager.InvalidDisplayException} if the window is on a* secondary {@link Display} and the specified display can't be found* (see {@link android.app.Presentation}).* @param view The view to be added to this window.* @param params The LayoutParams to assign to view.*/public void addView(View view, ViewGroup.LayoutParams params);public void updateViewLayout(View view, ViewGroup.LayoutParams params);public void removeView(View view); } ?* (1)ViewManager這個接口是由WindowManager來繼承的,WindowManager可以用來創(chuàng)建Window。
?* ViewManager這里面提供了三個方法,分別是添加、更新和刪除View。
?* (2)WindowManagerImpl繼承自WindowManager,
?* 而WindowManager又是繼承自ViewManager,
?* addView、updateViewLayout、removeView都是來自ViewManager的。
?* 所以在WindowManagerImpl具體實現(xiàn)了這三個方法。
繼承關(guān)系: ViewManager ?-->> WindowManager(繼承自ViewManager) -->> WindowManagerImpl(繼承自WindowManager) -->> WindowManagerGlobal(WindowManagerImpl內(nèi)部的一個對象)? 在ViewManager中有三個方法:addView、updateViewLayout、removeView。它們最終是在WindowManagerGlobal中具體實現(xiàn)的。
2、下面這個例子中的onTouch實現(xiàn)了拖動的Window效果
package com.ryg.chapter_8;import com.ryg.chapter_8.R;import android.annotation.SuppressLint; import android.app.Activity; import android.content.Context; import android.graphics.PixelFormat; import android.os.Bundle; import android.view.Gravity; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.WindowManager; import android.view.WindowManager.LayoutParams; import android.widget.Button;public class TestActivity extends Activity implements OnTouchListener {private static final String TAG = "TestActivity";private Button mCreateWindowButton;private Button mFloatingButton;private WindowManager.LayoutParams mLayoutParams;private WindowManager mWindowManager;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_test);initView();}private void initView() {mCreateWindowButton = (Button) findViewById(R.id.button1);mWindowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);}public void onButtonClick(View v) {if (v == mCreateWindowButton) {/** 下面的代碼演示了通過WindowManager添加Window的過程:* */mFloatingButton = new Button(this);mFloatingButton.setText("click me");mLayoutParams = new WindowManager.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, 0, 0,PixelFormat.TRANSPARENT);mLayoutParams.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL| LayoutParams.FLAG_NOT_FOCUSABLE| LayoutParams.FLAG_SHOW_WHEN_LOCKED;mLayoutParams.type = LayoutParams.TYPE_SYSTEM_ERROR;mLayoutParams.gravity = Gravity.LEFT | Gravity.TOP;mLayoutParams.x = 100;mLayoutParams.y = 300;mFloatingButton.setOnTouchListener(this);mWindowManager.addView(mFloatingButton, mLayoutParams);}}@SuppressLint("ClickableViewAccessibility") @Overridepublic boolean onTouch(View v, MotionEvent event) {int rawX = (int) event.getRawX();int rawY = (int) event.getRawY();switch (event.getAction()) {case MotionEvent.ACTION_DOWN: {break;}case MotionEvent.ACTION_MOVE: {/** 如果想要實現(xiàn)可以拖動的Window效果,* 只需要根據(jù)手指的位置來設(shè)定LayoutParams中的x和y的值即可改變Window的位置。* */int x = (int) event.getX();int y = (int) event.getY();mLayoutParams.x = rawX;mLayoutParams.y = rawY;mWindowManager.updateViewLayout(mFloatingButton, mLayoutParams);break;}case MotionEvent.ACTION_UP: {break;}default:break;}return false;}@Overrideprotected void onDestroy() {try {mWindowManager.removeView(mFloatingButton);} catch (IllegalArgumentException e) {e.printStackTrace();}super.onDestroy();} }三、Window的內(nèi)部機制
1、綜述
Window是一個抽象概念,每一個Window都對應(yīng)著一個View和一個ViewRootImpl,Window和View通過ViewRootImpl來建立聯(lián)系,因此Window并不是實際存在的,它是以View的形式存在。這點從WindowManager的定義也可以看出,它提供的三個接口方法addView、updateViewLayout、removeView都是針對View的,這說明View才是WindowManager存在的實體。在實際使用中無法直接訪問Window,對Window的訪問必須通過WindowManager。
ViewManager ?-->> WindowManager(繼承自ViewManager) -->> WindowManagerImpl(繼承自WindowManager) -->>WindowManagerGlobal(WindowManagerImpl內(nèi)部的一個對象)?-->> ViewRooImpl
2、Window的添加過程
(1)可以發(fā)現(xiàn)WindowManagerImpl并沒有實現(xiàn)WindowManager的三大操作,而是全部交給WindowManagerGlobal來處理,WindowManagerGlobal以工廠的形式向外提供自己的實例。就是說WindowManagerImpl將所有的操作全部委托給WindowManagerGlobal來實現(xiàn)。這是一種橋接模式。
(2)WindowManagerImpl.java文件:
package android.view;/*** Provides low-level communication with the system window manager for* operations that are bound to a particular context, display or parent window.* Instances of this object are sensitive to the compatibility info associated* with the running application.** This object implements the {@link ViewManager} interface,* allowing you to add any View subclass as a top-level window on the screen.* Additional window manager specific layout parameters are defined for* control over how windows are displayed. It also implements the {@link WindowManager}* interface, allowing you to control the displays attached to the device.* * <p>Applications will not normally use WindowManager directly, instead relying* on the higher-level facilities in {@link android.app.Activity} and* {@link android.app.Dialog}.* * <p>Even for low-level window manager access, it is almost never correct to use* this class. For example, {@link android.app.Activity#getWindowManager}* provides a window manager for adding windows that are associated with that* activity -- the window manager will not normally allow you to add arbitrary* windows that are not associated with an activity.** @see WindowManager* @see WindowManagerGlobal* @hide*/ public final class WindowManagerImpl implements WindowManager {private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();private final Display mDisplay;private final Window mParentWindow;public WindowManagerImpl(Display display) {this(display, null);}private WindowManagerImpl(Display display, Window parentWindow) {mDisplay = display;mParentWindow = parentWindow;}public WindowManagerImpl createLocalWindowManager(Window parentWindow) {return new WindowManagerImpl(mDisplay, parentWindow);}public WindowManagerImpl createPresentationWindowManager(Display display) {return new WindowManagerImpl(display, mParentWindow);}/** Window的添加,即View的添加:* */@Overridepublic void addView(View view, ViewGroup.LayoutParams params) {/** 這里變成了四個參數(shù):* */ mGlobal.addView(view, params, mDisplay, mParentWindow);}@Overridepublic void updateViewLayout(View view, ViewGroup.LayoutParams params) {mGlobal.updateViewLayout(view, params);}/** Window的刪除,即View的刪除:* */@Overridepublic void removeView(View view) {mGlobal.removeView(view, false);}@Overridepublic void removeViewImmediate(View view) {mGlobal.removeView(view, true);}@Overridepublic Display getDefaultDisplay() {return mDisplay;} } (3)WindowManagerGlobal.java文件中的addView方法首先檢查參數(shù)是否合法,如果是子Window那么還需要調(diào)整一些布局參數(shù): 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");}/** 如果是子Window,那么還需要調(diào)整一些布局參數(shù):* */final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;if (parentWindow != null) {parentWindow.adjustLayoutParamsForSubWindow(wparams);} (4)在WindowManagerGlobal內(nèi)部有如下幾個列表比較重要: // 存儲Window對應(yīng)的所有View:private final ArrayList<View> mViews = new ArrayList<View>();// 存儲Window對應(yīng)的所有ViewRootImpl:private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();// 存儲Window對應(yīng)的所有的布局參數(shù):private final ArrayList<WindowManager.LayoutParams> mParams =new ArrayList<WindowManager.LayoutParams>();// 存儲正在被刪除的View對象,或者說是那些已經(jīng)調(diào)用removeView方法但是刪除操作還未完成的Window對象:private final ArraySet<View> mDyingViews = new ArraySet<View>(); 在WindowManagerGlobal.java文件中的addView方法中通過如下方式將Window的一系列對象添加到列表中: root = new ViewRootImpl(view.getContext(), display);view.setLayoutParams(wparams);mViews.add(view);mRoots.add(root);mParams.add(wparams); (5)WindowManagerGlobal.java文件中的addView方法中通過ViewRootImpl來更新界面并完成Window的添加過程:這一步驟由ViewRootImpl的setView方法來完成。
在ViewRootImpl的setView方法中又會調(diào)用requestLayout方法來完成異步刷新請求。 /** setView方法的內(nèi)部會通過requestLayout方法來完成異步刷新請求。* */@Overridepublic void requestLayout() {if (!mHandlingLayoutInLayoutRequest) {checkThread();mLayoutRequested = true;/** 這里實際才是View繪制的入口:* */scheduleTraversals();}}在ViewRootImpl的setView方法中調(diào)用requestLayout方法后會接著通過WindowSession最終來完成Window的添加過程。
下面的mWindowSession的類型是IWindowSession,它是一個Binder對象,真正的實現(xiàn)類是Session,也就是Window的添加過程試一次IPC調(diào)用。在Session的內(nèi)部會通過WindowManagerService來實現(xiàn)Window的添加:?mWindowSession.addToDisplay。如此一來,Window的添加請求就交給WindowManagerService去處理了,?在WindowManagerService內(nèi)部會為每一個應(yīng)用保留一個單獨的Session。具體的WindowManagerService的內(nèi)部實現(xiàn),我們就不講了,深入進(jìn)去沒有太大的意義,?可以自行查看。 try {mOrigWindowType = mWindowAttributes.type;mAttachInfo.mRecomputeGlobalAttributes = true;collectViewAttributes();res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,getHostVisibility(), mDisplay.getDisplayId(),mAttachInfo.mContentInsets, mInputChannel);} catch (RemoteException e) {mAdded = false;mView = null;mAttachInfo.mRootView = null;mInputChannel = null;mFallbackEventHandler.setView(null);unscheduleTraversals();setAccessibilityFocus(null, null);throw new RuntimeException("Adding window failed", e);}在Session內(nèi)部會通過WindowManagerService來實現(xiàn)Window的添加: 略。 如此一來,Window的添加請求就交給WindowManagerService去處理了,在WindowManagerService內(nèi)部會為每一個應(yīng)用保留一個單獨的Session。具體Window在WindowManagerService內(nèi)部是如何添加的我們并不研究。
(6)下面給出WindowManagerGlobal.java文件中的addView方法的完整代碼: public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow) {/** 第一步:下面的四個if語句都是在檢查參數(shù)是否合法:* */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");}/** 如果是子Window,那么還需要調(diào)整一些布局參數(shù):* */final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;if (parentWindow != null) {parentWindow.adjustLayoutParamsForSubWindow(wparams);}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);}}}/** WindowManagerGlobal內(nèi)部有一些比較重要的列表,這些列表上面有聲明過,* 下面的幾條語句將Window的一系列對象添加到這些列表中,* */root = new ViewRootImpl(view.getContext(), display);view.setLayoutParams(wparams);mViews.add(view);mRoots.add(root);mParams.add(wparams);}// do this last because it fires off messages to start doing thingstry {/** 通過ViewRootImpl來更新界面并完成Window的添加過程。* 這個root就是ViewRootImpl類對象。* View的繪制過程就是由ViewRootImpl來完成的。* setView內(nèi)部會通過requestLayout來完成異步刷新請求。* */root.setView(view, wparams, panelParentView);} catch (RuntimeException e) {// BadTokenException or InvalidDisplayException, clean up.synchronized (mLock) {final int index = findViewLocked(view, false);if (index >= 0) {removeViewLocked(index, true);}}throw e;}}
(7)整體的調(diào)用關(guān)系是:
ViewManager ?-->> WindowManager(繼承自ViewManager) -->> WindowManagerImpl(繼承自WindowManager) -->>WindowManagerGlobal(WindowManagerImpl內(nèi)部的一個對象)?-->> ViewRooImpl.setView(ViewRooImpl是WindowManagerGlobal的addView方法中的一個對象) -->> requestLayout(setView中的一個方法調(diào)用) -->> WindowSession(在setView方法中,它是一個Binder對象,用于與WindowManagerService進(jìn)行IPC通信) -->> Session(WindowSession的具體實現(xiàn)) -->> WindowManagerService(實現(xiàn)Window的添加)
3、Window的刪除過程
(1)Window的刪除過程和添加過程一樣,都是先通過WindowManagerImpl后,再進(jìn)一步通過WindowManagerGlobal來實現(xiàn)的。 (2)WindowManagerGlobal.java中的removeView方法: /**** WindowManager的removeView的最終源頭:* @param view* @param immediate*/public void removeView(View view, boolean immediate) {if (view == null) {throw new IllegalArgumentException("view must not be null");}synchronized (mLock) {// 首先查找待刪除的View的索引,查找過程就是遍歷建立的數(shù)組:int index = findViewLocked(view, true);View curView = mRoots.get(index).getView();// 然后調(diào)用這個方法進(jìn)一步刪除:removeViewLocked(index, immediate);if (curView == view) {return;}throw new IllegalStateException("Calling with view " + view+ " but the ViewAncestor is attached to " + curView);}} (3)removeViewLocked是通過ViewRootImpl來完成刪除操作的。在WindowManager中提供了兩種刪除接口removeView和removeViewImmediate,它們分別表示異步刪除和同步刪除,其中removeViewImmediate使用起來需要特別注意,一般來說不需要使用此方法來刪除Window以免發(fā)生意外的錯誤。具體的刪除操作由ViewRootImpl的die方法來完成。在die的內(nèi)部會判斷是異步刪除還是同步刪除。在異步刪除的情況下,die方法只是發(fā)送了一個請求刪除的消息后就立刻返回了,這個時候View并沒有完成刪除操作,所以最后會將其添加到mDyingViews中,mDyingViews表示待刪除的View列表。 WindowManagerGlobal.java中的removeViewLocked方法:
/** 根據(jù)待刪除View的index來做進(jìn)一步刪除View,* removeViewLocked是通過ViewRootImpl來完成刪除操作的。* */private void removeViewLocked(int index, boolean immediate) {ViewRootImpl root = mRoots.get(index);View view = root.getView();if (view != null) {InputMethodManager imm = InputMethodManager.getInstance();if (imm != null) {imm.windowDismissed(mViews.get(index).getWindowToken());}}boolean deferred = root.die(immediate);if (view != null) {view.assignParent(null);if (deferred) {mDyingViews.add(view);}}}(4)具體的刪除操作由ViewRootImpl的die方法來完成。?在die的內(nèi)部會判斷是異步刪除還是同步刪除。在異步刪除的情況下,die方法只是發(fā)送了一個請求刪除的消息后就立刻返回了,這個時候View并沒有完成刪除操作。doDie內(nèi)部會調(diào)用dispatchDetachedFromWindow方法,真正刪除View的邏輯在dispatchDetachedFromWindow方法的內(nèi)部實現(xiàn)。 ViewRootImpl.java中die方法: boolean die(boolean immediate) {// Make sure we do execute immediately if we are in the middle of a traversal or the damage// done by dispatchDetachedFromWindow will cause havoc on return./** 如果是同步刪除(立即刪除),那么就不發(fā)送消息直接調(diào)用doDie方法。* */if (immediate && !mIsInTraversal) {doDie();return false;}if (!mIsDrawing) {destroyHardwareRenderer();} else {Log.e(TAG, "Attempting to destroy the window while drawing!\n" +" window=" + this + ", title=" + mWindowAttributes.getTitle());}/** 如果是異步操作,那么就發(fā)送一個MSG_DIE的消息,* ViewRootImpl中的Handler會處理此消息并調(diào)用doDie方法。* */mHandler.sendEmptyMessage(MSG_DIE);return true;}(5)ViewRootImpl.java中doDie方法: /** 在Die方法中會判斷是用異步刪除還是同步刪除,* 但歸根結(jié)底還是要用doDie來完成刪除View的操作。* 在doDie的內(nèi)部會調(diào)用dispatchDetachedFromWindow方法,* 真正刪除View的邏輯在dispatchDetachedFromWindow方法的內(nèi)部實現(xiàn)。* */void doDie() {checkThread();if (LOCAL_LOGV) Log.v(TAG, "DIE in " + this + " of " + mSurface);synchronized (this) {if (mRemoved) {return;}mRemoved = true;if (mAdded) {/** 這兒才是重點呢:* */dispatchDetachedFromWindow();}if (mAdded && !mFirst) {invalidateDisplayLists();destroyHardwareRenderer();if (mView != null) {int viewVisibility = mView.getVisibility();boolean viewVisibilityChanged = mViewVisibility != viewVisibility;if (mWindowAttributesChanged || viewVisibilityChanged) {// If layout params have been changed, first give them// to the window manager to make sure it has the correct// animation info.try {if ((relayoutWindow(mWindowAttributes, viewVisibility, false)& WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {mWindowSession.finishDrawing(mWindow);}} catch (RemoteException e) {}}mSurface.release();}}mAdded = false;}WindowManagerGlobal.getInstance().doRemoveView(this);}(5)doDie方法中調(diào)用的dispatchDetachedFromWindow是真正刪除View的邏輯。 在doDie方法中調(diào)用,實現(xiàn)真正的刪除View的邏輯。在這個方法中主要做四件事情:
(1)垃圾回收相關(guān)的工作,比如清除數(shù)據(jù)和消息、移除回調(diào)。
(2)通過Session的remove方法刪除Window:mWindowSession.remove(mWindow),這同樣是一個IPC過程,?最終會調(diào)用WindowManagerService的removeWindow方法。
(3)調(diào)用View的dispatchDetachedFromWindow方法:
? ? ?* 在內(nèi)部會調(diào)用View的onDetachedFromWindow()以及onDetachedFromWindowInternal()。
? ? ?* 對于onDetachedFromWindow()大家一定不陌生,當(dāng)View從Window中移除時,這個方法就會被調(diào)用,
? ? ?* 可以在這個方法內(nèi)部做一些資源回收的工作,
? ? ?* 比如終止動畫、停止線程等。 (4)調(diào)用WindowManagerGlobal的doRemoveView方法刷新數(shù)據(jù),包括mRoots、mParams以及mDyingViews,?需要將當(dāng)前Window所關(guān)聯(lián)的這三類對象從列表中刪除。
ViewRootImpl.java中dispatchDetachedFromWindow方法:
void dispatchDetachedFromWindow() {if (mView != null && mView.mAttachInfo != null) {if (mAttachInfo.mHardwareRenderer != null &&mAttachInfo.mHardwareRenderer.isEnabled()) {mAttachInfo.mHardwareRenderer.validate();}mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(false);mView.dispatchDetachedFromWindow();}mAccessibilityInteractionConnectionManager.ensureNoConnection();mAccessibilityManager.removeAccessibilityStateChangeListener(mAccessibilityInteractionConnectionManager);removeSendWindowContentChangedCallback();destroyHardwareRenderer();setAccessibilityFocus(null, null);mView.assignParent(null);mView = null;mAttachInfo.mRootView = null;mAttachInfo.mSurface = null;mSurface.release();if (mInputQueueCallback != null && mInputQueue != null) {mInputQueueCallback.onInputQueueDestroyed(mInputQueue);mInputQueue.dispose();mInputQueueCallback = null;mInputQueue = null;}if (mInputEventReceiver != null) {mInputEventReceiver.dispose();mInputEventReceiver = null;}try {mWindowSession.remove(mWindow);} catch (RemoteException e) {}// Dispose the input channel after removing the window so the Window Manager// doesn't interpret the input channel being closed as an abnormal termination.if (mInputChannel != null) {mInputChannel.dispose();mInputChannel = null;}unscheduleTraversals();}
(7)整體的調(diào)用關(guān)系是:
ViewManager ?-->> WindowManager(繼承自ViewManager) -->> WindowManagerImpl(繼承自WindowManager) -->>WindowManagerGlobal(WindowManagerImpl內(nèi)部的一個對象)?-->> ViewRooImpl.die(ViewRooImpl是WindowManagerGlobal的removeView方法中的一個對象) -->> doDie(die中的一個方法調(diào)用,判斷異步還是同步刪除) -->> dispatchDetachedFromWindow(在doDie方法中調(diào)用,真正用于刪除View的邏輯) -->> 通過Session的remove方法刪除Window(IPC過程) -->>?WindowManagerService.removeWindow -->>?dispatchDetachedFromWindow(這個是子View的dispatchDetachedFromWindow方法) -->>?onDetachedFromWindow和onDetachedFromWindowInternal(都是子View中的) -->>?WindowManagerGlobal.doRemoveView
4、Window的更新過程
(1)從WindowManagerGlobal的updateViewLayout方法看起: 首先它需要更新View的LayoutParams并替換掉老的LayoutParams,接著再更新ViewRootImpl中的LayoutParams,這一步是通過ViewRootImpl的setLayoutParams方法來實現(xiàn)的。在ViewRootImpl中會通過scheduleTraversals方法來對View重新布局,包括測量、布局、重繪這三個過程。除了View本身的重繪以外,ViewRootImpl還會通過WindowSession來更新Window的視圖,這個過程最終是由WindowManagerService的relayoutWindow()來具體實現(xiàn)的,它同樣是一個IPC過程。
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {if (view == null) {throw new IllegalArgumentException("view must not be null");}if (!(params instanceof WindowManager.LayoutParams)) {throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");}final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;/** 首先它需要更新View的LayoutParams并替換掉老的LayoutParams,* */view.setLayoutParams(wparams);synchronized (mLock) {int index = findViewLocked(view, true);ViewRootImpl root = mRoots.get(index);mParams.remove(index);mParams.add(index, wparams);root.setLayoutParams(wparams, false);}}(2)整體的調(diào)用關(guān)系是:
ViewManager ?-->> WindowManager(繼承自ViewManager) -->> WindowManagerImpl(繼承自WindowManager) -->>WindowManagerGlobal(WindowManagerImpl內(nèi)部的一個對象)?-->> updateViewLayout(WindowManagerGlobal中的一個方法) -->> setLayoutParams(updateViewLayout方法中調(diào)用) -->>?ViewRootImpl.setLayoutParams(ViewRootImpl是updateViewLayout中的一個對象) -->> scheduleTraversals方法(在setLayoutParams中調(diào)用,在ViewRootImpl中) -->>?WindowSession(在ViewRootImpl中,是一個Binder) -->> WindowManagerService.relayoutWindow(具體實現(xiàn),更新Window的視圖,IPC)
四、Window的創(chuàng)建過程
1、Window和View之間的關(guān)系
從上面的分析可以得出,View是Android中的視圖的呈現(xiàn)方式,但是View不能單獨存在,它必須依附在Window這個抽象的概念上面,因此有視圖的地方就有Window。 通常視圖的表現(xiàn)形式有:Activity、Dialog、Toast、PopUpWindow、菜單等。
2、Activity的Window創(chuàng)建過程
(1)要分析Activity中的Window的創(chuàng)建過程就必須了解Activity的啟動過程,詳細(xì)的啟動過程我們會有專門的blog來介紹,我們這里簡單的介紹一下。Activity的啟動過程很復(fù)雜,最終會由ActivityThread中的performLaunchActivity來完成整個啟動過程,這個方法內(nèi)部會通過類加載器創(chuàng)建Activity的實例對象,并調(diào)用其attach方法為其關(guān)聯(lián)運行過程中所一來的一系列上下文環(huán)境變量,代碼如下所示,在ActivityThread.java文件中的performLaunchActivity方法: ? ? ? ? Activity activity = null;try {java.lang.ClassLoader cl = r.packageInfo.getClassLoader();/** 通過類加載器創(chuàng)建Activity的實例對象:* */activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);StrictMode.incrementExpectedActivityCount(activity.getClass());r.intent.setExtrasClassLoader(cl);if (r.state != null) {r.state.setClassLoader(cl);}} catch (Exception e) {if (!mInstrumentation.onException(activity, e)) {throw new RuntimeException("Unable to instantiate activity " + component+ ": " + e.toString(), e);}}.....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);...}(2)在Activity的attach方法里,系統(tǒng)會創(chuàng)建Activity所屬的Window對象并為其設(shè)置回調(diào)接口,Window對象的創(chuàng)建是通過PolicyManager的makeNewWindow方法實現(xiàn)的。由于Activity實現(xiàn)了Window的Callback接口,因此當(dāng)Window接收到外界的狀態(tài)改變時就會回調(diào)Activity的方法。Callback接口中的方法很多,但是有幾個確實非常熟悉的,比如onAttachToWindow、onDetachedFromWindow、dispatchTouchEvent等,代碼如下,在Activity.java文件中的attach方法中: mWindow = PolicyManager.makeNewWindow(this);mWindow.setCallback(this);mWindow.getLayoutInflater().setPrivateFactory(this);if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {mWindow.setSoftInputMode(info.softInputMode);}if (info.uiOptions != 0) {mWindow.setUiOptions(info.uiOptions);}(3)從上面的分析可以看出,Activity的Window是通過PolicyManager的一個工廠方法來創(chuàng)建的。但從PolicyManager的類名可以看出,它不是一個普通的類,它是一個策略類。PolicyManager中實現(xiàn)的幾個工廠方法全部在策略接口IPolicy中聲明了,IPolicy的定義如下,IPolicy.java文件: /** Copyright (C) 2008 The Android Open Source Project** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at** http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/package com.android.internal.policy;import android.content.Context; import android.view.FallbackEventHandler; import android.view.LayoutInflater; import android.view.Window; import android.view.WindowManagerPolicy;/*** {@hide}*//* The implementation of this interface must be called Policy and contained* within the com.android.internal.policy.impl package */ public interface IPolicy {public Window makeNewWindow(Context context);public LayoutInflater makeNewLayoutInflater(Context context);public WindowManagerPolicy makeNewWindowManager();public FallbackEventHandler makeNewFallbackEventHandler(Context context); } 在實際的調(diào)用中,PolicyManager的真正實現(xiàn)類是Policy類,Policy類中的makeNewWindow方法的實現(xiàn)如下,在Policy.java文件中: public Window makeNewWindow(Context context) {return new PhoneWindow(context);}可以看出Window的具體實現(xiàn)確實是PhoneWindow。 注意點一:關(guān)于策略類PolicyManager是如何關(guān)聯(lián)到Policy上面的,這個無法從源碼中的調(diào)用關(guān)系中找出,這里猜測可能是由編譯環(huán)節(jié)動態(tài)控制的。因為我們找不到IPC的Binder。 (4)到這里Window已經(jīng)創(chuàng)建完成了,下面分析Activity的視圖是如何附屬在Window上面的。由于Activity的視圖由setContentView方法提供,我們只需要看setContentView方法的實現(xiàn)即可,在Activity.java文件中: public void setContentView(int layoutResID) {getWindow().setContentView(layoutResID);initActionBar();}Activity的視圖由setContentView方法提供,在這里面Activity將具體實現(xiàn)交給了Window處理,而Window的具體實現(xiàn)是由PhoneWindow,?所以只需要看PhoneWindow的相關(guān)邏輯即可。 (5)PhoneWindow的setContentView方法大致遵循如下幾個步驟: ? ? @Overridepublic void setContentView(int layoutResID) {// 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) { <span style="white-space:pre"> </span>/* <span style="white-space:pre"> </span>第一步,先要創(chuàng)建DecorView才行: <span style="white-space:pre"> </span>*/installDecor();} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {mContentParent.removeAllViews();}if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,getContext());transitionTo(newScene);} else {/* <span style="white-space:pre"> </span>第二步,將Activity的布局文件添加到DecorView的mContentParent中了: <span style="white-space:pre"> </span>*/mLayoutInflater.inflate(layoutResID, mContentParent);} <span style="white-space:pre"> </span>/* <span style="white-space:pre"> </span>第三步, <span style="white-space:pre"> </span>*/final Callback cb = getCallback();if (cb != null && !isDestroyed()) {cb.onContentChanged();}}第一步:如果沒有DecorView,那么就創(chuàng)建它。這個DecorView是Activity中的頂級View,包含標(biāo)題欄和內(nèi)容欄,內(nèi)容欄的id就是“content”,完整id是android.R.id.content。DecorView的創(chuàng)建過程由installDecor方法來完成,在方法內(nèi)部會通過generateDecor方法來直接創(chuàng)建DecorView,這個時候DecorView還只是一個空白的FrameLayout: protected DecorView generateDecor(){return new DecorView(getContext(), -1);}為了初始化DecorView的結(jié)構(gòu),PhoneWindow還需要通過generateLayout方法來加載具體的布局文件到DecorView中,具體的布局文件和系統(tǒng)版本以及主題有關(guān),這個過程如下所示,在PhoneWindow.java文件中的generateLayout方法中: View in = mLayoutInflater.inflate(layoutResource, null);decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));mContentRoot = (ViewGroup) in;ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);其中ID_ANDROID_CONTENT的定義如下,在Window.java文件中:這個id對應(yīng)的ViewGroup就是mContentParent。 public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;第二步:將View添加到DecorView的mContentParent中。就是上面的這句話:mLayoutInflater.inflate(layoutResID, mContentParent);。到此為止,Activity的布局文件已經(jīng)加載到DecorView里面了,由此可以理解Activity的setContentView這個方法的來歷了。Activity的布局文件只是被添加到DecorView的mContentParent中。
第三步:回調(diào)Activity的onContentChanged方法通知Activity視圖已經(jīng)發(fā)生改變。由于Activity實現(xiàn)了Window的Callback接口,于是要通知Activity,使其可以做相應(yīng)的處理。Activity的onContentChanged方法是個空實現(xiàn),我們可以在子Activity中處理這個回調(diào)。
(6)經(jīng)過上面的三個步驟,到這里為止DecorView已經(jīng)被創(chuàng)建并初始化完畢,Activity的布局文件也已經(jīng)成功添加到了DecorView的mContentParent中,但是這個時候DecorView還沒有被WindowManager正式添加到Window中。這里需要正確理解Window的概念,Window更多表示的是一種抽象的功能集合,雖然說早在Activity的attach方法中Window就已經(jīng)被創(chuàng)建了,但是這個時候由于DecorView并沒有被WindowManager識別,所以這個時候的Window無法提供具體功能,因為它還無法接收外界的輸入信息。在ActivityThread的handleResumeActivity方法中,首先會調(diào)用Activity的onResume方法,接著會調(diào)用Activity的makeVisible方法,正是在makeVisible方法中,DecorView真正地完成了添加和顯示這兩個過程,到這里Activity的視圖才能被用戶看到,如下所示,在Activity.java文件中: void makeVisible() {if (!mWindowAdded) {ViewManager wm = getWindowManager();wm.addView(mDecor, getWindow().getAttributes());mWindowAdded = true;}mDecor.setVisibility(View.VISIBLE);}
(7)整體流程:
ActivityThread -->> (在ActivityThread中的方法)performLaunchActivity -->> (在performLaunchActivity方法中)通過類加載器創(chuàng)建Activity的實例對象?-->> (在performLaunchActivity方法中)調(diào)用attach方法來關(guān)聯(lián)運行過程中所依賴的一系列上下文環(huán)境變量?-->> ?(在attach方法中) -->> 創(chuàng)建Activity所屬的Window對象并為其設(shè)置回調(diào)接口,這里就涉及到了PolicyManager -->> 現(xiàn)在轉(zhuǎn)到Activity的setContentView中 -->> PhoneWindowde的setContentView方法 -->> 在PhoneWindow的setContentView方法中,有三大步驟。完成了DecorView的創(chuàng)建和初始化,Activity的布局文件也成功添加到DecorView中 -->> ActivityThread的handleResumeActivity方法會調(diào)用Activity的onResume方法 -->> onResume方法中會調(diào)用Activity的makeVisible方法讓DecorView真正完成添加和顯示過程 -->> 結(jié)束?
五、Dialog的Window創(chuàng)建過程
Dialog的Window創(chuàng)建和Activity類似,有如下幾個步驟:
1、創(chuàng)建Window
(1)Dialog中Window的創(chuàng)建同樣是通過PolicyManager的makeNewWindow方法來完成的,創(chuàng)建的對象實際上就是PhoneWindow,這個過程和Activity的Window的創(chuàng)建過程是一樣的。 在Dialog.java文件中: Dialog(Context context, int theme, boolean createContextThemeWrapper) {if (createContextThemeWrapper) {if (theme == 0) {TypedValue outValue = new TypedValue();context.getTheme().resolveAttribute(com.android.internal.R.attr.dialogTheme,outValue, true);theme = outValue.resourceId;}mContext = new ContextThemeWrapper(context, theme);} else {mContext = context;} //主要看這里:mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);Window w = PolicyManager.makeNewWindow(mContext);mWindow = w;w.setCallback(this);w.setOnWindowDismissedCallback(this);w.setWindowManager(mWindowManager, null, null);w.setGravity(Gravity.CENTER);mListenersHandler = new ListenersHandler(this);}
2、初始化DecorView并將Dialog的視圖添加到DecorView中
這個也與Activity的類似,都是通過Window去添加指定的布局文件: 在Dialog.java文件中:
public void setContentView(View view) {mWindow.setContentView(view);}
3、將DecorView添加到Window中并顯示
在Dialog的show方法中,會通過WindowManager將DecorView添加到Window中,如下: 在Dialog.java文件中:
mWindowManager.addView(mDecor, 1);mShowing = true;
以上可以發(fā)現(xiàn)Activity的Window創(chuàng)建過程和Dialog的Window創(chuàng)建過程很類似,兩者幾乎沒有什么區(qū)別。
4、關(guān)閉Dialog
通過WindowManager來移除DecorView: mWindowManager.removeViewImmediate(mDecor);
5、一個注意點
普通的Dialog有一個特殊之處,那就是必須采用Activity的Context,如果采用Application的Context,就會報錯。 下面這個第一行是錯誤的: Dialog dailog = new Dialog(this.getApplicationContext());TextView textView = new TextView(this);textView.setText("this is a toast");dialog.setContentView(textView);dialog.show(); 報錯的信息會說是因為沒有應(yīng)用token所致,而應(yīng)用token一般只有Activity擁有,所以這里只需要用Activity作為Context來顯示對話框即可。另外系統(tǒng)Window比較特殊,它可以不需要token,因此在上面的例子中,只需要指定對話框的Window為系統(tǒng)Window類型就可以正常彈出對話框。我們在上面講過,WindowManager.LayoutParams中的type表示W(wǎng)indow的類型,而系統(tǒng)Window的層級范圍是2000~2999,這些層級范圍就對應(yīng)著type參數(shù)。系統(tǒng)Window的層級有很多值,對于本例來說,就可以選用TYPE_SYSTEM_OVERLAY來指定對話框的Window類型為系統(tǒng)Window: dialog.getWindow().setType(LayoutParams.TYPE_SYSTEM_OVERLAY);然后別忘了在AndroidManifest文件中聲明權(quán)限從而可以使用系統(tǒng)Window,如下所示: <user-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
六、Toast的Window創(chuàng)建過程
1、Toast和Dialog不同,它的工作過程就稍顯復(fù)雜
(1)Toast也是基于Window來實現(xiàn)的,但是由于Toast具有定時取消的功能,所以系統(tǒng)采用了Handler。 (2)在Toast內(nèi)部有兩類IPC過程,第一類是Toast訪問NotificationManagerService,第二類是NotificationManagerService回調(diào)Toast里的TN接口。 (3)Toast屬于系統(tǒng)Window,它內(nèi)部的視圖由兩種方式指定,一種是系統(tǒng)默認(rèn)的樣式,另一種是通過setView方法來指定一個自定義View,不管如何它們都對應(yīng)Toast的一個View類型的內(nèi)部成員mNextView。Toast提供了show和cancel分別用于顯示和隱藏Toast,它們的內(nèi)部是一個IPC過程。show和cancle都需要通過NotificationManagerService來實現(xiàn)。 (4)需要注意的是TN這個類,它是一個Binder類,在Toast和NotificationManagerService進(jìn)行IPC過程中,當(dāng)NotificationManagerService處理Toast的顯示或者隱藏請求時會跨進(jìn)程回調(diào)TN中的方法,這個時候由于TN運行在Binder線程池中,所以需要通過Handler將其切換到當(dāng)前線程中。這里的當(dāng)前線程是指發(fā)送Toast請求所在的線程。注意,由于這里使用了Handler,所以這意味著ToastRecord無法在沒有Looper的線程中彈出,這是因為Handler需要使用Looper才能完成切換線程的功能。 (5)在Toast.java文件中:show方法 ? ? /*** Show the view for the specified duration.*//** 顯示Toast:* 內(nèi)部是一個IPC過程。* */public void show() {if (mNextView == null) {throw new RuntimeException("setView must have been called");}/** 由于NotificationManagerService運行在系統(tǒng)的進(jìn)程中,* 所以只能通過遠(yuǎn)程調(diào)用的方式來顯示和隱藏Toast。* TN是一個Binder類,在Toast和NotificationManagerService進(jìn)行IPC的過程中,* 當(dāng)NotificationManagerService處理Toast的顯示或隱藏時會跨進(jìn)程回調(diào)TN中的方法,* 這個時候由于TN運行在Binder線程池中,所以需要通過Handler將其切換到當(dāng)前線程中。* 這里的當(dāng)前線程指的是發(fā)送Toast請求所在的線程。* 注意,由于這里使用了Handler,所以這意味著Toast無法在沒有Looper的線程中彈出,* 這是因為Handler需要使用Looper才能完成切換線程的功能。* */INotificationManager service = getService();String pkg = mContext.getPackageName();/** TN是Binder類,是Toast的內(nèi)部類* */TN tn = mTN;tn.mNextView = mNextView;try {<span style="white-space:pre"> </span>/*<span style="white-space:pre"> </span> * IPC體現(xiàn)在,我們這里可以使用NotificationManagerService內(nèi)部的方法了呢。<span style="white-space:pre"> </span> * 第一個參數(shù):表示當(dāng)前應(yīng)用的包名,<span style="white-space:pre"> </span> * 第二個參數(shù):表示遠(yuǎn)程回調(diào),<span style="white-space:pre"> </span> * 第三個參數(shù):表示Toast的顯示時長。<span style="white-space:pre"> </span> * 這個方法首先將Toast請求封裝為ToastRecord對象,并將其添加到一個名為mToastQueue的隊列中,<span style="white-space:pre"> </span> * mToastQueue其實是一個ArrayList。<span style="white-space:pre"> </span> * 對于非系統(tǒng)應(yīng)用來說,mToastQueue中最多能同時存在50個ToastRecord,<span style="white-space:pre"> </span> * 這樣做是為了防止DOS(Denial of Service),防止拒絕服務(wù)攻擊。<span style="white-space:pre"> </span> * */service.enqueueToast(pkg, tn, mDuration);} catch (RemoteException e) {// Empty}}/*** Close the view if it's showing, or don't show it if it isn't showing yet.* You do not normally have to call this. ?Normally view will disappear on its own* after the appropriate duration.*//** 關(guān)閉Toast:* 內(nèi)部是一個IPC過程。* */public void cancel() {mTN.hide();try {getService().cancelToast(mContext.getPackageName(), mTN);} catch (RemoteException e) {// Empty}} ? ? /** 第一個參數(shù):表示當(dāng)前應(yīng)用的包名, <span style="white-space:pre"> </span> * 第二個參數(shù):表示遠(yuǎn)程回調(diào),這個參數(shù)是一個Binder類,也就是Toast中的TN對象。 <span style="white-space:pre"> </span> * 第三個參數(shù):表示Toast的顯示時長。 <span style="white-space:pre"> </span> * 這個方法首先將Toast請求封裝為ToastRecord對象,并將其添加到一個名為mToastQueue的隊列中, <span style="white-space:pre"> </span> * mToastQueue其實是一個ArrayList。 <span style="white-space:pre"> </span> * 對于非系統(tǒng)應(yīng)用來說,mToastQueue中最多能同時存在50個ToastRecord, <span style="white-space:pre"> </span> * 這樣做是為了防止DOS(Denial of Service),防止拒絕服務(wù)攻擊。* */public void enqueueToast(String pkg, ITransientNotification callback, int duration){if (DBG) Slog.i(TAG, "enqueueToast pkg=" + pkg + " callback=" + callback + " duration=" + duration);if (pkg == null || callback == null) {Slog.e(TAG, "Not doing toast. pkg=" + pkg + " callback=" + callback);return ;}final boolean isSystemToast = isCallerSystem() || ("android".equals(pkg));if (ENABLE_BLOCKED_TOASTS && !noteNotificationOp(pkg, Binder.getCallingUid())) {if (!isSystemToast) {Slog.e(TAG, "Suppressing toast from package " + pkg + " by user request.");return;}}synchronized (mToastQueue) {int callingPid = Binder.getCallingPid();long callingId = Binder.clearCallingIdentity();try {ToastRecord record;int index = indexOfToastLocked(pkg, callback);// If it's already in the queue, we update it in place, we don't// move it to the end of the queue.if (index >= 0) {record = mToastQueue.get(index);record.update(duration);} else {// Limit the number of toasts that any given package except the android// package can enqueue. ?Prevents DOS attacks and deals with leaks./** 這個方法首先將Toast請求封裝為ToastRecord對象,并將其添加到一個名為mToastQueue的隊列中, <span style="white-space:pre"> </span> * mToastQueue其實是一個ArrayList。 <span style="white-space:pre"> </span> * 對于非系統(tǒng)應(yīng)用來說,mToastQueue中最多能同時存在50個ToastRecord, <span style="white-space:pre"> </span> * 這樣做是為了防止DOS(Denial of Service),防止拒絕服務(wù)攻擊。* */<span style="white-space:pre"> </span>if (!isSystemToast) {int count = 0;final int N = mToastQueue.size();for (int i=0; i<N; i++) {final ToastRecord r = mToastQueue.get(i);if (r.pkg.equals(pkg)) {count++;if (count >= MAX_PACKAGE_NOTIFICATIONS) {Slog.e(TAG, "Package has already posted " + count+ " toasts. Not showing more. Package=" + pkg);return;}}}}<span style="white-space:pre"> </span>/*<span style="white-space:pre"> </span> * 在ToastQueue中添加的ToastRecord對象的callback字段就是Toast的TN對象。<span style="white-space:pre"> </span> * */record = new ToastRecord(callingPid, pkg, callback, duration);mToastQueue.add(record);index = mToastQueue.size() - 1;keepProcessAliveLocked(callingPid);}// If it's at index 0, it's the current toast. ?It doesn't matter if it's// new or just been updated. ?Call back and tell it to show itself.// If the callback fails, this will remove it from the list, so don't// assume that it's valid after this.if (index == 0) {<span style="white-space:pre"> </span>/*<span style="white-space:pre"> </span> * 通過該方法來顯示當(dāng)前的Toast:<span style="white-space:pre"> </span> * */showNextToastLocked();}} finally {Binder.restoreCallingIdentity(callingId);}}}在enqueueToast中首先將Toast請求封裝成ToastRecord對象并將其添加到一個名為mToastQueue的隊列中。mToastQueue其實是一個ArrayList。對于非系統(tǒng)應(yīng)用來說,mToastQueue中最多能同時存在50個ToastRecord,這樣做是為了防止DOS(Denial of Service),放置拒絕服務(wù)攻擊。
(6)正常情況下,一個應(yīng)用不可能達(dá)到上限,當(dāng)ToastRecord被添加到mToastQueue中后,NotificationManagerService就會通過showNextToastLocked方法來顯示當(dāng)前的Toast。 /** 通過showNextToastLocked來顯示當(dāng)前的Toast。* Toast的顯示是由ToastRecord的callback字段來完成的,* 這個callback實際上就是Toast中的TN對象的遠(yuǎn)程Binder,* 通過callback來訪問TN中的方法是需要跨進(jìn)程來完成的,* 最終被調(diào)用的TN中的方法會運行在發(fā)起Toast請求的應(yīng)用的Binder線程池中。也就是在客戶端中運行的* */private void showNextToastLocked() {ToastRecord record = mToastQueue.get(0);while (record != null) {if (DBG) Slog.d(TAG, "Show pkg=" + record.pkg + " callback=" + record.callback);try {/** 在這里啦:調(diào)用Toast中TN對象的show方法:* */record.callback.show();/** 在Toast顯示之后,NotificationManagerService通過該方法來發(fā)送一個延時消息,* 具體延時取決于Toast的時長:* */scheduleTimeoutLocked(record);return;} catch (RemoteException e) {Slog.w(TAG, "Object died trying to show notification " + record.callback+ " in package " + record.pkg);// remove it from the list and let the process dieint index = mToastQueue.indexOf(record);if (index >= 0) {mToastQueue.remove(index);}keepProcessAliveLocked(record.pid);if (mToastQueue.size() > 0) {record = mToastQueue.get(0);} else {record = null;}}}}scheduleTimeoutLocked方法:用到了Handler /** 在Toast顯示之后,NotificationManagerService通過該方法來發(fā)送一個延時消息,* 具體延時取決于Toast的時長:* */private void scheduleTimeoutLocked(ToastRecord r){mHandler.removeCallbacksAndMessages(r);Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r);/** LONG_DELAY:3.5s* SHORT_DELAY:2.5s* */long delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY;mHandler.sendMessageDelayed(m, delay);}(7)延遲相應(yīng)時間后,NotificationManagerService會通過cancelToastLocked方法來隱藏Toast并將其從mToastQueue中移除,這個時候如果mToastQueue中還有其他Toast,那么NotificationManagerService就繼續(xù)顯示其他Toast: /** Toast的隱藏也是通過ToastRecord的callback來完成的,* 這同樣是一次IPC過程,它的工作方式和Toast的顯示過程是類似的。* */private void cancelToastLocked(int index) {ToastRecord record = mToastQueue.get(index);try {/** 這個callback就是TN對象,遠(yuǎn)程調(diào)用TN對象的hide方法。* */record.callback.hide();} catch (RemoteException e) {Slog.w(TAG, "Object died trying to hide notification " + record.callback+ " in package " + record.pkg);// don't worry about this, we're about to remove it from// the list anyway}mToastQueue.remove(index);keepProcessAliveLocked(record.pid);if (mToastQueue.size() > 0) {// Show the next one. If the callback fails, this will remove// it from the list, so don't assume that the list hasn't changed// after this point.showNextToastLocked();}}(8)通過上面的分析,大家知道Toast的顯示和影響過程實際上是通過Toast中的TN這個類來實現(xiàn)的,它有兩個方法show和hide,分別對應(yīng)Toast的顯示和隱藏。由于這兩個方法是被NMS以跨進(jìn)程的方式調(diào)用的,因此它們運行在Binder線程池中。為了將執(zhí)行環(huán)境切換到Toast請求所在的線程,在它們的內(nèi)部使用了Handler:mShow和mHide分別是兩個Runnable,它們內(nèi)部分別調(diào)用了handleShow和handleHide方法。由此可見,handleShow和handleHide才是真正完成顯示和隱藏Toast的地方。 /*** schedule handleShow into the right thread*//** 在NotificationManagerService中會通過TN對象遠(yuǎn)程調(diào)用TN對象的show方法來實現(xiàn)Toast的顯示,* 因此show方法運行在Binder線程池中。* 為了將執(zhí)行環(huán)境切換到Toast請求所在的線程,在它們的內(nèi)部都使用了Handler,* 所以Toast的顯示又跳轉(zhuǎn)到mShow中。* */@Overridepublic void show() {if (localLOGV) Log.v(TAG, "SHOW: " + this);mHandler.post(mShow);}/*** schedule handleHide into the right thread*//** 在NotificationManagerService中會通過TN對象遠(yuǎn)程調(diào)用TN對象的hide方法來實現(xiàn)Toast的隱藏,* 因此hide方法運行在Binder線程池中。* 為了將執(zhí)行環(huán)境切換到Toast請求所在的線程,在它們的內(nèi)部都使用了Handler,* 所以Toast的隱藏又跳轉(zhuǎn)到mHide中。* */@Overridepublic void hide() {if (localLOGV) Log.v(TAG, "HIDE: " + this);mHandler.post(mHide);} (9)在Toast.java文件中,TN是內(nèi)部類,它繼承了ITransientNotification.Stub,是一個Binder,內(nèi)部有兩個方法show和hide供其他人調(diào)用。也就是說如果Toast是服務(wù)端,服務(wù)端將TN這個Binder傳遞給NotificationManagerService這個客戶端,然后NotificationManagerService通過這個TN對象來調(diào)用Toast中的這兩個方法,屬于IPC遠(yuǎn)程調(diào)用。 private static class TN extends ITransientNotification.Stub {/** mShow是Runnable,內(nèi)部調(diào)用了handleShow方法,* 可見handleShow方法才是真正完成顯示Toast的地方。* TN的handleShow中會將Toast的視圖添加到Window中。 * */final Runnable mShow = new Runnable() {@Overridepublic void run() {handleShow();}};/** mHide是Runnable,內(nèi)部調(diào)用了handleHide方法,* 可見handleHide方法才是真正完成隱藏Toast的地方。* TN的handleHide中會將Toast的視圖從Window中移除。 * */final Runnable mHide = new Runnable() {@Overridepublic void run() {handleHide();// Don't do this in handleHide() because it is also invoked by handleShow()mNextView = null;}};private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();final Handler mHandler = new Handler(); int mGravity;int mX, mY;float mHorizontalMargin;float mVerticalMargin;View mView;View mNextView;WindowManager mWM;TN() {// XXX This should be changed to use a Dialog, with a Theme.Toast// defined that sets up the layout params appropriately.final WindowManager.LayoutParams params = mParams;params.height = WindowManager.LayoutParams.WRAP_CONTENT;params.width = WindowManager.LayoutParams.WRAP_CONTENT;params.format = PixelFormat.TRANSLUCENT;params.windowAnimations = com.android.internal.R.style.Animation_Toast;params.type = WindowManager.LayoutParams.TYPE_TOAST;params.setTitle("Toast");params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;}/*** schedule handleShow into the right thread*//** 在NotificationManagerService中會通過TN對象遠(yuǎn)程調(diào)用TN對象的show方法來實現(xiàn)Toast的顯示,* 因此show方法運行在Binder線程池中。* 為了將執(zhí)行環(huán)境切換到Toast請求所在的線程,在它們的內(nèi)部都使用了Handler,* 所以Toast的顯示又跳轉(zhuǎn)到mShow中。* */@Overridepublic void show() {if (localLOGV) Log.v(TAG, "SHOW: " + this);mHandler.post(mShow);}/*** schedule handleHide into the right thread*//** 在NotificationManagerService中會通過TN對象遠(yuǎn)程調(diào)用TN對象的hide方法來實現(xiàn)Toast的隱藏,* 因此hide方法運行在Binder線程池中。* 為了將執(zhí)行環(huán)境切換到Toast請求所在的線程,在它們的內(nèi)部都使用了Handler,* 所以Toast的隱藏又跳轉(zhuǎn)到mHide中。* */@Overridepublic void hide() {if (localLOGV) Log.v(TAG, "HIDE: " + this);mHandler.post(mHide);}/** handleShow方法才是真正完成顯示Toast的地方。* TN的handleShow中會將Toast的視圖添加到Window中。 * */public void handleShow() {if (localLOGV) Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView+ " mNextView=" + mNextView);if (mView != mNextView) {// remove the old view if necessaryhandleHide();mView = mNextView;Context context = mView.getContext().getApplicationContext();if (context == null) {context = mView.getContext();}mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);// We can resolve the Gravity here by using the Locale for getting// the layout directionfinal Configuration config = mView.getContext().getResources().getConfiguration();final int gravity = Gravity.getAbsoluteGravity(mGravity, config.getLayoutDirection());mParams.gravity = gravity;if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) {mParams.horizontalWeight = 1.0f;}if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) {mParams.verticalWeight = 1.0f;}mParams.x = mX;mParams.y = mY;mParams.verticalMargin = mVerticalMargin;mParams.horizontalMargin = mHorizontalMargin;if (mView.getParent() != null) {if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);mWM.removeView(mView);}if (localLOGV) Log.v(TAG, "ADD! " + mView + " in " + this);/** 在這里呢* */mWM.addView(mView, mParams);trySendAccessibilityEvent();}}private void trySendAccessibilityEvent() {AccessibilityManager accessibilityManager =AccessibilityManager.getInstance(mView.getContext());if (!accessibilityManager.isEnabled()) {return;}// treat toasts as notifications since they are used to// announce a transient piece of information to the userAccessibilityEvent event = AccessibilityEvent.obtain(AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED);event.setClassName(getClass().getName());event.setPackageName(mView.getContext().getPackageName());mView.dispatchPopulateAccessibilityEvent(event);accessibilityManager.sendAccessibilityEvent(event);} /** handleHide方法才是真正完成隱藏Toast的地方。* TN的handleHide中會將Toast的視圖從Window中移除。* */public void handleHide() {if (localLOGV) Log.v(TAG, "HANDLE HIDE: " + this + " mView=" + mView);if (mView != null) {// note: checking parent() just to make sure the view has// been added... i have seen cases where we get here when// the view isn't yet added, so let's try not to crash.if (mView.getParent() != null) {if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);/** 在這里呢* */mWM.removeView(mView);}mView = null;}}}
(10)整體過程:
Toast.show -->> NotificationManagerService的enqueueToast方法(IPC過程) -->> 在enqueueToast方法中Toast被封裝成ToastRecord對象并添加到mToastQueue隊列中?-->> enqueueToast方法隨后調(diào)用showNextToastLocked來顯示下一個Toast-->> 在showNextToastLocked方法中通過Toast的TN Binder對象回調(diào)客戶端的show方法進(jìn)行Toast顯示(IPC過程)?-->> 在TN的show方法中調(diào)用Handler來執(zhí)行真正的Toast顯示,添加到Window中等等 -->> 在showNextToastLocked方法中又調(diào)用scheduleTimeoutLocked來設(shè)置Toast顯示的延時?-->> NotificationManagerService的cancelToastLocked方法來隱藏Toast并將其從mToastQueue中移除 -->> 在cancelToastLocked方法中通過TN調(diào)用了hide方法,hide中又調(diào)用了Handler來執(zhí)行真正的Toast隱藏?-->>?如果mToastQueue中還有其他Toast,那么cancelToastLocked就調(diào)用showNextToastLocked來顯示下一個Toast。
總結(jié)
以上是生活随笔為你收集整理的理解Window和WindowManager的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 基于python的语料库数据处理_【知用
- 下一篇: Quality control of s