java.lang.IllegalArgumentException:ビューがウィンドウマネージャにアタッチされていません


148

AsyncTaskを開始し、操作中に進行状況ダイアログを表示するアクティビティがあります。アクティビティは、回転またはキーボードスライドによって再作成されないと宣言されています。

    <activity android:name=".MyActivity" 
              android:label="@string/app_name"
              android:configChanges="keyboardHidden|orientation"
              >
        <intent-filter>
        </intent-filter>
    </activity>

タスクが完了したら、ダイアログを閉じますが、一部の電話(フレームワーク:1.5、1.6)では、このようなエラーがスローされます。

java.lang.IllegalArgumentException: View not attached to window manager
    at android.view.WindowManagerImpl.findViewLocked(WindowManagerImpl.java:356)
    at android.view.WindowManagerImpl.removeView(WindowManagerImpl.java:201)
    at android.view.Window$LocalWindowManager.removeView(Window.java:400)
    at android.app.Dialog.dismissDialog(Dialog.java:268)
    at android.app.Dialog.access$000(Dialog.java:69)
    at android.app.Dialog$1.run(Dialog.java:103)
    at android.app.Dialog.dismiss(Dialog.java:252)
    at xxx.onPostExecute(xxx$1.java:xxx)

私のコードは:

final Dialog dialog = new AlertDialog.Builder(context)
    .setTitle("Processing...")
    .setCancelable(true)
    .create();

final AsyncTask<MyParams, Object, MyResult> task = new AsyncTask<MyParams, Object, MyResult>() {

    @Override
    protected MyResult doInBackground(MyParams... params) {
        // Long operation goes here
    }

    @Override
    protected void onPostExecute(MyResult result) {
        dialog.dismiss();
        onCompletion(result);
    }
};

task.execute(...);

dialog.setOnCancelListener(new OnCancelListener() {
    @Override
    public void onCancel(DialogInterface arg0) {
        task.cancel(false);
    }
});

dialog.show();

私が読んだもの(http://bend-ing.blogspot.com/2008/11/properly-handle-progress-dialog-in.html)から見て、Androidソースで見て、それを取得する唯一の可能な状況のようです例外は、アクティビティが破棄された場合です。しかし、私が述べたように、私は基本的なイベントのための活動のレクリエーションを禁止します。

だから、どんな提案も大歓迎です。


1
この質問には多くの答えがあります。もしそれらのどれかがあなたを助けたなら、それを正しい答えとして選択してください。
Parag Kadam

回答:


228

ダイアログを閉じてonPostExecuteメソッドからのアクティビティを終了する、このエラー発生することがあります。ダイアログが正常に終了する前に、アクティビティが終了することがあります。

シンプルで効果的な解決策

@Override
protected void onPostExecute(MyResult result) {
    try {
        if ((this.mDialog != null) && this.mDialog.isShowing()) {
            this.mDialog.dismiss();
        }
    } catch (final IllegalArgumentException e) {
        // Handle or log or ignore
    } catch (final Exception e) {
        // Handle or log or ignore
    } finally {
        this.mDialog = null;
    }  
}

44
簡単な解決策?はい。効果的ですか?おそらくこの場合。それをお勧めしますか?番号!そのようなすべての例外を飲み込まないでください!IllegalArgumentExceptionもキャッチしませんが、別の解決策を探します。
Simon Forsberg、2013年

6
通常、空のトライキャッチは悪い考えです...時にはそれが正しいことであるかもしれませんが。
トーマス

3
@Damjanあなたの応答によって、あなたはキャッチタイプの例外を提案します。まあ、これはグーグルの悪い習慣です。あなたはそれについてここで読むことができます:一般的な例外をキャッチしないでください
Yaniv 2013

17
これは効果的な修正だと思います。一般的なケースではこれを行うべきではありませんが、Androidフレームワークは簡単なチェックを提供しないため、通常とは異なる方法を使用する必要があります。また、ダイアログのisShowing()呼び出しが期待どおりに機能する場合、この種のハックは必要ありません。
SXC 2013

1
より良いものが見つかるまでの迅速な修正
Rohit Tigga 14

13

これが私の「防弾」ソリューションです。これは、このトピックで見つけたすべての良い回答をまとめたものです(@Damjanと@Kachiに感謝)。ここで、例外は、他のすべての検出方法が成功しなかった場合にのみ飲み込まれます。私の場合、ダイアログを自動的に閉じる必要があり、これがアプリをクラッシュから保護する唯一の方法です。お役に立てれば幸いです。コメントやより良い解決策がある場合は、投票してコメントを残してください。ありがとうございました!

public void dismissWithCheck(Dialog dialog) {
        if (dialog != null) {
            if (dialog.isShowing()) {

                //get the Context object that was used to great the dialog
                Context context = ((ContextWrapper) dialog.getContext()).getBaseContext();

                // if the Context used here was an activity AND it hasn't been finished or destroyed
                // then dismiss it
                if (context instanceof Activity) {

                    // Api >=17
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
                        if (!((Activity) context).isFinishing() && !((Activity) context).isDestroyed()) {
                            dismissWithTryCatch(dialog);
                        }
                    } else {

                        // Api < 17. Unfortunately cannot check for isDestroyed()
                        if (!((Activity) context).isFinishing()) {
                            dismissWithTryCatch(dialog);
                        }
                    }
                } else
                    // if the Context used wasn't an Activity, then dismiss it too
                    dismissWithTryCatch(dialog);
            }
            dialog = null;
        }
    }

    public void dismissWithTryCatch(Dialog dialog) {
        try {
            dialog.dismiss();
        } catch (final IllegalArgumentException e) {
            // Do nothing.
        } catch (final Exception e) {
            // Do nothing.
        } finally {
            dialog = null;
        }
    }

1
非常に良い解決策です!設定dialog = nullによる影響はありません。そして、StatusEventDialogただ読むべきDialogです。
hgoebl 2016年

1
StatusEventDialogをダイアログに変更する必要があります。
Sreekanth Karumanaghat

この答えは非常にうまく処理、受け入れられたものでなければならない
blueware

「正しく」なりたいので、try / catchがisDestroyed()使用できない場合にのみ使用することを理解しましたが、実際には、try / catchを常に使用するのと同じではないでしょうか?
zundi

11

回避策があるかもしれません。

私はAに(ファイルシステムを介して)項目の多くをロードしています同じ問題、持っていたListView経由をAsyncTask。をonPreExecute()起動しProgressDialog、次に(onPostExecute()およびonCancelled()を介してタスクが明示的にキャンセルされたときに呼び出されたAsyncTask.cancel())を介してそれを閉じた.cancel()

onCancelled()メソッドでダイアログを強制終了すると、同じ「java.lang.IllegalArgumentException:ビューがウィンドウマネージャにアタッチされていません」というエラーが発生しましたAsyncTask(優れたShelvesアプリでこれが実行されたのを確認しました)。

回避策は、AsyncTaskを含むパブリックフィールドを作成することでしたProgressDialog

public ProgressDialog mDialog;

次に、onDestroy()をキャンセルするときにAsyncTask、関連するダイアログを次の方法で強制終了することもできます。

AsyncTask.mDialog.cancel();

AsyncTask.cancel()DOESトリガーonCancelled()を呼び出すとAsyncTask、何らかの理由でそのメソッドが呼び出されるまでに、ビューがすでに破棄されているため、ダイアログのキャンセルが失敗します。


@Paulが述べたように、UserTask実装は非常に優れています。ソースコードはここにある: code.google.com/p/shelves/source/browse/trunk/Shelves/src/org/...
エヴィソング

:ユースケースは、同じプロジェクトで見つけることができますが code.google.com/p/shelves/source/browse/trunk/Shelves/src/org/...
エヴィソング

9

この問題を解決する正しい解決策は次のとおりです。

public void hideProgress() {
    if(mProgressDialog != null) {
        if(mProgressDialog.isShowing()) { //check if dialog is showing.

            //get the Context object that was used to great the dialog
            Context context = ((ContextWrapper)mProgressDialog.getContext()).getBaseContext();

            //if the Context used here was an activity AND it hasn't been finished or destroyed
            //then dismiss it
            if(context instanceof Activity) { 
                if(!((Activity)context).isFinishing() && !((Activity)context).isDestroyed()) 
                    mProgressDialog.dismiss();
            } else //if the Context used wasnt an Activity, then dismiss it too
                mProgressDialog.dismiss();
        }
        mProgressDialog = null;
    }
}

すべての例外を盲目的にキャッチするのではなく、この解決策は問題の根本に対処します。ダイアログの初期化に使用されるアクティビティがすでに終了しているときにダイアログを無視しようとすることです。KitKatを実行しているNexus 4で作業していますが、Androidのすべてのバージョンで機能するはずです。


3
isDestroyedAPI 17+が必要
Androiderson

なぜmProgressDialogをnullに設定する必要があるのですか?それはメモリリークに関連していますか?説明していただけますか?
Pawan、2016

@Pawan、それは私の側の実装の詳細です。これは必須ではなく、このクラスの関数が機能する方法にすぎません。進行状況ダイアログが非表示になった後、nullに設定しました。ユーザーが別の進行状況ダイアログを表示したい場合、新しいインスタンスがインスタンス化されます。
カチ

間違いなく!((Activity)context).isFinishing()が必要です、ありがとう!:)
Daniel Krzyczkowski 16

5

「ダムジャン」の意見に同意します。
多くのダイアログを使用する場合は、onDestroy()またはonStop()ですべてのダイアログを閉じる必要があります。
次に、「java.lang.IllegalArgumentException:ビューがウィンドウマネージャにアタッチされていない」例外が発生する頻度を減らすことができる場合があります。

@Override
protected void onDestroy() {
    Log.d(TAG, "called onDestroy");
    mDialog.dismiss();
    super.onDestroy();
}



しかし、少し超えています...
より明確にするために、onDestroyが呼び出された後にダイアログを表示しないようにします。
以下のように使用しません。しかしそれは明らかです。

private boolean mIsDestroyed = false;

private void showDialog() {
    closeDialog();

    if (mIsDestroyed) {
        Log.d(TAG, "called onDestroy() already.");
        return;
    }

    mDialog = new AlertDialog(this)
        .setTitle("title")
        .setMessage("This is DialogTest")
        .setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int which) {
                dialog.dismiss();
            }
        })
        .create();
    mDialog.show();
}

private void closeDialog() {
    if (mDialog != null) {
        mDialog.dismiss();
    }
}

@Override
protected void onDestroy() {
    Log.d(TAG, "called onDestroy");
    mIsDestroyed = true;
    closeDialog();
    super.onDestroy();
}


幸運を!


私は常に空のcatchブロックを回避することを好みます。試してみる価値はありますが、このエラーが発生するのは難しいため、実際に機能しているかどうかを判断できるのは時間だけです。とにかくありがとう。
Dror Fichman 2013年

空のキャッチブロックとは try / catchは使用しません。mIsDestroyed変数が動作を超えています。ただし、他のスレッドで作業した後にダイアログが表示するコードを記述する場合は、この変数が必要になることがあります。他のスレッドが動作しているときに、アクティビティが終了した場合は、この例外を確認できます。
Hogun

同じ問題があり、@ Override public void onPause(){if(dialog!= null)dialog.dismiss();を追加したとき、super.onPause(); }現在まで、このエラーはありません...ですから、あなたの答えと同じように、本当に便利だと思います
Chris Sim

@ChrisSimこんにちは!onPuase()とonDestroy()は違いです。ActivityがonPuaseの場合、ダイアログは閉じます。アプリを実行しても、ダイアログは表示されません。あなたはそれをしたいですか?
Hogun、2015

@Hogunはい、もちろん、同じ考えです。破壊ではなく一時停止で必要なので、破壊ではなく一時停止でダイアログを閉じます。次に、nullでない場合にのみ閉じます。これを他の人に説明してくれてありがとう。
Chris Sim、

4

これを使って。

if(_dialog!=null && _dialog.isShowing())
_dialog.dismiss();

2
@Damjanが提案したソリューションとほぼ同じです。
Yury

28
これだけでは不十分です。このチェックでもIllegalArgumentExceptionが発生します。
マーフィー

私は同じ解決策をとりましたが、それが効果的であるかどうかはまだわかりません。唯一の違いは、2番目の部分.isShowing()がnullの場合に評価されないことを確認するために、2つをネストしました。
Nick

2
これでは不十分です。
2013

1
@Nick:ifこのような場合、複数のをネストする必要はありません。Javaの&&演算子は遅延評価(短絡とも呼ばれます)を持っています。つまり、最初のオペランドが評価された場合、2番目のオペランドは評価されませんfalse(つまり、これ&&は常にfalseとにかく、したがって「遅延」評価です。同様に||、最初のオペランドがに評価される場合、2番目のオペランドは評価されませんtrue。注:&and |演算子にはこの動作がないため、常に両方のオペランドを評価します。
マティアス

3

私は同じ問題を抱えていました、あなたはそれを次の方法で解決できます:

@Override
protected void onPostExecute(MyResult result) {
    try {
        if ((this.mDialog != null) && this.mDialog.isShowing()) {
            this.mDialog.dismiss();
        }
    } catch (final IllegalArgumentException e) {
        // Handle or log or ignore
    } catch (final Exception e) {
        // Handle or log or ignore
    } finally {
        this.mDialog = null;
    }  
}

2

私はあなたのコードは提案された他の答えとは異なり正しいと思います。onPostExecuteはUIスレッドで実行されます。これがAsyncTaskの要点です。runOnUiThreadの呼び出しやハンドラーの処理について心配する必要はありません。さらに、ドキュメントによれば、dismiss()は任意のスレッドから安全に呼び出すことができます(例外がこれに当てはまるかどうかは不明です)。

おそらく、アクティビティが表示されなくなった後にdialog.dismiss()が呼び出されるタイミングの問題ですか?

setOnCancelListenerをコメントアウトし、バックグラウンドタスクの実行中にアクティビティを終了するとどうなるかをテストする場合はどうでしょうか。次に、onPostExecuteは、すでに閉じられているダイアログを閉じようとします。アプリがクラッシュした場合は、ダイアログを閉じる前に、ダイアログが開いているかどうかを確認できます。

私はまったく同じ問題を抱えているので、コードで試してみましょう。


dimiss()コードも調べましたが、実際、どのスレッドからも安全に呼び出すことができます。ところで、この問題はユーザーの電話で発生するため、テストに問題があります。自分で再現することはできませんでした:-(コードを分析して理解しようとしています...タイミングによると、これについて考えていました、しかし、ダイアログの前にアクティビティを閉じることができる状況を想像することはできません。[戻る]を押すと、ダイアログが最初にキャンセルされます。そして、アクティビティの自動再作成はマニフェストファイルによって禁止されていますが、それでもいくつかの方法で再作成できる場合があります。何か見つけたら教えてください!
alex2k8

2

アレックス、

私はここで間違っている可能性がありますが、「実在する」複数の電話に、静的指向としてマークされているアプリケーションで方向を切り替える原因となるバグがあると思います。これは私の個人用電話と、私たちのグループが使用する多くのテスト用電話(droid、n1、g1、heroなど)でかなり発生します。通常、静的な方向(おそらく垂直)とマークされたアプリは、水平方向を使用して1〜2秒レイアウトされ、すぐに元に戻ります。最終的には、アプリの向きを変えたくない場合でも、そうなるように準備する必要があります。どのような条件下でこの動作を再現できるかはわかりません。Androidのバージョンに固有のものかどうかはわかりません。私が知っているのは、それが何度も起こるのを見たということです:(

提供されたソリューションを使用することをお勧めします Activity onCreateDialogメソッドをオーバーライドし、Android OSがダイアログのライフサイクルを管理できるようにすることを提案するリンクでます。アクティビティで方向を切り替えたくない場合でも、どこかで方向を切り替えているように見えます。方向の切り替えを常に防ぐ方法を追跡することもできますが、私は個人的に、市場に出ている現在のすべてのAndroidフォンで動作する確実な方法があるとは信じていません。


1
デバイスの向きが切り替わらないようにすることはできますが、アクティビティを破棄/再作成する他の設定変更がたくさんあります。一般的なのは、キーボードのスライドインまたはスライドアウトです。
MaximumGoat 2012

2

ほとんどの場合、私にとってうまくいったのは、アクティビティが終了していないかどうかを確認することです。

if (!mActivity.isFinishing()) {
    dialog.dismiss();
}

2

アクティビティは、回転またはキーボードスライドによって再作成されないと宣言されています。

同じ問題が発生しました。APIレベル13以上の修正。
Androidドキュメントから:

注:アプリケーションがAPIレベル13以上をターゲットにしている場合(minSdkVersionおよびtargetSdkVersion属性で宣言されているとおり)、「screenSize」構成も宣言する必要があります。これは、デバイスが縦向きと横向きの間で切り替わるときにも変化するためです。

だから私は私のマニフェストをこれに変更しました:

<activity
        android:name="MyActivity"
        android:configChanges="orientation|screenSize"
        android:label="MyActivityName" >
</activity>

そして今それはうまく働きます。電話を回転してもアクティビティが再作成されず、進行状況ダイアログとビューは同じままです。エラーはありません。


これは解決策ではありません。たとえば、私はadmobからのAdviewアクティビティを持っているので、サイズ変更のアクティビティを再作成する必要があります。
batmaci

2

まず最初に、ダイアログを閉じようとするときにエラー処理を行います。

 if ((progressDialog != null) && progressDialog.isShowing()) {
            progressDialog.dismiss();
            progressDialog = null;
        }

それが解決しない場合は、アクティビティのonStop()メソッドで閉じます。

 @Override
    protected void onStop() {
        super.onStop();
        if ((progressDialog != null) && progressDialog.isShowing()) {
            progressDialog.dismiss();
            progressDialog = null;
        }
    }

1

ボタンを使用してサーバーからリストを同期しているときにも同じ問題が発生しました:1)ボタンをクリックします2)サーバーからリストをダウンロードしているときに進行ダイアログが表示されます3)デバイスを別の方向に向けます4)java.lang .IllegalArgumentException:progress.dismiss()中にAsyncTaskのpostExecute()でビューがウィンドウマネージャーにアタッチされていません。

修正を試してみたところ、問題が発生していなくても、リストにすべてのアイテムが表示されていないことがわかりました。

アクティビティが破棄される前にAsyncTaskが終了する(そしてダイアログを閉じる)ことを望んでいたので、asynctaskオブジェクトを属性にして、onDestroy()メソッドをオーバーライドしました。

非同期タスクに長い時間がかかる場合、ユーザーはデバイスが遅いと感じるかもしれませんが、それは、進行状況ダイアログが表示されているときにデバイスの向きを変更しようとする代償です。また、時間がかかってもアプリはクラッシュしません。

private AsyncTask<Boolean, Void, Boolean> atask;

@Override
protected void onDestroy() {
    if (atask!=null)
        try {
            atask.get();
        } catch (InterruptedException e) {
        } catch (ExecutionException e) {
        }
    super.onDestroy();
}

1
@Override
        protected void onPostExecute(Void result) {
            super.onPostExecute(result);

            if (progressDialog != null && progressDialog.isShowing()) {
                Log.i(TAG, "onPostexucte");
                progressDialog.dismiss();
}
}

3
このコードスニペットは問題にどのように対処するかを説明する質問に回答する場合がありますが、サイトへの将来の訪問者があなたの答えを理解するのに役立ちます
RobV

0

以下のコードはあなたのために働きます、それは私のために完全にうまくいきます:

private void viewDialog() {
    try {
        Intent vpnIntent = new Intent(context, UtilityVpnService.class);
        context.startService(vpnIntent);
        final View Dialogview = View.inflate(getBaseContext(), R.layout.alert_open_internet, null);
        final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
                WindowManager.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.TYPE_SYSTEM_ALERT,
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                        | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_DIM_BEHIND,
                PixelFormat.TRANSLUCENT);
        params.gravity = Gravity.CENTER_HORIZONTAL | Gravity.CENTER_VERTICAL;
        windowManager.addView(Dialogview, params);

        Button btn_cancel = (Button) Dialogview.findViewById(R.id.btn_canceldialog_internetblocked);
        Button btn_okay = (Button) Dialogview.findViewById(R.id.btn_openmainactivity);
        RelativeLayout relativeLayout = (RelativeLayout) Dialogview.findViewById(R.id.rellayout_dialog);

            btn_cancel.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    Handler handler = new Handler(Looper.getMainLooper());
                    handler.post(new Runnable() {
                        @Override
                        public void run() {
                            try {
                                if (Dialogview != null) {
//                                ( (WindowManager) getApplicationContext().getSystemService(WINDOW_SERVICE)).removeView(Dialogview);
                                    windowManager.removeView(Dialogview);
                                }
                            } catch (final IllegalArgumentException e) {
                                e.printStackTrace();
                                // Handle or log or ignore
                            } catch (final Exception e) {
                                e.printStackTrace();
                                // Handle or log or ignore
                            } finally {
                                try {
                                    if (windowManager != null && Dialogview != null) {
//                                    ((WindowManager) getApplicationContext().getSystemService(WINDOW_SERVICE)).removeView(Dialogview);
                                        windowManager.removeView(Dialogview);
                                    }
                                } catch (Exception e) {
                                    e.printStackTrace();
                                }
                            }
                            //    ((WindowManager) getApplicationContext().getSystemService(WINDOW_SERVICE)).removeView(Dialogview);
//                        windowManager.removeView(Dialogview);


                        }
                    });
                }
            });
            btn_okay.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    Handler handler = new Handler(Looper.getMainLooper());
                    handler.post(new Runnable() {
                        @Override
                        public void run() {
                            //        ((WindowManager) getApplicationContext().getSystemService(WINDOW_SERVICE)).removeView(Dialogview);
                            try {
                                if (windowManager != null && Dialogview != null)
                                    windowManager.removeView(Dialogview);
                                Intent intent = new Intent(getBaseContext(), SplashActivity.class);
                                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
//                        intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
//                        intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);


                                context.startActivity(intent);
                            } catch (Exception e) {
                                windowManager.removeView(Dialogview);
                                e.printStackTrace();
                            }
                        }
                    });
                }
            });
        } catch (Exception e) {
            //` windowManager.removeView(Dialogview);
            e.printStackTrace();
        }
    }

バックグラウンドサービスから呼び出す場合は、ビューをグローバルに定義しないでください。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.