【问题标题】:AlertDialog show() giving WindowManager BadTokenExceptionAlertDialog show() 给出 WindowManager BadTokenException
【发布时间】:2018-02-14 16:41:17
【问题描述】:

我在 google play 开发者控制台上收到了很多关于该异常的报告,但我不明白为什么,因为我无法重现该错误,但它在我的所有设备上都能完美运行。

这是我的自定义 AlertDialog 的源代码,崩溃所在的行位于方法末尾的 show() 调用上。怎么了?

我在stackoverflow中检查了一些与here相关的问题,但实际上我在声明对话框时使用了final,这是其他问题的解决方案,我仍然有异常报告。

非常感谢

public static void showPoliciesDialog(final Activity activity, final Runnable runnable) {
    int sw = App.getInstance().getSmallSideSize();
    final int LIGHT_GRAY = 0xFFc7c7c7;

    final AlertDialog.Builder builder = new AlertDialog.Builder(new ContextThemeWrapper(activity, R.style.PolicyStyle));
    builder.setCancelable(false);

    LinearLayout ll = new LinearLayout(activity);
    ll.setPadding((int)(sw*0.025), 0, (int)(sw*0.025), 0);
    ll.setOrientation(LinearLayout.VERTICAL);

    final TextView title = new TextView(activity);
    LinearLayout.LayoutParams titleLP = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT );
    titleLP.setMargins( Util.dpToPx(activity, 15), Util.dpToPx(activity, 15), Util.dpToPx(activity, 15), Util.dpToPx(activity, 15) );
    title.setLayoutParams(titleLP );
    title.setTextSize(18);
    title.setText(R.string.POLICY_TITLE);
    title.setTextColor(0xFF307282); // Blue color
    ll.addView(title);

    final CheckBox acceptCheckbox = new CheckBox(activity);
    acceptCheckbox.setTextColor(LIGHT_GRAY);
    acceptCheckbox.setText(R.string.I_WANT_DATA_COLLECT);
    acceptCheckbox.setLayoutParams( new ViewGroup.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT) );
    acceptCheckbox.setVisibility(View.GONE);
    acceptCheckbox.setChecked(App.getInstance().retrieveBooleanValue(Constants.SEND_DATA_CHECKBOX, ConfigManager.getInstance().defaultStorable()));
    acceptCheckbox.setOnCheckedChangeListener(
        new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton compoundButton, boolean isChecked) {
                App.getInstance().storeBooleanValue(Constants.SEND_DATA_CHECKBOX, isChecked);
            }
        }
    );
    acceptCheckbox.setPadding(5,5,5,5);

    // Detects if the end of the web page has been reached & in this case it shows the accept checkbox.
    // Based on http://stackoverflow.com/questions/10956443/android-making-a-button-visible-once-webview-is-done-scrolling
    // And on
    // [1] - http://stackoverflow.com/questions/10794647/detect-if-webview-scroll-reach-the-end
    // [2] - (To avoid use of an deprecated method) http://stackoverflow.com/questions/16079863/how-get-webview-scale-in-android-4
    final WebView policiesWebView = new WebView(activity){
        float currentScale = -1;
        {
            this.setWebViewClient(new WebViewClient(){
                @Override public void onScaleChanged(WebView view, float oldScale, float newScale) {
                    super.onScaleChanged(view, oldScale, newScale);
                    currentScale = newScale;
                }
            });
            this.getSettings().setJavaScriptEnabled(true);
        }
        @Override public void onScrollChanged(int l, int t, int oldl, int oldt) {
            // Only called on Android versions where onScaleChanged is not called
            if(currentScale == -1)
                currentScale = this.getScale();
            int height = (int) Math.floor(this.getContentHeight() * currentScale);
            int webViewHeight = this.getHeight();
            int cutoff = height - webViewHeight - 10; // Don't be too strict on the cutoff point
            if (t >= cutoff) {
                // We need to know if it's necessary to show the accept checkbox. It should be visible only if it's not the first time that this dialog has been showed, so only
                // if the policies have been accepted previously. if we have the key stored, then, it's not the first time this dialog is being showed, so we must show the accept checkbox
                if(App.getInstance().containsKey(Constants.PRIVACY_POLICIES_ACCEPTED)){
                    acceptCheckbox.setVisibility(View.VISIBLE);
                }
            }
        }
    };

    policiesWebView.loadUrl(ConfigManager.getInstance().getPrivacyUrl());
    LinearLayout.LayoutParams policiesWebViewLayoutParams =  new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,0);
    policiesWebViewLayoutParams.weight=1;
    policiesWebView.setLayoutParams(policiesWebViewLayoutParams);
    policiesWebView.setVisibility(View.GONE);
    ll.addView(policiesWebView);
    ll.addView(acceptCheckbox);

    final TextView readPolicies = new TextView(activity);
    LinearLayout.LayoutParams readPoliciesLP = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
    readPoliciesLP.setMargins( Util.dpToPx(activity, 15), 0, Util.dpToPx(activity, 10), Util.dpToPx(activity, 30) );
    readPolicies.setPadding(0,0,0,Util.dpToPx(activity, 20));
    readPolicies.setTextColor(LIGHT_GRAY);
    readPolicies.setLayoutParams(readPoliciesLP);
    SpannableString content = new SpannableString(activity.getString(R.string.PLEASE_READ_POLICIES));
    content.setSpan(new UnderlineSpan(), 0, content.length(), 0);
    readPolicies.setText(content);
    readPolicies.setTextSize(16);
    readPolicies.setGravity(Gravity.LEFT);
    readPolicies.setOnClickListener(new View.OnClickListener() {
        @Override public void onClick(View view) {
            title.setVisibility(View.GONE);
            policiesWebView.setVisibility(View.VISIBLE);
            readPolicies.setVisibility(View.GONE);
        }
    });
    ll.addView(readPolicies);

    // We need to know if we should treat this buttonas an agree button or a back button.
    // Agree mode: If it's the first time this dialog is being showed. Policies has not been accepted previously.
    // Back mode: If it's not the first time. The policies has been accepted previously.
    String buttonText = App.getInstance().containsKey(Constants.PRIVACY_POLICIES_ACCEPTED) == false ? activity.getString(R.string.AGREE) : activity.getString(R.string.BACK);

    builder.setPositiveButton(buttonText,
        new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialogInterface, int i) {
                if(App.getInstance().containsKey(Constants.PRIVACY_POLICIES_ACCEPTED) == false) { //if agree mode
                    App.getInstance().storeBooleanValue(Constants.SEND_DATA_CHECKBOX, true);
                    App.getInstance().storeBooleanValue(Constants.PRIVACY_POLICIES_ACCEPTED, true);
                }

                dialogInterface.dismiss();
                if(runnable != null)
                    runnable.run();
            }
        }
    );

    // We will show close app button only if it's the first time we show this dialog.
    if(App.getInstance().containsKey(Constants.PRIVACY_POLICIES_ACCEPTED) == false)
        builder.setNegativeButton(R.string.CLOSE_APP,
            new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialogInterface, int i) {

                    //launching navigator with non accept policies text
                    Intent intent = new Intent(Intent.ACTION_VIEW);
                    String url = "https://myurl.com/no-terms.php?idApp={appId}";
                    url=GlobalVariablesManager.getInstance().replaceValues(url, false, false);
                    intent.setData(Uri.parse(URLParser.parse(url)));
                    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                    activity.startActivity(intent);

                    //App.getInstance().storeBooleanValue(Constants.PRIVACY_POLICIES_ACCEPTED, false);
                    SectionManager.getInstance().exit(false);
                    App.getInstance().clean();
                    activity.finish();
                }
            }
        );

    builder.setView(ll);
    final AlertDialog dialog = builder.create();
    dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);

    // This will set the color of negative button to a gray color
    dialog.setOnShowListener(new DialogInterface.OnShowListener() {
        @Override
        public void onShow(DialogInterface arg0) {
            Button buttonNegative = dialog.getButton(AlertDialog.BUTTON_NEGATIVE);
            buttonNegative.setTextColor(LIGHT_GRAY);
        }
    });

    dialog.show();
}

【问题讨论】:

    标签: android dialog android-alertdialog android-dialog


    【解决方案1】:

    前段时间我自己也遇到过这个错误。以下是我从 Crashlytics 获得的有用见解:

    “此崩溃通常是由于您的应用程序尝试使用先前完成的 Activity 作为上下文来显示对话框。例如,如果 Activity 触发 AsyncTask 并在完成时尝试显示对话框,则可能会发生这种情况,但用户在任务完成之前从 Activity 导航回来。”

    Crashlytics 建议链接:
    Android – Displaying Dialogs From Background Threads
    Error : BinderProxy@45d459c0 is not valid; is your activity running?

    【讨论】:

    • 谢谢,添加 if (activity.isFinishing() == false) 解决了这个问题
    【解决方案2】:

    来自 BadTokenException 的文档:

     Exception that is thrown when trying to add view whose
     {@link LayoutParams} {@link LayoutParams#token}
     is invalid.
    

    这个令牌被赋予它的附加方法上的活动,所以我猜你在某些情况下试图过早地显示对话框。尝试在 post 块中发布 show 方法

    【讨论】:

    • 为什么这么快?你什么意思?
    • 这会是一个好的解决方案吗? new Handler(Looper.getMainLooper()).postDelayed(new Runnable() { @Override public void run() { dialog.show(); } }, 1000);
    • 哎呀,对不起,不知道我为什么写延迟,没有理由真正延迟它,只是将它发布在主线程上,以便我在队列末尾执行。所以 new Handler().post(new Runnable() { @Override public void run() { dialog.show(); } });会正常工作。这段代码是从主线程运行的吗?如果是,则无需获取主循环器,如果不是,则无论如何它都不起作用
    • 哎呀,这意味着没有帖子它就不会工作,还想说如果你在 onStart 或类似的东西之后运行它,你应该没问题 2...
    • 感谢您的帮助,但最后另一个解决方案是正确的
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-06-06
    • 2013-08-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多