首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >安卓AlertDialog与BadTokenException崩溃?

安卓AlertDialog与BadTokenException崩溃?
EN

Stack Overflow用户
提问于 2015-12-17 00:31:24
回答 3查看 959关注 0票数 1

这是我的第一篇文章,请耐心等待。我是一名高中开发者,最近在play商店上发布了一款Android应用程序。我使用Crashlytics来捕获异常,出于某种原因,它抛出了这个错误。

代码语言:javascript
复制
Fatal Exception: android.view.WindowManager$BadTokenException: Unable to add window -- token android.os.BinderProxy@1989547c is not valid; is your activity running?

在5.0、5.0.2、5.1.1和6.0.1版本的LG D855、Nexus5和华为AL10上都有相关报道。我在网上看过,发现当一个活动不存在时,就会发生这种情况。此错误发生在应用程序的初始启动时。

下面是我用于报警对话框的代码,它只是询问用户是否想要查看教程(y/n)

代码语言:javascript
复制
public void showTutorialDialog() {
    AlertDialog tutorialDialog = new AlertDialog.Builder(this)
            .setTitle(R.string.tutorial_question_title)
            .setCancelable(false)
            .setMessage(R.string.tutorial_question)
            .setPositiveButton(getResources().getString(R.string.tutorial_question_pos), new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    // Take to tutorial
                    // Assume isn't backer for now..
                    finish();
                    Intent i = new Intent(StartupActivity.this, TutorialActivity.class);
                    i.putExtra("from", "StartupActivity");
                    startActivity(i);
                }
            })
            .setNegativeButton(getResources().getString(R.string.tutorial_question_neg), new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    // No tutorial, ask if they are a backer
                    showBackerDialog();
                }
            }).show();

在应用程序初始启动时,我在一个单独的类中使用IabHelper加载用户的购买详细信息。这个类称为PurchaseRetriever,它异步检索用户购买的内容并将其存储在ArrayList中。这就是我的代码的工作方式。

代码语言:javascript
复制
            if (mManager.isUserFirstTime()) {
                // Initialize purchase retriever.
                // The rest will be done when the observer reports that purchase data has been retrieved.
                mPurchases = PurchaseRetriever.getInstance(StartupActivity.this);
                mPurchases.addObserver(new FirstStartupObserver(this));
                StartupManager.FIRST = true;
                loadImageContent();

它使用观察者模式运行,所以当查询购买详细信息时,它调用FirstStartupObserver中的更新()方法,然后通过对StartupActivity的引用,在发生错误的地方调用startupActivity.showTutorialDialog();

我已经在我和我朋友个人拥有的多个设备上进行了测试(Nexus 6,Nexus 5,Nexus 7平板电脑,三星Galaxy Tab,三星远程实验室的各种设备),但它在我这一端工作得很好……任何建议,谢谢。

编辑:这里是StartupActivity。

代码语言:javascript
复制
/**
 * Main startup activity. Determines which activity to launch.
 * Puts the user in one place or another depending on if they are a backer.
 */
public class StartupActivity extends AppCompatActivity {

    private StartupManager mManager;
    private ProgressBar bar;

    // --- Used if first time app loading to query purchase info
    private PurchaseRetriever mPurchases;


    @Override
    protected void onCreate(Bundle savedInstanceState) {

        // Used in either cases
        // If first time, displayed, if not, hidden//
        //hideNavBar();
        User.init(this);
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_startup);
        bar = (ProgressBar)this.findViewById(R.id.progressBar);
        mManager = new StartupManager(this);

        // Returns true if data was corrupt before
        if (mManager.isDataCorrupt()) {
            bar.setVisibility(View.VISIBLE);
            loadImageContent();
            // Reset watch to default black
            // Internally starts NewMainActivity
            ErrorManager.fixCorruptData(bar, this);
        } else {
            // Stays true until user selects watch
            if (mManager.isUserFirstTime()) {
                // Initialize purchase retriever.
                // The rest will be done when the observer reports that purchase data has been retrieved.
                mPurchases = PurchaseRetriever.getInstance(StartupActivity.this);
                mPurchases.addObserver(new FirstStartupObserver(this));
                StartupManager.FIRST = true;
                loadImageContent();
            } else {
                // NOT first time starting app.
                mPurchases = PurchaseRetriever.getInstance(StartupActivity.this);
                mPurchases.addObserver(new AfterFirstStartupObserver(this));
                loadImageContent();
            }
        }


    }

    // Two main dialogs used
    public void showTutorialDialog() {
        AlertDialog tutorialDialog = new AlertDialog.Builder(this)
                .setTitle(R.string.tutorial_question_title)
                .setCancelable(false)
                .setMessage(R.string.tutorial_question)
                .setPositiveButton(getResources().getString(R.string.tutorial_question_pos), new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        // Take to tutorial
                        // Assume isn't backer for now..
                        finish();
                        Intent i = new Intent(StartupActivity.this, TutorialActivity.class);
                        i.putExtra("from", "StartupActivity");
                        startActivity(i);
                    }
                })
                .setNegativeButton(getResources().getString(R.string.tutorial_question_neg), new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        // No tutorial, ask if they are a backer
                        showBackerDialog();
                    }
                }).show();

        tutorialDialog.getButton(AlertDialog.BUTTON_NEGATIVE).setTextColor(Color.RED);
        tutorialDialog.getButton(AlertDialog.BUTTON_POSITIVE).setTextColor(Color.RED);
    }
    private void showBackerDialog() {
        // Show AlertDialog ask if they are kickstarter backer
        AlertDialog askDialog = new AlertDialog.Builder(this)
                .setTitle(getResources().getString(R.string.startup_dialog_title))
                .setCancelable(false)
                .setMessage(getResources().getString(R.string.startup_dialog_message))
                .setPositiveButton(getResources().getString(R.string.startup_dialog_pos), new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        // User is a backer, take to watch chooser screen, then it takes to login screen
                        // Also look at Timer with TimerTask
                        new Thread(new Runnable() {
                            @Override
                            public void run() {
                                try {
                                    Intent i = new Intent(StartupActivity.this, WatchChooserActivity.class);
                                    i.putExtra("from", "StartupActivityBacker");
                                    startActivity(i);
                                } finally {
                                    finish();
                                }
                            }
                        }).start();
                    }
                })
                .setNegativeButton(getResources().getString(R.string.startup_dialog_neg), new DialogInterface.OnClickListener() {

                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        // User is not a backer, take to MainActivity

                        new Thread(new Runnable() {

                            @Override
                            public void run() {
                                try {
                                    Intent i = new Intent(StartupActivity.this, WatchChooserActivity.class);
                                    i.putExtra("from", "StartupActivityNonBacker");
                                    startActivity(i);
                                } finally {
                                    finish();
                                }
                            }
                        }).start();
                    }
                }).show();

        askDialog.getButton(AlertDialog.BUTTON_NEGATIVE).setTextColor(Color.RED);
        askDialog.getButton(AlertDialog.BUTTON_POSITIVE).setTextColor(Color.RED);
    }

下面是FirstStartupObserver的代码。

代码语言:javascript
复制
public class FirstStartupObserver implements Observer {
    private StartupActivity startupActivity;
    public FirstStartupObserver(StartupActivity startupActivity) {
        this.startupActivity = startupActivity;
    }

    // Called when the observable is done loading purchase detail
    // (Only called when user runs app first time)
    @Override
    public void update(Observable observable, Object data) {
        // Set default first-time watch
        //  Query product data (the Watchfaces purchased in the form of a WatchFace object)
        PurchaseRetriever mPurchases = PurchaseRetriever.getInstance(startupActivity);
        if (mPurchases.hasSuccess()) {
            ArrayList<DynamicLoader.WatchFace> facesOwned = mPurchases.getPurchasedFaces();
            for (DynamicLoader.WatchFace f : facesOwned) {
                f.setPurchased(true);
            }
            // Check if coming from v1.4
            if (UpgradeManager.isUpgrading(startupActivity)) {
                // Then it calls the code below, but after the async task.
                String accessCode = UpgradeManager.getOldAccessCode(startupActivity);
                String accessToken = UpgradeManager.getOldAccessToken(startupActivity);
                UpgradeManager.migrateBacker(startupActivity, accessCode, accessToken);
            } else {
                // Ask if they want to see tutorial.
                // This is when the exception occurs!!!

                startupActivity.showTutorialDialog();

            }
            return;
        } else {
            Log.d("TAG", "Showing fail dialog");
            DialogUtils.showIabFailDialog(startupActivity, this);
        }
    }
}
EN

回答 3

Stack Overflow用户

发布于 2015-12-17 01:33:51

令牌android.os.BinderProxy@1989547c无效;您的活动是否正在运行?

当活动不存在时,您正试图过早地加载AlertDialog!在我的应用程序中,当活动生命周期完成时,我会加载一个小教程:

代码语言:javascript
复制
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.splash_screen);

    ...
    ...
    ...

    showTutorialDialog();
}
票数 0
EN

Stack Overflow用户

发布于 2015-12-17 01:48:48

令牌android.os.BinderProxy@1989547c无效;您的活动是否正在运行?

这意味着当你的活动被销毁或销毁后,你正试图显示你的弹出窗口。

您可以检查您的活动isDestroyed是否如下所示:

代码语言:javascript
复制
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 && !isDestroyed()) {
    showTutorialDialog();
}

如果你支持API17以下的设备,你可以尝试在其他情况下使用isFinishing。我没有测试它是否像预期的那样工作。(如果我错了,请纠正我。)

代码语言:javascript
复制
else {
        if (!isFinishing()) { 
             showTutorialDialog();
         }
   }

或者,为了快速修复,您可以使用try catch

票数 0
EN

Stack Overflow用户

发布于 2015-12-17 11:15:23

这通常是由于在AsyncTask或其他后台任务中执行某些操作导致的,这些任务包含对Activity的引用,并在工作完成时尝试显示对话框。在这种情况下,听起来您的FirstStartupObserver持有对活动的引用并试图显示一个对话框,但在PurchaseRetriever完成其工作时,该活动可能已被销毁。

不要试图测试活动状态,也不要捕获BadTokenException。这只是掩盖了问题。最简单的解决方案是在活动暂停时取消PurchaseRetriever。如果您希望后台工作在配置更改(如轮换)中幸存下来,但仍限于活动的用户感知生命周期,请在保留的片段中执行工作。最后,如果当用户在活动之间导航或将应用程序放在后台时,后台工作应该继续,请在Service中完成工作,并将结果保存到活动可以检索的位置。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/34317247

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档