android View không được đính kèm với trình quản lý cửa sổ


111

Tôi gặp một số trường hợp ngoại lệ sau:

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)

Tôi đã tìm kiếm nó và thấy rằng nó có liên quan đến cửa sổ bật lên và quay màn hình, nhưng không có tham chiếu đến mã của tôi.

Các câu hỏi là:

  1. có cách nào để tìm ra chính xác khi nào vấn đề này đang xảy ra không?
  2. Ngoài việc xoay màn hình, có sự kiện hoặc hành động nào khác gây ra lỗi này không?
  3. làm cách nào để ngăn điều này xảy ra?

1
Xem liệu bạn có thể giải thích cách hoạt động được mô tả trong tệp kê khai hay không và hoạt động nào hiển thị trên màn hình khi lỗi xảy ra. Xem liệu bạn có thể giải quyết vấn đề của mình thành một testcase tối thiểu hay không.
Josh Lee

Có thể bạn đang cố gắng sửa đổi chế độ xem của mình trước khi View#onAttachedToWindow()được gọi?
Alex Lockwood

Câu trả lời:


163

Tôi đã gặp sự cố này trong đó trên một thay đổi hướng màn hình, hoạt động đã hoàn tất trước khi AsyncTask với hộp thoại tiến trình hoàn tất. Tôi dường như đã giải quyết điều này bằng cách đặt hộp thoại thành null onPause()và sau đó kiểm tra điều này trong AsyncTask trước khi loại bỏ.

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

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

... trong AsyncTask của tôi:

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

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

12
@YekhezkelYovel the AsyncTask đang chạy trong một chuỗi tồn tại sau khi khởi động lại hoạt động và có thể giữ các tham chiếu đến Hoạt động hiện đã chết và Chế độ xem của nó (đây thực sự là một sự cố rò rỉ bộ nhớ, mặc dù có thể là một sự cố ngắn hạn - tùy thuộc vào thời gian tác vụ hoàn thành ). Giải pháp trên hoạt động tốt, nếu bạn hài lòng chấp nhận tình trạng rò rỉ bộ nhớ ngắn hạn. Phương pháp được đề xuất là hủy () mọi AsyncTask đang chạy khi Hoạt động bị tạm dừng.
Stevie

8

Sau khi đấu tranh với vấn đề này, cuối cùng tôi đã kết thúc với giải pháp này:

/**
 * 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;
    }
}

Đôi khi, xử lý ngoại lệ tốt sẽ hoạt động tốt nếu không có giải pháp tốt hơn cho vấn đề này.


5

Nếu bạn có một Activityđối tượng xung quanh, bạn có thể sử dụng isDestroyed()phương pháp:

Activity activity;

// ...

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

Điều này thật tuyệt nếu bạn có một AsyncTasklớp con không ẩn danh mà bạn sử dụng ở nhiều nơi khác nhau.


3

Tôi đang sử dụng một lớp tĩnh tùy chỉnh tạo và ẩn một hộp thoại. lớp học này đang được sử dụng bởi các hoạt động khác không chỉ một hoạt động. Bây giờ vấn đề bạn mô tả cũng xuất hiện với tôi và tôi đã ở lại qua đêm để tìm ra giải pháp ..

Cuối cùng tôi trình bày cho bạn giải pháp!

nếu bạn muốn hiển thị hoặc loại bỏ một hộp thoại và bạn không biết hoạt động nào đã khởi tạo hộp thoại để chạm vào hộp thoại đó thì mã sau là dành cho bạn ..

 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

Giải pháp trên không làm việc cho tôi. Vì vậy, những gì tôi đã làm là tính ProgressDialogtoàn cầu và sau đó thêm nó vào hoạt động của tôi

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

để trong trường hợp nếu hoạt động bị phá hủy thì ProgressDialog cũng sẽ bị hủy.


0

Đối với câu hỏi 1):

Xem xét rằng thông báo lỗi dường như không cho biết dòng mã nào của bạn đang gây ra sự cố, bạn có thể theo dõi nó bằng cách sử dụng các điểm ngắt. Các điểm ngắt tạm dừng việc thực thi chương trình khi chương trình đến các dòng mã cụ thể. Bằng cách thêm các điểm ngắt vào các vị trí quan trọng, bạn có thể xác định dòng mã nào gây ra sự cố. Ví dụ: nếu chương trình của bạn gặp sự cố ở dòng setContentView (), bạn có thể đặt một điểm ngắt ở đó. Khi chương trình chạy, nó sẽ tạm dừng trước khi chạy dòng đó. Nếu sau đó việc tiếp tục lại khiến chương trình gặp sự cố trước khi đạt đến điểm ngắt tiếp theo, thì bạn biết rằng dòng đã giết chương trình nằm giữa hai điểm ngắt.

Việc thêm các điểm ngắt rất dễ dàng nếu bạn đang sử dụng Eclipse. Nhấp chuột phải vào lề ngay bên trái mã của bạn và chọn "Chuyển đổi điểm ngắt". Sau đó, bạn cần chạy ứng dụng của mình ở chế độ gỡ lỗi, nút trông giống như một con côn trùng màu xanh lá cây bên cạnh nút chạy bình thường. Khi chương trình chạm đến một điểm ngắt, Eclipse sẽ chuyển sang phối cảnh gỡ lỗi và hiển thị cho bạn dòng nó đang đợi. Để bắt đầu chương trình chạy lại, hãy tìm nút 'Tiếp tục', trông giống như 'Phát' bình thường nhưng có thanh dọc ở bên trái của hình tam giác.

Bạn cũng có thể điền vào ứng dụng của mình bằng Log.d ("Ứng dụng của tôi", "Một số thông tin ở đây cho bạn biết dòng nhật ký ở đâu"), sau đó đăng thông báo trong cửa sổ LogCat của Eclipse. Nếu bạn không thể tìm thấy cửa sổ đó, hãy mở nó bằng Window -> Show View -> Other ... -> Android -> LogCat.

Hy vọng rằng sẽ giúp!


7
Sự cố đang xảy ra trên điện thoại khách hàng, vì vậy tôi không có tùy chọn để gỡ lỗi. Ngoài ra vấn đề đang xảy ra mọi lúc, chỉ xảy ra đôi khi, bí quyết để dont làm thế nào để theo dõi nó
Daniel Benedykt

Bạn vẫn có thể nhận được tin nhắn gỡ lỗi từ điện thoại, nếu bạn có thể nhúng tay vào. Trong menu -> cài đặt -> ứng dụng -> phát triển, có một tùy chọn cho Gỡ lỗi USB. Nếu được bật, bạn có thể cắm điện thoại vào và LogCat bắt tất cả các dòng gỡ lỗi thông thường. Ngoài ra ... tốt ... khả năng gián đoạn có thể được giải thích bởi nó tùy thuộc vào trạng thái của chương trình. Tôi cho rằng bạn phải sử dụng chương trình một cách lộn xộn để xem liệu bạn có thể tạo lại sự cố hay không.
Steve Haley

4
Như tôi đã nói trước đây, sự cố đang xảy ra trên điện thoại khách và tôi không có quyền truy cập vào nó. Khách hàng có thể ở lục địa khác :)
Daniel Benedykt

Có những ứng dụng trên thị trường sẽ gửi email cho bạn logcat của họ.
Tim Green

1
Xin lưu ý rằng bạn có thể xoay trình giả lập bằng cách nhấn phím numpad 9
Rob

0

Tôi đã thêm phần sau vào tệp kê khai cho hoạt động đó

android:configChanges="keyboardHidden|orientation|screenLayout"

19
Đây không phải là một giải pháp: Hệ thống cũng có thể phá hủy hoạt động của bạn vì các lý do khác.
Roel

1
Bạn có thể giải thích câu trả lời của bạn được không?
Leebeedev

0

theo mã của windowManager (liên kết tại đây ), điều này xảy ra khi dạng xem bạn đang cố cập nhật (có thể thuộc về hộp thoại, nhưng không cần thiết) không còn được gắn vào gốc thực của cửa sổ.

như những người khác đã đề xuất, bạn nên kiểm tra trạng thái của hoạt động trước khi thực hiện các thao tác đặc biệt trên hộp thoại của mình.

đây là mã liên quan, là nguyên nhân gây ra sự cố (được sao chép từ mã nguồn 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;
        }
    }

mã tôi đã viết ở đây là những gì google làm. những gì tôi đã viết là để chỉ ra nguyên nhân của vấn đề này.
nhà phát triển android

0

Sự cố của tôi đã được giải quyết bằng cách mở khóa xoay màn hình trên Android của tôi, ứng dụng gây ra sự cố cho tôi hiện hoạt động hoàn hảo


0

Một tùy chọn khác là không bắt đầu tác vụ không đồng bộ cho đến khi hộp thoại được gắn vào cửa sổ bằng cách ghi đè onAttachedToWindow () trên hộp thoại, theo cách đó nó luôn có thể loại bỏ.


0

Hoặc đơn giản bạn có thể thêm

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

điều này sẽ làm ProgressDialogcho không thể hủy bỏ


-2

Tại sao không thử bắt, như thế này:

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

IllegalStateOfException nên được xử lý theo cách tốt hơn, thay vì chỉ để hoạt động bất thường.
Lavakush

-3

khi bạn khai báo hoạt động trong tệp kê khai, bạn cần android: configChanges = "direction"

thí dụ:

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

1
Thẻ này chỉ được yêu cầu nếu nhà phát triển không muốn phá hủy và tạo lại hoạt động của hệ thống android.
Ankit
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.