Android: 在PreferenceActivity显示对话框时出现“BadTokenException:无法添加窗口;您的活动是否正在运行?”

58

我想请你帮忙:在我的应用程序中,我只有一个活动,一个PreferenceActivity(不需要其他活动,它只是一个简单的后台同步应用程序,因此PrefsActivity是主/启动器)。用户设置完偏好后,勾选checkBoxPreference,然后启动(或停止)服务。在启动时,会显示一个对话框。

但是问题在于:如果用户按返回键离开活动,再次启动它,然后尝试勾选checkBoxPref,则prefsactivity将崩溃。对话框不会显示。我不知道为什么,也不知道如何修复它。

这段代码与给我带来问题的那部分完全相同:

PrefsActivity.java:

   package is.it.works;

   // imports .....

   public class PrefsActivity extends PreferenceActivity implements OnSharedPreferenceChangeListener {
SharedPreferences prefs;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    addPreferencesFromResource(R.xml.prefs);
    prefs = PreferenceManager.getDefaultSharedPreferences(this);
    prefs.registerOnSharedPreferenceChangeListener(this);
}// onCreate

@Override
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
    if (key.equals("checkTest")) {
        showDialog(1);
    }
    if (key.equals("cancel")) {
        dismissDialog(1);
    }
}// onSPC

@Override
protected Dialog onCreateDialog(int id) {
    switch (id) {
    case 1: {
        ProgressDialog dialog = new ProgressDialog(this);
        dialog.setMessage("press back twice, start the app again, and click checkbox...");
        dialog.setIndeterminate(true);
        dialog.setCancelable(true);
        dialog.setOnCancelListener(new OnCancelListener() {

            @Override
            public void onCancel(DialogInterface dialog) {
                prefs.edit().putBoolean("cancel", false).commit();
            }
        });
        return dialog;
    }// case
    }// switch
    return null;
}// onCreateDialog
}// PrefsActivity

prefs.xml:

<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">

    <CheckBoxPreference android:key="checkTest" android:title="test" />

</PreferenceScreen>

以及清单文件:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="is.it.works" android:versionCode="1" android:versionName="1.0">
<uses-sdk android:minSdkVersion="4" />

<application android:icon="@drawable/icon" android:label="@string/app_name">
    <activity android:name=".PrefsActivity" android:label="@string/app_name">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>

</application>
</manifest>

日志记录错误:

09-14 10:34:34.472: ERROR/AndroidRuntime(281): Uncaught handler: thread main exiting due to uncaught exception
09-14 10:34:34.502: ERROR/AndroidRuntime(281): android.view.WindowManager$BadTokenException: Unable to add window -- token android.os.BinderProxy@43756de8 is not valid; is your activity running?
09-14 10:34:34.502: ERROR/AndroidRuntime(281):     at android.view.ViewRoot.setView(ViewRoot.java:456)
09-14 10:34:34.502: ERROR/AndroidRuntime(281):     at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:177)
09-14 10:34:34.502: ERROR/AndroidRuntime(281):     at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:91)
09-14 10:34:34.502: ERROR/AndroidRuntime(281):     at android.view.Window$LocalWindowManager.addView(Window.java:409)
09-14 10:34:34.502: ERROR/AndroidRuntime(281):     at android.app.Dialog.show(Dialog.java:238)
09-14 10:34:34.502: ERROR/AndroidRuntime(281):     at android.app.Activity.showDialog(Activity.java:2413)
09-14 10:34:34.502: ERROR/AndroidRuntime(281):     at is.it.works.PrefsActivity.onSharedPreferenceChanged(PrefsActivity.java:27)
09-14 10:34:34.502: ERROR/AndroidRuntime(281):     at android.app.ApplicationContext$SharedPreferencesImpl$EditorImpl.commit(ApplicationContext.java:2727)
09-14 10:34:34.502: ERROR/AndroidRuntime(281):     at android.preference.Preference.tryCommit(Preference.java:1199)
09-14 10:34:34.502: ERROR/AndroidRuntime(281):     at android.preference.Preference.persistBoolean(Preference.java:1404)
09-14 10:34:34.502: ERROR/AndroidRuntime(281):     at android.preference.CheckBoxPreference.setChecked(CheckBoxPreference.java:155)
09-14 10:34:34.502: ERROR/AndroidRuntime(281):     at android.preference.CheckBoxPreference.onClick(CheckBoxPreference.java:143)
09-14 10:34:34.502: ERROR/AndroidRuntime(281):     at android.preference.Preference.performClick(Preference.java:811)
09-14 10:34:34.502: ERROR/AndroidRuntime(281):     at android.preference.PreferenceScreen.onItemClick(PreferenceScreen.java:190)
09-14 10:34:34.502: ERROR/AndroidRuntime(281):     at android.widget.AdapterView.performItemClick(AdapterView.java:284)
09-14 10:34:34.502: ERROR/AndroidRuntime(281):     at android.widget.ListView.performItemClick(ListView.java:3246)
09-14 10:34:34.502: ERROR/AndroidRuntime(281):     at android.widget.AbsListView$PerformClick.run(AbsListView.java:1635)
09-14 10:34:34.502: ERROR/AndroidRuntime(281):     at android.os.Handler.handleCallback(Handler.java:587)
09-14 10:34:34.502: ERROR/AndroidRuntime(281):     at android.os.Handler.dispatchMessage(Handler.java:92)
09-14 10:34:34.502: ERROR/AndroidRuntime(281):     at android.os.Looper.loop(Looper.java:123)
09-14 10:34:34.502: ERROR/AndroidRuntime(281):     at android.app.ActivityThread.main(ActivityThread.java:4203)
09-14 10:34:34.502: ERROR/AndroidRuntime(281):     at java.lang.reflect.Method.invokeNative(Native Method)
09-14 10:34:34.502: ERROR/AndroidRuntime(281):     at java.lang.reflect.Method.invoke(Method.java:521)
09-14 10:34:34.502: ERROR/AndroidRuntime(281):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:791)
09-14 10:34:34.502: ERROR/AndroidRuntime(281):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:549)
09-14 10:34:34.502: ERROR/AndroidRuntime(281):     at dalvik.system.NativeStart.main(Native Method)
09-14 10:34:34.522: INFO/Process(52): Sending signal. PID: 281 SIG: 3
09-14 10:34:34.532: INFO/dalvikvm(281): threadid=7: reacting to signal 3
09-14 10:34:34.592: INFO/dalvikvm(281): Wrote stack trace to '/data/anr/traces.txt'
09-14 10:34:38.533: DEBUG/dalvikvm(107): GC freed 437 objects / 21560 bytes in 136ms
09-14 10:34:39.183: INFO/global(175): Default buffer size used in BufferedReader constructor. It would be better to be explicit if an 8k-char buffer is required.
09-14 10:34:44.632: INFO/global(175): Default buffer size used in BufferedReader constructor. It would be better to be explicit if an 8k-char buffer is required.
09-14 10:34:47.412: INFO/Process(281): Sending signal. PID: 281 SIG: 9
09-14 10:34:47.472: INFO/ActivityManager(52): Process is.it.works (pid 281) has died.
09-14 10:34:47.492: INFO/WindowManager(52): WIN DEATH: Window{4394f638 is.it.works/is.it.works.PrefsActivity paused=false} 

经过大量Google搜索后,我认为问题出在 ProgressDialog dialog = new ProgressDialog(this); 中的 this 上。因为 this 会发生变化。但是将它更改为 getApplicationContext()PrefsActivity.this 都没有帮助,问题仍然存在。请告诉我这是为什么,并且可能的解决方案有哪些!谢谢!我陷入了困境,现在我一点头绪也没有...


4
请使用getParent()替换this,然后尝试。 - Abhi
4
请查看此链接 BadTokenException,它会对您有所帮助。谢谢Venky。 - Venky
1
使用getParent(),会给我NullpointerException。 :( - Lama
谢谢您的回答,我也找到了这个页面。但是只有在活动没有完成时显示对话框并不是解决方案。活动处于运行状态,在按下返回键后再次启动。问题应该是(我认为)活动“改变”了(更改了“this”引用),而不是它正在结束。 - Lama
尝试在活动的生命周期方法中放置日志消息。您会发现,在按下返回按钮时,如果您在任何活动中,则应调用onDestroy方法。这就是异常发生的原因,您需要检查活动是否实际完成或正在运行。按下返回按钮和主页按钮时会有差异,请尝试两者并查看差异。 - sunil
显示剩余4条评论
6个回答

84

我遇到了一个非常类似的问题(也是通过这里找到的),并且找到了一个非常简单的解决方法。虽然我的代码不同,但应该很容易适应。这是我的解决方法:

public void showBox() {
    mActive = true;
    if (! ((Activity) mContext).isFinishing()) {
        mDialogBox.show();
    }
} 

所以,在问题中的示例代码中,修复措施可能是(猜测):

@Override
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
    if (key.equals("checkTest")) {
        if (! this.isFinishing()) {
            showDialog(1);
        }
    }
    if (key.equals("cancel")) {
        dismissDialog(1);
    }
}// onSPC

2
你不应该将一个布尔值与另一个布尔值进行比较,而应该使用布尔运算符 !(或者什么都不用)来代替。 - Darkhogg
根据Android文档 - isFinishing()返回truefalse。在研究了何时使用if (! boolean)if (boolean == false)时,我未能找到任何明确的信息。您是否有链接或其他可以交叉检查以支持您的说法? - Tigger
9
这是一种风格问题,而非功能问题。从技术上讲,两者是相同的,但是: https://dev59.com/-nE85IYBdhLWcg3wr1ge http://programmers.stackexchange.com/questions/136908/why-use-boolean-variable-over-boolean-variable-false 我喜欢后一篇文章的答案:它更易读,因为!读作“not”,Java标识符通常是英文可读的。 - Darkhogg
1
感谢Darkhogg提供的链接。其中提出了一些好的观点。上面的答案示例已经被更改,我也将改变我的代码风格。 - Tigger
1
这个转换为 Activity 是多余的。 - Androiderson
1
对于第一个示例代码,需要将mContext强制转换为Activity类型,因为它作为Context对象传递。然而,对于第二个代码示例,我认为你是正确的,所以我将其删除了。 - Tigger

4

也许您在活动中没有关闭或注销某些内容。在这种情况下,请尝试在 onDestroy 中注销广播接收器。


3

这通常是由于您的应用程序尝试使用已完成的Activity作为context来显示对话框引起的。然后,请检查在显示对话框之前,该活动是否未被其他应用程序或其他触发器关闭。

if (!isFinishing()) {
    //showdialog here
    }

1
对我来说,这解决了问题...检查对话框是否为空或未显示,如果是,则重新创建。
    // create alert dialog
    if (enableNetworkDialog == null || !enableNetworkDialog.isShowing())
        enableNetworkDialog = alertDialogBuilder.create();
    if (context instanceof AppCompatActivity && !((AppCompatActivity) context).isFinishing())
        enableNetworkDialog.show(); 

1

在将崩溃跟踪引入项目后,我发现这个问题经常出现,并且发现同样的解决方法可以在整个项目中消除崩溃:

  • 不要将Dialogs声明/实例化为本地变量。
  • 将所有Dialogs作为Activity的实例变量。
  • 重写onDestroy并调用if(dialog != null) dialog.dismiss();

示例:

MyActivity extends Activity {
  ProgressDialog mProgressDialog;
  AlertDialog mAlertDialog;


  @Override
  public void onCreate(Bundle savedInstanceState) {
    mProgressDialog = new ProgressDialog(MyActivity.this);
    mAlertDialog = new AlertDialog.Builder(MyActivity.this).show();
  }

  @Override
  public void onDestroy() {
    super.onDestroy();
    if(mProgressDialog != null) {
      mProgressDialog.dismiss();
    }
    if(mAlertDialog != null) {
      mAlertDialog.dismiss();
    }
  }

错误提示信息说“无法添加窗口”是具有误导性的,因为我发现当您离开一个Activity并且传递给您的Dialog的上下文已经失效时,该错误会发生。

你确定需要自己关闭对话框吗?Android活动的代码似乎已经为您关闭了它们:http://grepcode.com/file_/repository.grepcode.com/java/ext/com.google.android/android/2.2_r1.1/android/app/Activity.java/?v=source - android developer
是的,这看起来是正确的。我通常处理由碎片组成的代码库,因此我猜当我在八月份撰写这篇帖子时,我是想针对碎片进行讨论的。我会进一步研究并更新帖子。 - Brendan Weinstein

0

最佳解决方案只是防止崩溃。对我来说,问题在于我引用了错误的context来显示警报对话框。在传递正确的context之后,问题得到了解决。


正确的上下文是什么?应用程序上下文吗? - Juuso Ohtonen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接