ウィンドウマネージャーに接続されていないAndroidビュー


111

次の例外があります。

java.lang.IllegalArgumentException: View not attached to window manager
at android.view.WindowManagerImpl.findViewLocked(WindowManagerImpl.java:355)
at android.view.WindowManagerImpl.updateViewLayout(WindowManagerImpl.java:191)
at android.view.Window$LocalWindowManager.updateViewLayout(Window.java:428)
at android.app.Dialog.onWindowAttributesChanged(Dialog.java:596)
at android.view.Window.setDefaultWindowFormat(Window.java:1013)
at com.android.internal.policy.impl.PhoneWindow.access$700(PhoneWindow.java:86)
at com.android.internal.policy.impl.PhoneWindow$DecorView.drawableChanged(PhoneWindow.java:1951)
at com.android.internal.policy.impl.PhoneWindow$DecorView.fitSystemWindows(PhoneWindow.java:1889)
at android.view.ViewRoot.performTraversals(ViewRoot.java:727)
at android.view.ViewRoot.handleMessage(ViewRoot.java:1633)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:123)
at android.app.ActivityThread.main(ActivityThread.java:4338)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:521)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:860)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:618)
at dalvik.system.NativeStart.main(Native Method)

私はそれをグーグルで調べて、それがポップアップと画面の回転に関係していることを確認しましたが、私のコードへの参照はありません。

質問は次のとおりです。

  1. この問題がいつ発生しているのかを正確に知る方法はありますか?
  2. 画面を回転させる以外に、このエラーをトリガーする別のイベントまたはアクションはありますか?
  3. これを防ぐにはどうすればよいですか?

1
アクティビティがマニフェストにどのように記述されているか、およびエラーが発生したときに画面に表示されているアクティビティを説明できるかどうかを確認してください。問題を最小限のテストケースにチップできるかどうかを確認します。
Josh Lee、

View#onAttachedToWindow()呼び出される前にビューを変更しようとしているのでしょうか?
Alex Lockwood 2013年

回答:


163

画面の向きを変更すると、進捗ダイアログが完了するAsyncTaskの前にアクティビティが終了するという問題がありました。ダイアログをnullに設定してからonPause()、閉じる前にAsyncTaskでこれを確認することで、これを解決したようです。

@Override
public void onPause() {
    super.onPause();

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

...私のAsyncTaskで:

protected void onPreExecute() {
    mDialog = ProgressDialog.show(mContext, "", "Saving changes...",
            true);
}

protected void onPostExecute(Object result) {
   if ((mDialog != null) && mDialog.isShowing()) { 
        mDialog.dismiss();
   }
}

12
@YekhezkelYovel asyncTaskは、アクティビティの再起動後も存続し、現在は無効になっているアクティビティとそのビューへの参照を保持している可能性があるスレッドで実行されています(これは事実上メモリリークですが、おそらく短期的なものですが、タスクが完了するまでにかかる時間によって異なります) )。上記のソリューションは、短期間のメモリリークを受け入れても問題なく機能します。推奨されるアプローチは、アクティビティが一時停止されているときに実行中のAsyncTaskをすべてキャンセル()することです。
スティービー2013年

8

この問題との戦いの後、私は最終的にこの回避策に行き着きます:

/**
 * Dismiss {@link ProgressDialog} with check for nullability and SDK version
 *
 * @param dialog instance of {@link ProgressDialog} to dismiss
 */
public void dismissProgressDialog(ProgressDialog dialog) {
    if (dialog != null && 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 (!((Activity) context).isFinishing() {
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
                        if (!((Activity) context).isDestroyed()) {
                            dismissWithExceptionHandling(dialog);
                        }
                    } else { 
                        // Api < 17. Unfortunately cannot check for isDestroyed()
                        dismissWithExceptionHandling(dialog);
                    }
                }
            } else
                // if the Context used wasn't an Activity, then dismiss it too
                dismissWithExceptionHandling(dialog);
        }
        dialog = null;
    }
}

/**
 * Dismiss {@link ProgressDialog} with try catch
 *
 * @param dialog instance of {@link ProgressDialog} to dismiss
 */
public void dismissWithExceptionHandling(ProgressDialog dialog) {
    try {
        dialog.dismiss();
    } catch (final IllegalArgumentException e) {
        // Do nothing.
    } catch (final Exception e) {
        // Do nothing.
    } finally {
        dialog = null;
    }
}

この問題のより良い解決策がなかった場合、優れた例外処理がうまく機能することがあります。


5

Activityオブジェクトがぶらぶらしている場合は、次のisDestroyed()メソッドを使用できます。

Activity activity;

// ...

if (!activity.isDestroyed()) {
    // ...
}

これはAsyncTask、さまざまな場所で使用する非匿名のサブクラスがある場合に便利です。


3

ダイアログの表示と非表示を切り替えるカスタム静的クラスを使用しています。このクラスは、1つのアクティビティだけでなく、他のアクティビティでも使用されています。今、あなたが説明した問題も私に現れました、そして私は解決策を見つけるために一晩滞在しました。

最後に私はあなたに解決策を提示します!

ダイアログを表示または非表示にしたいが、それに触れるためにどのアクティビティがダイアログを開始したのかわからない場合は、次のコードが適しています。

 static class CustomDialog{

     public static void initDialog(){
         ...
         //init code
         ...
     }

      public static void showDialog(){
         ...
         //init code for show dialog
         ...
     }

     /****This is your Dismiss dialog code :D*******/
     public static void dismissProgressDialog(Context context) {                
            //Can't touch other View of other Activiy..
            //http://stackoverflow.com/questions/23458162/dismiss-progress-dialog-in-another-activity-android
            if ( (progressdialog != null) && progressdialog.isShowing()) {

                //is it the same context from the caller ?
                Log.w("ProgressDIalog dismiss", "the dialog is from"+progressdialog.getContext());

                Class caller_context= context.getClass();
                Activity call_Act = (Activity)context;
                Class progress_context= progressdialog.getContext().getClass();

                Boolean is_act= ( (progressdialog.getContext()) instanceof  Activity )?true:false;
                Boolean is_ctw= ( (progressdialog.getContext()) instanceof  ContextThemeWrapper )?true:false;

                if (is_ctw) {
                    ContextThemeWrapper cthw=(ContextThemeWrapper) progressdialog.getContext();
                    Boolean is_same_acivity_with_Caller= ((Activity)(cthw).getBaseContext() ==  call_Act )?true:false;

                    if (is_same_acivity_with_Caller){
                        progressdialog.dismiss();
                        progressdialog = null;
                    }
                    else {
                        Log.e("ProgressDIalog dismiss", "the dialog is NOT from the same context! Can't touch.."+((Activity)(cthw).getBaseContext()).getClass());
                        progressdialog = null;
                    }
                }


            }
        } 

 }

1

上記の解決策は私にとってはうまくいきませんでした。だから私がやったことProgressDialogはグローバルに取って、これを私の活動に追加することです

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

そのため、アクティビティが破棄された場合、ProgressDialogも破棄されます。


0

質問1)の場合:

エラーメッセージがコードのどの行が問題の原因であるかを示していないようであることを考えると、ブレークポイントを使用してそれを追跡できます。プログラムが特定のコード行に到達すると、ブレークポイントはプログラムの実行を一時停止します。重要な場所にブレークポイントを追加することにより、クラッシュの原因となったコード行を特定できます。たとえば、プログラムがsetContentView()行でクラッシュする場合、そこにブレークポイントを置くことができます。プログラムが実行されると、その行を実行する前に一時停止します。その後、再開するとプログラムがクラッシュしてから次のブレークポイントに到達する場合は、プログラムを強制終了した行が2つのブレークポイントの間にあったことがわかります。

Eclipseを使用している場合、ブレークポイントの追加は簡単です。コードのすぐ左のマージンを右クリックして、「ブレークポイントの切り替え」を選択します。次に、アプリケーションをデバッグモードで実行する必要があります。これは、通常の実行ボタンの横にある緑色の虫のようなボタンです。プログラムがブレークポイントに到達すると、Eclipseはデバッグパースペクティブに切り替わり、待機している行を表示します。プログラムの実行を再開するには、「再開」ボタンを探します。「再開」ボタンは、通常の「再生」に似ていますが、三角形の左側に垂直バーがあります。

また、アプリケーションにLog.d( "My application"、 "Log some where the tells the log line is where")を入力して、EclipseのLogCatウィンドウにメッセージを投稿することもできます。そのウィンドウが見つからない場合は、ウィンドウ->ビューの表示->その他...-> Android-> LogCatで開きます。

お役に立てば幸いです。


7
クライアントの電話で問題が発生しているため、デバッグするオプションがありません。また、問題は常に発生しており、時々発生しているだけなので、追跡方法がわからない
Daniel Benedykt

あなたがそれを手に入れることができるならば、あなたはまだ電話からデバッグメッセージを得ることができます。メニュー->設定->アプリケーション->開発には、USBデバッグのオプションがあります。有効になっている場合は、電話を接続すると、LogCatがすべての通常のデバッグ行をキャッチします。それ以外...まあ...プログラムの状態によっては、断続性はそれによって説明できます。問題を再現できるかどうかを確認するために、プログラムをいじくり回す必要があると思います。
スティーブヘイリー

4
前に述べたように、問題はクライアントの電話で発生しており、アクセスできません。クライアントは別の大陸にいる可能性があります:)
Daniel Benedykt

市場には、logcatをメールで送信するアプリがあります。
Tim Green

1
テンキー9を押してエミュレータを回転できることを知っているだけ
Rob

0

そのアクティビティのマニフェストに以下を追加しました

android:configChanges="keyboardHidden|orientation|screenLayout"

19
これは解決策ではありません。他の理由により、システムがアクティビティを破壊する可能性があります。
Roel

1
あなたの答えを説明していただけますか?
Leebeedev

0

windowManagerのコードによると(ここにリンク)、これは更新しようとしているビュー(おそらくダイアログに属しているが必要ではない)がウィンドウの実際のルートにアタッチされていない場合に発生します。

他の人が提案したように、ダイアログで特別な操作を実行する前に、アクティビティのステータスを確認する必要があります。

問題の原因である関連コードは次のとおりです(Androidソースコードからコピー)。

public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
    if (!(params instanceof WindowManager.LayoutParams)) {
        throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
    }

    final WindowManager.LayoutParams wparams
            = (WindowManager.LayoutParams)params;

    view.setLayoutParams(wparams);

    synchronized (this) {
        int index = findViewLocked(view, true);
        ViewRootImpl root = mRoots[index];
        mParams[index] = wparams;
        root.setLayoutParams(wparams, false);
    }
}

private int findViewLocked(View view, boolean required) {
        synchronized (this) {
            final int count = mViews != null ? mViews.length : 0;
            for (int i=0; i<count; i++) {
                if (mViews[i] == view) {
                    return i;
                }
            }
            if (required) {
                throw new IllegalArgumentException(
                        "View not attached to window manager");
            }
            return -1;
        }
    }

ここで私が書いたコードは、グーグルが行うことです。私が書いたのは、この問題の原因を示すためです。
android開発者

0

私の問題は、Androidの画面の回転をuhlockすることで解決しました。問題を引き起こしていたアプリは、完全に機能するようになりました。


0

別のオプションは、ダイアログがonAttachedToWindow()をオーバーライドしてダイアログがウィンドウにアタッチされるまで非同期タスクを開始しないことです。これにより、常に非表示になります。


0

または単に追加できます

protected void onPreExecute() {
    mDialog = ProgressDialog.show(mContext, "", "Saving changes...", true, false);
}

ようになりますこれはProgressDialogキャンセル可能なのではないために


-2

次のようにキャッチしてみてください:

protected void onPostExecute(Object result) {
        try {
            if ((mDialog != null) && mDialog.isShowing()) {
                mDialog.dismiss();
            }
        } catch (Exception ex) {
            Log.e(TAG, ex.getMessage(), ex);
        }
    }

IllegalStateOfException異常な動作をするだけでなく、より適切な方法で処理する必要があります。
ラバクシュ2016年

-3

マニフェストでアクティビティを宣言するときは、android:configChanges = "orientation"が必要です

例:

<activity android:theme="@android:style/Theme.Light.NoTitleBar" android:configChanges="orientation"  android:label="traducción" android:name=".PantallaTraductorAppActivity"></activity>

1
このタグは、開発者がAndroidシステムによってアクティビティを破棄して再作成したくない場合にのみ必要です。
Ankit 2013年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.