成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

Android之Window和彈窗問題

Lorry_Lu / 2542人閱讀

摘要:指向的主要是實(shí)現(xiàn)和通信的。子不能多帶帶存在,需附屬特定的父。系統(tǒng)需申明權(quán)限才能創(chuàng)建。和類似,同樣是通過來實(shí)現(xiàn)。將添加到中顯示。方法完成的顯示。執(zhí)行的檢查參數(shù)等設(shè)置檢查將保存到中將保存到中。因?yàn)橥ㄟ^和的將無法獲取到從而導(dǎo)致失敗。

目錄介紹

10.0.0.1 Window是什么?如何通過WindowManager添加Window(代碼實(shí)現(xiàn))?WindowManager的主要功能是什么?

10.0.0.2 Window概念解析?WindowSession的創(chuàng)建過程是怎樣的?WindowSession的作用?Token的使用場(chǎng)景?

10.0.0.3 Activity、View、Window三者之間的關(guān)系,Window有哪幾種類型?

10.0.0.5 Activity的啟動(dòng)過程是怎樣的?Activity創(chuàng)建和Dialog創(chuàng)建過程的異同?

10.0.0.6 如何處理快速連續(xù)點(diǎn)擊了多次按鈕時(shí)Toast就觸發(fā)了多次而關(guān)閉不掉?

10.0.0.7 DecorView何時(shí)才被WindowManager真正添加到Window中?Window的addView源碼分析?

10.0.0.8 Dialog的Window創(chuàng)建過程?為什么Dialog不能用Application的Context?

10.0.0.9 什么是DecorView?如何獲取到DecorView?DecorView的職責(zé)是什么?DecorView如何被加載到Window中?

10.0.1.0 DecorView如何顯示出來,為什么setContentView()設(shè)置的界面,為什么在onResume()之后才對(duì)用戶可見呢?

10.0.1.1 什么是ViewRoot?ViewRoot屬于View樹的一份子嗎?ViewRoot的工作流程是怎么樣的?

10.0.1.2 吐司為何會(huì)出現(xiàn)內(nèi)存泄漏?在Toast構(gòu)造方法中創(chuàng)建NT對(duì)象是干什么用的?Toast是怎么show出來的?

10.0.1.3 連續(xù)吐司是如何確定吐司的先后順序?為什么Toast執(zhí)行show后過了一會(huì)兒就自動(dòng)銷毀?

10.0.1.4 如何理解普通應(yīng)用的Toast顯示數(shù)量是有限制的?為什么要判斷是否是系統(tǒng)吐司?為何Activity銷毀后Toast仍會(huì)顯示?

10.0.1.5 為什么說Toast盡量用全局上下文?說一下Toast的顯示和隱藏重點(diǎn)邏輯,說下你的理解?

10.0.1.6 Toast報(bào)錯(cuò)Unable to add window是什么意思?Toast運(yùn)行在子線程會(huì)問題,在子線程或者service中能運(yùn)行嗎?

10.0.1.7 為什么建議用DialogFragment替代Dialog?如何定義DialogFragment樣式?使用dialogFragment有何好處?

10.0.1.8 Dialog的Window創(chuàng)建過程是怎樣的?為什么Dialog不能用Application的Context,說一下原因?

10.0.1.9 Dialog和Window有什么關(guān)系?Dialog的dismiss和cancel()方法都可銷毀彈窗,它們有什么區(qū)別?

10.0.2.0 PopupWindow中不設(shè)置為什么必須設(shè)置寬高?PopupWindow和Dialog有什么區(qū)別?說下創(chuàng)建和銷毀的大概流程?

10.0.2.1 Snackbar與吐司有何區(qū)別在哪里?Snackbar控件show時(shí)為何從下往上移出來?為什么顯示在最下面?

10.0.2.2 說一下Snackbar和SnackbarManager類的設(shè)計(jì)有哪些奧妙的地方,如何處理消息的顯示順序?

好消息

博客筆記大匯總【15年10月到至今】,包括Java基礎(chǔ)及深入知識(shí)點(diǎn),Android技術(shù)博客,Python學(xué)習(xí)筆記等等,還包括平時(shí)開發(fā)中遇到的bug匯總,當(dāng)然也在工作之余收集了大量的面試題,長(zhǎng)期更新維護(hù)并且修正,持續(xù)完善……開源的文件是markdown格式的!同時(shí)也開源了生活博客,從12年起,積累共計(jì)500篇[近100萬字],將會(huì)陸續(xù)發(fā)表到網(wǎng)上,轉(zhuǎn)載請(qǐng)注明出處,謝謝!

鏈接地址:https://github.com/yangchong2...

如果覺得好,可以star一下,謝謝!當(dāng)然也歡迎提出建議,萬事起于忽微,量變引起質(zhì)變!

彈窗博客筆記匯總

02.Toast源碼深度分析

最簡(jiǎn)單的創(chuàng)建,簡(jiǎn)單改造避免重復(fù)創(chuàng)建,show()方法源碼分析,scheduleTimeoutLocked吐司如何自動(dòng)銷毀的,TN類中的消息機(jī)制是如何執(zhí)行的,普通應(yīng)用的Toast顯示數(shù)量是有限制的,用代碼解釋為何Activity銷毀后Toast仍會(huì)顯示,Toast偶爾報(bào)錯(cuò)Unable to add window是如何產(chǎn)生的,Toast運(yùn)行在子線程問題,Toast如何添加系統(tǒng)窗口的權(quán)限等等

03.DialogFragment源碼分析

最簡(jiǎn)單的使用方法,onCreate(@Nullable Bundle savedInstanceState)源碼分析,重點(diǎn)分析彈窗展示和銷毀源碼,使用中show()方法遇到的IllegalStateException分析

04.Dialog源碼分析

AlertDialog源碼分析,通過AlertDialog.Builder對(duì)象設(shè)置屬性,Dialog生命周期,Dialog中show方法展示彈窗分析,Dialog的dismiss銷毀彈窗,Dialog彈窗問題分析等等

05.PopupWindow源碼分析

顯示PopupWindow,注意問題寬和高屬性,showAsDropDown()源碼,dismiss()源碼分析,PopupWindow和Dialog有什么區(qū)別?為何彈窗點(diǎn)擊一下就dismiss呢?

06.Snackbar源碼分析

最簡(jiǎn)單的創(chuàng)建,Snackbar的make方法源碼分析,Snackbar的show顯示與點(diǎn)擊消失源碼分析,顯示和隱藏中動(dòng)畫源碼分析,Snackbar的設(shè)計(jì)思路,為什么Snackbar總是顯示在最下面

07.彈窗常見問題

DialogFragment使用中show()方法遇到的IllegalStateException,什么常見產(chǎn)生的?Toast偶爾報(bào)錯(cuò)Unable to add window,Toast運(yùn)行在子線程導(dǎo)致崩潰如何解決?

08.Builder模式

你會(huì)發(fā)現(xiàn),在這個(gè)彈窗封裝庫中,很多地方用到了builder模式,那么可以先了解下Builder模式使用場(chǎng)景,簡(jiǎn)單案例,Builder模式實(shí)際案例Demo展示,看看AlertDialog.Builder源代碼如何實(shí)現(xiàn),為什么AlertDialog要使用builder模式呢?builder模式優(yōu)缺點(diǎn)分析。

10.0.0.1 Window是什么?如何通過WindowManager添加Window(代碼實(shí)現(xiàn))?WindowManager的主要功能是什么?

Window是什么?

表示一個(gè)窗口的概念,是所有View的直接管理者,任何視圖都通過Window呈現(xiàn)(點(diǎn)擊事件由Window->DecorView->View; Activity的setContentView底層通過Window完成)

Window是一個(gè)抽象類,具體實(shí)現(xiàn)是PhoneWindow

創(chuàng)建Window需要通過WindowManager創(chuàng)建

WindowManager是外界訪問Window的入口

Window具體實(shí)現(xiàn)位于WindowManagerService中

WindowManager和WindowManagerService的交互是通過IPC完成

如何通過WindowManager添加Window(代碼實(shí)現(xiàn))?

如下所示

//1. 控件 
Button button = new Button(this); 
button.setText("Window Button"); 
//2. 布局參數(shù) 
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, 0, 0, PixelFormat.TRANSPARENT); 
layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED; 
layoutParams.gravity = Gravity.LEFT | Gravity.TOP; 
layoutParams.x = 100; 
layoutParams.y = 300; 
// 必須要有type不然會(huì)異常: the specified window type 0 is not valid 
layoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR; 
//3. 獲取WindowManager并添加控件到Window中 
WindowManager windowManager = getWindowManager(); 
windowManager.addView(button, layoutParams);

WindowManager的主要功能是什么?

添加、更新、刪除View

public interface ViewManager{ 
    public void addView(View view, ViewGroup.LayoutParams params); 
    //添加View 
    public void updateViewLayout(View view, ViewGroup.LayoutParams params); 
    //更新View 
    public void removeView(View view); 
    //刪除View 
}

10.0.0.2 Window概念解析?WindowSession的創(chuàng)建過程是怎樣的?WindowSession的作用?Token的使用場(chǎng)景?

Window概念解析?

Window和View通過ViewRootImpl建立聯(lián)系

Window并不是實(shí)際存在的,而是以View的形式存在

WindowManager的三個(gè)接口方法也是針對(duì)View的

實(shí)際使用中無法直接訪問Window,必須通過WindowManager

View是視圖的呈現(xiàn)方式,但是不能多帶帶存在,必須依附在Window這個(gè)抽象的概念上

WMS把所有的用戶消息發(fā)給View/ViewGroup,但是在View/ViewGroup處理消息的過程中,有一些操作是公共的, Window把這些公共行為抽象出來, 這就是Window。

WindowSession的創(chuàng)建過程是怎樣的?

在WindowManager的addView中會(huì)創(chuàng)建ViewRootImpl,內(nèi)部會(huì)通過WMS去獲取WindowSession

WindowSession的類型是IWindowSession,本身是Binder對(duì)象,真正實(shí)現(xiàn)類是Session

WindowSession的作用?博客

表示一個(gè)Active Client Session

每個(gè)進(jìn)程一般都有一個(gè)Session對(duì)象

用于WindowManager交互

Token的使用場(chǎng)景?

Popupwindow的showAtLocation第一個(gè)參數(shù)需要傳入View,這個(gè)View就是用來獲取Token的。

Android 5.0新增空間SnackBar同理也需要一個(gè)View來獲取Token

Token是什么?

類型為IBinder,是一個(gè)Binder對(duì)象。

主要分兩種Token:

指向Window的token: 主要是實(shí)現(xiàn)WmS和應(yīng)用所在進(jìn)程通信。

指向ActivityRecord的token: 主要是實(shí)現(xiàn)WmS和AmS通信的。

Activity中的Token

ActivityRecord是AmS中用來保存一個(gè)Activity信息的輔助類。

AMS中需要根據(jù)Token去找到對(duì)應(yīng)的ActivityRecord。

10.0.0.3 Activity、View、Window三者之間的關(guān)系,Window有哪幾種類型?

Activity、View、Window三者之間的關(guān)系

在Activity啟動(dòng)過程其中的attach()方法中初始化了PhoneWindow,而PhoneWindow是Window的唯一實(shí)現(xiàn)類,然后Activity通過setContentView將View設(shè)置到了PhoneWindow上,而View通過WindowManager的addView()、removeView()、updateViewLayout()對(duì)View進(jìn)行管理。

Window有哪幾種類型

應(yīng)用Window:對(duì)應(yīng)一個(gè)Activity。

子Window:不能多帶帶存在,需附屬特定的父Window。如Dialog。

系統(tǒng)Window: 需申明權(quán)限才能創(chuàng)建。如Toast。

Activity 與 PhoneWindow 與 DecorView 關(guān)系圖

10.0.0.5 Activity的啟動(dòng)過程是怎樣的?Activity的視圖加載的源碼分析?Activity創(chuàng)建和Dialog創(chuàng)建過程的異同?

Activity的啟動(dòng)過程是怎樣的?

最終會(huì)由ActivityThread中的performLauchActivity來完成整個(gè)啟動(dòng)過程

performLauchActivity內(nèi)部會(huì)通過類加載器創(chuàng)建Activity的實(shí)例對(duì)象

并為Activity的實(shí)例對(duì)象調(diào)用attach方法,為其關(guān)聯(lián)運(yùn)行過程中所以來的上下文環(huán)境變量

attch方法中,系統(tǒng)會(huì)創(chuàng)建Activity所屬的Window對(duì)象,并為其設(shè)置回調(diào)接口

Window對(duì)象的創(chuàng)建是通過PolicyManager的makeNewWindow方法實(shí)現(xiàn)。博客

Activity實(shí)現(xiàn)了window的callback接口,因此外界狀態(tài)改變時(shí)會(huì)回調(diào)Activity的方法(onAttachedToWindow、dispatchTouchEvent等等)

Activity的視圖加載的源碼分析

Dialog的Window創(chuàng)建過程

創(chuàng)建WindowDialog。和Activity類似,同樣是通過PolicyManager.makeNewWindow() 來實(shí)現(xiàn)。

初始化DecorView并將Dialog的視圖添加到DecorView中去。和Activity類似,同樣是通過Window.setContentView() 來實(shí)現(xiàn)。

將DecorView添加到Window中顯示。和Activity一樣,都是在自身要出現(xiàn)在前臺(tái)時(shí)才會(huì)將添加Window。

Dialog.show() 方法:完成DecorView的顯示。

WindowManager.remoteViewImmediate() 方法:當(dāng)Dialog被dismiss時(shí)移除DecorView。

10.0.0.6 如何處理快速連續(xù)點(diǎn)擊了多次按鈕時(shí)Toast就觸發(fā)了多次而關(guān)閉不掉?

使用中遇到的問題

例如:當(dāng)點(diǎn)擊有些按鈕,需要吐司進(jìn)行提示時(shí);快速連續(xù)點(diǎn)擊了多次按鈕,Toast就觸發(fā)了多次??赡軐?dǎo)致Toast就長(zhǎng)時(shí)間關(guān)閉不掉了。又或者我們其實(shí)已在進(jìn)行其他操作了,應(yīng)該彈出新的Toast提示,而上一個(gè)Toast卻還沒顯示結(jié)束。博客

解決的辦法

創(chuàng)建工具類:
/**
* 吐司工具類    避免點(diǎn)擊多次導(dǎo)致吐司多次,最后導(dǎo)致Toast就長(zhǎng)時(shí)間關(guān)閉不掉了
* @param context

*/
private static Toast toast;
public static void showToast(Context context, String content) {
    if (toast == null) {
        toast = Toast.makeText(context.getApplicationContext(), content, Toast.LENGTH_SHORT);
    } else {
        toast.setText(content);
    }
    toast.show();
}
```

這樣用的原理

先判斷Toast對(duì)象是否為空,如果是空的情況下才會(huì)調(diào)用makeText()方法來去生成一個(gè)Toast對(duì)象,否則就直接調(diào)用setText()方法來設(shè)置顯示的內(nèi)容,最后再調(diào)用show()方法將Toast顯示出來。由于不會(huì)每次調(diào)用的時(shí)候都生成新的Toast對(duì)象,因此剛才我們遇到的問題在這里就不會(huì)出現(xiàn)

10.0.0.7 DecorView何時(shí)才被WindowManager真正添加到Window中?Window的addView源碼分析?

DecorView何時(shí)才被WindowManager真正添加到Window中?

即使Activity的布局已經(jīng)成功添加到DecorView中,DecorView此時(shí)還沒有添加到Window中

ActivityThread的handleResumeActivity方法中,首先會(huì)調(diào)用Activity的onResume方法,接著調(diào)用Activity的makeVisible()方法

makeVisible()中完成了DecorView的添加和顯示兩個(gè)過程

Window的addView源碼分析?

WindowManager是一個(gè)接口,真正實(shí)現(xiàn)類是WindowManagerImpl,并最終以代理模式交給WindowManagerGlobal實(shí)現(xiàn)。

addView: 1-創(chuàng)建ViewRootImpl;2-將ViewRoor、DecorView、布局參數(shù)保存到WM的內(nèi)部列表中;3-ViewRoot.setView()建立ViewRoot和DecorView的聯(lián)系。

setView:1-進(jìn)行View繪制三大流程;2-會(huì)通過WindowSession完成Window的添加過程(一次IPC調(diào)用)

requestLayout:內(nèi)部調(diào)用scheduleTraversals(), 底層通過mChoreographer去監(jiān)聽下一幀的刷新信號(hào)。

mWindowSession.addToDisplay: 執(zhí)行WindowManangerService的addWindow

addWindow: 檢查參數(shù)等設(shè)置;檢查Token;將Token、Window保存到WMS中;將WindowState保存到Session中。

Window的remove源碼與解析

WindowManager中提供了兩種刪除接口:removeView異步刪除、removeViewImmediate同步刪除(不建議使用)

調(diào)用WMGlobal的removeView

調(diào)用到WMGlobal的removeViewLocked進(jìn)行真正的移除

執(zhí)行ViewRoot的die方法(): 1-同步方法直接調(diào)用doDie 2-異步方法直接發(fā)送Message

doDie(): 調(diào)用dispatchDetachedFromWindow()和WindowManagerGlobal.getInstance().doRemoveView(this)

dispatchDetachedFromWindow:博客

1回調(diào)onDetachedFromeWindow;

2垃圾回收相關(guān)操作;

3通過Session的remove()在WMS中刪除Window;

4通過Choreographer移除監(jiān)聽器

10.0.0.8 Dialog的Window創(chuàng)建過程?為什么Dialog不能用Application的Context?

Dialog的Window創(chuàng)建過程?

創(chuàng)建Window——同樣是通過PolicyManager的makeNewWindow方法完成,與Activity創(chuàng)建過程一致

初始化DecorView并將Dialog的視圖添加到DecorView中——和Activity一致(setContentView)

將DecorView添加到Window中并顯示——在Dialog的show方法中,通過WindowManager將DecorView添加到Window中(mWindowManager.addView(mDecor, 1))

Dialog關(guān)閉時(shí)會(huì)通過WindowManager來移除DecorView:mWindowManager.removeViewImmediate(mDecor)

Dialog必須采用Activity的Context,因?yàn)橛袘?yīng)用token(Application的Context沒有應(yīng)用token),也可以將Dialog的Window通過type設(shè)置為系統(tǒng)Window就不再需要token。

為什么Dialog不能用Application的Context?

Dialog本身的Token為null,在初始化時(shí)如果是使用Application或者Service的Context,在獲取到WindowManager時(shí),獲取到的token依然是null。

Dialog如果采用Activity的Context,獲取到的WindowManager是在activity.attach()方法中創(chuàng)建,token指向了activity的token。

因?yàn)橥ㄟ^Application和Service的Context將無法獲取到Token從而導(dǎo)致失敗。

10.0.0.9 什么是DecorView?如何獲取到DecorView?DecorView的職責(zé)是什么?DecorView如何被加載到Window中?

什么是DecorView

DecorView是FrameLayout的子類,它可以被認(rèn)為是Android視圖樹的根節(jié)點(diǎn)視圖。

DecorView作為頂級(jí)View,一般情況下它內(nèi)部包含一個(gè)豎直方向的LinearLayout,在這個(gè)LinearLayout里面有上下三個(gè)部分,上面是個(gè)ViewStub,延遲加載的視圖(應(yīng)該是設(shè)置ActionBar,根據(jù)Theme設(shè)置),中間的是標(biāo)題欄(根據(jù)Theme設(shè)置,有的布局沒有),下面的是內(nèi)容欄。

如何獲取到DecorView

在Activity中通過setContentView所設(shè)置的布局文件其實(shí)就是被加到內(nèi)容欄之中的,成為其唯一子View,就是上面的id為content的FrameLayout中,在代碼中可以通過content來得到對(duì)應(yīng)加載的布局。博客

ViewGroup content = (ViewGroup)findViewById(android.R.id.content);
ViewGroup rootView = (ViewGroup) content.getChildAt(0);

DecorView的職責(zé)是什么

通過源碼了解可以知道,Activity就像個(gè)控制器,不負(fù)責(zé)視圖部分。Window像個(gè)承載器,裝著內(nèi)部視圖。DecorView就是個(gè)頂層視圖,是所有View的最外層布局。ViewRoot像個(gè)連接器,負(fù)責(zé)溝通,通過硬件的感知來通知視圖,進(jìn)行用戶之間的交互。

DecorView如何被加載到Window中?博客

從Activity中的setContentView()開始。在Activity中的attach()方法中,生成了PhoneWindow實(shí)例。既然有了Window對(duì)象,那么我們就可以**設(shè)置DecorView給Window對(duì)象了。

從中獲取mContentParent。獲得到之后,然后通過installDecor方法,然后生成DecorView,不過這里操作很復(fù)雜,大概流程先從主題中獲取樣式,然后根據(jù)樣式,加載對(duì)應(yīng)的布局到DecorView中,為mContentParent添加View,即Activity中的布局。

具體可以看這篇文章:10.DecorView介紹

10.0.1.0 DecorView如何顯示出來,為什么setContentView()設(shè)置的界面,為什么在onResume()之后才對(duì)用戶可見呢?

通過setContentView()設(shè)置的界面,為什么在onResume()之后才對(duì)用戶可見呢?這就要從ActivityThread開始說起。

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {

    //就是在這里調(diào)用了Activity.attach()呀,接著調(diào)用了Activity.onCreate()和Activity.onStart()生命周期,
    //但是由于只是初始化了mDecor,添加了布局文件,還沒有把
    //mDecor添加到負(fù)責(zé)UI顯示的PhoneWindow中,所以這時(shí)候?qū)τ脩魜碚f,是不可見的
    Activity a = performLaunchActivity(r, customIntent);

    ......

    if (a != null) {
    //這里面執(zhí)行了Activity.onResume()
    handleResumeActivity(r.token, false, r.isForward,
                        !r.activity.mFinished && !r.startsNotResumed);

    if (!r.activity.mFinished && r.startsNotResumed) {
        try {
                r.activity.mCalled = false;
                //執(zhí)行Activity.onPause()
                mInstrumentation.callActivityOnPause(r.activity);
                }
        }
    }
}

重點(diǎn)看下handleResumeActivity(),在這其中,DecorView將會(huì)顯示出來,同時(shí)重要的一個(gè)角色:ViewRoot也將登場(chǎng)。

這個(gè)方法里面會(huì)調(diào)用performResumeActivity方法,這個(gè)時(shí)候,Activity.onResume()已經(jīng)調(diào)用了,但是現(xiàn)在界面還是不可見的

接著講decorView添加進(jìn)WindowManager了,但是這個(gè)時(shí)候,還是不可見的

最后執(zhí)行makeVisible,執(zhí)行了重要的操作,使得DecorView可見

當(dāng)我們執(zhí)行了Activity.makeVisible()方法之后,界面才對(duì)我們是可見的。博客

void makeVisible() {
   if (!mWindowAdded) {
        ViewManager wm = getWindowManager();
        wm.addView(mDecor, getWindow().getAttributes());//將DecorView添加到WindowManager
        mWindowAdded = true;
    }
    mDecor.setVisibility(View.VISIBLE);//DecorView可見
}

到此DecorView便可見,顯示在屏幕中。但是在這其中,wm.addView(mDecor, getWindow().getAttributes());起到了重要的作用,因?yàn)槠鋬?nèi)部創(chuàng)建了一個(gè)ViewRootImpl對(duì)象,負(fù)責(zé)繪制顯示各個(gè)子View。

最后通過WindowManagerImpl的addView方法將DecorView加載出來

看到其中實(shí)例化了ViewRootImpl對(duì)象,然后調(diào)用其setView()方法。其中setView()方法經(jīng)過一些列折騰,最終調(diào)用了performTraversals()方法,然后依照下圖流程層層調(diào)用,完成繪制,最終界面才顯示出來。

具體更加詳細(xì)的過程,可以看10.DecorView介紹

10.0.1.1 什么是ViewRoot?ViewRoot屬于View樹的一份子嗎?ViewRoot的工作流程是怎么樣的?

什么是ViewRoot

ViewRoot可能比較陌生,但是其作用非常重大。所有View的繪制以及事件分發(fā)等交互都是通過它來執(zhí)行或傳遞的。

ViewRoot對(duì)應(yīng)ViewRootImpl類,它是連接WindowManagerService和DecorView的紐帶,View的三大流程(測(cè)量(measure),布局(layout),繪制(draw))均通過ViewRoot來完成。

ViewRoot屬于View樹的一份子嗎?

ViewRoot并不屬于View樹的一份子。

從源碼實(shí)現(xiàn)上來看,它既非View的子類,也非View的父類,但是,它實(shí)現(xiàn)了ViewParent接口,這讓它可以作為View的名義上的父視圖。RootView繼承了Handler類,可以接收事件并分發(fā),Android的所有觸屏事件、按鍵事件、界面刷新等事件都是通過ViewRoot進(jìn)行分發(fā)的。博客

下面結(jié)構(gòu)圖可以清晰的揭示四者之間的關(guān)系:

10.0.1.2 吐司為何會(huì)出現(xiàn)內(nèi)存泄漏?在Toast構(gòu)造方法中創(chuàng)建NT對(duì)象是干什么用的?Toast是怎么show出來的?

吐司為何會(huì)出現(xiàn)內(nèi)存泄漏

原因在于:如果在 Toast 消失之前,Toast 持有了當(dāng)前 Activity,而此時(shí),用戶點(diǎn)擊了返回鍵,導(dǎo)致 Activity 無法被 GC 銷毀, 這個(gè) Activity 就引起了內(nèi)存泄露。

在Toast構(gòu)造方法中創(chuàng)建NT對(duì)象是干什么用的?

TN是屬于Toast內(nèi)部一個(gè)私有靜態(tài)類,它是通過aidl進(jìn)行通信,主要作用是實(shí)現(xiàn)吐司的show和hide功能。

在構(gòu)造方法中,創(chuàng)建了NT對(duì)象,那么有人便會(huì)問,NT是什么東西呢?看看NT的源碼,可以發(fā)現(xiàn)NT實(shí)現(xiàn)了ITransientNotification.Stub,提到這個(gè)感覺是不是很熟悉,沒錯(cuò),在aidl中就會(huì)用到這個(gè)。

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

在TN類中,可以看到,實(shí)現(xiàn)了AIDL的show與hide方法

TN是Toast內(nèi)部的一個(gè)私有靜態(tài)類,繼承自ITransientNotification.Stub,ITransientNotification.Stub是出現(xiàn)在服務(wù)端實(shí)現(xiàn)的Service中,就是一個(gè)Binder對(duì)象,也就是對(duì)一個(gè)aidl文件的實(shí)現(xiàn)而已

@Override
public void show(IBinder windowToken) {
    if (localLOGV) Log.v(TAG, "SHOW: " + this);
    mHandler.obtainMessage(0, windowToken).sendToTarget();
}

@Override
public void hide() {
    if (localLOGV) Log.v(TAG, "HIDE: " + this);
    mHandler.post(mHide);
}

接著看下這個(gè)ITransientNotification.aidl文件

/** @hide */
oneway interface ITransientNotification {
    void show();
    void hide();
}

Toast是怎么show出來的?

通過AIDL(Binder)通信拿到NotificationManagerService的服務(wù)訪問接口,然后把TN對(duì)象和一些參數(shù)傳遞到遠(yuǎn)程N(yùn)otificationManagerService中去

當(dāng) Toast在show的時(shí)候,然后把這個(gè)請(qǐng)求放在 NotificationManager 所管理的隊(duì)列中,并且為了保證 NotificationManager 能跟進(jìn)程交互,會(huì)傳遞一個(gè)TN類型的Binder對(duì)象給NotificationManager系統(tǒng)服

然后通過service.enqueueToast方法,record是將Toast封裝成ToastRecord對(duì)象,放入mToastQueue中。通過下面代碼可以得知:通過isSystemToast判斷是否為系統(tǒng)Toast。如果當(dāng)前Toast所屬的進(jìn)程的包名為“android”,則為系統(tǒng)Toast。如果是系統(tǒng)Toast一定可以進(jìn)入到系統(tǒng)Toast隊(duì)列中,不會(huì)被黑名單阻止。

10.0.1.3 連續(xù)吐司是如何確定吐司的先后順序?為什么Toast執(zhí)行show后過了一會(huì)兒就自動(dòng)銷毀?

連續(xù)吐司是如何確定吐司的先后順序?

主要是說一下showNextToastLocked()方法中的源代碼

首先獲取吐司消息隊(duì)列中第一個(gè)ToastRecord對(duì)象,然后判斷該對(duì)象如果不為null的話,就開始通過callback進(jìn)行show,且傳遞了token參數(shù),注意這個(gè)show是通知進(jìn)程顯示。然后再調(diào)用scheduleTimeoutLocked(record)方法執(zhí)行超時(shí)后自動(dòng)取消的邏輯。同時(shí)需要注意的時(shí),如果出現(xiàn)了異常,則會(huì)從吐司消息隊(duì)列中移除該record……

那么callback是干嘛的呢,一般印象中callback是處理回調(diào)的?從ITransientNotification callback得知,這個(gè)callback哥們竟然是是一個(gè) ITransientNotification 類型的對(duì)象,也就是前面說到的TN的Binder代理對(duì)象。

簡(jiǎn)而言之,也就是說,TN中的消息機(jī)制也是通過handler進(jìn)行實(shí)現(xiàn)的。在show方法中發(fā)送消息,當(dāng)mHandler接受到消息之后,就調(diào)用handleShow(token)處理邏輯,通過WindowManager將view添加進(jìn)來,同時(shí)在該方法中也設(shè)置了大量的布局屬性。

為什么Toast執(zhí)行show后過了一會(huì)兒就自動(dòng)銷毀?博客

回調(diào)了Toast的TN的show,當(dāng)timeout可能就是hide呢。分析NotificationManagerService源碼中的showNextToastLocked()的scheduleTimeoutLocked(record)源碼,可以知道在NotificationManagerService通過handler延遲delay時(shí)間發(fā)送消息,然后通過callback調(diào)用hide,由于callback是TN中Binder的代理對(duì)象, 所以便可以調(diào)用到TN中的hide方法達(dá)到銷毀吐司的目的。

handleHide()源碼如下所示,可知當(dāng)銷毀后先將view移除,然后在置空操作。

public void handleHide() {
    if (localLOGV) Log.v(TAG, "HANDLE HIDE: " + this + " mView=" + mView);
    if (mView != null) {
        // note: checking parent() just to make sure the view has
        // been added...  i have seen cases where we get here when
        // the view isn"t yet added, so let"s try not to crash.
        if (mView.getParent() != null) {
            if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
            mWM.removeViewImmediate(mView);
        }

        mView = null;
    }
}

10.0.1.4 如何理解普通應(yīng)用的Toast顯示數(shù)量是有限制的?為什么要判斷是否是系統(tǒng)吐司?為何Activity銷毀后Toast仍會(huì)顯示?

如何理解普通應(yīng)用的Toast顯示數(shù)量是有限制的?

如何判斷是否是系統(tǒng)吐司呢?如果當(dāng)前Toast所屬的進(jìn)程的包名為“android”,則為系統(tǒng)Toast,或者調(diào)用isCallerSystem()方法

final boolean isSystemToast = isCallerSystem() || ("android".equals(pkg));

接著看看isCallerSystem()方法源碼,isCallerSystem的源碼也比較簡(jiǎn)單,就是判斷當(dāng)前Toast所屬進(jìn)程的uid是否為SYSTEM_UID、0、PHONE_UID中的一個(gè),如果是,則為系統(tǒng)Toast;如果不是,則不為系統(tǒng)Toast。

private static boolean isUidSystem(int uid) {
    final int appid = UserHandle.getAppId(uid);
    return (appid == Process.SYSTEM_UID || appid == Process.PHONE_UID || uid == 0);
}

private static boolean isCallerSystem() {
    return isUidSystem(Binder.getCallingUid());
}

為什么要判斷是否是系統(tǒng)吐司?

從源碼可知:首先系統(tǒng)Toast一定可以進(jìn)入到系統(tǒng)Toast隊(duì)列中,不會(huì)被黑名單阻止。然后系統(tǒng)Toast在系統(tǒng)Toast隊(duì)列中沒有數(shù)量限制,而普通pkg所發(fā)送的Toast在系統(tǒng)Toast隊(duì)列中有數(shù)量限制。

那么關(guān)于數(shù)量限制這個(gè)結(jié)果從何而來,大概是多少呢?查看將要入隊(duì)的Toast是否已經(jīng)在系統(tǒng)Toast隊(duì)列中。這是通過比對(duì)pkg和callback來實(shí)現(xiàn)的。通過下面源碼分析可知:只要Toast的pkg名稱和tn對(duì)象是一致的,則系統(tǒng)把這些Toast認(rèn)為是同一個(gè)Toast。

然后再看看下面這個(gè)源碼截圖,可知,非系統(tǒng)Toast,每個(gè)pkg在當(dāng)前mToastQueue中Toast有總數(shù)限制,不能超過MAX_PACKAGE_NOTIFICATIONS,也就是50

為何Activity銷毀后Toast仍會(huì)顯示

記得以前昊哥問我,為何toast在activity銷毀后仍然會(huì)彈出呢,我毫不思索地說,因?yàn)閠oast是系統(tǒng)級(jí)別的呀。那么是如何實(shí)現(xiàn)的呢,我就無言以對(duì)呢……今天終于可以回答呢!

還是回到NotificationManagerService類中的enqueueToast方法中,直接查看keepProcessAliveIfNeededLocked(callingPid)方法。這段代碼的意思是將當(dāng)前Toast所在進(jìn)程設(shè)置為前臺(tái)進(jìn)程,這里的mAm = ActivityManager.getService(),調(diào)用了setProcessImportant方法將當(dāng)前pid的進(jìn)程置為前臺(tái)進(jìn)程,保證不會(huì)系統(tǒng)殺死。這也就解釋了為什么當(dāng)我們finish當(dāng)前Activity時(shí),Toast還可以顯示,因?yàn)楫?dāng)前進(jìn)程還在執(zhí)行。

10.0.1.5 為什么說Toast盡量用全局上下文?說一下Toast的顯示和隱藏重點(diǎn)邏輯,說下你的理解?

為什么說Toast盡量用全局上下文?

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

有時(shí)候我們會(huì)發(fā)現(xiàn)Toast彈出過多就會(huì)延遲顯示,因?yàn)樯厦嬖创a分析可以看見Toast.makeText是一個(gè)靜態(tài)工廠方法,每次調(diào)用這個(gè)方法都會(huì)產(chǎn)生一個(gè)新的Toast對(duì)象,當(dāng)我們?cè)谶@個(gè)新new的對(duì)象上調(diào)用show方法就會(huì)使這個(gè)對(duì)象加入到NotificationManagerService管理的mToastQueue消息顯示隊(duì)列里排隊(duì)等候顯示;所以如果我們不每次都產(chǎn)生一個(gè)新的Toast對(duì)象(使用單例來處理)就不需要排隊(duì),也就能及時(shí)更新呢。

說一下Toast的顯示和隱藏重點(diǎn)邏輯,說下你的理解?博客

Toast調(diào)用show方法 ,其實(shí)就是是將自己納入到NotificationManager的Toast管理中去,期間傳遞了一個(gè)本地的TN類型或者是 ITransientNotification.Stub的Binder對(duì)象

NotificationManager 收到 Toast 的顯示請(qǐng)求后,將生成一個(gè) Binder 對(duì)象,將它作為一個(gè)窗口的 token 添加到 WMS 對(duì)象,并且類型是 TOAST

NotificationManager 將這個(gè)窗口token通過ITransientNotification的show方法傳遞給遠(yuǎn)程的TN對(duì)象,并且拋出一個(gè)超時(shí)監(jiān)聽消息 scheduleTimeoutLocked

TN 對(duì)象收到消息以后將往 Handler 對(duì)象中 post 顯示消息,然后調(diào)用顯示處理函數(shù)將 Toast 中的 View 添加到了 WMS 管理中,Toast窗口顯示

NotificationManager的WorkerHandler收到MESSAGE_TIMEOUT消息, NotificationManager遠(yuǎn)程調(diào)用hide方法進(jìn)程隱藏Toast 窗口,然后將窗口token從WMS中刪除,并且判斷吐司消息隊(duì)列中是否還有消息,如果有,則繼續(xù)吐司!

10.0.1.6 Toast報(bào)錯(cuò)Unable to add window是什么意思?Toast運(yùn)行在子線程會(huì)問題,在子線程或者service中能運(yùn)行嗎?

Toast偶爾報(bào)錯(cuò)Unable to add window

報(bào)錯(cuò)日志,是不是有點(diǎn)眼熟呀?更多可以看我的開源項(xiàng)目:https://github.com/yangchong211

android.view.WindowManager$BadTokenException
    Unable to add window -- token android.os.BinderProxy@7f652b2 is not valid; is your activity running?

查詢報(bào)錯(cuò)日志是從哪里來的

發(fā)生該異常的原因

這個(gè)異常發(fā)生在Toast顯示的時(shí)候,原因是因?yàn)閠oken失效。通常情況下,一般是不會(huì)出現(xiàn)這種異常。但是由于在某些情況下, Android進(jìn)程某個(gè)UI線程的某個(gè)消息阻塞。導(dǎo)致 TN 的 show 方法 post 出來 0 (顯示) 消息位于該消息之后,遲遲沒有執(zhí)行。這時(shí)候,NotificationManager 的超時(shí)檢測(cè)結(jié)束,刪除了 WMS 服務(wù)中的 token 記錄。刪除 token 發(fā)生在 Android 進(jìn)程 show 方法之前。這就導(dǎo)致了上面的異常。

測(cè)試代碼。模擬一下異常的發(fā)生場(chǎng)景,其實(shí)很容易,只需要這樣做就可以出現(xiàn)上面這個(gè)問題

 Toast.makeText(this,"瀟湘劍雨-yc",Toast.LENGTH_SHORT).show();
    try {
        Thread.sleep(20000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

解決辦法,目前見過好幾種,思考一下那種比較好……

第一種,既然是報(bào)is your activity running,那可以不可以在吐司之前先判斷一下activity是否running呢?

第二種,拋出異常增加try-catch,代碼如下所示,最后仍然無法解決問題

按照源碼分析,異常是發(fā)生在下一個(gè)UI線程消息中,因此在上一個(gè)ui線程消息中加入try-catch是沒有意義的。而且用到吐司地方這么多,這樣做也不方便啦!

第三種,那就是自定義類似吐司Toast的view控件。個(gè)人建議除非要求非常高,不然不要這樣做。畢竟發(fā)生這種異常還是比較少見的

哪些情況會(huì)發(fā)生該問題?

UI 線程執(zhí)行了一條非常耗時(shí)的操作,比如加載圖片等等,就類似上面用 sleep 模擬情況

進(jìn)程退后臺(tái)或者息屏了,系統(tǒng)為了減少電量或者某種原因,分配給進(jìn)程的cpu時(shí)間減少,導(dǎo)致進(jìn)程內(nèi)的指令并不能被及時(shí)執(zhí)行,這樣一樣會(huì)導(dǎo)致進(jìn)程看起來”卡頓”的現(xiàn)象

當(dāng)TN拋出消息的時(shí)候,前面有大量的 UI 線程消息等待執(zhí)行,而每個(gè) UI 線程消息雖然并不卡頓,但是總和如果超過了 NotificationManager 的超時(shí)時(shí)間,還是會(huì)出現(xiàn)問題

Toast運(yùn)行在子線程問題

先來看看問題代碼,會(huì)出現(xiàn)什么問題呢?

new Thread(new Runnable() {
    @Override
    public void run() {
        ToastUtils.showRoundRectToast("瀟湘劍雨-楊充");
    }
}).start();

報(bào)錯(cuò)日志如下所示:

子線程中吐司的正確做法,代碼如下所示

new Thread(new Runnable() {
    @Override
    public void run() {
        Looper.prepare();
        ToastUtils.showRoundRectToast("瀟湘劍雨-楊充");
        Looper.loop();
    }
}).start();

得出的結(jié)論

Toast也可以在子線程執(zhí)行,不過需要手動(dòng)提供Looper環(huán)境的。

Toast在調(diào)用show方法顯示的時(shí)候,內(nèi)部實(shí)現(xiàn)是通過Handler執(zhí)行的,因此自然是不阻塞Binder線程,另外,如果addView的線程不是Loop線程,執(zhí)行完就結(jié)束了,當(dāng)然就沒機(jī)會(huì)執(zhí)行后續(xù)的請(qǐng)求,這個(gè)是由Hanlder的構(gòu)造函數(shù)保證的。可以看看handler的構(gòu)造函數(shù),如果Looper==null就會(huì)報(bào)錯(cuò),而Toast對(duì)象在實(shí)例化的時(shí)候,也會(huì)為自己實(shí)例化一個(gè)Hanlder,這就是為什么說“一定要在主線程”,其實(shí)準(zhǔn)確的說應(yīng)該是 “一定要在Looper非空的線程”。博客

10.0.1.7 為什么建議用DialogFragment替代Dialog?如何定義DialogFragment樣式?使用dialogFragment有何好處?

為什么建議用DialogFragment替代Dialog

Android比較推薦采用DialogFragment實(shí)現(xiàn)對(duì)話框,它完全能夠?qū)崿F(xiàn)Dialog的所有需求,并且還能復(fù)用Fragment的生命周期管理,被后臺(tái)殺死后,可以恢復(fù)重建。

在手機(jī)配置變化導(dǎo)致 Activity 需要重新創(chuàng)建時(shí),例如旋轉(zhuǎn)屏幕,基于 DialogFragment 的對(duì)話框?qū)?huì)由 FragmentManager 自動(dòng)重建,然而基于 Dialog 實(shí)現(xiàn)的對(duì)話框卻沒有這樣的能力。

如何定義DialogFragment樣式

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    if (local == BOTTOM) {
        setStyle(DialogFragment.STYLE_NO_TITLE, R.style.BottomDialog);
    } else if (local == CENTER || local == TOP) {
        setStyle(DialogFragment.STYLE_NO_TITLE, R.style.CenterDialog);
    }
}

創(chuàng)建theme主題樣式,并且進(jìn)行設(shè)置

設(shè)置樣式,以DialogFragment為例,只需要在onCreate中setStyle(DialogFragment.STYLE_NO_TITLE, R.style.CenterDialog)即可。

注意,CenterDialog中可以設(shè)置彈窗的動(dòng)畫效果。

注意一下style常量,這里只是展示常用的。

STYLE_NORMAL:會(huì)顯示一個(gè)普通的dialog
STYLE_NO_TITLE:不帶標(biāo)題的dialog
STYLE_NO_FRAME:無框的dialog
STYLE_NO_INPUT:無法輸入內(nèi)容的dialog,即不接收輸入的焦點(diǎn),而且觸摸無效。

注意動(dòng)畫設(shè)置如下所示

使用dialogFragment有何好處?

DialogFragment是繼承Fragment,具有Fragment的生命周期,本質(zhì)上說就是Fragment,只是其內(nèi)部還有一個(gè)dialog而已。你既可以當(dāng)它是Dialog使用,也可以把它作為Fragment使用。

onCreateView可以加載客戶化更高的對(duì)話框,onCreateDialog加載系統(tǒng)AlertDialog類型對(duì)話框比較合適。

DialogFragmnet對(duì)話框橫屏?xí)r對(duì)話框不會(huì)關(guān)閉,因?yàn)镈ailogFragment有Fragment屬性,會(huì)在屏幕發(fā)生變化時(shí)重新創(chuàng)建DialogFragment。博客

setStyle的調(diào)用點(diǎn),要放在onCreateView前,一般是放在onCreat方法中執(zhí)行,否則,設(shè)置的style和theme將不起作用!setStyle中,style的參數(shù)是不可以相互一起使用的,只能用一個(gè),如果還不滿足你使用,可以通過設(shè)置theme來滿足。

10.0.1.8 Dialog的Window創(chuàng)建過程是怎樣的?為什么Dialog不能用Application的Context,說一下原因?

Dialog的Window創(chuàng)建過程是怎樣的?

創(chuàng)建Window——同樣是通過PolicyManager的makeNewWindow方法完成,與Activity創(chuàng)建過程一致

初始化DecorView并將Dialog的視圖添加到DecorView中——和Activity一致(setContentView)

將DecorView添加到Window中并顯示——在Dialog的show方法中,通過WindowManager將DecorView添加到Window中(mWindowManager.addView(mDecor, 1))

Dialog關(guān)閉時(shí)會(huì)通過WindowManager來移除DecorView:mWindowManager.removeViewImmediate(mDecor)

Dialog必須采用Activity的Context,因?yàn)橛袘?yīng)用token(Application的Context沒有應(yīng)用token),也可以將Dialog的Window通過type設(shè)置為系統(tǒng)Window就不再需要token。

為什么Dialog不能用Application的Context,說一下原因?

Dialog本身的Token為null,在初始化時(shí)如果是使用Application或者Service的Context,在獲取到WindowManager時(shí),獲取到的token依然是null。

Dialog如果采用Activity的Context,獲取到的WindowManager是在activity.attach()方法中創(chuàng)建,token指向了activity的token。

因?yàn)橥ㄟ^Application和Service的Context將無法獲取到Token從而導(dǎo)致失敗。

10.0.1.9 Dialog和Window有什么關(guān)系?Dialog的dismiss和cancel()方法都可銷毀彈窗,它們有什么區(qū)別?

Dialog和Window有什么關(guān)系?

看源碼可知在Dialog的構(gòu)造方法中直接直接構(gòu)造了一個(gè)PhoneWindow,并賦值給Dialog的成員變量mWindow,從這里可以看出其實(shí)Dialog和Activity的顯示邏輯都是類似的,都是通過對(duì)應(yīng)的Window變量來實(shí)現(xiàn)窗口的加載與顯示的。然后我們執(zhí)行了一些Window對(duì)象的初始化操作,比如設(shè)置回調(diào)函數(shù)為本身,然后調(diào)用Window類的setWindowManager方法,并傳入了WindowManager。然后創(chuàng)建一個(gè)對(duì)話框監(jiān)聽handler對(duì)象。

Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
    if (createContextThemeWrapper) {
        if (themeResId == 0) {
            final TypedValue outValue = new TypedValue();
            context.getTheme().resolveAttribute(R.attr.dialogTheme, outValue, true);
            themeResId = outValue.resourceId;
        }
        //創(chuàng)建一個(gè)Context
        mContext = new ContextThemeWrapper(context, themeResId);
    } else {
        mContext = context;
    }

    //獲取一個(gè)WindowManager對(duì)象
    mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
    //創(chuàng)建一個(gè)Window對(duì)象
    final Window w = new PhoneWindow(mContext);
    //將Window對(duì)象w賦值給mWindow
    mWindow = w;
    //為Windowd對(duì)象設(shè)置回調(diào),并且它本身實(shí)現(xiàn)了這些回調(diào)函數(shù)
    w.setCallback(this);
    w.setOnWindowDismissedCallback(this);
    //為Window對(duì)象設(shè)置WindowManager對(duì)象
    w.setWindowManager(mWindowManager, null, null);
    w.setGravity(Gravity.CENTER);
    //創(chuàng)建一個(gè)對(duì)話框監(jiān)聽Handler
    mListenersHandler = new ListenersHandler(this);
}

Dialog的dismiss和cancel()方法都可銷毀彈窗,它們有什么區(qū)別?

調(diào)用alertDialog.cancel()或者alertDialog.dismiss()都可以達(dá)到銷毀彈窗的效果。

如果沒有設(shè)置setOnCancelListener事件,則兩個(gè)方法是等效的。為什么這樣說呢?

首先看一下Dialog的cannel方法的具體實(shí)現(xiàn):可以看到方法體中,若當(dāng)前Dialog沒有取消,并且設(shè)置了取消message,則調(diào)用Message.obtain(mCancelMessage).sendToTarget()。而這個(gè)mCancelMessage則是在setOnCancelListener方法中創(chuàng)建的。調(diào)用的是設(shè)置的OnCancelListener的onCancel方法,也就是說調(diào)用dialog.cancel方法時(shí)首先會(huì)判斷dialog是否調(diào)用了setOnCancelListener若設(shè)置了,則先調(diào)用OnCancelListener的onCancel方法,然后再次執(zhí)行dismiss方法,若我們沒有為Dialog.Builder設(shè)置OnCancelListener那么cancel方法和dismiss方法是等效的。博客

public void cancel() {
    if (!mCanceled && mCancelMessage != null) {
        mCanceled = true;
        // Obtain a new message so this dialog can be re-used
        Message.obtain(mCancelMessage).sendToTarget();
    }
    dismiss();
}

public void setOnCancelListener(final OnCancelListener listener) {
    if (listener != null) {
        mCancelMessage = mListenersHandler.obtainMessage(CANCEL, listener);
    } else {
        mCancelMessage = null;
    }
}

private static final class ListenersHandler extends Handler {
    private WeakReference mDialog;

    public ListenersHandler(Dialog dialog) {
        mDialog = new WeakReference(dialog);
    }

    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case DISMISS:
                ((OnDismissListener) msg.obj).onDismiss(mDialog.get());
                break;
            case CANCEL:
                ((OnCancelListener) msg.obj).onCancel(mDialog.get());
                break;
            case SHOW:
                ((OnShowListener) msg.obj).onShow(mDialog.get());
                break;
        }
    }
}

dismiss方法主要是做了什么?

可以看到,這里首先判斷當(dāng)前線程的Looper是否是主線程的Looper(由于mHandler是在主線程中創(chuàng)建的,所以mHandler.getLooper返回的是主線程中創(chuàng)建的Looper對(duì)象),若是的話,則直接執(zhí)行dismissDialog()方法,否則的話,通過mHandler發(fā)送異步消息至主線程中,簡(jiǎn)單來說就是判斷當(dāng)前線程是否是主線程,若是主線程則執(zhí)行dismissDialog方法否則發(fā)送異步消息。而這里的異步消息最終也是調(diào)用的dismissDialog方法

public void dismiss() {
    if (Looper.myLooper() == mHandler.getLooper()) {
        dismissDialog();
    } else {
        mHandler.post(mDismissAction);
    }
}

10.0.2.0 PopupWindow中不設(shè)置為什么必須設(shè)置寬高?PopupWindow和Dialog有什么區(qū)別?說下創(chuàng)建和銷毀的大概流程?

PopupWindow中不設(shè)置為什么必須設(shè)置寬高?

先看問題代碼,下面這個(gè)不會(huì)出現(xiàn)彈窗,思考:為什么?

PopupWindow popupWindow = new PopupWindow(this);
View inflate = LayoutInflater.from(this).inflate(R.layout.view_pop_custom, null);
popupWindow.setContentView(inflate);
popupWindow.setAnimationStyle(R.style.BottomDialog);
popupWindow.showAsDropDown(mTv1);

注意:必須設(shè)置寬和高,否則不顯示任何東西

這里的WRAP_CONTENT可以換成fill_parent 也可以是具體的數(shù)值,它是指PopupWindow的大小,也就是contentview的大小,注意popupwindow根據(jù)這個(gè)大小顯示你的View,如果你的View本身是從xml得到的,那么xml的第一層view的大小屬性將被忽略。相當(dāng)于popupWindow的width和height屬性直接和第一層View相對(duì)應(yīng)。

PopupWindow和Dialog有什么區(qū)別?

兩者最根本的區(qū)別在于有沒有新建一個(gè)window,PopupWindow沒有新建,而是將view加到DecorView;Dialog是新建了一個(gè)window,相當(dāng)于走了一遍Activity中創(chuàng)建window的流程

從源碼中可以看出,PopupWindow最終是執(zhí)行了mWindowManager.addView方法,全程沒有新建window

說下創(chuàng)建和銷毀的大概流程?

創(chuàng)建PopupWindow的時(shí)候,先創(chuàng)建WindowManager,因?yàn)閃IndowManager擁有控制view的添加和刪除、修改的能力。這一點(diǎn)關(guān)于任主席的藝術(shù)探索書上寫的很詳細(xì)……博客

然后是setContentView,保存contentView,這個(gè)步驟就做了這個(gè)

顯示PopupWindow,這個(gè)步驟稍微復(fù)雜點(diǎn),創(chuàng)建并初始化LayoutParams,設(shè)置相關(guān)參數(shù),作為以后PopupWindow在應(yīng)用DecorView里哪里顯示的憑據(jù)。然后創(chuàng)建PopupView,并且將contentView插入其中。最后使用WindowManager將PopupView添加到應(yīng)用DecorView里。

銷毀PopupView,WindowManager把PopupView移除,PopupView再把contentView移除,最后把對(duì)象置為null

為何彈窗點(diǎn)擊一下就dismiss呢?

PopupWindow通過為傳入的View添加一層包裹的布局,并重寫該布局的點(diǎn)擊事件,實(shí)現(xiàn)點(diǎn)擊PopupWindow之外的區(qū)域PopupWindow消失的效果

10.0.2.1 Snackbar與吐司有何區(qū)別在哪里?Snackbar控件show時(shí)為何從下往上移出來?為什么顯示在最下面?

Snackbar與吐司有何區(qū)別

與Toast進(jìn)行比較,SnackBar有優(yōu)勢(shì):

1.SnackBar可以自動(dòng)消失,也可以手動(dòng)取消(側(cè)滑取消,但是需要在特殊的布局中,后面會(huì)仔細(xì)說)

2.SnackBar可以通過setAction()來與用戶進(jìn)行交互

3.通過CallBack我們可以獲取SnackBar的狀態(tài)

Snackbar控件show時(shí)為何從下往上移出來?

至于說Snackbar控件show時(shí)為何從下往上移出來,看下面這段代碼就知道呢,如下所示

為什么顯示在最下面?

直接找到make方法中的填充布局,然后去看design_layout_snackbar_include的布局參數(shù),結(jié)果如下:

Snackbar顯示會(huì)導(dǎo)致FloatingActionButton上移?

為什么CoordinatorLayout + FloatingActionButton,當(dāng)Snackbar顯示的時(shí)候FloatingActionButton會(huì)上移呢,這個(gè)是怎么實(shí)現(xiàn)的?

把CoordinatorLayout替換成FrameLayout確不行。這個(gè)問題我們還沒說。其實(shí)這個(gè)不是在Snackbar里面處理的,是通過CoordinatorLayout和Behavior來處理的。那具體的處理在哪里呢。FloatingActionButton類里面Behavior類。正是Behavior里面的兩個(gè)函數(shù)layoutDependsOn()和onDependentViewChanged()函數(shù)作用的結(jié)果。直接進(jìn)去看下FloatingActionButton內(nèi)部類Behavior里面這兩個(gè)函數(shù)的代碼。

10.0.2.2 說一下Snackbar和SnackbarManager類的設(shè)計(jì)有哪些奧妙的地方,如何處理消息的顯示順序?

Snackbar和SnackbarManager,SnackbarManager內(nèi)部有兩個(gè)SnackbarRecord,一個(gè)mCurrentSnackbar,一個(gè)mNextSnackbar,SnackbarManager通過這兩個(gè)對(duì)象實(shí)現(xiàn)Snackbar的順序顯示,如果在一個(gè)Snackbar顯示之前有Snackbar正在顯示,那么使用mNextSnackbar保存第二個(gè)Snackbar,然后讓第一個(gè)Snackbar消失,然后消失之后再調(diào)用SnackbarManager顯示下一個(gè)Snackbar,如此循環(huán),實(shí)現(xiàn)了Snackbar的順序顯示。 博客

Snackbar負(fù)責(zé)顯示和消失,具體來說其實(shí)就是添加和移除View的過程。Snackbar和SnackbarManager的設(shè)計(jì)很巧妙,利用一個(gè)SnackbarRecord對(duì)象保存Snackbar的顯示時(shí)間以及SnackbarManager.Callback對(duì)象,前面說到每一個(gè)Snackbar都有一個(gè)叫做mManagerCallback的SnackbarManager.Callback對(duì)象,下面看一下SnackRecord類的定義:

Snackbar向SnackbarManager發(fā)送消息主要是調(diào)用SnackbarManager.getInstace()返回一個(gè)單例對(duì)象;而SnackManager向Snackbar發(fā)送消息就是通過show方法傳入的Callback對(duì)象。SnackbarManager中的Handler只處理一個(gè)MSG_TIMEOUT事件,最后是調(diào)用Snackbar的hideView消失的;Snackbar的sHandler處理兩個(gè)消息,showView和hideView,而消息的發(fā)送者是mManagerCallback,控制者是SnackbarManager。

其他介紹 01.關(guān)于博客匯總鏈接

1.技術(shù)博客匯總

2.開源項(xiàng)目匯總

3.生活博客匯總

4.喜馬拉雅音頻匯總

5.其他匯總

02.關(guān)于我的博客

github:https://github.com/yangchong211

知乎:https://www.zhihu.com/people/...

簡(jiǎn)書:http://www.jianshu.com/u/b7b2...

csdn:http://my.csdn.net/m0_37700275

喜馬拉雅聽書:http://www.ximalaya.com/zhubo...

開源中國:https://my.oschina.net/zbj161...

泡在網(wǎng)上的日子:http://www.jcodecraeer.com/me...

郵箱:[email protected]

阿里云博客:https://yq.aliyun.com/users/a... 239.headeruserinfo.3.dT4bcV

segmentfault頭條:https://segmentfault.com/u/xi...

掘金:https://juejin.im/user/593943...

彈窗開源庫地址:https://github.com/yangchong2... 筆記開源庫地址:https://github.com/yangchong2...

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/74480.html

相關(guān)文章

  • 移動(dòng)常見疑難問題

    摘要:是在系列事件發(fā)生后大約才觸發(fā)的,混用和就會(huì)導(dǎo)致點(diǎn)透問題。獲取視圖原始高度方案二能較好地處理滾動(dòng)的問題。禁止蒙層底下頁面跟隨滾動(dòng)原因彈窗是常見的交互方式,而蒙層是彈窗必不可少的元素。 平時(shí)的開發(fā)過程中,經(jīng)常會(huì)遇到一些疑難雜癥,在這里記錄一下常用的解決方案。 UI小姐姐要求的0.5px線 原因:不同手機(jī)的兼容不一樣,尤其安卓 IOS的Safari表現(xiàn)是比較好的,safari是可以支持浮...

    klivitamJ 評(píng)論0 收藏0
  • 移動(dòng)常見疑難問題

    摘要:是在系列事件發(fā)生后大約才觸發(fā)的,混用和就會(huì)導(dǎo)致點(diǎn)透問題。獲取視圖原始高度方案二能較好地處理滾動(dòng)的問題。禁止蒙層底下頁面跟隨滾動(dòng)原因彈窗是常見的交互方式,而蒙層是彈窗必不可少的元素。 平時(shí)的開發(fā)過程中,經(jīng)常會(huì)遇到一些疑難雜癥,在這里記錄一下常用的解決方案。 UI小姐姐要求的0.5px線 原因:不同手機(jī)的兼容不一樣,尤其安卓 IOS的Safari表現(xiàn)是比較好的,safari是可以支持浮...

    KevinYan 評(píng)論0 收藏0
  • 關(guān)于 vue 彈窗組件的一些感想

    摘要:最近是用開發(fā)了一套組件庫在開發(fā)過程對(duì)對(duì)于組件化的開發(fā)有一些感想,于是開始記錄下這些。彈窗組件一直是開發(fā)中必備的,使用頻率相當(dāng)高,最常見的莫過于,,這些曾經(jīng)我們都會(huì)用來調(diào)試程序不同的組件庫對(duì)于彈窗的處理也是不一樣的。 最近是用 vue 開發(fā)了一套組件庫 vue-carbon , 在開發(fā)過程對(duì)對(duì)于組件化的開發(fā)有一些感想,于是開始記錄下這些。 彈窗組件一直是 web 開發(fā)中必備的,使用頻率相...

    idealcn 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<