android View ไม่ได้แนบกับตัวจัดการหน้าต่าง


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)

ฉันได้ googled และเห็นว่ามันเกี่ยวข้องกับป๊อปอัปและการพลิกหน้าจอ แต่ไม่มีการอ้างอิงถึงรหัสของฉัน

คำถามคือ:

  1. มีวิธีใดที่จะทราบได้อย่างแน่ชัดว่าปัญหานี้เกิดขึ้นเมื่อใด
  2. นอกเหนือจากการพลิกหน้าจอมีเหตุการณ์หรือการกระทำอื่นที่ทำให้เกิดข้อผิดพลาดนี้หรือไม่
  3. ฉันจะป้องกันไม่ให้สิ่งนี้เกิดขึ้นได้อย่างไร

1
ดูว่าคุณสามารถอธิบายกิจกรรมที่อธิบายไว้ในไฟล์ Manifest ได้อย่างไรและกิจกรรมใดที่อยู่บนหน้าจอเมื่อเกิดข้อผิดพลาด ดูว่าคุณสามารถตัดปัญหาของคุณไปสู่การทดสอบขั้นต่ำได้หรือไม่
Josh Lee

บางทีคุณอาจพยายามปรับเปลี่ยนมุมมองของคุณก่อนที่จะView#onAttachedToWindow()ถูกเรียก?
Alex Lockwood

คำตอบ:


163

ฉันประสบปัญหานี้เมื่อมีการเปลี่ยนแปลงการวางแนวหน้าจอกิจกรรมจะเสร็จสิ้นก่อนที่ AsyncTask พร้อมกล่องโต้ตอบความคืบหน้าจะเสร็จสมบูรณ์ ดูเหมือนว่าฉันจะแก้ไขปัญหานี้ได้โดยตั้งค่ากล่องโต้ตอบเป็นโมฆะ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
Yekhezkel Yovel AsyncTask กำลังทำงานในเธรดที่ยังคงอยู่จากการรีสตาร์ทกิจกรรมและอาจมีการอ้างอิงถึงกิจกรรมที่ตายแล้วในขณะนี้และมุมมองของมัน (นี่เป็นการรั่วไหลของหน่วยความจำอย่างมีประสิทธิภาพแม้ว่าอาจจะเป็นระยะสั้นก็ตาม - ขึ้นอยู่กับระยะเวลาที่งานจะเสร็จสมบูรณ์ ). วิธีแก้ปัญหาข้างต้นใช้งานได้ดีหากคุณยินดีที่จะยอมรับการรั่วไหลของหน่วยความจำระยะสั้น แนวทางที่แนะนำคือการยกเลิก () AsyncTask ที่กำลังทำงานอยู่เมื่อกิจกรรมถูกหยุดชั่วคราว
Stevie

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

ฉันใช้คลาสแบบคงที่ที่กำหนดเองซึ่งแสดงและซ่อนกล่องโต้ตอบ ชั้นเรียนนี้ถูกใช้โดยกิจกรรมอื่น ๆ ด้วยไม่ใช่แค่กิจกรรมเดียว ตอนนี้ปัญหาที่คุณอธิบายก็ดูเหมือนกับฉันและฉันได้พักค้างคืนเพื่อหาทางแก้ไข ..

ในที่สุดฉันก็เสนอทางออกให้คุณ!

หากคุณต้องการแสดงหรือปิดกล่องโต้ตอบและคุณไม่ทราบว่ากิจกรรมใดเริ่มต้นกล่องโต้ตอบเพื่อที่จะสัมผัสมันรหัสต่อไปนี้เหมาะสำหรับคุณ ..

 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 () คุณสามารถวางเบรกพอยต์ไว้ที่นั่นได้ เมื่อโปรแกรมทำงานโปรแกรมจะหยุดชั่วคราวก่อนที่จะรันบรรทัดนั้น หากการเริ่มต้นใหม่ทำให้โปรแกรมหยุดทำงานก่อนที่จะถึงเบรกพอยต์ถัดไปคุณจะรู้ว่าบรรทัดที่ฆ่าโปรแกรมอยู่ระหว่างจุดพักทั้งสอง

การเพิ่มเบรกพอยต์ทำได้ง่ายหากคุณใช้ Eclipse คลิกขวาที่ขอบด้านซ้ายของโค้ดแล้วเลือก "Toggle breakpoint" จากนั้นคุณต้องเรียกใช้แอปพลิเคชันของคุณในโหมดดีบักปุ่มที่ดูเหมือนแมลงสีเขียวถัดจากปุ่มเรียกใช้ปกติ เมื่อโปรแกรมถึงจุดพัก Eclipse จะเปลี่ยนไปใช้มุมมองการดีบักและแสดงให้คุณเห็นบรรทัดที่กำลังรออยู่ ในการเริ่มโปรแกรมทำงานอีกครั้งให้มองหาปุ่ม 'Resume' ซึ่งดูเหมือน 'Play' ปกติ แต่มีแถบแนวตั้งอยู่ทางด้านซ้ายของสามเหลี่ยม

คุณยังสามารถกรอกแอปพลิเคชันของคุณด้วย Log.d ("แอปพลิเคชันของฉัน", "ข้อมูลบางอย่างที่นี่ที่บอกคุณว่าบรรทัดบันทึกอยู่ที่ใด") ซึ่งจะโพสต์ข้อความในหน้าต่าง LogCat ของ Eclipse หากคุณไม่พบหน้าต่างนั้นให้เปิดขึ้นด้วย Window -> Show View -> Other ... -> Android -> LogCat

หวังว่าจะช่วยได้!


7
ปัญหานี้เกิดขึ้นกับโทรศัพท์ไคลเอนต์ดังนั้นฉันจึงไม่มีตัวเลือกในการดีบัก นอกจากนี้ปัญหายังเกิดขึ้นตลอดเวลาเพิ่งเกิดขึ้นในบางครั้งดังนั้นไม่รู้ว่าจะติดตามอย่างไร
Daniel Benedykt

คุณยังคงได้รับการแก้ไขข้อบกพร่องข้อความจากโทรศัพท์หากคุณสามารถรับมือกับมันได้ ในเมนู -> การตั้งค่า -> แอปพลิเคชัน -> การพัฒนามีตัวเลือกสำหรับการดีบัก USB หากเปิดใช้งานคุณสามารถเสียบโทรศัพท์และ LogCat จะตรวจจับบรรทัดการดีบักปกติทั้งหมด นอกเหนือจากนั้น ... ดี ... ความไม่ต่อเนื่องสามารถอธิบายได้โดยขึ้นอยู่กับสถานะของโปรแกรม ฉันคิดว่าคุณต้องวุ่นวายกับการใช้โปรแกรมเพื่อดูว่าคุณสามารถสร้างปัญหาขึ้นมาใหม่ได้หรือไม่
Steve Haley

4
ดังที่ฉันได้กล่าวไปก่อนหน้านี้ปัญหาเกิดขึ้นในโทรศัพท์ไคลเอนต์และฉันไม่สามารถเข้าถึงได้ ลูกค้าอาจอยู่ในทวีปอื่น :)
Daniel Benedykt

มีแอพในตลาดที่จะส่งอีเมลถึงคุณ Logcat
Tim Green

1
เพียงเพื่อให้คุณรู้ว่าคุณสามารถหมุนโปรแกรมจำลองได้โดยกดแป้นตัวเลข 9
Rob

0

ฉันได้เพิ่มสิ่งต่อไปนี้ในรายการสำหรับกิจกรรมนั้น

android:configChanges="keyboardHidden|orientation|screenLayout"

19
นี่ไม่ใช่วิธีแก้ปัญหา: ระบบอาจทำลายกิจกรรมของคุณเนื่องจากสาเหตุอื่นเช่นกัน
Roel

1
คุณช่วยอธิบายคำตอบของคุณได้ไหม
Leebeedev

0

ตามรหัสของ windowManager (ลิงก์ที่นี่ ) สิ่งนี้เกิดขึ้นเมื่อมุมมองที่คุณพยายามอัปเดต (ซึ่งอาจเป็นของกล่องโต้ตอบ แต่ไม่จำเป็น) ไม่ได้แนบกับรูทจริงของหน้าต่างอีกต่อไป

ตามที่ผู้อื่นแนะนำคุณควรตรวจสอบสถานะของกิจกรรมก่อนดำเนินการพิเศษในกล่องโต้ตอบของคุณ

นี่คือรหัส relavant ซึ่งเป็นสาเหตุของปัญหา (คัดลอกมาจากซอร์สโค้ด 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;
        }
    }

รหัสที่ฉันเขียนไว้ที่นี่คือสิ่งที่ Google ทำ สิ่งที่ฉันเขียนมีขึ้นเพื่อแสดงสาเหตุของปัญหานี้
นักพัฒนา Android

0

ปัญหาของฉันได้รับการแก้ไขโดย uhlocking การหมุนหน้าจอบนแอนดรอยด์ของฉันแอพที่ทำให้ฉันมีปัญหาตอนนี้ทำงานได้อย่างสมบูรณ์


0

อีกทางเลือกหนึ่งคือไม่ต้องเริ่มงาน async จนกว่ากล่องโต้ตอบจะแนบกับหน้าต่างโดยการแทนที่ 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 ควรได้รับการจัดการด้วยวิธีที่ดีกว่าเพียง แต่ทำตัวผิดปกติ
Lavakush

-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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.