填坑之旅 -- android.view.WindowManager$BadTokenException崩潰

語言: CN / TW / HK

此文包含android.view.WindowManager$BadTokenException的4種情形:

  • 1.Unable to add window --token null is not valid; is your activity running

  • 2.Unable to add window --token null is not for an application

  • 3.Unable to add window -- token [email protected] is not valid;is your activity running

  • 4.Unable to add window -- token android.app.LocalActivityManager$LocalActivityRecord @xxx is not valid; is your activity running

情形1.android.view.WindowManager$BadTokenException: Unable to add window --token null is not valid; is your activity running?異常處理。

``` $BadTokenException: Unable to add window --

token null is not valid; is your activity running

E/AndroidRuntime(1412): at android.view.ViewRootImpl.setView(ViewRootImpl.java:538)

...... ```

該異常多見於Popup Window元件的使用中丟擲。

原因:錯誤在PopupWindow.showAtLocation(findViewById(R.id.main), Gravity.BOTTOM,0,0); popwindow必須依附於某一個view,而在oncreate中view還沒有載入完畢,必須要等activity的生命週期函式全部執行完畢,你需要依附的view載入好後才可以執行popwindow。

解決辦法:showAtLocation()函式可以這樣改:

``` //修正後代碼

findviewById(R.id.mView).post(new Runnable() {

                    @Override

                    public void run() {

                            popwindow.showAtLocation(mView, Gravity.CENTER, 0, 0);

                    }

            });

```

總結: PopupWindow必須在某個事件中顯示或者是開啟一個新執行緒去呼叫,不能直接在onCreate方法中顯示一個Popupwindow,否則永遠會有以上的錯誤。

參考

http://stackoverflow.com/questions/4187673/problems-creating-a-popup-window-in-android-activity

情形2.android.view.WindowManager$BadTokenException: Unable to add window --token null is not for an application ?異常處理。

``` $BadTokenException: Unable to add window --

token null is not for an application

E/AndroidRuntime(1412): at android.view.ViewRootImpl.setView(ViewRootImpl.java:538)

...... ```

該異常多見於AlertDialog元件的使用中丟擲。

``` //拋異常程式碼

new AlertDialog.Builder(getApplicationContext()) //不能用getApplicationContext()

        .setIcon(android.R.drawable.ic_dialog_alert)  
        .setTitle("Warnning")  
        .setPositiveButton("Yes", positiveListener)
        .setNegativeButton(  "No", negativeListener)
        .create().show();

```

原因:導致報這個錯是在於new AlertDialog.Builder(mcontext),雖然這裡的引數是AlertDialog.Builder(Context context),但我們不能使用getApplicationContext()獲得的Context,而必須使用Activity,因為只有一個Activity才能新增一個窗體。

解決方法:將new AlertDialog.Builder(Context context)中的引數用Activity.this(Activity是你的Activity的名稱)或者getActivity()來填充就可以正確的建立一個Dialog了。

``` //修正後代碼

new AlertDialog.Builder(this) //this可以替換為MainActivity.this或getActivity()

        .setIcon(android.R.drawable.ic_dialog_alert)  
        .setTitle("Warnning")  
        .setPositiveButton("Yes", positiveListener)
        .setNegativeButton(  "No", negativeListener)
        .create().show();

```

參考

http://stackoverflow.com/questions/20779377/android-custom-dialog-gives-an-error

情形3.android.view.WindowManager$BadTokenException: Unable to add window -- token [email protected] is not valid; is your activity running?異常處理。

``` android.view.WindowManager$BadTokenException: Unable to add window --

token [email protected] is not valid; is your activity running?

at android.view.ViewRootImpl.setView(ViewRootImpl.java:698)

......

at dalvik.system.NativeStart.main(Native Method)

```

原因:從錯誤資訊我們也可以明白其原因,此問題根本原因就是由於將要彈出的dialog所要依附的View已經不存在導致的。當介面銷燬後再彈出來;或者介面跳轉時我們的view發生改變,dialog依附的context發生變化或者介面未運行了。

解決方法:介面已經銷燬引起的錯誤就只能判斷介面是否存在然後再彈出了。

``` //修正後代碼

if(!isFinishing()) {

 alert.show();

} ```

參考

http://stackoverflow.com/questions/25554279/unable-to-add-window-token-android-os-binderproxy4250d6d8-is-not-valid-is-your

https://github.com/VKCOM/vk-android-sdk/issues/21

情形4.android.view.WindowManager$LocalActivityRecord @xxx is not valid; is your activity running? 異常處理。

``` android.view.WindowManager$BadTokenException: Unable to add window --

token [email protected]

is not valid; is your activity running?
```

``` //異常程式碼

TipDialog dialog = new TipDialog(XXX.this) ; ```

原因:因為new對話方塊的時候,引數context 指定成了this,即指向當前子Activity的context。但子Activity是動態建立的,不能保證一直存在。其父Activity的context是穩定存在的,所以有下面的解決辦法。

解決方法:將context替換為getParent()即可。 注意:要建立dialog物件,上下文環境必須是activity,同時若ActivityGroup中巢狀ActivityGroup,巢狀多少就該使用多少個getParent()。

``` //修正後代碼,只有最多一個parent的情形

TipDialog dialog = new TipDialog(getParent()) ; ```

``` //修正後代碼,適用於一個或多個parent的情形

Activity activity = TestActivity.this;

while (activity.getParent() != null) {

activity = activity.getParent();

}

TipDialog dialog = new TipDialog(activity) ;
```

參考

http://stackoverflow.com/questions/9914195/webview-in-activity-group-crashing-on-dialogs

注:為什麼要使用getParent我們可以從ActivityGroup的內部機制來理解:

TabActivity的父類是ActivityGroup,而ActivityGroup的父類是Activity。

因此從Ams的角度來看,ActivityGroup與普通的Activity沒有什麼區別,其生命週期包括標準的start,stop,resume,destroy等,而且系統中只允許同時允許一個ActivityGroup.

ActivityGroup內部有一個重要成員變數,其型別為LocalActivityManager,該類的最大特點在於它可以訪問應用程序的主類,即ActivityThread類。Ams要啟動某個Activity或者贊同某個Activity都是通過ActivityThread類執行的,而LocalActivityManager類就意味著可以通過它來裝載不同的Activity,並控制Activity的不同的狀態。注意,這裡是裝載,而不是啟動,這點很重要。

所謂的啟動,一般是指會建立一個程序(如果所在的應用經常還不存在)執行該Activity 裝載僅僅是指把該Activity作為一個普通類進行載入,並建立一個該類的物件而已,而該類的任何函式都沒有被執行。裝載Activity物件的過程對AmS來講是完全不可見的,那些嵌入的Activity僅僅貢獻了自己所包含的Window視窗而已。而子Activity的不同狀態是通過moveToState來處理的。

所以子Activity不是像普通的Activity一樣,它只是提供Window而已,所以在建立Dialog時就應該使用getParent獲取ActivityGroup真正的Activity,才可以加Dialog加入Activity中。

連結:https://www.jianshu.com/p/4c5fafe08fa7