精品伊人久久大香线蕉,开心久久婷婷综合中文字幕,杏田冲梨,人妻无码aⅴ不卡中文字幕

打開APP
userphoto
未登錄

開通VIP,暢享免費電子書等14項超值服

開通VIP
Android應(yīng)用Activity、Dialog、PopWindow、Toast窗口添加機制及源碼分析

Android應(yīng)用Activity、Dialog、PopWindow、Toast窗口添加機制及源碼分析

hello 發(fā)表于 6月前 (2015-06-10 16:34:02)  |  評論(0)  |  閱讀次數(shù)(1308)| 0 人收藏此文章,   我要收藏   

【工匠若水 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é)果嗎?就是如下這幅圖:

在那篇文章里我們當時重點是Activity的View加載解析xml機制分析,當時說到了Window的東西,但只是皮毛的分析了Activity相關(guān)的一些邏輯。(PS:看到這不清楚上面啥意思的建議先移步到《Android應(yīng)用setContentView與LayoutInflater加載解析機制源碼分析》,完事再回頭繼續(xù)看這篇文章。)當時給大家承諾過我們要從應(yīng)用控件一點一點往下慢慢深入分析,所以現(xiàn)在開始深入,但是本篇的深入也只是僅限Window相關(guān)的東東,之后文章還會繼續(xù)慢慢深入。

【工匠若水 http://blog.csdn.net/yanbober 轉(zhuǎn)載煩請注明出處,尊重勞動成果】

2 淺析Window與WindowManager相關(guān)關(guān)系及源碼

通過上面那幅圖可以很直觀的看見,Android屏幕顯示的就是Window和各種View,Activity在其中的作用主要是管理生命周期、建立窗口等。也就是說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.LayoutParams implements Parcelable {        ......    }}

看見沒有,WindowManager繼承自ViewManager,然后自己還是一個接口,同時又定義了一個靜態(tài)內(nèi)部類LayoutParams(這個類比較重要,后面會分析。提前透漏下,如果你在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 level        requestLayout();        invalidate(true);        addViewInner(child, index, params, false);    }    ......}

這下理解上面那幅圖了吧,所以說View通過ViewGroup的addView方法添加到ViewGroup中,而ViewGroup層層嵌套到最頂級都會顯示在在一個窗口Window中(正如上面背景介紹中《Android應(yīng)用setContentView與LayoutInflater加載解析機制源碼分析》的示意圖一樣),其中每個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類型成員變量mWindowManager        mWindowManager = 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類型成員mWindowManager        mWindowManager = ((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)這行代碼是什么意思,現(xiàn)在告訴你。

《Android應(yīng)用Context詳解及源碼解析》一文中第三部分曾經(jīng)說過ActivityThread中創(chuàng)建了Acitivty(執(zhí)行attach等方法)等東東,在創(chuàng)建這個Activity之前得到了Context的實例。記不記得當時說Context的實現(xiàn)類就是ContextImpl嗎?下面我們看下ContextImpl類的靜態(tài)方法塊,如下:

class ContextImpl extends Context {    ......    //靜態(tài)代碼塊,類加載時執(zhí)行一次    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實例    @Override    public 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)代碼塊是類加載是執(zhí)行一次的,也就相當于一個全局的,這樣就相當于每個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方法,傳入的是mDecorView            wm.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成員的是當前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實例化時傳入的當前Window        //view是Activity中最頂層的mDecor        mGlobal.addView(view, params, mDisplay, mParentWindow);    }    ......}

這里當前傳入的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;        //如果是Activity中調(diào)運的,parentWindow=Window,如果不是Activity的,譬如是Context的靜態(tài)代碼塊的實例化則parentWindow為null        if (parentWindow != null) {            //依據(jù)當前Activity的Window調(diào)節(jié)sub Window的LayoutParams            parentWindow.adjustLayoutParamsForSubWindow(wparams);        } else {            ......        }        ViewRootImpl root;        ......        synchronized (mLock) {            ......            //為當前Window創(chuàng)建ViewRoot            root = new ViewRootImpl(view.getContext(), display);            view.setLayoutParams(wparams);            //把當前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 things        try {            //把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的管理策略?,F(xiàn)在我們來看下這個類(PS:在AD上也可以看見,自備梯子點我看AD的),如下:

    public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable {        //窗口的絕對XY位置,需要考慮gravity屬性        public int x;        public int y;        //在橫縱方向上為相關(guān)的View預(yù)留多少擴展像素,如果是0則此view不能被拉伸,其他情況下擴展像素被widget均分        public float horizontalWeight;        public float verticalWeight;        //窗口類型        //有3種主要類型如下:        //ApplicationWindows取值在FIRST_APPLICATION_WINDOW與LAST_APPLICATION_WINDOW之間,是常用的頂層應(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)提示,出現(xiàn)在應(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:信息窗口,用于顯示Toast        public 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)先,當鎖屏?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)部輸入法對話框,顯示于當前輸入法窗口之上        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:音量級別的覆蓋對話框,顯示當用戶更改系統(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:當該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之后的其他window        public static final int FLAG_NOT_TOUCH_MODAL    = 0x00000020;        //Flag:當手機處于睡眠狀態(tài)時,如果屏幕被按下,那么該window將第一個收到        public static final int FLAG_TOUCHABLE_WHEN_WAKING = 0x00000040;        //Flag:當該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:當該window在進行顯示的時候,不允許截屏        public static final int FLAG_SECURE             = 0x00002000;        //Flag:一個特殊模式的布局參數(shù)用于執(zhí)行擴展表面合成時到屏幕上        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_SC        public static final int FLAG_LAYOUT_INSET_DECOR = 0x00010000;        //Flag:轉(zhuǎn)化的狀態(tài)FLAG_NOT_FOCUSABLE對這個窗口當前如何進行交互的方法        public static final int FLAG_ALT_FOCUSABLE_IM = 0x00020000;        //Flag:如果你設(shè)置了該flag,那么在你FLAG_NOT_TOUNCH_MODAL的情況下,即使觸摸屏事件發(fā)送在該window之外,其事件被發(fā)送到了后面的window,那么該window仍然將以MotionEvent.ACTION_OUTSIDE形式收到該觸摸屏事件        public static final int FLAG_WATCH_OUTSIDE_TOUCH = 0x00040000;        //Flag:當鎖屏的時候,顯示該window        public static final int FLAG_SHOW_WHEN_LOCKED = 0x00080000;        //Flag:在該window后顯示系統(tǒng)的墻紙        public static final int FLAG_SHOW_WALLPAPER = 0x00100000;        //Flag:當window被顯示的時候,系統(tǒng)將把它當做一個用戶活動事件,以點亮手機屏幕        public static final int FLAG_TURN_SCREEN_ON = 0x00200000;        //Flag:消失鍵盤        public static final int FLAG_DISMISS_KEYGUARD = 0x00400000;        //Flag:當該window在可以接受觸摸屏情況下,讓因在該window之外,而發(fā)送到后面的window的觸摸屏可以支持split touch        public 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的mask        public 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的mask        public static final int SOFT_INPUT_MASK_ADJUST = 0xf0;        //SOFT_INPUT:不指定顯示軟件盤時,window的調(diào)整方式        public static final int SOFT_INPUT_ADJUST_UNSPECIFIED = 0x00;        //SOFT_INPUT:當顯示軟鍵盤時,調(diào)整window內(nèi)的控件大小以便顯示軟鍵盤        public static final int SOFT_INPUT_ADJUST_RESIZE = 0x10;        //SOFT_INPUT:當顯示軟鍵盤時,調(diào)整window的空白區(qū)域來顯示軟鍵盤,即使調(diào)整空白區(qū)域,軟鍵盤還是有可能遮擋一些有內(nèi)容區(qū)域,這時用戶就只有退出軟鍵盤才能看到這些被遮擋區(qū)域并進行        public static final int SOFT_INPUT_ADJUST_PAN = 0x20;        //SOFT_INPUT:當顯示軟鍵盤時,不調(diào)整window的布局        public static final int SOFT_INPUT_ADJUST_NOTHING = 0x30;        //SOFT_INPUT:用戶導(dǎo)航(navigate)到了你的window        public 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.PixelFormat        public int format;        //窗口所使用的動畫設(shè)置,它必須是一個系統(tǒng)資源而不是應(yīng)用程序資源,因為窗口管理器不能訪問應(yīng)用程序        public int windowAnimations;        //整個窗口的半透明值,1.0表示不透明,0.0表示全透明        public float alpha = 1.0f;        //當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窗口類型主要分成了三大類:

  1. 應(yīng)用程序窗口。一般應(yīng)用程序的窗口,比如我們應(yīng)用程序的Activity的窗口。

  2. 子窗口。一般在Activity里面的窗口,比如對話框等。

  3. 系統(tǒng)窗口。系統(tǒng)的窗口,比如輸入法,Toast,墻紙等。

同時還可以看見,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)典實例

有了上面分析相信你一定覺得WindowManager.LayoutParams還是蠻熟悉的,不信我們來看下。

Part1:開發(fā)APP時設(shè)置Activity全屏常亮的一種辦法(設(shè)置Activity也就是Activity的Window):

public class MainActivity extends ActionBarActivity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        //設(shè)置Activity的Window為全屏,當然也可以在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)士的小浮標),退出當前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;    @Override    public IBinder onBind(Intent intent) {        return null;    }    @Override    public 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的type        layoutParams.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);        //添加到Window        mWindowManager.addView(mImageView, layoutParams);        //設(shè)置監(jiān)聽        mImageView.setOnTouchListener(touchListener);    }    @Override    public void onDestroy() {        super.onDestroy();        if (mImageView != null) {            //講WindowManager時說過,add,remove成對出現(xiàn),所以需要remove            mWindowManager.removeView(mImageView);        }    }    private View.OnTouchListener touchListener = new View.OnTouchListener() {        @Override        public 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成員的是當前Window對象,而ContextImpl的static塊中單例實例化WindowManagerImpl時傳入給WindowManagerImpl中mParentWindow成員的是null值),所以上面模擬蘋果浮動小圖標使用了Application的WindowManager而不是Activity的,原因就在于這里;使用Activity的WindowManager時當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為true        if (createContextThemeWrapper) {            //默認構(gòu)造函數(shù)的theme為0            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;        }        //mContext已經(jīng)從外部傳入的context對象獲得值(一般是個Activity)?。?!非常重要,先記?。。?!        //獲取WindowManager對象        mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);        //為Dialog創(chuàng)建新的Window        Window 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對象是null        w.setWindowManager(mWindowManager, null, null);        w.setGravity(Gravity.CENTER);        mListenersHandler = new ListenersHandler(this);    }    ......}

可以看到,Dialog構(gòu)造函數(shù)首先把外部傳入的參數(shù)context對象賦值給了當前類的成員(我們的Dialog一般都是在Activity中啟動的,所以這個context一般是個Activity),然后調(diào)用context.getSystemService(Context.WINDOW_SERVICE)獲取WindowManager,這個WindowManager是哪來的呢?先按照上面說的context一般是個Activity來看待,可以發(fā)現(xiàn)這句實質(zhì)就是Activity的getSystemService方法,我們看下源碼,如下:

    @Override    public 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的WindowManager            return 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為當前window的回調(diào)接口,這樣Dialog就能夠接收事件處理了;接著把從Activity拿到的WindowManager對象關(guān)聯(lián)到新創(chuàng)建的Window中。

至此Dialog的創(chuàng)建過程Window處理已經(jīng)完畢,很簡單,所以接下來我們繼續(xù)看看Dialog的show與cancel方法,如下:

    public void show() {        ......        if (!mCreated) {            //回調(diào)Dialog的onCreate方法            dispatchOnCreate(null);        }        //回調(diào)Dialog的onStart方法        onStart();        //類似于Activity,獲取當前新Window的DecorView對象,所以有一種自定義Dialog布局的方式就是重寫Dialog的onCreate方法,使用setContentView傳入布局,就像前面文章分析Activity類似        mDecor = mWindow.getDecorView();        ......        //獲取新Window的WindowManager.LayoutParams參數(shù),和上面分析的Activity一樣type為TYPE_APPLICATION        WindowManager.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時傳入的為當前Activity的Window,而當前Activity的Window里面的mAppToken值又為當前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,如果Dialog彈出來之前Activity已經(jīng)被銷毀了,則這個Dialog在彈出的時候就會拋出異常,因為token不可用了。在Dialog的構(gòu)造函數(shù)中我們關(guān)聯(lián)了新Window的callback事件監(jiān)聽處理,所以當Dialog顯示時Activity無法消費當前的事件。

到此Dialog的窗口加載機制就分析完畢了,接下來我們說說應(yīng)用開發(fā)中常見的一個詭異問題。

3-3 從Dialog窗口加載分析引出的應(yīng)用開發(fā)問題

有了上面的分析我們接下來看下平時開發(fā)App初學(xué)者容易犯的幾個錯誤。

實現(xiàn)在一個Activity中顯示一個Dialog,如下代碼:

public class MainActivity extends AppCompatActivity {    @Override    protected 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,符合上面的分析,所以不會報錯,正常執(zhí)行。

實現(xiàn)在一個Activity中顯示一個Dialog,如下代碼:

public class MainActivity extends AppCompatActivity {    @Override    protected 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 application            at 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)

實現(xiàn)在一個Service中顯示一個Dialog,如下代碼:

public class WindowService extends Service {    @Override    public IBinder onBind(Intent intent) {        return null;    }    @Override    public 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 application            at 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不當出現(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是一個阻塞的對話框,如果你直接在Activity的onCreate等方法中顯示它則會報錯,所以PopupWindow必須在某個事件中顯示地或者是開啟一個新線程去調(diào)用。

說這么多還是直接看代碼吧。

4-1 PopWindow窗口源碼分析

依據(jù)PopWindow的使用,我們選擇最常用的方式來分析,如下先看其中常用的一種構(gòu)造函數(shù):

public class PopupWindow {    ......    //我們只分析最常用的一種構(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.LayoutParams        WindowManager.LayoutParams p = createPopupLayout(anchor.getWindowToken());        //第二步        preparePopup(p);        ......        //第三步        invokePopup(p);    }

可以看見,當我們想將PopWindow展示在anchor的下方向(Z軸是在anchor的上面)旁邊時經(jīng)理了上面三步,我們一步一步來分析,先看第一步,源碼如下:

    private WindowManager.LayoutParams createPopupLayout(IBinder token) {        //實例化一個默認的WindowManager.LayoutParams,其中type=TYPE_APPLICATION        WindowManager.LayoutParams p = new WindowManager.LayoutParams();        //設(shè)置Gravity        p.gravity = Gravity.START | Gravity.TOP;        //設(shè)置寬高        p.width = mLastWidth = mWidth;        p.height = mLastHeight = mHeight;        //依據(jù)背景設(shè)置format        if (mBackground != null) {            p.format = mBackground.getOpacity();        } else {            p.format = PixelFormat.TRANSLUCENT;        }        //設(shè)置flags        p.flags = computeFlags(p.flags);        //修改type=WindowManager.LayoutParams.TYPE_APPLICATION_PANEL,mWindowLayoutType有初始值,type類型為子窗口        p.type = mWindowLayoutType;        //設(shè)置token為Activity的token        p.token = token;        ......        return p;    }

接著回到showAsDropDown方法看看第二步,如下源碼:

    private void preparePopup(WindowManager.LayoutParams p) {        ......        //有無設(shè)置PopWindow的background區(qū)別        if (mBackground != null) {            ......            //如果有背景則創(chuàng)建一個PopupViewContainer對象的ViewGroup            PopupViewContainer popupViewContainer = new PopupViewContainer(mContext);            PopupViewContainer.LayoutParams listParams = new PopupViewContainer.LayoutParams(                    ViewGroup.LayoutParams.MATCH_PARENT, height            );            //把背景設(shè)置給PopupViewContainer的ViewGroup            popupViewContainer.setBackground(mBackground);            //把我們構(gòu)造函數(shù)傳入的View添加到這個ViewGroup            popupViewContainer.addView(mContentView, listParams);            //返回這個ViewGroup            mPopupView = popupViewContainer;        } else {            //如果沒有通過PopWindow的setBackgroundDrawable設(shè)置背景則直接賦值當前傳入的View為PopWindow的View            mPopupView = mContentView;        }        ......    }

可以看見preparePopup方法的作用就是判斷設(shè)置View,如果有背景則會在傳入的contentView外面包一層PopupViewContainer(實質(zhì)是一個重寫了事件處理的FrameLayout)之后作為mPopupView,如果沒有背景則直接用contentView作為mPopupView。我們再來看下這里的PopupViewContainer類,如下源碼:

    private class PopupViewContainer extends FrameLayout {        ......        @Override        protected int[] onCreateDrawableState(int extraSpace) {            ......        }        @Override        public boolean dispatchKeyEvent(KeyEvent event) {            ......        }        @Override        public boolean dispatchTouchEvent(MotionEvent ev) {            if (mTouchInterceptor != null && mTouchInterceptor.onTouch(this, ev)) {                return true;            }            return super.dispatchTouchEvent(ev);        }        @Override        public boolean onTouchEvent(MotionEvent event) {            ......            if(xxx) {                dismiss();            }            ......        }        @Override        public void sendAccessibilityEvent(int eventType) {            ......        }    }

可以看見,這個PopupViewContainer是一個PopWindow的內(nèi)部私有類,它繼承了FrameLayout,在其中重寫了Key和Touch事件的分發(fā)處理邏輯。同時查閱PopupView可以發(fā)現(xiàn),PopupView類自身沒有重寫Key和Touch事件的處理,所以如果沒有將傳入的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,相對來說比較簡單容易記理解。

再來看一個開發(fā)技巧:

如果設(shè)置了PopupWindow的background,則點擊Back鍵或者點擊PopupWindow以外的區(qū)域時PopupWindow就會dismiss;如果不設(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的例子,客戶端調(diào)運遠程Service的start與stop方法控制遠程Service的操作。

Android系統(tǒng)中的應(yīng)用程序都運行在各自的進程中,進程之間是無法直接交換數(shù)據(jù)的,但是Android為開發(fā)者提供了AIDL跨進程調(diào)用Service的功能。其實AIDL就相當于雙方約定的一個規(guī)則而已。

先看下在Android Studio中AIDL開發(fā)的工程目錄結(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方法,該方法負責將service返回至client的對象轉(zhuǎn)換為ITestService.Stub        //把遠程Service的Binder對象傳遞進去,得到的是遠程服務(wù)的本地代理        public static io.github.yanbober.myapplication.ITestService asInterface(android.os.IBinder obj)        {            ......        }        ......        //遠程服務(wù)的本地代理,也會繼承自ITestService        private static class Proxy implements io.github.yanbober.myapplication.ITestService {            ......            @Override            public void start(int id) throws android.os.RemoteException            {                ......            }            @Override            public 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 {        @Override        public void start(int id) throws RemoteException {            Log.i(null, "Server Service is start!");        }        @Override        public void stop(int id) throws RemoteException {            Log.i(null, "Server Service is stop!");        }    }    @Override    public IBinder onBind(Intent intent) {        //返回Binder        return mTestBinder;    }    @Override    public void onCreate() {        super.onCreate();        //實例化Binder        mTestBinder = new TestBinder();    }}

現(xiàn)在服務(wù)端App的代碼已經(jīng)OK,我們來看下客戶端的代碼。客戶端首先也要像上面的工程結(jié)構(gòu)一樣,把AIDL文件放好,接著在客戶端使用遠程服務(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() {        @Override        public void onServiceConnected(ComponentName name, IBinder service) {            //獲得另一個進程中的Service傳遞過來的IBinder對象            //用IMyService.Stub.asInterface方法轉(zhuǎn)換該對象            mBinder = ITestService.Stub.asInterface(service);        }        @Override        public void onServiceDisconnected(ComponentName name) {        }    };    @Override    protected 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);        //綁定遠程跨進程Service        bindService(new Intent(REMOT_SERVICE_ACTION), connection, BIND_AUTO_CREATE);    }    @Override    protected void onDestroy() {        super.onDestroy();        //取消綁定遠程跨進程Service        unbindService(connection);    }    private View.OnClickListener clickListener = new View.OnClickListener() {        @Override        public 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窗口源碼分析

我們常用的Toast窗口其實和前面分析的Activity、Dialog、PopWindow都是不同的,因為它和輸入法、墻紙類似,都是系統(tǒng)窗口。

我們還是按照最常用的方式來分析源碼吧。

我們先看下Toast的靜態(tài)makeText方法吧,如下:

    public static Toast makeText(Context context, CharSequence text, @Duration int duration) {        //new一個Toast對象        Toast result = new Toast(context);        //獲取前面有篇文章分析的LayoutInflater        LayoutInflater 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);        //取出布局中的TextView        TextView tv = (TextView)v.findViewById(com.android.internal.R.id.message);        //把我們的文字設(shè)置到TextView上        tv.setText(text);        //設(shè)置一些屬性        result.mNextView = v;        result.mDuration = duration;        //返回新建的Toast        return result;    }

可以看見,這個方法構(gòu)造了一個Toast,然后把要顯示的文本放到這個View的TextView中,然后初始化相關(guān)屬性后返回這個新的Toast對象。

當我們有了這個Toast對象之后,可以通過show方法來顯示出來,如下看下show方法源碼:

    public void show() {        ......        //通過AIDL(Binder)通信拿到NotificationManagerService的服務(wù)訪問接口,當前Toast類相當于上面例子的客戶端?。。∠喈斨匾。。?       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)部類Binder    private static class TN extends ITransientNotification.Stub {        ......        //實現(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);        }        ......    }

看見沒有,TN是Toast內(nèi)部的一個私有靜態(tài)類,繼承自ITransientNotification.Stub。你這時指定好奇ITransientNotification.Stub是個啥玩意,對吧?其實你在上面的腦補實例中見過它的,他出現(xiàn)在服務(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我們當前程序的Binder引用是什么(也就是TN)。是不是覺得和上面例子有些不同,這里感覺Toast又充當客戶端,又充當服務(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        // ============================================================================        @Override        public void enqueueToast(String pkg, ITransientNotification callback, int duration)        {            ......            synchronized (mToastQueue) {                int callingPid = Binder.getCallingPid();                long callingId = Binder.clearCallingIdentity();                try {                    ToastRecord record;                    //查看該Toast是否已經(jī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)存在則直接取出update                    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中                        record = new ToastRecord(callingPid, pkg, callback, duration);                        //把他添加到ToastQueue隊列中                        mToastQueue.add(record);                        index = mToastQueue.size() - 1;                        //將當前Toast所在的進程設(shè)置為前臺進程                        keepProcessAliveLocked(callingPid);                    }                    //如果index為0,說明當前入隊的Toast在隊頭,需要調(diào)用showNextToastLocked方法直接顯示                    if (index == 0) {                        showNextToastLocked();                    }                } finally {                    Binder.restoreCallingIdentity(callingId);                }            }        }   }

繼續(xù)看下該方法中調(diào)運的showNextToastLocked方法,如下:

    void showNextToastLocked() {        //取出ToastQueue中隊列最前面的ToastRecord        ToastRecord 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) {            ......        }        //從隊列移除當前顯示的Toast        mToastQueue.remove(index);        keepProcessAliveLocked(record.pid);        if (mToastQueue.size() > 0) {            //如果當前的Toast顯示完畢隊列里還有其他的Toast則顯示其他的Toast            showNextToastLocked();        }    }

到此可以發(fā)現(xiàn),Toast的遠程管理NotificationManagerService類的處理實質(zhì)是通過Handler發(fā)送延時消息顯示取消Toast的,而且在遠程NotificationManagerService類中又遠程回調(diào)了Toast的TN類實現(xiàn)的show與hide方法。

現(xiàn)在我們就回到Toast的TN類再看看這個show與hide方法,如下:

```java    private 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來執(zhí)行的一個方法,而這個Handler僅僅只是new了一下,也就是說,如果我們寫APP時使用Toast在子線程中則需要自行準備Looper對象,只有主線程Activity創(chuàng)建時幫忙準備了Looper(關(guān)于Handler與Looper如果整不明白請閱讀《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                //如果有必要就通過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添加到窗口,其中mParams.type在構(gòu)造函數(shù)中賦值為TYPE_TOAST?。。。。。√貏e重要                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.                //注釋說得很清楚了,不解釋,就是remove                if (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風格都差不多,所以如果我們不統(tǒng)一管理就會出現(xiàn)覆蓋疊加現(xiàn)象,同時導(dǎo)致不好控制,所以Google把Toast設(shè)計成為了系統(tǒng)級的窗口類型,由NotificationManagerService統(tǒng)一隊列管理。

在我們開發(fā)應(yīng)用程序時使用Toast注意事項:

  1. 通過分析TN類的handler可以發(fā)現(xiàn),如果想在非UI線程使用Toast需要自行聲明Looper,否則運行會拋出Looper相關(guān)的異常;UI線程不需要,因為系統(tǒng)已經(jīng)幫忙聲明。

  2. 在使用Toast時context參數(shù)盡量使用getApplicationContext(),可以有效的防止靜態(tài)引用導(dǎo)致的內(nèi)存泄漏。

  3. 有時候我們會發(fā)現(xiàn)Toast彈出過多就會延遲顯示,因為上面源碼分析可以看見Toast.makeText是一個靜態(tài)工廠方法,每次調(diào)用這個方法都會產(chǎn)生一個新的Toast對象,當我們在這個新new的對象上調(diào)用show方法就會使這個對象加入到NotificationManagerService管理的mToastQueue消息顯示隊列里排隊等候顯示;所以如果我們不每次都產(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是否共用的問題。

本站僅提供存儲服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊舉報
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
Android下Dialog及Activity屏蔽Home鍵詳解
Android: Service中創(chuàng)建窗口顯示(TYPE
在Activity,Service,Window中監(jiān)聽Home鍵和返回鍵的一些思考,如何把事件傳遞出來的做法!
Android學(xué)習(xí)筆記
Android Dialog 狀態(tài)欄顏色變黑問題處理
Android無需權(quán)限顯示懸浮窗, 兼談逆向分析app
更多類似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長圖 關(guān)注 下載文章
綁定賬號成功
后續(xù)可登錄賬號暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點擊這里聯(lián)系客服!

聯(lián)系客服

主站蜘蛛池模板: 长治市| 长葛市| 华安县| 宁津县| 台安县| 鹿泉市| 晋州市| 屯门区| 泸水县| 庄河市| 新和县| 永州市| 新泰市| 香港 | 涟源市| 金坛市| 嫩江县| 大石桥市| 巴彦淖尔市| 来安县| 溧水县| 南岸区| 利津县| 南陵县| 久治县| 延吉市| 山丹县| 松江区| 彝良县| 潜山县| 德庆县| 宜良县| 延吉市| 宁化县| 万宁市| 贵德县| 榕江县| 文安县| 南康市| 瓦房店市| 襄城县|