“ android.view.WindowManager $ BadTokenException: ไม่สามารถเพิ่มหน้าต่าง” บน buider.show ()


120

จากหลักของฉันฉันต้องการที่จะเรียกระดับชั้นและวิธีการที่อยู่ในระดับที่ฉันต้องแสดงactivity AlertDialogหลังจากปิดแล้วเมื่อกดปุ่มตกลงให้ส่งต่อไปยัง 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

นี่ไม่ใช่รหัสที่สมบูรณ์นี่เป็นรหัสที่ค่อนข้างใหญ่ดังนั้นฉันจึงเพิ่มเฉพาะส่วนที่นี่ซึ่งฉันพบปัญหาจากรายงานข้อขัดข้อง
MSIslam

ตกลงดี. โดยปกติคุณสามารถโพสต์ชื่อฟังก์ชันและแสดงความคิดเห็นว่าคุณกำลังทำสิ่งต่างๆอยู่ที่นั่น (อย่างที่ทำตอนนี้) เข้าใจง่ายกว่า :)
Shobhit Puri

คุณกำลังไปที่กิจกรรมอื่น ๆ จากกิจกรรมนี้อยู่ที่ไหนสักแห่งหรือไม่?
Shobhit Puri

1
//send to some other activityคุณได้เขียนความคิดเห็น ฉันคิดว่าถ้าคุณจะแสดงความคิดเห็นในส่วนที่คุณกำลังจะไปยังกิจกรรมใหม่ข้อผิดพลาดนี้จะหายไป ข้อผิดพลาดดูเหมือนจะเกิดขึ้นเนื่องจากกล่องโต้ตอบก่อนหน้าของคุณถูกปิดโดยสิ้นเชิงกิจกรรมใหม่ของคุณจะเริ่มขึ้น ในonPostExecute()กล่องโต้ตอบการแจ้งเตือนคุณมีและคุณกำลังให้บริบทของloginกิจกรรม แต่คุณกำลังนำทางไปยังกิจกรรมอื่นบริบทจึงไม่ถูกต้อง ดังนั้นคุณได้รับข้อผิดพลาดนี้! ดูstackoverflow.com/questions/15104677/…คำถามที่คล้ายกัน
Shobhit Puri

คำตอบ:


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

ปัญหา:

ข้อยกเว้นนี้เกิดขึ้นเมื่อแอปพยายามแจ้งผู้ใช้จากเธรดพื้นหลัง (AsyncTask) โดยการเปิดกล่องโต้ตอบ

หากคุณกำลังพยายามแก้ไข UI จากเธรดพื้นหลัง (โดยปกติจะมาจาก onPostExecute () ของ AsyncTask) และหากกิจกรรมเข้าสู่ขั้นตอนสุดท้ายเช่น) การโทรเสร็จสิ้นอย่างชัดเจน () ผู้ใช้กดปุ่มโฮมหรือย้อนกลับหรือกิจกรรมที่ทำโดย Android แล้วคุณ รับข้อผิดพลาดนี้

เหตุผล :

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

สารละลาย:

ใช้isFinishing()วิธีการที่ Android เรียกเพื่อตรวจสอบว่ากิจกรรมนี้อยู่ระหว่างการดำเนินการเสร็จสิ้นหรือไม่ไม่ว่าจะเป็นการโทรเสร็จสิ้นอย่างชัดเจน () หรือล้างกิจกรรมที่สร้างโดย 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();
      }
    }
  }
}

อัปเดต:

หน้าต่างโทเค็น:

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

 สถานการณ์จริงในโลก:

เมื่อแอปพลิเคชันเริ่มต้นเป็นครั้งแรก  ActivityManagerService  จะสร้างโทเค็นหน้าต่างชนิดพิเศษที่เรียกว่าโทเค็นหน้าต่างแอปพลิเคชันซึ่งระบุหน้าต่างคอนเทนเนอร์ระดับบนสุดของแอปพลิเคชันโดยไม่ซ้ำกัน ตัวจัดการกิจกรรมมอบโทเค็นนี้ให้กับทั้งแอปพลิเคชันและตัวจัดการหน้าต่างและแอปพลิเคชันจะส่งโทเค็นไปยังตัวจัดการหน้าต่างทุกครั้งที่ต้องการเพิ่มหน้าต่างใหม่ลงในหน้าจอ สิ่งนี้ช่วยให้มั่นใจได้ถึงการโต้ตอบที่ปลอดภัยระหว่างแอปพลิเคชันและตัวจัดการหน้าต่าง (โดยทำให้ไม่สามารถเพิ่มหน้าต่างที่ด้านบนของแอปพลิเคชันอื่น ๆ ได้) และยังทำให้ตัวจัดการกิจกรรมสามารถร้องขอโดยตรงไปยังตัวจัดการหน้าต่างได้


นี่เข้าท่ามาก! วิธีแก้ปัญหาของคุณก็ฟังดูดีสำหรับฉัน (y)
MSIslam

ข้อความ 'loginActivityWeakRef ช่องสุดท้ายว่างเปล่าอาจไม่ได้รับการเตรียมใช้งาน' และพยายามในลักษณะนี้: WeakReference สุดท้ายส่วนตัว <login> loginActivityWeakRef = WeakReference ใหม่ <login> (login.this); ไม่แน่ใจว่าถูกต้องหรือไม่
MSIslam

นอกจากนี้ฉันยังลบขั้นสุดท้ายก่อน WeakReference <login> loginActivityWeakRef เนื่องจากแสดงข้อผิดพลาดในตัวสร้าง
MSIslam

1
ลองใช้ chkCubscription ใหม่ (this) .execute (""); แทน chkCubscription.execute ("") ใหม่ ตามที่คุณโพสต์ไว้ด้านบน
Ritesh Gune

2
ข้อผิดพลาดสุดสยอง !! ฉันกำลังติดตามบทช่วยสอนและในฐานะ @PhilRoggenbuck ปัญหาของฉันเกิดจากการเรียก Toast..Show () ก่อนที่จะเรียก StartActivity (... ) เพื่อแก้ปัญหาฉันย้ายขนมปังปิ้งในกิจกรรมที่เพิ่งเรียกใหม่แทน!
Thierry

27

ฉันมีกล่องโต้ตอบแสดงฟังก์ชัน:

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
  • ก่อนอื่นคุณไม่สามารถขยาย AsyncTask ได้โดยไม่ต้องแทนที่ doInBackground
  • ครั้งที่สองลองสร้าง 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

ในกรณีนี้ผู้ใช้จะออกจากกิจกรรมของคุณก่อนที่จะเรียก onPostExecute ดังนั้นจึงไม่มีหน้าต่างให้ถือกล่องโต้ตอบและทำให้แอปพลิเคชันของคุณหยุดทำงาน เพิ่มการตั้งค่าสถานะไปที่ onStop เพื่อให้ทราบว่ากิจกรรมของคุณไม่ปรากฏให้เห็นอีกหรือไม่แล้วอย่าแสดงกล่องโต้ตอบ
moh.sukhni

onPostExecute ได้รับการเรียกใช้จริงเนื่องจาก builder.show () อยู่ภายใต้เงื่อนไขเมื่อฉันตรวจสอบว่าผู้ใช้ไม่ได้สมัครตามผลการเรียกใช้บริการเว็บจาก doInBackground () หรือไม่ ดังนั้นหากไม่ได้เรียก onPostExecute มันจะไม่มาถึงบรรทัด builder.show ()
MSIslam

onPostExecute ถูกเรียกโดยค่าเริ่มต้นหลังจาก doInBackground คุณไม่สามารถเรียกมันได้และสิ่งที่เคยมีจะถูกเรียกใช้
moh.sukhni

1
งาน async ของคุณจะทำงานต่อไปหลังจากที่ผู้ใช้นำทางจากกิจกรรมของคุณและสิ่งนี้จะทำให้ builder.show () ยึดแอปพลิเคชันของคุณเนื่องจากไม่มีกิจกรรมที่จะจัดการ UI ให้คุณ ดังนั้นแอปของคุณจึงดึงข้อมูลจากเว็บ แต่กิจกรรมของคุณถูกทำลายก่อนที่คุณจะได้รับข้อมูล
moh.sukhni

1

ฉันสร้างโต้ตอบในonCreateและใช้มันด้วยและshow hideสำหรับฉันแล้วสาเหตุที่แท้จริงไม่ได้ถูกยกเลิก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โดยไม่ต้องปิด / ปิดกล่องโต้ตอบ

เมื่อฉันปิดกล่องโต้ตอบความผิดพลาดก็หายไป

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

ด้วยแนวคิดตัวแปร globals นี้ฉันบันทึกอินสแตนซ์ 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 ใหม่จะเริ่มขึ้น (และบันทึกอินสแตนซ์เป็น globals)
  6. เปิดกล่องโต้ตอบจากเธรดแรก -> จะเปิดขึ้นและใช้งานได้

:)


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