Android应用Activity、Dialog、PopWindow、Toast窗体加入机制及源代码分析
【工匠若水 http://blog.csdn.net/yanbober 轉(zhuǎn)載煩請注明出處。尊重勞動成果】
1 背景
之所以寫這一篇博客的原因是由于之前有寫過一篇《Android應(yīng)用setContentView與LayoutInflater載入解析機制源代碼分析》。然后有人在文章以下評論和微博私信中問我關(guān)于Android應(yīng)用Activity、Dialog、PopWindow載入顯示機制是咋回事,所以我就寫一篇文章來分析分析吧(本文以Android5.1.1 (API 22)源代碼為基礎(chǔ)分析),以便大家在應(yīng)用層開發(fā)時不再迷糊。
PS一句:不僅有人微博私信我這個問題,還有人問博客插圖這些是用啥畫的,這里告訴大家。就是我,快來猛戳我
還記得之前《Android應(yīng)用setContentView與LayoutInflater載入解析機制源代碼分析》這篇文章的最后分析結(jié)果嗎?就是例如以下這幅圖:
在那篇文章里我們當(dāng)時重點是Activity的View載入解析xml機制分析,當(dāng)時說到了Window的東西,但僅僅是皮毛的分析了Activity相關(guān)的一些邏輯。(PS:看到這不清楚上面啥意思的建議先移步到《Android應(yīng)用setContentView與LayoutInflater載入解析機制源代碼分析》,完事再回頭繼續(xù)看這篇文章。)當(dāng)時給大家承諾過我們要從應(yīng)用控件一點一點往下慢慢深入分析,所以如今開始深入,可是本篇的深入也僅僅是僅限Window相關(guān)的東東。之后文章還會繼續(xù)慢慢深入。
【工匠若水 http://blog.csdn.net/yanbober 轉(zhuǎn)載煩請注明出處。尊重勞動成果】
2 淺析Window與WindowManager相關(guān)關(guān)系及源代碼
通過上面那幅圖能夠非常直觀的看見,Android屏幕顯示的就是Window和各種View,Activity在當(dāng)中的作用主要是管理生命周期、建立窗體等。也就是說Window相關(guān)的東西對于Android屏幕來說是至關(guān)重要的(盡管前面分析Activity的setContentView等原理時說過一點Window。但那僅僅是皮毛。),所以有必要在分析Android應(yīng)用Activity、Dialog、PopWindow載入顯示機制前再看看Window相關(guān)的一些東西。
2-1 Window與WindowManager基礎(chǔ)關(guān)系
在分析Window與WindowManager之前我們先看一張圖:
接下來看一點代碼,例如以下:
/** 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 {public void addView(View view, ViewGroup.LayoutParams params);public void updateViewLayout(View view, ViewGroup.LayoutParams params);public void removeView(View view); }能夠看見,ViewManager接口定義了一組規(guī)則。也就是add、update、remove的操作View接口。也就是說ViewManager是用來加入和移除activity中View的接口。
繼續(xù)往下看:
public interface WindowManager extends ViewManager {......public Display getDefaultDisplay();public void removeViewImmediate(View view);......public static class LayoutParams extends ViewGroup.LayoutParamsimplements Parcelable {......} }看見沒有。WindowManager繼承自ViewManager,然后自己還是一個接口,同一時候又定義了一個靜態(tài)內(nèi)部類LayoutParams(這個類比較重要。后面會分析。提前透漏下。假設(shè)你在APP做過相似360助手屏幕的那個懸浮窗或者做過那種相似IOS的小白圓點,點擊展開菜單功能,你或多或少就能猜到這個類的重要性。)。
WindowManager用來在應(yīng)用與Window之間的接口、窗體順序、消息等的管理。繼續(xù)看下ViewManager的還有一個實現(xiàn)子類ViewGroup。例如以下:
public abstract class ViewGroup extends View implements ViewParent, ViewManager {//protected ViewParent mParent;//這個成員是View定義的,ViewGroup繼承自View,所以也能夠擁有。//這個變量就是前面我們一系列文章分析View向上傳遞的父節(jié)點,相似于一個鏈表Node的next一樣//終于指向了ViewRoot......public void addView(View child, LayoutParams params) {addView(child, -1, params);}......public void addView(View child, int index, LayoutParams params) {......// addViewInner() will call child.requestLayout() when setting the new LayoutParams// therefore, we call requestLayout() on ourselves before, so that the child's request// will be blocked at our levelrequestLayout();invalidate(true);addViewInner(child, index, params, false);}...... }這下理解上面那幅圖了吧,所以說View通過ViewGroup的addView方法加入到ViewGroup中。而ViewGroup層層嵌套到最頂級都會顯示在在一個窗體Window中(正如上面背景介紹中《Android應(yīng)用setContentView與LayoutInflater載入解析機制源代碼分析》的示意圖一樣)。當(dāng)中每一個View都有一個ViewParent類型的父節(jié)點mParent,最頂上的節(jié)點也是一個viewGroup,也即前面文章分析的Window的內(nèi)部類DecorView(從《Android應(yīng)用setContentView與LayoutInflater載入解析機制源代碼分析》的總結(jié)部分或者《Android應(yīng)用層View繪制流程與源代碼分析》的5-1小節(jié)都能夠驗證這個結(jié)論)對象。同一時候通過上面背景中那幅圖能夠看出來。對于一個Activity僅僅有一個DecorView(ViewRoot)。也僅僅有一個Window。
2-2 Activity窗體加入流程拓展
前面文章說過。ActivityThread類的performLaunchActivity方法中調(diào)運了activity.attach(…)方法進行初始化。例如以下是Activity的attach方法源代碼:
final void attach(Context context, ActivityThread aThread,Instrumentation instr, IBinder token, int ident,Application application, Intent intent, ActivityInfo info,CharSequence title, Activity parent, String id,NonConfigurationInstances lastNonConfigurationInstances,Configuration config, String referrer, IVoiceInteractor voiceInteractor) {......//創(chuàng)建Window類型的mWindow對象。實際為PhoneWindow類實現(xiàn)了抽象Window類mWindow = PolicyManager.makeNewWindow(this);......//通過抽象Window類的setWindowManager方法給Window類的成員變量WindowManager賦值實例化mWindow.setWindowManager((WindowManager)context.getSystemService(Context.WINDOW_SERVICE),mToken, mComponent.flattenToString(),(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);......//把抽象Window類相關(guān)的WindowManager對象拿出來關(guān)聯(lián)到Activity的WindowManager類型成員變量mWindowManagermWindowManager = mWindow.getWindowManager();......}看見沒有。Activity類中的attach方法又創(chuàng)建了Window類型的新成員變量mWindow(PhoneWindow實現(xiàn)類)與Activity相關(guān)聯(lián),接著在Activity類的attach方法最后又通過mWindow.setWindowManager(…)方法創(chuàng)建了與Window相關(guān)聯(lián)的WindowManager對象,最后又通過mWindow.getWindowManager()將Window的WindowManager成員變量賦值給Activity的WindowManager成員變量mWindowManager。
接下來我們看下上面代碼中的mWindow.setWindowManager(…)方法源代碼(PhoneWindow沒有重寫抽象Window的setWindowManager方法,所以直接看Window類的該方法源代碼),例如以下:
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,boolean hardwareAccelerated) {......if (wm == null) {wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);}//實例化Window類的WindowManager類型成員mWindowManagermWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);}能夠看見,Window的setWindowManager方法中通過WindowManagerImpl實例的createLocalWindowManager方法獲取了WindowManager實例。例如以下:
public final class WindowManagerImpl implements WindowManager {......private WindowManagerImpl(Display display, Window parentWindow) {mDisplay = display;mParentWindow = parentWindow;}......public WindowManagerImpl createLocalWindowManager(Window parentWindow) {return new WindowManagerImpl(mDisplay, parentWindow);}...... }看見沒有?這樣就把Activity的Window與WindowManager關(guān)聯(lián)起來了。Activity類的Window類型成員變量mWindow及WindowManager類型成員變量mWindowManager就是這么來的。
回過頭繼續(xù)看上面剛剛貼的Activity的attach方法代碼,看見mWindow.setWindowManager方法傳遞的第一個參數(shù)沒?有人會想(WindowManager)context.getSystemService(Context.WINDOW_SERVICE)這行代碼是什么意思。如今告訴你。
《Android應(yīng)用Context具體解釋及源代碼解析》一文中第三部分以前說過ActivityThread中創(chuàng)建了Acitivty(運行attach等方法)等東東,在創(chuàng)建這個Activity之前得到了Context的實例。
記不記得當(dāng)時說Context的實現(xiàn)類就是ContextImpl嗎?以下我們看下ContextImpl類的靜態(tài)方法塊,例如以下:
class ContextImpl extends Context {......//靜態(tài)代碼塊。類載入時運行一次static {......//這里有一堆相似的XXX_SERVICE的注冊......registerService(WINDOW_SERVICE, new ServiceFetcher() {Display mDefaultDisplay;public Object getService(ContextImpl ctx) {//搞一個Display實例Display display = ctx.mDisplay;if (display == null) {if (mDefaultDisplay == null) {DisplayManager dm = (DisplayManager)ctx.getOuterContext().getSystemService(Context.DISPLAY_SERVICE);mDefaultDisplay = dm.getDisplay(Display.DEFAULT_DISPLAY);}display = mDefaultDisplay;}//返回一個WindowManagerImpl實例return new WindowManagerImpl(display);}});......}//這就是你在外面調(diào)運Context的getSystemService獲取到的WindowManagerImpl實例@Overridepublic Object getSystemService(String name) {ServiceFetcher fetcher = SYSTEM_SERVICE_MAP.get(name);return fetcher == null ? null : fetcher.getService(this);}//上面static代碼塊創(chuàng)建WindowManagerImpl實例用到的方法private static void registerService(String serviceName, ServiceFetcher fetcher) {if (!(fetcher instanceof StaticServiceFetcher)) {fetcher.mContextCacheIndex = sNextPerContextServiceCacheIndex++;}SYSTEM_SERVICE_MAP.put(serviceName, fetcher);} }看見沒有,我們都知道Java的靜態(tài)代碼塊是類載入是運行一次的。也就相當(dāng)于一個全局的,這樣就相當(dāng)于每一個Application僅僅有一個WindowManagerImpl(display)實例。
還記不記得《Android應(yīng)用setContentView與LayoutInflater載入解析機制源代碼分析》一文2-6小節(jié)中說的,setContentView的實質(zhì)顯示是觸發(fā)了Activity的resume狀態(tài)。也就是觸發(fā)了makeVisible方法。那我們再來看下這種方法。例如以下:
void makeVisible() {if (!mWindowAdded) {//也就是獲取Activity的mWindowManager//這個mWindowManager是在Activity的attach中通過mWindow.getWindowManager()獲得ViewManager wm = getWindowManager();//調(diào)運的實質(zhì)就是ViewManager接口的addView方法,傳入的是mDecorViewwm.addView(mDecor, getWindow().getAttributes());mWindowAdded = true;}mDecor.setVisibility(View.VISIBLE);}特別注意,看見makeVisible方法的wm變量沒。這個變量就是Window類中通過調(diào)運WindowManagerImpl的createLocalWindowManager創(chuàng)建的實例,也就是說每一個Activity都會新創(chuàng)建這么一個WindowManager實例來顯示Activity的界面的,有點和上面分析的ContextImpl中static塊創(chuàng)建的WindowManager不太一樣的地方就在于Context的WindowManager對每一個APP來說是一個全局單例的,而Activity的WindowManager是每一個Activity都會新創(chuàng)建一個的(事實上你從上面分析的兩個實例化WindowManagerImpl的構(gòu)造函數(shù)參數(shù)傳遞就能夠看出來,Activity中Window的WindowManager成員在構(gòu)造實例化時傳入給WindowManagerImpl中mParentWindow成員的是當(dāng)前Window對象,而ContextImpl的static塊中單例實例化WindowManagerImpl時傳入給WindowManagerImpl中mParentWindow成員的是null值)。
繼續(xù)看makeVisible中調(diào)運的WindowManagerImpl的addView方法例如以下:
public final class WindowManagerImpl implements WindowManager {//繼承自O(shè)bject的單例類private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();......public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {applyDefaultToken(params);//mParentWindow是上面分析的在Activity中獲取WindowManagerImpl實例化時傳入的當(dāng)前Window//view是Activity中最頂層的mDecormGlobal.addView(view, params, mDisplay, mParentWindow);}...... }這里當(dāng)前傳入的view是mDecor。LayoutParams呢?能夠看見是getWindow().getAttributes()。那我們進去看看Window類的這個屬性。例如以下:
// The current window attributes.private final WindowManager.LayoutParams mWindowAttributes = new WindowManager.LayoutParams();原來是WindowManager的靜態(tài)內(nèi)部類LayoutParams的默認構(gòu)造函數(shù):
public LayoutParams() {super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);type = TYPE_APPLICATION;format = PixelFormat.OPAQUE; }看見沒有。Activity窗體的WindowManager.LayoutParams類型是TYPE_APPLICATION的。
繼續(xù)回到WindowManagerImpl的addView方法。分析能夠看見WindowManagerImpl中有一個單例模式的WindowManagerGlobal成員mGlobal。addView終于調(diào)運了WindowManagerGlobal的addView,源代碼例如以下:
public final class WindowManagerGlobal {......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>();......public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow) {......//獲取Activity的Window的getWindow().getAttributes()的LayoutParams final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;//假設(shè)是Activity中調(diào)運的,parentWindow=Window。假設(shè)不是Activity的。譬如是Context的靜態(tài)代碼塊的實例化則parentWindow為nullif (parentWindow != null) {//根據(jù)當(dāng)前Activity的Window調(diào)節(jié)sub Window的LayoutParamsparentWindow.adjustLayoutParamsForSubWindow(wparams);} else {......}ViewRootImpl root;......synchronized (mLock) {......//為當(dāng)前Window創(chuàng)建ViewRootroot = new ViewRootImpl(view.getContext(), display);view.setLayoutParams(wparams);//把當(dāng)前Window相關(guān)的東西存入各自的List中,在remove中會刪掉mViews.add(view);mRoots.add(root);mParams.add(wparams);}// do this last because it fires off messages to start doing thingstry {//把View和ViewRoot關(guān)聯(lián)起來。非常重要!!! root.setView(view, wparams, panelParentView); } catch (RuntimeException e) { ...... } } ...... }能夠看見,在addView方法中會利用LayoutParams獲得Window的屬性,然后為每一個Window創(chuàng)建ViewRootImpl。最后通過ViewRootImpl的setView方法通過mSession向WindowManagerService發(fā)送加入窗體請求把窗體加入到WindowManager中。并且由WindowManager來管理窗體的view、事件、消息收集處理等(ViewRootImpl的這一加入過程后面會寫文章分析,這里先記住這個概念就可以)。
至此我們對上面背景中那幅圖,也就是《Android應(yīng)用setContentView與LayoutInflater載入解析機制源代碼分析》這篇文章總結(jié)部分的那幅圖又進行了更深入的一點分析。其目的也就是為了以下分析Android應(yīng)用Dialog、PopWindow、Toast載入顯示機制做鋪墊準備。
2-3 繼續(xù)順藤摸瓜WindowManager.LayoutParams類的源代碼
上面2-1分析Window與WindowManager基礎(chǔ)關(guān)系時提到了WindowManager有一個靜態(tài)內(nèi)部類LayoutParams。它繼承于ViewGroup.LayoutParams。用于向WindowManager描寫敘述Window的管理策略。如今我們來看下這個類(PS:在AD上也能夠看見,自備梯子點我看AD的),例如以下:
public static class LayoutParams extends ViewGroup.LayoutParamsimplements Parcelable {//窗體的絕對XY位置,須要考慮gravity屬性public int x;public int y;//在橫縱方向上為相關(guān)的View預(yù)留多少擴展像素。假設(shè)是0則此view不能被拉伸。其它情況下擴展像素被widget均分public float horizontalWeight;public float verticalWeight;//窗體類型//有3種主要類型例如以下://ApplicationWindows取值在FIRST_APPLICATION_WINDOW與LAST_APPLICATION_WINDOW之間,是經(jīng)常使用的頂層應(yīng)用程序窗體,須將token設(shè)置成Activity的token;//SubWindows取值在FIRST_SUB_WINDOW和LAST_SUB_WINDOW之間,與頂層窗體相關(guān)聯(lián)。需將token設(shè)置成它所附著宿主窗體的token。//SystemWindows取值在FIRST_SYSTEM_WINDOW和LAST_SYSTEM_WINDOW之間,不能用于應(yīng)用程序。使用時須要有特殊權(quán)限。它是特定的系統(tǒng)功能才干使用;public int type;//WindowType:開始應(yīng)用程序窗體public static final int FIRST_APPLICATION_WINDOW = 1;//WindowType:全部程序窗體的base窗體,其它應(yīng)用程序窗體都顯示在它上面public static final int TYPE_BASE_APPLICATION = 1;//WindowType:普通應(yīng)用程序窗體,token必須設(shè)置為Activity的token來指定窗體屬于誰public static final int TYPE_APPLICATION = 2;//WindowType:應(yīng)用程序啟動時所顯示的窗體,應(yīng)用自己不要使用這樣的類型,它被系統(tǒng)用來顯示一些信息,直到應(yīng)用程序能夠開啟自己的窗體為止public static final int TYPE_APPLICATION_STARTING = 3;//WindowType:結(jié)束應(yīng)用程序窗體public static final int LAST_APPLICATION_WINDOW = 99;//WindowType:SubWindows子窗體。子窗體的Z序和坐標空間都依賴于他們的宿主窗體public static final int FIRST_SUB_WINDOW = 1000;//WindowType: 面板窗體。顯示于宿主窗體的上層public static final int TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW;//WindowType:媒體窗體(比如視頻)。顯示于宿主窗體下層public static final int TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW+1;//WindowType:應(yīng)用程序窗體的子面板。顯示于全部面板窗體的上層public static final int TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW+2;//WindowType:對話框,相似于面板窗體,繪制相似于頂層窗體,而不是宿主的子窗體public static final int TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW+3;//WindowType:媒體信息。顯示在媒體層和程序窗體之間。須要實現(xiàn)半透明效果public static final int TYPE_APPLICATION_MEDIA_OVERLAY = FIRST_SUB_WINDOW+4;//WindowType:子窗體結(jié)束public static final int LAST_SUB_WINDOW = 1999;//WindowType:系統(tǒng)窗體,非應(yīng)用程序創(chuàng)建public static final int FIRST_SYSTEM_WINDOW = 2000;//WindowType:狀態(tài)欄,僅僅能有一個狀態(tài)欄。位于屏幕頂端,其它窗體都位于它下方public static final int TYPE_STATUS_BAR = FIRST_SYSTEM_WINDOW;//WindowType:搜索欄。僅僅能有一個搜索欄,位于屏幕上方public static final int TYPE_SEARCH_BAR = FIRST_SYSTEM_WINDOW+1;//WindowType:電話窗體,它用于電話交互(特別是呼入),置于全部應(yīng)用程序之上,狀態(tài)欄之下public static final int TYPE_PHONE = FIRST_SYSTEM_WINDOW+2;//WindowType:系統(tǒng)提示,出如今應(yīng)用程序窗體之上public static final int TYPE_SYSTEM_ALERT = FIRST_SYSTEM_WINDOW+3;//WindowType:鎖屏窗體public static final int TYPE_KEYGUARD = FIRST_SYSTEM_WINDOW+4;//WindowType:信息窗體。用于顯示Toastpublic static final int TYPE_TOAST = FIRST_SYSTEM_WINDOW+5;//WindowType:系統(tǒng)頂層窗體。顯示在其它一切內(nèi)容之上,此窗體不能獲得輸入焦點。否則影響鎖屏public static final int TYPE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW+6;//WindowType:電話優(yōu)先,當(dāng)鎖屏?xí)r顯示,此窗體不能獲得輸入焦點,否則影響鎖屏public static final int TYPE_PRIORITY_PHONE = FIRST_SYSTEM_WINDOW+7;//WindowType:系統(tǒng)對話框public static final int TYPE_SYSTEM_DIALOG = FIRST_SYSTEM_WINDOW+8;//WindowType:鎖屏?xí)r顯示的對話框public static final int TYPE_KEYGUARD_DIALOG = FIRST_SYSTEM_WINDOW+9;//WindowType:系統(tǒng)內(nèi)部錯誤提示,顯示于全部內(nèi)容之上public static final int TYPE_SYSTEM_ERROR = FIRST_SYSTEM_WINDOW+10;//WindowType:內(nèi)部輸入法窗體,顯示于普通UI之上,應(yīng)用程序可又一次布局以免被此窗體覆蓋public static final int TYPE_INPUT_METHOD = FIRST_SYSTEM_WINDOW+11;//WindowType:內(nèi)部輸入法對話框。顯示于當(dāng)前輸入法窗體之上public static final int TYPE_INPUT_METHOD_DIALOG= FIRST_SYSTEM_WINDOW+12;//WindowType:墻紙窗體public static final int TYPE_WALLPAPER = FIRST_SYSTEM_WINDOW+13;//WindowType:狀態(tài)欄的滑動面板public static final int TYPE_STATUS_BAR_PANEL = FIRST_SYSTEM_WINDOW+14;//WindowType:安全系統(tǒng)覆蓋窗體。這些窗戶必須不帶輸入焦點。否則會干擾鍵盤public static final int TYPE_SECURE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW+15;//WindowType:拖放偽窗體,僅僅有一個阻力層(最多),它被放置在全部其它窗體上面public static final int TYPE_DRAG = FIRST_SYSTEM_WINDOW+16;//WindowType:狀態(tài)欄下拉面板public static final int TYPE_STATUS_BAR_SUB_PANEL = FIRST_SYSTEM_WINDOW+17;//WindowType:鼠標指針public static final int TYPE_POINTER = FIRST_SYSTEM_WINDOW+18;//WindowType:導(dǎo)航欄(有別于狀態(tài)欄時)public static final int TYPE_NAVIGATION_BAR = FIRST_SYSTEM_WINDOW+19;//WindowType:音量級別的覆蓋對話框。顯示當(dāng)用戶更改系統(tǒng)音量大小public static final int TYPE_VOLUME_OVERLAY = FIRST_SYSTEM_WINDOW+20;//WindowType:起機進度框,在一切之上public static final int TYPE_BOOT_PROGRESS = FIRST_SYSTEM_WINDOW+21;//WindowType:假窗,消費導(dǎo)航欄隱藏時觸摸事件public static final int TYPE_HIDDEN_NAV_CONSUMER = FIRST_SYSTEM_WINDOW+22;//WindowType:夢想(屏保)窗體,略高于鍵盤public static final int TYPE_DREAM = FIRST_SYSTEM_WINDOW+23;//WindowType:導(dǎo)航欄面板(不同于狀態(tài)欄的導(dǎo)航欄)public static final int TYPE_NAVIGATION_BAR_PANEL = FIRST_SYSTEM_WINDOW+24;//WindowType:universe背后真正的窗戶public static final int TYPE_UNIVERSE_BACKGROUND = FIRST_SYSTEM_WINDOW+25;//WindowType:顯示窗體覆蓋,用于模擬輔助顯示設(shè)備public static final int TYPE_DISPLAY_OVERLAY = FIRST_SYSTEM_WINDOW+26;//WindowType:放大窗體覆蓋。用于突出顯示的放大部分可訪問性放大時啟用public static final int TYPE_MAGNIFICATION_OVERLAY = FIRST_SYSTEM_WINDOW+27;//WindowType:......public static final int TYPE_KEYGUARD_SCRIM = FIRST_SYSTEM_WINDOW+29;public static final int TYPE_PRIVATE_PRESENTATION = FIRST_SYSTEM_WINDOW+30;public static final int TYPE_VOICE_INTERACTION = FIRST_SYSTEM_WINDOW+31;public static final int TYPE_ACCESSIBILITY_OVERLAY = FIRST_SYSTEM_WINDOW+32;//WindowType:系統(tǒng)窗體結(jié)束public static final int LAST_SYSTEM_WINDOW = 2999;//MemoryType:窗體緩沖位于主內(nèi)存public static final int MEMORY_TYPE_NORMAL = 0;//MemoryType:窗體緩沖位于能夠被DMA訪問,或者硬件加速的內(nèi)存區(qū)域public static final int MEMORY_TYPE_HARDWARE = 1;//MemoryType:窗體緩沖位于可被圖形加速器訪問的區(qū)域public static final int MEMORY_TYPE_GPU = 2;//MemoryType:窗體緩沖不擁有自己的緩沖區(qū),不能被鎖定,緩沖區(qū)由本地方法提供public static final int MEMORY_TYPE_PUSH_BUFFERS = 3;//指出窗體所使用的內(nèi)存緩沖類型,默覺得NORMAL public int memoryType;//Flag:當(dāng)該window對用戶可見的時候。同意鎖屏public static final int FLAG_ALLOW_LOCK_WHILE_SCREEN_ON = 0x00000001;//Flag:讓該window后全部的東西都成暗淡public static final int FLAG_DIM_BEHIND = 0x00000002;//Flag:讓該window后全部東西都模糊(4.0以上已經(jīng)放棄這樣的毛玻璃效果)public static final int FLAG_BLUR_BEHIND = 0x00000004;//Flag:讓window不能獲得焦點,這樣用戶快就不能向該window發(fā)送按鍵事public static final int FLAG_NOT_FOCUSABLE = 0x00000008;//Flag:讓該window不接受觸摸屏事件public static final int FLAG_NOT_TOUCHABLE = 0x00000010;//Flag:即使在該window在可獲得焦點情況下,依然把該window之外的不論什么event發(fā)送到該window之后的其它windowpublic static final int FLAG_NOT_TOUCH_MODAL = 0x00000020;//Flag:當(dāng)手機處于睡眠狀態(tài)時,假設(shè)屏幕被按下。那么該window將第一個收到public static final int FLAG_TOUCHABLE_WHEN_WAKING = 0x00000040;//Flag:當(dāng)該window對用戶可見時,讓設(shè)備屏幕處于高亮(bright)狀態(tài)public static final int FLAG_KEEP_SCREEN_ON = 0x00000080;//Flag:讓window占滿整個手機屏幕。不留不論什么邊界public static final int FLAG_LAYOUT_IN_SCREEN = 0x00000100;//Flag:window大小不再不受手機屏幕限制大小,即window可能超出屏幕之外public static final int FLAG_LAYOUT_NO_LIMITS = 0x00000200;//Flag:window全屏顯示public static final int FLAG_FULLSCREEN = 0x00000400;//Flag:恢復(fù)window非全屏顯示public static final int FLAG_FORCE_NOT_FULLSCREEN = 0x00000800;//Flag:開啟抖動(dithering)public static final int FLAG_DITHER = 0x00001000;//Flag:當(dāng)該window在進行顯示的時候,不同意截屏public static final int FLAG_SECURE = 0x00002000;//Flag:一個特殊模式的布局參數(shù)用于運行擴展表面合成時到屏幕上public static final int FLAG_SCALED = 0x00004000;//Flag:用于windows時,經(jīng)常會使用屏幕用戶持有反對他們的臉,它將積極過濾事件流,以防止意外按在這樣的情況下,可能不須要為特定的窗體,在檢測到這樣一個事件流時,應(yīng)用程序?qū)⒔邮杖∠\動事件表明,這樣應(yīng)用程序能夠處理這相應(yīng)地採取不論什么行動的事件,直到手指釋放public static final int FLAG_IGNORE_CHEEK_PRESSES = 0x00008000;//Flag:一個特殊的選項僅僅用于結(jié)合FLAG_LAYOUT_IN_SCpublic static final int FLAG_LAYOUT_INSET_DECOR = 0x00010000;//Flag:轉(zhuǎn)化的狀態(tài)FLAG_NOT_FOCUSABLE對這個窗體當(dāng)前怎樣進行交互的方法public static final int FLAG_ALT_FOCUSABLE_IM = 0x00020000;//Flag:假設(shè)你設(shè)置了該flag,那么在你FLAG_NOT_TOUNCH_MODAL的情況下。即使觸摸屏事件發(fā)送在該window之外。其事件被發(fā)送到了后面的window,那么該window仍然將以MotionEvent.ACTION_OUTSIDE形式收到該觸摸屏事件public static final int FLAG_WATCH_OUTSIDE_TOUCH = 0x00040000;//Flag:當(dāng)鎖屏的時候,顯示該windowpublic static final int FLAG_SHOW_WHEN_LOCKED = 0x00080000;//Flag:在該window后顯示系統(tǒng)的墻紙public static final int FLAG_SHOW_WALLPAPER = 0x00100000;//Flag:當(dāng)window被顯示的時候,系統(tǒng)將把它當(dāng)做一個用戶活動事件,以點亮手機屏幕public static final int FLAG_TURN_SCREEN_ON = 0x00200000;//Flag:消失鍵盤public static final int FLAG_DISMISS_KEYGUARD = 0x00400000;//Flag:當(dāng)該window在能夠接受觸摸屏情況下。讓因在該window之外,而發(fā)送到后面的window的觸摸屏能夠支持split touchpublic static final int FLAG_SPLIT_TOUCH = 0x00800000;//Flag:對該window進行硬件加速,該flag必須在Activity或Dialog的Content View之前進行設(shè)置public static final int FLAG_HARDWARE_ACCELERATED = 0x01000000;//Flag:讓window占滿整個手機屏幕。不留不論什么邊界public static final int FLAG_LAYOUT_IN_OVERSCAN = 0x02000000;//Flag:請求一個半透明的狀態(tài)欄背景以最小的系統(tǒng)提供保護public static final int FLAG_TRANSLUCENT_STATUS = 0x04000000;//Flag:請求一個半透明的導(dǎo)航欄背景以最小的系統(tǒng)提供保護public static final int FLAG_TRANSLUCENT_NAVIGATION = 0x08000000;//Flag:......public static final int FLAG_LOCAL_FOCUS_MODE = 0x10000000;public static final int FLAG_SLIPPERY = 0x20000000;public static final int FLAG_LAYOUT_ATTACHED_IN_DECOR = 0x40000000;public static final int FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS = 0x80000000;//行為選項標記public int flags;//PrivateFlags:......public static final int PRIVATE_FLAG_FAKE_HARDWARE_ACCELERATED = 0x00000001;public static final int PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED = 0x00000002;public static final int PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS = 0x00000004;public static final int PRIVATE_FLAG_SHOW_FOR_ALL_USERS = 0x00000010;public static final int PRIVATE_FLAG_NO_MOVE_ANIMATION = 0x00000040;public static final int PRIVATE_FLAG_COMPATIBLE_WINDOW = 0x00000080;public static final int PRIVATE_FLAG_SYSTEM_ERROR = 0x00000100;public static final int PRIVATE_FLAG_INHERIT_TRANSLUCENT_DECOR = 0x00000200;public static final int PRIVATE_FLAG_KEYGUARD = 0x00000400;public static final int PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS = 0x00000800;//私有的行為選項標記public int privateFlags;public static final int NEEDS_MENU_UNSET = 0;public static final int NEEDS_MENU_SET_TRUE = 1;public static final int NEEDS_MENU_SET_FALSE = 2;public int needsMenuKey = NEEDS_MENU_UNSET;public static boolean mayUseInputMethod(int flags) {......}//SOFT_INPUT:用于描寫敘述軟鍵盤顯示規(guī)則的bite的maskpublic static final int SOFT_INPUT_MASK_STATE = 0x0f;//SOFT_INPUT:沒有軟鍵盤顯示的約定規(guī)則public static final int SOFT_INPUT_STATE_UNSPECIFIED = 0;//SOFT_INPUT:可見性狀態(tài)softInputMode,請不要改變軟輸入?yún)^(qū)域的狀態(tài)public static final int SOFT_INPUT_STATE_UNCHANGED = 1;//SOFT_INPUT:用戶導(dǎo)航(navigate)到你的窗體時隱藏軟鍵盤public static final int SOFT_INPUT_STATE_HIDDEN = 2;//SOFT_INPUT:總是隱藏軟鍵盤public static final int SOFT_INPUT_STATE_ALWAYS_HIDDEN = 3;//SOFT_INPUT:用戶導(dǎo)航(navigate)到你的窗體時顯示軟鍵盤public static final int SOFT_INPUT_STATE_VISIBLE = 4;//SOFT_INPUT:總是顯示軟鍵盤public static final int SOFT_INPUT_STATE_ALWAYS_VISIBLE = 5;//SOFT_INPUT:顯示軟鍵盤時用于表示window調(diào)整方式的bite的maskpublic static final int SOFT_INPUT_MASK_ADJUST = 0xf0;//SOFT_INPUT:不指定顯示軟件盤時,window的調(diào)整方式public static final int SOFT_INPUT_ADJUST_UNSPECIFIED = 0x00;//SOFT_INPUT:當(dāng)顯示軟鍵盤時,調(diào)整window內(nèi)的控件大小以便顯示軟鍵盤public static final int SOFT_INPUT_ADJUST_RESIZE = 0x10;//SOFT_INPUT:當(dāng)顯示軟鍵盤時,調(diào)整window的空白區(qū)域來顯示軟鍵盤,即使調(diào)整空白區(qū)域,軟鍵盤還是有可能遮擋一些有內(nèi)容區(qū)域。這時用戶就僅僅有退出軟鍵盤才干看到這些被遮擋區(qū)域并進行public static final int SOFT_INPUT_ADJUST_PAN = 0x20;//SOFT_INPUT:當(dāng)顯示軟鍵盤時,不調(diào)整window的布局public static final int SOFT_INPUT_ADJUST_NOTHING = 0x30;//SOFT_INPUT:用戶導(dǎo)航(navigate)到了你的windowpublic static final int SOFT_INPUT_IS_FORWARD_NAVIGATION = 0x100;//軟輸入法模式選項public int softInputMode;//窗體怎樣停靠public int gravity;//水平邊距,容器與widget之間的距離,占容器寬度的百分率public float horizontalMargin;//縱向邊距public float verticalMargin;//積極的insets畫圖表面和窗體之間的內(nèi)容public final Rect surfaceInsets = new Rect();//期望的位圖格式,默覺得不透明,參考android.graphics.PixelFormatpublic int format;//窗體所使用的動畫設(shè)置,它必須是一個系統(tǒng)資源而不是應(yīng)用程序資源,由于窗體管理器不能訪問應(yīng)用程序public int windowAnimations;//整個窗體的半透明值,1.0表示不透明,0.0表示全透明public float alpha = 1.0f;//當(dāng)FLAG_DIM_BEHIND設(shè)置后生效,該變量指示后面的窗體變暗的程度。1.0表示全然不透明,0.0表示沒有變暗public float dimAmount = 1.0f;public static final float BRIGHTNESS_OVERRIDE_NONE = -1.0f;public static final float BRIGHTNESS_OVERRIDE_OFF = 0.0f;public static final float BRIGHTNESS_OVERRIDE_FULL = 1.0f;public float screenBrightness = BRIGHTNESS_OVERRIDE_NONE;//用來覆蓋用戶設(shè)置的屏幕亮度,表示應(yīng)用用戶設(shè)置的屏幕亮度。從0到1調(diào)整亮度從暗到最亮發(fā)生變化public float buttonBrightness = BRIGHTNESS_OVERRIDE_NONE;public static final int ROTATION_ANIMATION_ROTATE = 0;public static final int ROTATION_ANIMATION_CROSSFADE = 1;public static final int ROTATION_ANIMATION_JUMPCUT = 2;//定義出入境動畫在這個窗體旋轉(zhuǎn)設(shè)備時使用public int rotationAnimation = ROTATION_ANIMATION_ROTATE;//窗體的標示符public IBinder token = null;//此窗體所在的包名public String packageName = null;//屏幕方向public int screenOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;//首選的刷新率的窗體public float preferredRefreshRate;//控制status bar是否顯示public int systemUiVisibility;//ui能見度所請求的視圖層次結(jié)構(gòu)public int subtreeSystemUiVisibility;//得到關(guān)于系統(tǒng)ui能見度變化的回調(diào)public boolean hasSystemUiListeners;public static final int INPUT_FEATURE_DISABLE_POINTER_GESTURES = 0x00000001;public static final int INPUT_FEATURE_NO_INPUT_CHANNEL = 0x00000002;public static final int INPUT_FEATURE_DISABLE_USER_ACTIVITY = 0x00000004;public int inputFeatures;public long userActivityTimeout = -1;......public final int copyFrom(LayoutParams o) {......}......public void scale(float scale) {......}......}看見沒有。從上面類能夠看出,Android窗體類型主要分成了三大類:
一般應(yīng)用程序的窗體,比方我們應(yīng)用程序的Activity的窗體。
同一時候還能夠看見,WindowManager.LayoutParams里面窗體的type類型值定義是一個遞增保留的連續(xù)增大數(shù)值,從凝視能夠看出來事實上就是窗體的Z-ORDER序列(值越大顯示的位置越在上面,你須要將屏幕想成三維坐標模式)。創(chuàng)建不同類型的窗體須要設(shè)置不同的type值,譬如上面拓展Activity窗體載入時分析的makeVisible方法中的Window默認屬性的type=TYPE_APPLICATION。
既然說這個類非常重要。那總得感性的體驗一下重要性吧,所以我們先來看幾個實例。
2-4 通過上面WindowManager.LayoutParams分析引出的應(yīng)用層開發(fā)經(jīng)常使用經(jīng)典實例
有了上面分析相信你一定覺得WindowManager.LayoutParams還是蠻熟悉的,不信我們來看下。
Part1:開發(fā)APP時設(shè)置Activity全屏常亮的一種辦法(設(shè)置Activity也就是Activity的Window):
public class MainActivity extends ActionBarActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);//設(shè)置Activity的Window為全屏。當(dāng)然也能夠在xml中設(shè)置Window window = getWindow();WindowManager.LayoutParams windowAttributes = window.getAttributes();windowAttributes.flags = WindowManager.LayoutParams.FLAG_FULLSCREEN | windowAttributes.flags;window.setAttributes(windowAttributes);//設(shè)置Activity的Window為保持屏幕亮window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);setContentView(R.layout.activity_main);} }這是運行結(jié)果:
Part2:App開發(fā)中彈出軟鍵盤時以下的輸入框被軟件盤擋住問題的解決的方法:
在Activity中的onCreate中setContentView之前寫例如以下代碼:
//你也能夠在xml文件里設(shè)置。一樣的效果 getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE|WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN);Part3:創(chuàng)建懸浮窗體(仿IPhone的小圓點或者魅族的小白點或者360手機衛(wèi)士的小浮標)。退出當(dāng)前Activity依然可見的一種實現(xiàn)方法:
省略了Activity的start與stop Service的按鈕代碼,直接給出了核心代碼例如以下:
/*** Author : yanbo* Time : 14:47* Description : 手機屏幕懸浮窗,仿IPhone小圓點* (未全然實現(xiàn)。僅僅提供思路,如需請自行實現(xiàn))* Notice : <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />*/ public class WindowService extends Service {private WindowManager mWindowManager;private ImageView mImageView;@Overridepublic IBinder onBind(Intent intent) {return null;}@Overridepublic void onCreate() {super.onCreate();//創(chuàng)建懸浮窗createFloatWindow();}private void createFloatWindow() {//這里的參數(shù)設(shè)置上面剛剛講過。不再說明WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();mWindowManager = (WindowManager) getApplication().getSystemService(Context.WINDOW_SERVICE);//設(shè)置window的typelayoutParams.type = WindowManager.LayoutParams.TYPE_PHONE;//設(shè)置效果為背景透明layoutParams.format = PixelFormat.RGBA_8888;//設(shè)置浮動窗體不可聚焦layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;layoutParams.gravity = Gravity.BOTTOM | Gravity.RIGHT;layoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT;layoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;layoutParams.x = -50;layoutParams.y = -50;mImageView = new ImageView(this);mImageView.setImageResource(android.R.drawable.ic_menu_add);//加入到WindowmWindowManager.addView(mImageView, layoutParams);//設(shè)置監(jiān)聽mImageView.setOnTouchListener(touchListener);}@Overridepublic void onDestroy() {super.onDestroy();if (mImageView != null) {//講WindowManager時說過。add,remove成對出現(xiàn),所以須要removemWindowManager.removeView(mImageView);}}private View.OnTouchListener touchListener = new View.OnTouchListener() {@Overridepublic boolean onTouch(View v, MotionEvent event) {//模擬觸摸觸發(fā)的事件Intent intent = new Intent(Intent.ACTION_VIEW);intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);startActivity(intent);return false;}}; }例如以下是運行過程模擬,特別留意屏幕右下角的變化:
怎么樣,通過最后這個樣例你是不是就能體會到WindowManager.LayoutParams的Z-ORDER序列類型,值越大顯示的位置越在上面。
2-5 總結(jié)Activity的窗體加入機制
有了上面這么多分析和前幾篇的分析,我們對Activity的窗體載入再次深入分析總結(jié)例如以下:
能夠看見Context的WindowManager對每一個APP來說是一個全局單例的。而Activity的WindowManager是每一個Activity都會新創(chuàng)建一個的(事實上你從上面分析的兩個實例化WindowManagerImpl的構(gòu)造函數(shù)參數(shù)傳遞就能夠看出來,Activity中Window的WindowManager成員在構(gòu)造實例化時傳入給WindowManagerImpl中mParentWindow成員的是當(dāng)前Window對象。而ContextImpl的static塊中單例實例化WindowManagerImpl時傳入給WindowManagerImpl中mParentWindow成員的是null值)。所以上面模擬蘋果浮動小圖標使用了Application的WindowManager而不是Activity的,原因就在于這里;使用Activity的WindowManager時當(dāng)Activity結(jié)束時WindowManager就無效了。所以使用Activity的getSysytemService(WINDOW_SERVICE)獲取的是Local的WindowManager。同一時候能夠看出來Activity中的WindowManager.LayoutParams的type為TYPE_APPLICATION。
好了。上面也說了不少了,有了上面這些知識點以后我們就來開始分析Android應(yīng)用Activity、Dialog、PopWindow窗體顯示機制。
【工匠若水 http://blog.csdn.net/yanbober 轉(zhuǎn)載煩請注明出處,尊重勞動成果】
3 Android應(yīng)用Dialog窗體加入顯示機制源代碼
3-1 Dialog窗體源代碼分析
寫過APP都知道,Dialog是一系列XXXDialog的基類,我們能夠new隨意Dialog或者通過Activity提供的onCreateDialog(……)、onPrepareDialog(……)和showDialog(……)等方法來管理我們的Dialog,可是究事實上質(zhì)都是來源于Dialog基類,所以我們對于各種XXXDialog來說僅僅用分析Dialog的窗體載入就能夠了。
例如以下從Dialog的構(gòu)造函數(shù)開始分析:
public class Dialog implements DialogInterface, Window.Callback,KeyEvent.Callback, OnCreateContextMenuListener, Window.OnWindowDismissedCallback {......public Dialog(Context context) {this(context, 0, true);}//構(gòu)造函數(shù)終于都調(diào)運了這個默認的構(gòu)造函數(shù)Dialog(Context context, int theme, boolean createContextThemeWrapper) {//默認構(gòu)造函數(shù)的createContextThemeWrapper為trueif (createContextThemeWrapper) {//默認構(gòu)造函數(shù)的theme為0if (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;}//mContext已經(jīng)從外部傳入的context對象獲得值(通常是個Activity)。!。非常重要。先記住!!。//獲取WindowManager對象mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);//為Dialog創(chuàng)建新的WindowWindow w = PolicyManager.makeNewWindow(mContext);mWindow = w;//Dialog能夠接受到按鍵事件的原因w.setCallback(this);w.setOnWindowDismissedCallback(this);//關(guān)聯(lián)WindowManager與新Window。特別注意第二個參數(shù)token為null。也就是說Dialog沒有自己的token//一個Window屬于Dialog的話。那么該Window的mAppToken對象是nullw.setWindowManager(mWindowManager, null, null);w.setGravity(Gravity.CENTER);mListenersHandler = new ListenersHandler(this);}...... }能夠看到。Dialog構(gòu)造函數(shù)首先把外部傳入的參數(shù)context對象賦值給了當(dāng)前類的成員(我們的Dialog一般都是在Activity中啟動的。所以這個context通常是個Activity),然后調(diào)用context.getSystemService(Context.WINDOW_SERVICE)獲取WindowManager。這個WindowManager是哪來的呢?先依照上面說的context通常是個Activity來看待,能夠發(fā)現(xiàn)這句實質(zhì)就是Activity的getSystemService方法,我們看下源代碼,例如以下:
@Overridepublic Object getSystemService(@ServiceName @NonNull String name) {if (getBaseContext() == null) {throw new IllegalStateException("System services not available to Activities before onCreate()");}//我們Dialog中獲得的WindowManager對象就是這個分支if (WINDOW_SERVICE.equals(name)) {//Activity的WindowManagerreturn mWindowManager;} else if (SEARCH_SERVICE.equals(name)) {ensureSearchManager();return mSearchManager;}return super.getSystemService(name);}看見沒有。Dialog中的WindowManager成員實質(zhì)和Activity里面是一樣的,也就是共用了一個WindowManager。
回到Dialog的構(gòu)造函數(shù)繼續(xù)分析,在得到了WindowManager之后,程序又新建了一個Window對象(類型是PhoneWindow類型,和Activity的Window新建過程相似)。接著通過w.setCallback(this)設(shè)置Dialog為當(dāng)前window的回調(diào)接口。這樣Dialog就能夠接收事件處理了;接著把從Activity拿到的WindowManager對象關(guān)聯(lián)到新創(chuàng)建的Window中。
至此Dialog的創(chuàng)建過程Window處理已經(jīng)完成,非常easy。所以接下來我們繼續(xù)看看Dialog的show與cancel方法。例如以下:
public void show() {......if (!mCreated) {//回調(diào)Dialog的onCreate方法dispatchOnCreate(null);}//回調(diào)Dialog的onStart方法onStart();//相似于Activity,獲取當(dāng)前新Window的DecorView對象,所以有一種自己定義Dialog布局的方式就是重寫Dialog的onCreate方法,使用setContentView傳入布局。就像前面文章分析Activity相似mDecor = mWindow.getDecorView();......//獲取新Window的WindowManager.LayoutParams參數(shù)。和上面分析的Activity一樣type為TYPE_APPLICATIONWindowManager.LayoutParams l = mWindow.getAttributes();......try {//把一個View加入到Activity共用的windowManager里面去mWindowManager.addView(mDecor, l);......} finally {}}能夠看見Dialog的新Window與Activity的Window的type相同都為TYPE_APPLICATION,上面介紹WindowManager.LayoutParams時TYPE_APPLICATION的凝視明白說過,普通應(yīng)用程序窗體TYPE_APPLICATION的token必須設(shè)置為Activity的token來指定窗體屬于誰。所以能夠看見,既然Dialog和Activity共享同一個WindowManager(也就是上面分析的WindowManagerImpl),而WindowManagerImpl里面有個Window類型的mParentWindow變量。這個變量在Activity的attach中創(chuàng)建WindowManagerImpl時傳入的為當(dāng)前Activity的Window,而當(dāng)前Activity的Window里面的mAppToken值又為當(dāng)前Activity的token。所以Activity與Dialog共享了同一個mAppToken值,僅僅是Dialog和Activity的Window對象不同。
3-2 Dialog窗體載入總結(jié)
通過上面分析Dialog的窗體載入原理,我們總結(jié)例如以下圖:
從圖中能夠看出。Activity和Dialog共用了一個Token對象,Dialog必須依賴于Activity而顯示(通過別的context搞完之后token都為null,終于會在ViewRootImpl的setView方法中載入時由于token為null拋出異常)。所以Dialog的Context傳入?yún)?shù)通常是一個存在的Activity,假設(shè)Dialog彈出來之前Activity已經(jīng)被銷毀了,則這個Dialog在彈出的時候就會拋出異常。由于token不可用了。在Dialog的構(gòu)造函數(shù)中我們關(guān)聯(lián)了新Window的callback事件監(jiān)聽處理,所以當(dāng)Dialog顯示時Activity無法消費當(dāng)前的事件。
到此Dialog的窗體載入機制就分析完成了,接下來我們說說應(yīng)用開發(fā)中常見的一個詭異問題。
3-3 從Dialog窗體載入分析引出的應(yīng)用開發(fā)問題
有了上面的分析我們接下來看下平時開發(fā)App剛開始學(xué)習(xí)的人easy犯的幾個錯誤。
實如今一個Activity中顯示一個Dialog,例如以下代碼:
public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {setContentView(R.layout.activity_main);//重點關(guān)注構(gòu)造函數(shù)的參數(shù),創(chuàng)建一個Dialog然后顯示出來Dialog dialog = new ProgressDialog(this);dialog.setTitle("TestDialogContext");dialog.show();} }分析:使用了Activity為context,也即和Activity共用token,符合上面的分析。所以不會報錯,正常運行。
實如今一個Activity中顯示一個Dialog,例如以下代碼:
public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {setContentView(R.layout.activity_main);//重點關(guān)注構(gòu)造函數(shù)的參數(shù),創(chuàng)建一個Dialog然后顯示出來Dialog dialog = new ProgressDialog(getApplicationContext());dialog.setTitle("TestDialogContext");dialog.show();} }分析:傳入的是Application的Context,導(dǎo)致TYPE_APPLICATION類型Dialog的token為null。所以拋出例如以下異常,無法顯示對話框。
Caused by: android.view.WindowManager$BadTokenException: Unable to add window -- token null is not for an applicationat android.view.ViewRootImpl.setView(ViewRootImpl.java:566)at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:272)at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:69)at android.app.Dialog.show(Dialog.java:298)實如今一個Service中顯示一個Dialog。例如以下代碼:
public class WindowService extends Service {@Overridepublic IBinder onBind(Intent intent) {return null;}@Overridepublic void onCreate() {super.onCreate();//重點關(guān)注構(gòu)造函數(shù)的參數(shù)Dialog dialog = new ProgressDialog(this);dialog.setTitle("TestDialogContext");dialog.show();} }分析:傳入的Context是一個Service,相似上面?zhèn)魅階pplicationContext一樣的后果,一樣的原因。拋出例如以下異常:
Caused by: android.view.WindowManager$BadTokenException: Unable to add window -- token null is not for an applicationat android.view.ViewRootImpl.setView(ViewRootImpl.java:566)at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:272)at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:69)at android.app.Dialog.show(Dialog.java:298)至此通過我們平時使用最多的Dialog也驗證了Dialog成功顯示的必要條件,同一時候也讓大家避免了再次使用Dialog不當(dāng)出現(xiàn)異常的情況,或者出現(xiàn)相似異常后知道真實的背后原因是什么的問題。
能夠看見。Dialog的實質(zhì)無非也是使用WindowManager的addView、updateViewLayout、removeView進行一些操作展示。
【工匠若水 http://blog.csdn.net/yanbober 轉(zhuǎn)載煩請注明出處,尊重勞動成果】
4 Android應(yīng)用PopWindow窗體加入顯示機制源代碼
PopWindow實質(zhì)就是彈出式菜單。它與Dialag不同的地方是不會使依賴的Activity組件失去焦點(PopupWindow彈出后能夠繼續(xù)與依賴的Activity進行交互)。Dialog卻不能這樣。同一時候PopupWindow與Dialog還有一個不同點是PopupWindow是一個堵塞的對話框。假設(shè)你直接在Activity的onCreate等方法中顯示它則會報錯,所以PopupWindow必須在某個事件中顯示地或者是開啟一個新線程去調(diào)用。
說這么多還是直接看代碼吧。
4-1 PopWindow窗體源代碼分析
根據(jù)PopWindow的使用,我們選擇最經(jīng)常使用的方式來分析。例如以下先看當(dāng)中經(jīng)常使用的一種構(gòu)造函數(shù):
public class PopupWindow {......//我們僅僅分析最經(jīng)常使用的一種構(gòu)造函數(shù)public PopupWindow(View contentView, int width, int height, boolean focusable) {if (contentView != null) {//獲取mContext。contentView實質(zhì)是View,View的mContext都是構(gòu)造函數(shù)傳入的,View又層級傳遞,所以終于這個mContext實質(zhì)是Activity。!!非常重要
mContext = contentView.getContext(); //獲取Activity的getSystemService的WindowManager mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); } //進行一些Window類的成員變量初始化賦值操作 setContentView(contentView); setWidth(width); setHeight(height); setFocusable(focusable); } ...... }能夠看見,構(gòu)造函數(shù)僅僅是初始化了一些變量,看完構(gòu)造函數(shù)繼續(xù)看下PopWindow的展示函數(shù)。例如以下:
public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) {......//anchor是Activity中PopWindow準備依附的View,這個View的token實質(zhì)也是Activity的Window中的token,也即Activity的token//第一步 初始化WindowManager.LayoutParamsWindowManager.LayoutParams p = createPopupLayout(anchor.getWindowToken());//第二步preparePopup(p);......//第三步invokePopup(p);}能夠看見,當(dāng)我們想將PopWindow展示在anchor的下方向(Z軸是在anchor的上面)旁邊時經(jīng)理了上面三步。我們一步一步來分析。先看第一步。源代碼例如以下:
private WindowManager.LayoutParams createPopupLayout(IBinder token) {//實例化一個默認的WindowManager.LayoutParams。當(dāng)中type=TYPE_APPLICATIONWindowManager.LayoutParams p = new WindowManager.LayoutParams();//設(shè)置Gravityp.gravity = Gravity.START | Gravity.TOP;//設(shè)置寬高p.width = mLastWidth = mWidth;p.height = mLastHeight = mHeight;//根據(jù)背景設(shè)置formatif (mBackground != null) {p.format = mBackground.getOpacity();} else {p.format = PixelFormat.TRANSLUCENT;}//設(shè)置flagsp.flags = computeFlags(p.flags);//改動type=WindowManager.LayoutParams.TYPE_APPLICATION_PANEL,mWindowLayoutType有初始值,type類型為子窗體p.type = mWindowLayoutType;//設(shè)置token為Activity的tokenp.token = token;......return p;}接著回到showAsDropDown方法看看第二步,例如以下源代碼:
private void preparePopup(WindowManager.LayoutParams p) {......//有無設(shè)置PopWindow的background差別if (mBackground != null) {......//假設(shè)有背景則創(chuàng)建一個PopupViewContainer對象的ViewGroupPopupViewContainer popupViewContainer = new PopupViewContainer(mContext);PopupViewContainer.LayoutParams listParams = new PopupViewContainer.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, height);//把背景設(shè)置給PopupViewContainer的ViewGrouppopupViewContainer.setBackground(mBackground);//把我們構(gòu)造函數(shù)傳入的View加入到這個ViewGrouppopupViewContainer.addView(mContentView, listParams);//返回這個ViewGroupmPopupView = popupViewContainer;} else {//假設(shè)沒有通過PopWindow的setBackgroundDrawable設(shè)置背景則直接賦值當(dāng)前傳入的View為PopWindow的ViewmPopupView = mContentView;}......}能夠看見preparePopup方法的作用就是推斷設(shè)置View。假設(shè)有背景則會在傳入的contentView外面包一層PopupViewContainer(實質(zhì)是一個重寫了事件處理的FrameLayout)之后作為mPopupView。假設(shè)沒有背景則直接用contentView作為mPopupView。
我們再來看下這里的PopupViewContainer類。例如以下源代碼:
private class PopupViewContainer extends FrameLayout {......@Overrideprotected int[] onCreateDrawableState(int extraSpace) {......}@Overridepublic boolean dispatchKeyEvent(KeyEvent event) {......}@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {if (mTouchInterceptor != null && mTouchInterceptor.onTouch(this, ev)) {return true;}return super.dispatchTouchEvent(ev);}@Overridepublic boolean onTouchEvent(MotionEvent event) {......if(xxx) {dismiss();}......}@Overridepublic void sendAccessibilityEvent(int eventType) {......}}能夠看見,這個PopupViewContainer是一個PopWindow的內(nèi)部私有類,它繼承了FrameLayout。在當(dāng)中重寫了Key和Touch事件的分發(fā)處理邏輯。同一時候查閱PopupView能夠發(fā)現(xiàn),PopupView類自身沒有重寫Key和Touch事件的處理,所以假設(shè)沒有將傳入的View對象放入封裝的ViewGroup中。則點擊Back鍵或者PopWindow以外的區(qū)域PopWindow是不會消失的(事實上PopWindow中沒有向Activity及Dialog一樣new新的Window,所以不會有新的callback設(shè)置。也就沒法處理事件消費了)。
接著繼續(xù)回到showAsDropDown方法看看第三步。例如以下源代碼:
private void invokePopup(WindowManager.LayoutParams p) {if (mContext != null) {p.packageName = mContext.getPackageName();}mPopupView.setFitsSystemWindows(mLayoutInsetDecor);setLayoutDirectionFromAnchor();mWindowManager.addView(mPopupView, p);}能夠看見。這里使用了Activity的WindowManager將我們的PopWindow進行了顯示。
到此能夠發(fā)現(xiàn)。PopWindow的實質(zhì)無非也是使用WindowManager的addView、updateViewLayout、removeView進行一些操作展示。與Dialog不同的地方是沒有新new Window而已(也就沒法設(shè)置callback。無法消費事件,也就是前面說的PopupWindow彈出后能夠繼續(xù)與依賴的Activity進行交互的原因)。
到此PopWindw的窗體載入顯示機制就分析完成了,接下來進行總結(jié)與應(yīng)用開發(fā)技巧提示。
4-2 PopWindow窗體源代碼分析總結(jié)及應(yīng)用開發(fā)技巧提示
通過上面分析能夠發(fā)現(xiàn)總結(jié)例如以下圖:
能夠看見,PopWindow全然使用了Activity的Window與WindowManager,相對來說比較簡單easy記理解。
再來看一個開發(fā)技巧:
假設(shè)設(shè)置了PopupWindow的background。則點擊Back鍵或者點擊PopupWindow以外的區(qū)域時PopupWindow就會dismiss;假設(shè)不設(shè)置PopupWindow的background。則點擊Back鍵或者點擊PopupWindow以外的區(qū)域PopupWindow不會消失。
【工匠若水 http://blog.csdn.net/yanbober 轉(zhuǎn)載煩請注明出處,尊重勞動成果】
5 Android應(yīng)用Toast窗體加入顯示機制源代碼
5-1 基礎(chǔ)知識準備
在開始分析這幾個窗體之前須要腦補一點東東,我們從應(yīng)用層開發(fā)來直觀腦補,這樣以下分析源代碼時就不蛋疼了。
例如以下是一個我們寫的兩個應(yīng)用實現(xiàn)Service跨進程調(diào)用服務(wù)ADIL的樣例,client調(diào)運遠程Service的start與stop方法控制遠程Service的操作。
Android系統(tǒng)中的應(yīng)用程序都運行在各自的進程中,進程之間是無法直接交換數(shù)據(jù)的。可是Android為開發(fā)人員提供了AIDL跨進程調(diào)用Service的功能。事實上AIDL就相當(dāng)于兩方約定的一個規(guī)則而已。
先看下在Android Studio中AIDL開發(fā)的project文件夾結(jié)構(gòu),例如以下:
由于AIDL文件里不能出現(xiàn)訪問修飾符(如public),同一時候AIDL文件在兩個項目中要全然一致并且僅僅支持基本類型。所以我們定義的AIDL文件例如以下:
ITestService.aidl
package io.github.yanbober.myapplication;interface ITestService {void start(int id);void stop(int id); }再來看下根據(jù)aidl文件自己主動生成的ITestService.java文件吧。例如以下:
/** This file is auto-generated. DO NOT MODIFY.*/ package io.github.yanbober.myapplication; public interface ITestService extends android.os.IInterface {//Stub類是ITestService接口的內(nèi)部靜態(tài)抽象類,該類繼承了Binder類public static abstract class Stub extends android.os.Binder implements io.github.yanbober.myapplication.ITestService{......//這是抽象靜態(tài)Stub類中的asInterface方法,該方法負責(zé)將service返回至client的對象轉(zhuǎn)換為ITestService.Stub//把遠程Service的Binder對象傳遞進去,得到的是遠程服務(wù)的本地代理public static io.github.yanbober.myapplication.ITestService asInterface(android.os.IBinder obj){......}......//遠程服務(wù)的本地代理,也會繼承自ITestServiceprivate static class Proxy implements io.github.yanbober.myapplication.ITestService{......@Overridepublic void start(int id) throws android.os.RemoteException{......}@Overridepublic void stop(int id) throws android.os.RemoteException{......}}......}//兩個方法是aidl文件里定義的方法public void start(int id) throws android.os.RemoteException;public void stop(int id) throws android.os.RemoteException; }這就是自己主動生成的java文件,接下來我們看看服務(wù)端的Service源代碼,例如以下:
//記得在AndroidManifet.xml中注冊Service的<action android:name="io.github.yanbober.myapplication.aidl" />public class TestService extends Service {private TestBinder mTestBinder;//該類繼承ITestService.Stub類而不是Binder類,由于ITestService.Stub是Binder的子類//進程內(nèi)的Service定義TestBinder內(nèi)部類是繼承Binder類public class TestBinder extends ITestService.Stub {@Overridepublic void start(int id) throws RemoteException {Log.i(null, "Server Service is start!");}@Overridepublic void stop(int id) throws RemoteException {Log.i(null, "Server Service is stop!");}}@Overridepublic IBinder onBind(Intent intent) {//返回Binderreturn mTestBinder;}@Overridepublic void onCreate() {super.onCreate();//實例化BindermTestBinder = new TestBinder();} }如今服務(wù)端App的代碼已經(jīng)OK,我們來看下client的代碼。client首先也要像上面的project結(jié)構(gòu)一樣,把AIDL文件放好。接著在client使用遠程服務(wù)端的Service代碼例如以下:
public class MainActivity extends Activity {private static final String REMOT_SERVICE_ACTION = "io.github.yanbober.myapplication.aidl";private Button mStart, mStop;private ITestService mBinder;private ServiceConnection connection = new ServiceConnection() {@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {//獲得還有一個進程中的Service傳遞過來的IBinder對象//用IMyService.Stub.asInterface方法轉(zhuǎn)換該對象mBinder = ITestService.Stub.asInterface(service);}@Overridepublic void onServiceDisconnected(ComponentName name) {}};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mStart = (Button) this.findViewById(R.id.start);mStop = (Button) this.findViewById(R.id.stop);mStart.setOnClickListener(clickListener);mStop.setOnClickListener(clickListener);//綁定遠程跨進程ServicebindService(new Intent(REMOT_SERVICE_ACTION), connection, BIND_AUTO_CREATE);}@Overrideprotected void onDestroy() {super.onDestroy();//取消綁定遠程跨進程ServiceunbindService(connection);}private View.OnClickListener clickListener = new View.OnClickListener() {@Overridepublic void onClick(View v) {調(diào)用遠程Service中的start與stop方法switch (v.getId()) {case R.id.start:try {mBinder.start(0x110);} catch (RemoteException e) {e.printStackTrace();}break;case R.id.stop:try {mBinder.stop(0x120);} catch (RemoteException e) {e.printStackTrace();}break;}}}; }到此你相應(yīng)用層通過AIDL使用遠程Service的形式已經(jīng)非常熟悉了,至于實質(zhì)的通信使用Binder的機制我們后面會寫文章一步一步往下分析。到此的準備知識已經(jīng)足夠用來理解以下我們的源代碼分析了。
5-2 Toast窗體源代碼分析
我們經(jīng)常使用的Toast窗體事實上和前面分析的Activity、Dialog、PopWindow都是不同的。由于它和輸入法、墻紙相似。都是系統(tǒng)窗體。
我們還是依照最經(jīng)常使用的方式來分析源代碼吧。
我們先看下Toast的靜態(tài)makeText方法吧,例如以下:
public static Toast makeText(Context context, CharSequence text, @Duration int duration) {//new一個Toast對象Toast result = new Toast(context);//獲取前面有篇文章分析的LayoutInflaterLayoutInflater inflate = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);//載入解析Toast的布局。實質(zhì)transient_notification.xml是一個LinearLayout中套了一個@android:id/message的TextView而已View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null);//取出布局中的TextViewTextView tv = (TextView)v.findViewById(com.android.internal.R.id.message);//把我們的文字設(shè)置到TextView上tv.setText(text);//設(shè)置一些屬性result.mNextView = v;result.mDuration = duration;//返回新建的Toastreturn result;}能夠看見。這種方法構(gòu)造了一個Toast,然后把要顯示的文本放到這個View的TextView中,然后初始化相關(guān)屬性后返回這個新的Toast對象。
當(dāng)我們有了這個Toast對象之后。能夠通過show方法來顯示出來。例如以下看下show方法源代碼:
public void show() {......//通過AIDL(Binder)通信拿到NotificationManagerService的服務(wù)訪問接口。當(dāng)前Toast類相當(dāng)于上面樣例的client!!。相當(dāng)重要!!!
INotificationManager service = getService(); String pkg = mContext.getOpPackageName(); TN tn = mTN; tn.mNextView = mNextView; try { //把TN對象和一些參數(shù)傳遞到遠程NotificationManagerService中去 service.enqueueToast(pkg, tn, mDuration); } catch (RemoteException e) { // Empty } }我們看看show方法中調(diào)運的getService方法,例如以下:
//遠程NotificationManagerService的服務(wù)訪問接口private static INotificationManager sService;static private INotificationManager getService() {//單例模式if (sService != null) {return sService;}//通過AIDL(Binder)通信拿到NotificationManagerService的服務(wù)訪問接口sService = INotificationManager.Stub.asInterface(ServiceManager.getService("notification"));return sService;}通過上面我們的基礎(chǔ)腦補實例你也能看懂這個getService方法了吧。那接著我們來看mTN吧,好像mTN在Toast的構(gòu)造函數(shù)里見過一眼,我們來看看。例如以下:
public Toast(Context context) {mContext = context;mTN = new TN();mTN.mY = context.getResources().getDimensionPixelSize(com.android.internal.R.dimen.toast_y_offset);mTN.mGravity = context.getResources().getInteger(com.android.internal.R.integer.config_toastDefaultGravity);}能夠看見mTN確實是在構(gòu)造函數(shù)中實例化的,那我們就來看看這個TN類,例如以下:
//相似于上面樣例的服務(wù)端實例化的Service內(nèi)部類Binderprivate static class TN extends ITransientNotification.Stub {......//實現(xiàn)了AIDL的show與hide方法@Overridepublic void show() {if (localLOGV) Log.v(TAG, "SHOW: " + this);mHandler.post(mShow);}@Overridepublic void hide() {if (localLOGV) Log.v(TAG, "HIDE: " + this);mHandler.post(mHide);}......}看見沒有,TN是Toast內(nèi)部的一個私有靜態(tài)類,繼承自ITransientNotification.Stub。你這時指定好奇ITransientNotification.Stub是個啥玩意,對吧?事實上你在上面的腦補實例中見過它的,他出如今服務(wù)端實現(xiàn)的Service中,就是一個Binder對象。也就是對一個aidl文件的實現(xiàn)而已,我們看下這個ITransientNotification.aidl文件。例如以下:
package android.app;/** @hide */ oneway interface ITransientNotification {void show();void hide(); }看見沒有,和我們上面的樣例非常相似吧。
再回到上面分析的show()方法中能夠看到。我們的Toast是傳給遠程的NotificationManagerService管理的,為了NotificationManagerService回到我們的應(yīng)用程序(回調(diào))。我們須要告訴NotificationManagerService我們當(dāng)前程序的Binder引用是什么(也就是TN)。是不是覺得和上面樣例有些不同,這里感覺Toast又充當(dāng)client。又充當(dāng)服務(wù)端的樣子,實質(zhì)就是一個回調(diào)過程而已。
繼續(xù)來看Toast中的show方法的service.enqueueToast(pkg, tn, mDuration);語句。service實質(zhì)是遠程的NotificationManagerService。所以enqueueToast方法就是NotificationManagerService類的。例如以下:
private final IBinder mService = new INotificationManager.Stub() {// Toasts// ============================================================================@Overridepublic void enqueueToast(String pkg, ITransientNotification callback, int duration){......synchronized (mToastQueue) {int callingPid = Binder.getCallingPid();long callingId = Binder.clearCallingIdentity();try {ToastRecord record;//查看該Toast是否已經(jīng)在隊列當(dāng)中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.//凝視說了,已經(jīng)存在則直接取出updateif (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中record = new ToastRecord(callingPid, pkg, callback, duration);//把他加入到ToastQueue隊列中mToastQueue.add(record);index = mToastQueue.size() - 1;//將當(dāng)前Toast所在的進程設(shè)置為前臺進程keepProcessAliveLocked(callingPid);}//假設(shè)index為0,說明當(dāng)前入隊的Toast在隊頭,須要調(diào)用showNextToastLocked方法直接顯示if (index == 0) {showNextToastLocked();}} finally {Binder.restoreCallingIdentity(callingId);}}}}繼續(xù)看下該方法中調(diào)運的showNextToastLocked方法,例如以下:
void showNextToastLocked() {//取出ToastQueue中隊列最前面的ToastRecordToastRecord record = mToastQueue.get(0);while (record != null) {try {//Toast類中實現(xiàn)的ITransientNotification.Stub的Binder接口TN,調(diào)運了那個類的show方法record.callback.show();scheduleTimeoutLocked(record);return;} catch (RemoteException e) {......}}}繼續(xù)先看下該方法中調(diào)運的scheduleTimeoutLocked方法。例如以下:
private void scheduleTimeoutLocked(ToastRecord r){//移除上一條消息mHandler.removeCallbacksAndMessages(r);//根據(jù)Toast傳入的duration參數(shù)LENGTH_LONG=1來推斷決定多久發(fā)送消息Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r);long delay = r.duration == Toast.LENGTH_LONG ?LONG_DELAY : SHORT_DELAY; //根據(jù)設(shè)置的MESSAGE_TIMEOUT后發(fā)送消息 mHandler.sendMessageDelayed(m, delay); }
能夠看見這里先回調(diào)了Toast的TN的show,以下timeout可能就是hide了。接著還在該類的mHandler處理了這條消息。然后調(diào)運了例如以下處理方法:
private void handleTimeout(ToastRecord record){......synchronized (mToastQueue) {int index = indexOfToastLocked(record.pkg, record.callback);if (index >= 0) {cancelToastLocked(index);}}}我們繼續(xù)看cancelToastLocked方法,例如以下:
void cancelToastLocked(int index) {ToastRecord record = mToastQueue.get(index);try {//回調(diào)Toast的TN中實現(xiàn)的hide方法record.callback.hide();} catch (RemoteException e) {......}//從隊列移除當(dāng)前顯示的ToastmToastQueue.remove(index);keepProcessAliveLocked(record.pid);if (mToastQueue.size() > 0) {//假設(shè)當(dāng)前的Toast顯示完成隊列里還有其它的Toast則顯示其它的ToastshowNextToastLocked();}}到此能夠發(fā)現(xiàn),Toast的遠程管理NotificationManagerService類的處理實質(zhì)是通過Handler發(fā)送延時消息顯示取消Toast的,并且在遠程NotificationManagerService類中又遠程回調(diào)了Toast的TN類實現(xiàn)的show與hide方法。
如今我們就回到Toast的TN類再看看這個show與hide方法,例如以下:
```javaprivate static class TN extends ITransientNotification.Stub {......//僅僅是實例化了一個Handler。非常重要!!!!。!
!!
final Handler mHandler = new Handler(); ...... final Runnable mShow = new Runnable() { @Override public void run() { handleShow(); } }; final Runnable mHide = new Runnable() { @Override public void run() { handleHide(); // Don't do this in handleHide() because it is also invoked by handleShow() mNextView = null; } }; ...... //實現(xiàn)了AIDL的show與hide方法 @Override public void show() { if (localLOGV) Log.v(TAG, "SHOW: " + this); mHandler.post(mShow); } @Override public void hide() { if (localLOGV) Log.v(TAG, "HIDE: " + this); mHandler.post(mHide); } ...... }能夠看見。這里實現(xiàn)aidl接口的方法實質(zhì)是通過handler的post來運行的一個方法,而這個Handler僅僅僅僅是new了一下。也就是說。假設(shè)我們寫APP時使用Toast在子線程中則須要自行準備Looper對象。僅僅有主線程Activity創(chuàng)建時幫忙準備了Looper(關(guān)于Handler與Looper假設(shè)整不明白請閱讀《Android異步消息處理機制具體解釋及源代碼分析》)。
那我們重點關(guān)注一下handleShow與handleHide方法。例如以下:
public void handleShow() {if (localLOGV) Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView+ " mNextView=" + mNextView);if (mView != mNextView) {// remove the old view if necessary//假設(shè)有必要就通過WindowManager的remove刪掉舊的handleHide();mView = mNextView;Context context = mView.getContext().getApplicationContext();String packageName = mView.getContext().getOpPackageName();if (context == null) {context = mView.getContext();}//通過得到的context(通常是ContextImpl的context)獲取WindowManager對象(上一篇文章分析的單例的WindowManager)mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);......//在把Toast的View加入之前發(fā)現(xiàn)Toast的View已經(jīng)被加入過(有partent)則刪掉if (mView.getParent() != null) {......mWM.removeView(mView);}......//把Toast的View加入到窗體,當(dāng)中mParams.type在構(gòu)造函數(shù)中賦值為TYPE_TOAST!!!。!!
特別重要
mWM.addView(mView, mParams); ...... } } public void handleHide() {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.//凝視說得非常清楚了,不解釋,就是removeif (mView.getParent() != null) {mWM.removeView(mView);}mView = null;}}到此Toast的窗體加入原理就分析完成了,接下來我們進行總結(jié)。
5-3 Toast窗體源代碼分析總結(jié)及應(yīng)用開發(fā)技巧
經(jīng)過上面的分析我們總結(jié)例如以下:
通過上面分析及上圖直觀描寫敘述能夠發(fā)現(xiàn),之所以Toast的顯示交由遠程的NotificationManagerService管理是由于Toast是每一個應(yīng)用程序都會彈出的,并且位置和UI風(fēng)格都差點兒相同。所以假設(shè)我們不統(tǒng)一管理就會出現(xiàn)覆蓋疊加現(xiàn)象,同一時候?qū)е虏缓每刂啤K訥oogle把Toast設(shè)計成為了系統(tǒng)級的窗體類型,由NotificationManagerService統(tǒng)一隊列管理。
在我們開發(fā)應(yīng)用程序時使用Toast注意事項:
通過分析TN類的handler能夠發(fā)現(xiàn),假設(shè)想在非UI線程使用Toast須要自行聲明Looper。否則運行會拋出Looper相關(guān)的異常;UI線程不須要,由于系統(tǒng)已經(jīng)幫忙聲明。
在使用Toast時context參數(shù)盡量使用getApplicationContext()。能夠有效的防止靜態(tài)引用導(dǎo)致的內(nèi)存泄漏。
有時候我們會發(fā)現(xiàn)Toast彈出過多就會延遲顯示。由于上面源代碼分析能夠看見Toast.makeText是一個靜態(tài)工廠方法,每次調(diào)用這種方法都會產(chǎn)生一個新的Toast對象,當(dāng)我們在這個新new的對象上調(diào)用show方法就會使這個對象加入到NotificationManagerService管理的mToastQueue消息顯示隊列里排隊等候顯示;所以假設(shè)我們不每次都產(chǎn)生一個新的Toast對象(使用單例來處理)就不須要排隊。也就能及時更新了。
6 Android應(yīng)用Activity、Dialog、PopWindow、Toast窗體顯示機制總結(jié)
能夠看見上面不管Acitivty、Dialog、PopWindow、Toast的實質(zhì)事實上都是例如以下接口提供的方法操作:
public interface ViewManager {public void addView(View view, ViewGroup.LayoutParams params);public void updateViewLayout(View view, ViewGroup.LayoutParams params);public void removeView(View view); }整個應(yīng)用各種窗體的顯示都離不開這三個方法而已。僅僅是token及type與Window是否共用的問題。
【工匠若水 http://blog.csdn.net/yanbober 轉(zhuǎn)載煩請注明出處,尊重勞動成果】
總結(jié)
以上是生活随笔為你收集整理的Android应用Activity、Dialog、PopWindow、Toast窗体加入机制及源代码分析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 微软发布 Win10 Build 190
- 下一篇: common_test使用注意事项