buider.show()の「android.view.WindowManager $ BadTokenException:ウィンドウを追加できません」


118

私のメインから、私activityは内部クラスを呼び出す必要があり、クラス内のメソッドで、私は示す必要がありますAlertDialog。閉じてからOKボタンを押すと、Google Playに転送して購入します。

物事は時代のほとんどのために完璧に動作しますが、少数のユーザーのためにそれが上のクラッシュしbuilder.show()、私は見ることができます"android.view.WindowManager$BadTokenException:クラッシュログから「ウィンドウを追加することができません。お勧めしてください。

私のコードは次のようになります:

public class classname1 extends Activity{

  public void onCreate(Bundle savedInstanceState) {
    this.requestWindowFeature(Window.FEATURE_NO_TITLE);
    super.onCreate(savedInstanceState);
    setContentView(R.layout.<view>); 

    //call the <className1> class to execute
  }

  private class classNamename2 extends AsyncTask<String, Void, String>{

    protected String doInBackground(String... params) {}

    protected void onPostExecute(String result){
      if(page.contains("error")) 
      {
        AlertDialog.Builder builder = new AlertDialog.Builder(classname1.this);
        builder.setCancelable(true);
        builder.setMessage("");
        builder.setInverseBackgroundForced(true);
        builder.setNeutralButton("Ok",new DialogInterface.OnClickListener() {
          public void onClick(DialogInterface dialog, int whichButton){
            dialog.dismiss();
            if(!<condition>)
            {
              try
              {
                String pl = ""; 

                mHelper.<flow>(<class>.this, SKU, RC_REQUEST, 
                  <listener>, pl);
              }

              catch(Exception e)
              {
                e.printStackTrace();
              }
            }  
          }
        });

        builder.show();
      }
    }
  }
}

また、他に転送していない別のアラートでエラーが発生しましたactivity。それはこのように簡単です:

AlertDialog.Builder builder = new AlertDialog.Builder(classname1.this);
    builder.setCancelable(true);

    //if successful
    builder.setMessage(" ");
    builder.setInverseBackgroundForced(true);
    builder.setNeutralButton("Ok",new DialogInterface.OnClickListener() {
        public void onClick(DialogInterface dialog, int whichButton){
            // dialog.dismiss();
                   }
    });
    builder.show();
}

2
これが完全なコードである場合、本当にAsyncTaskが必要ですか?
Shobhit Puri 2013

これは完全なコードではなく、非常に大きなコードなので、クラッシュレポートの問題が発生している部分のみを追加しました
MSIslam

大丈夫です。通常は、関数名を投稿し、そこで(今のように)たくさんのことを行っていることをコメントできます。理解しやすいです。:)。
Shobhit Puri 2013

このアクティビティから他のアクティビティに移動していますか?
Shobhit Puri 2013

1
あなたが書いたのはコメント//send to some other activityです。新しいアクティビティに行く部分をコメントすると、このエラーはなくなると思います。前のダイアログが完全に閉じられ、新しいアクティビティが開始されるため、エラーが発生するようです。にonPostExecute()は、アラートダイアログがあり、loginアクティビティのコンテキストを示しています。ただし、他のアクティビティに移動しているため、コンテキストが間違っています。したがって、このエラーが発生しています!stackoverflow.com/questions/15104677/…同様の質問を参照してください。
Shobhit Puri 2013

回答:


264
android.view.WindowManager$BadTokenException: Unable to add window"

問題点:

この例外は、アプリがダイアログを開いてバックグラウンドスレッド(AsyncTask)からユーザーに通知しようとしたときに発生します。

バックグラウンドスレッドからUIを変更しようとしている場合(通常はAsyncTaskのonPostExecute()から)、アクティビティが終了段階に入った場合(つまり、finish()を明示的に呼び出している場合)、ユーザーがホームボタンまたは戻るボタンを押すか、Androidによって作成されたアクティビティをクリーンアップします。このエラーが発生します。

理由:

この例外の理由は、例外メッセージが示すように、アクティビティは終了しましたが、終了したアクティビティのコンテキストを含むダイアログを表示しようとしているためです。ダイアログがAndroidランタイムを表示するためのウィンドウがないため、この例外がスローされます。

解決:

isFinishing()Androidによって呼び出されるメソッドを使用して、このアクティビティが終了処理中であるかどうかを確認します。明示的なfinish()呼び出しか、Androidによって行われたアクティビティのクリーンアップかを確認します。このメソッドを使用することで、アクティビティの終了時にバックグラウンドスレッドからダイアログを開かないようにするのは非常に簡単です。

またweak reference、アクティビティのを維持し(強力な参照ではなく、不要になったアクティビティを破棄できるようにする)、このアクティビティ参照を使用してUIを実行する(つまり、ダイアログを表示する)前に、アクティビティが終了していないことを確認します。

例えば

private class chkSubscription extends AsyncTask<String, Void, String>{

  private final WeakReference<login> loginActivityWeakRef;

  public chkSubscription (login loginActivity) {
    super();
    this.loginActivityWeakRef= new WeakReference<login >(loginActivity)
  }

  protected String doInBackground(String... params) {
    //web service call
  }

  protected void onPostExecute(String result) {
    if(page.contains("error")) //when not subscribed
    {
      if (loginActivityWeakRef.get() != null && !loginActivityWeakRef.get().isFinishing()) {
        AlertDialog.Builder builder = new AlertDialog.Builder(login.this);
        builder.setCancelable(true);
        builder.setMessage(sucObject);
        builder.setInverseBackgroundForced(true);

        builder.setNeutralButton("Ok",new DialogInterface.OnClickListener() {
          public void onClick(DialogInterface dialog, int whichButton){
            dialog.dismiss();
          }
        });

        builder.show();
      }
    }
  }
}

更新:

ウィンドウトークン:

その名前が示すように、ウィンドウトークンは、ウィンドウマネージャーがシステム内のウィンドウを一意に識別するために使用する特殊な種類のバインダートークンです。ウィンドウトークンは、悪意のあるアプリケーションが他のアプリケーションのウィンドウの上に描画できないようにするため、セキュリティにとって重要です。ウィンドウマネージャーは、アプリケーションがウィンドウを追加または削除する各要求の一部としてアプリケーションのウィンドウトークンを渡すように要求することで、これを防ぎます。トークンが一致しない場合、ウィンドウマネージャーはリクエストを拒否し、BadTokenExceptionをスローし ます。ウィンドウトークンがないと、この必要な識別手順は実行できず、ウィンドウマネージャーは悪意のあるアプリケーションから自身を保護することができません。

 実際のシナリオ:

アプリケーションが初めて起動すると、  ActivityManagerService  は、アプリケーションウィンドウトークンと呼ばれる特別な種類のウィンドウトークンを作成します。これは、アプリケーションのトップレベルのコンテナーウィンドウを一意に識別します。アクティビティマネージャーは、このトークンをアプリケーションとウィンドウマネージャーの両方に渡します。アプリケーションは、画面に新しいウィンドウを追加するたびに、トークンをウィンドウマネージャーに送信します。これにより、(他のアプリケーションの上にウィンドウを追加できないようにすることによって)アプリケーションとウィンドウマネージャー間の安全なやり取りが保証され、また、アクティビティマネージャーがウィンドウマネージャーに直接要求することが容易になります。


これは非常に理にかなっています!また、あなたの解決策は私にとって素晴らしい音です。(y)
MSIslam 2013

「空白の最終フィールドloginActivityWeakRefが初期化されていない可能性があります」というメッセージが表示され、次のように試行されました。それが正しいことかどうかわからない
MSIslam

また、コンストラクタでエラーが表示されていたため、WeakReference <login> loginActivityWeakRefの前のファイナルを削除しました。
MSIslam 2013

1
新しいchkCubscription(this).execute( "");を使用してみてください 新しいchkCubscription.execute( "");の代わりに あなたが上に投稿したように。
Ritesh Gune 2013

2
恐ろしいエラー!! 私はチュートリアルに従っていて、@ PhilRoggenbuckとして、私の問題はStartActivity(...)を呼び出す直前にToast..Show()を呼び出すことによって引き起こされました。修正するために、代わりに新しく呼び出されたアクティビティでトーストを移動しました!
ティエリー

26

機能を示すダイアログがありました:

void showDialog(){
    new AlertDialog.Builder(MyActivity.this)
    ...
    .show();
}

このエラーが発生し、isFinishing()このダイアログ表示機能を呼び出す前に確認する必要がありました。

if(!isFinishing())
    showDialog();

1
書いてはいけませんif(!MyActivity.this.isFinishing())か?MyActivityで正しくない場合
Bibaswann Bandyopadhyay

2
Androidがすでに終了しているのになぜコードが実行されるのですか?このソリューションに従う場合は、同様の問題を回避するためにisFinishingを実際に使用する必要がある時間を想像してください。
デビッド

@Davidダイアログにはバックグラウンドスレッドで呼び出されるなど、いくつかの詳細が欠けていると思いますが、今のところ、あなたの意見には完全に同意します。
放棄されたカート

すばらしい点ですが、なぜisFinishingをチェックする必要があるのでしょうか。
Chibueze Opata

9

考えられる理由は、アラートダイアログのコンテキストです。あなたはその活動を終えたので、そのコンテキストで開こうとしましたが、すでに閉じられています。ダイアログは最後まで完了しないので、最初のアクティビティにそのコンテキストを変更してみてください。

例えば

これではなく。

AlertDialog alertDialog = new AlertDialog.Builder(this).create();

使ってみる

AlertDialog alertDialog = new AlertDialog.Builder(FirstActivity.getInstance()).create();

3
  • 最初に、doInBackgroundをオーバーライドせずにAsyncTaskを拡張することはできません。
  • 次に、ビルダーからAlterDailogを作成して、show()を呼び出します。

    private boolean visible = false;
    class chkSubscription extends AsyncTask<String, Void, String>
    {
    
        protected void onPostExecute(String result)
        {
            AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
            builder.setCancelable(true);
            builder.setMessage(sucObject);
            builder.setInverseBackgroundForced(true);
            builder.setNeutralButton("Ok", new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int whichButton)
                {
                    dialog.dismiss();
                }
            });
    
            AlertDialog myAlertDialog = builder.create();
            if(visible) myAlertDialog.show();
        }
    
        @Override
        protected String doInBackground(String... arg0)
        {
            // TODO Auto-generated method stub
            return null;
        }
    }
    
    
    @Override
    protected void onResume()
    {
        // TODO Auto-generated method stub
        super.onResume();
        visible = true;
    }
    
    @Override
    protected void onStop()
    {
        visible = false; 
        super.onStop();
    }

1
答えてくれてありがとう。実際にはdoInBackgroundメソッドを使用しましたが、アラートとは関係がないため、ここでは触れません。builder.create()の追加に関しては、正常に機能しているように見えますが、誰にとっても正常に機能するかどうかはわかりません。IIが前に述べたように、私の現在のコードも問題なく機能しますが、ウィンドウを追加できないという問題が表示されるのは、ごく一部のユーザーにとって数回だけです。これを引き起こす可能性のあるコーディングの実際の問題は何ですか?
MSIslam 2013

この場合、ユーザーはonPostExecuteが呼び出される前にアクティビティを終了するため、ダイアログを保持するウィンドウがなく、これによりアプリケーションがクラッシュします。onStopにフラグを追加して、アクティビティが表示されなくなったかどうかを確認し、ダイアログを表示しないようにします。
moh.sukhni 2013

doInBackground()からのWebサービス呼び出し結果に基づいてユーザーがサブスクライブされていないかどうかを確認しているときに、builder.show()が条件を満たしているため、onPostExecuteが実際に呼び出されます。したがって、onPostExecuteが呼び出されなかった場合は、builder.show()行に到達していません。
MSIslam 2013

onPostExecuteは、デフォルトでdoInBackgroundの後に呼び出され、それを呼び出すことはできません。
moh.sukhni 2013

1
ユーザーがアクティビティから移動した後も非同期タスクは機能し続け、UIを処理するアクティビティがないため、builder.show()がアプリケーションをクラッキングします。そのため、アプリはWebからデータを取得していますが、データを取得する前にアクティビティが破棄されています。
moh.sukhni 2013

1

私は、ダイアログを作成していますonCreateし、それを使用してshowhide。私にとっての根本的な原因は解雇onBackPressedではなく、Home活動を終えることでした。

@Override
public void onBackPressed() {
new AlertDialog.Builder(this)
                .setTitle("Really Exit?")
                .setMessage("Are you sure you want to exit?")
                .setNegativeButton(android.R.string.no, null)
                .setPositiveButton(android.R.string.yes,
                        new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog,
                                    int which) {
                                Home.this.finish();
                                return;
                            }
                        }).create().show();

onBackPressedダイアログを閉じたり閉じたりせずにHomeアクティビティを終了していました。

ダイアログを閉じると、クラッシュは消えました。

new AlertDialog.Builder(this)
                .setTitle("Really Exit?")
                .setMessage("Are you sure you want to exit?")
                .setNegativeButton(android.R.string.no, null)
                .setPositiveButton(android.R.string.yes,
                        new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog,
                                    int which) {
                                networkErrorDialog.dismiss() ;
                                homeLocationErrorDialog.dismiss() ;
                                currentLocationErrorDialog.dismiss() ;
                                Home.this.finish();
                                return;
                            }
                        }).create().show();

0

解決しました。

 AlertDialog.Builder builder = new AlertDialog.Builder(
                   this);
            builder.setCancelable(true);
            builder.setTitle("Opss!!");

            builder.setMessage("You Don't have anough coins to withdraw. ");
            builder.setMessage("Please read the Withdraw rules.");
            builder.setInverseBackgroundForced(true);
            builder.setPositiveButton("OK",
                    (dialog, which) -> dialog.dismiss());
            builder.create().show();

-1

これを試して :

    public class <class> extends Activity{

    private AlertDialog.Builder builder;

    public void onCreate(Bundle savedInstanceState) {
                    this.requestWindowFeature(Window.FEATURE_NO_TITLE);
                    super.onCreate(savedInstanceState);

                setContentView(R.layout.<view>); 

                builder = new AlertDialog.Builder(<class>.this);
                builder.setCancelable(true);
                builder.setMessage(<message>);
                builder.setInverseBackgroundForced(true);

        //call the <className> class to execute
}

    private class <className> extends AsyncTask<String, Void, String>{

    protected String doInBackground(String... params) {

    }
    protected void onPostExecute(String result){
        if(page.contains("error")) //when not subscribed
        {   
           if(builder!=null){
                builder.setNeutralButton("Ok",new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int whichButton){
                    dialog.dismiss();
                        if(!<condition>)
                        {
                        try
                        {
                        String pl = ""; 

                        mHelper.<flow>(<class>.this, SKU, RC_REQUEST, 
                        <listener>, pl);
                        }

                        catch(Exception e)
                        {
                        e.printStackTrace();
                        }
                    }  
                }
            });

            builder.show();
        }
    }

}
}

-4

このグローバル変数の考えで、MainActivityインスタンスをonCreate()に保存しました。 Androidグローバル変数

public class ApplicationController extends Application {

    public static MainActivity this_MainActivity;
}

そして、このようなダイアログを開く。出来た。

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Global Var
    globals = (ApplicationController) this.getApplication();
    globals.this_MainActivity = this;
}

そしてスレッドで、私はこのようなダイアログを開きます。

AlertDialog.Builder alert = new AlertDialog.Builder(globals.this_MainActivity);
  1. MainActivityを開く
  2. スレッドを開始します。
  3. スレッドからダイアログを開く->作業。
  4. 「戻るボタン」をクリックします(onCreateが呼び出され、最初のMainActivityが削除されます)
  5. 新しいMainActivityが開始されます。(そしてそのインスタンスをグローバルに保存します)
  6. 最初のスレッドからダイアログを開く->開いて機能します。

:)


4
アクティビティへの静的参照を保持しないでください。メモリリークが発生します
Leandroid
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.