1. 程式人生 > >在Activity的onCreate方法中顯示PopupWindow導致異常的原因分析及解決方案

在Activity的onCreate方法中顯示PopupWindow導致異常的原因分析及解決方案

一、前言
        在某些情況下,我們需要一進入Activity就顯示PopupWindow,比如常見的選擇介面。但由於PopupWindow是依附於Activity的,如果Activity沒有建立完成,Activity還沒完全顯示出來就顯示PopupWindow的話,會出現異常現象。

二、問題復現
我在Activity的onCreate()方法中呼叫如下方法:
    public void show( ){
        if( null != mPopupWindow ){
            mPopupWindow.showAtLocation(mView, Gravity.CENTER, 0, 0);
        }
    }

執行程式的時候出現如下異常:



三、解決方案
        在StackOverflow上搜索這個問題你會發現,都沒有原因分析,但我在《Android開發精要》一書中找到了答案(P158):

        PopupWindow不像對話方塊那樣從螢幕的固定位置彈出,而是依賴於錨點控制元件物件的位置,所謂錨點控制元件物件,就是介面元件中的某個控制元件,PopupWindow的展示和功能都是以它為核心,作為錨點控制元件的擴充套件互動介面,以增強該控制元件物件的功能。

        彈出視窗與描點控制元件有著緊密的聯絡,在構造並展示彈出視窗前,需要保證錨點控制元件所在的控制元件樹已經與視窗管理服務建立連線,因為在彈出視窗的展示過程中,需要通過該視窗物件來獲取相關資訊。在介面元件的構造過程中,視窗連線的建立是個非同步過程,也就是說,當Activity.onCreate()等函式被呼叫時,介面與視窗管理服務的雙向通訊連線尚未建立,如果在此時構造彈出視窗則會丟擲異常。因此,如果期望在介面元件展現之處便構造彈出視窗,可以將彈出視窗物件構造也轉換成一個非同步過程。


// 在介面元件onCreate函式中呼叫
final View anchor = findViewById(R.id.anchor);
    anchor.post(new Runnable(){
    @Override
    public void run(){
        // 構造和展現彈出視窗
        PopupWindow window = createWindow();
        window.showAsDropDown(anchor);
    }
});

        在與視窗管理服務未建立連線之前,介面元件將通過View.post函式傳送過來的訊息放入一個靜態隊列當中,在通訊連線建立完成後,再從該佇列中讀取訊息並一一執行。因此,通過這樣的實現模型可以保證彈出視窗展現時視窗通訊連線已經構建成功。


所以對於上面的問題,最簡單的處理方法是,非同步顯示PopupWindow就好了:
    public void show( ){
        mView.post( new Runnable( ) {
            @Override
            public void run() {
                if( null != mPopupWindow ){
                    mPopupWindow.showAtLocation(mView, Gravity.CENTER, 0, 0);
                }
            }
        });
    }

四、題外話

今天想做一個在PopupWindow裡面播放視訊的功能,結果發現SurfaceView在PopupWindow中是無法正常顯示的,如果需要顯示SurfaceView,建議用View、Fragment或者Dialog Activity代替PopupWindow。我在StackOverflow上查了一下,說是SurfaceView必須要依附於window,但PopupWindow是依附於View的,所以Surfaceview在PopupWindow中無法正常建立,可參見:SurfaceView not working inside PopupWindow