กล่องโต้ตอบการขว้าง "ไม่สามารถเพิ่มหน้าต่าง - โทเค็น null ไม่ได้สำหรับแอปพลิเคชัน" ที่มี getApplication () เป็นบริบท


665

กิจกรรมของฉันพยายามสร้าง AlertDialog ซึ่งต้องใช้บริบทเป็นพารามิเตอร์ ทำงานได้ตามที่คาดไว้หากฉันใช้:

AlertDialog.Builder builder = new AlertDialog.Builder(this);

อย่างไรก็ตามฉันมักจะใช้ "สิ่งนี้" เป็นบริบทเนื่องจากมีโอกาสเกิดการรั่วไหลของหน่วยความจำเมื่อกิจกรรมถูกทำลายและสร้างใหม่แม้ในช่วงที่เรียบง่ายเช่นการหมุนหน้าจอ จากโพสต์ที่เกี่ยวข้องในบล็อกของนักพัฒนา Android :

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

แต่สำหรับAlertDialog()ทั้งgetApplicationContext()หรือgetApplication()ยอมรับว่าเป็นบริบทตามที่มันโยนข้อยกเว้น:

"ไม่สามารถเพิ่มหน้าต่าง - โทเค็น null ไม่ได้สำหรับแอปพลิเคชัน"

ต่อการอ้างอิง: 1 , 2 , 3 , ฯลฯ

ดังนั้นสิ่งนี้ควรได้รับการพิจารณาว่าเป็น "บั๊ก" เนื่องจากเราได้รับคำแนะนำให้ใช้อย่างเป็นทางการActivity.getApplication()แต่มันก็ไม่ทำงานตามที่โฆษณาไว้?

จิม


การอ้างอิงสำหรับรายการแรกที่ R.Guy แนะนำให้ใช้ getApplication: android-developers.blogspot.com/2009/01/…
gymshoe

การอ้างอิงอื่น ๆ : stackoverflow.com/questions/1561803/…
gymshoe

1
การอ้างอิงอื่น ๆ : stackoverflow.com/questions/2634991/…
gymshoe


คำตอบ:


1354

แทนการใช้เพียงgetApplicationContext()ActivityName.this


67
ที่ดี! เพียงแค่แสดงความคิดเห็นในเรื่องนั้น .. บางครั้งคุณอาจต้องเก็บ "นี้" ไว้ทั่วโลก (ตัวอย่าง) เพื่อเข้าถึงโดยใช้วิธีการที่ผู้ฟังใช้ซึ่งเป็นเจ้าของ 'นี่' ในกรณีดังกล่าวคุณจะต้องกำหนด "บริบทบริบท" ทั่วโลกจากนั้นใน onCreate ให้ตั้ง "context = this" และจากนั้นอ้างถึง "บริบท" หวังว่ามันจะมีประโยชน์เช่นกัน
Steven L

8
ที่จริงแล้วในListenerชั้นเรียนมักจะเป็นแบบไม่ระบุตัวตนภายในฉันมักจะทำfinal Context ctx = this;และฉันไม่อยู่;)
อเล็กซ์

28
@StevenL ในการทำสิ่งที่คุณพูดคุณควรใช้ ExternalClassName.this เพื่ออ้างถึง "this" ของคลาสภายนอกอย่างชัดเจน
Artem Russakovskii

11
จะไม่ใช้ "สิ่งนี้" รั่วถ้าใช้กล่องโต้ตอบของคุณในการติดต่อกลับและคุณออกจากกิจกรรมก่อนที่จะมีการโทรกลับ? อย่างน้อยนั่นคือสิ่งที่ Android ดูเหมือนจะบ่นใน logcat
Artem Russakovskii

6
ฉันจะไม่แนะนำวิธีการ @StevenLs เพราะคุณสามารถรั่วไหลของความทรงจำของกิจกรรมนั้นได้อย่างง่ายดายเว้นแต่ว่าคุณจะจำข้อมูลอ้างอิงคงที่ใน onDestroy - Artem ได้อย่างถูกต้อง วิธีการของ StevenL นั้นเกิดจากการขาดความเข้าใจว่า Java ทำงานอย่างไร
Dori

192

การใช้thisไม่ได้ผลสำหรับฉัน แต่MyActivityName.thisทำได้ หวังว่านี่จะช่วยให้ทุกคนที่ไม่thisสามารถทำงานได้


63
นั่นคือสิ่งที่เกิดขึ้นเมื่อคุณใช้thisจากภายในของชั้นใน OuterClass.thisหากคุณต้องการที่จะอ้างอิงอินสแตนซ์ของชั้นนอกคุณต้องระบุเช่นนั้นที่คุณทำกับ ใช้thisอินสแตนซ์ของคลาสภายในสุดเสมอ
kaka

60

คุณสามารถใช้งานต่อไปได้getApplicationContext()แต่ก่อนการใช้งานคุณควรเพิ่มการตั้งค่าสถานะนี้: dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT)และข้อผิดพลาดจะไม่ปรากฏขึ้น

เพิ่มสิทธิ์ต่อไปนี้ในรายการของคุณ:

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

1
ฉันไม่สามารถเพิ่มหน้าต่าง android.view.ViewRootImpl$W@426ce670 - ปฏิเสธสิทธิ์สำหรับประเภทหน้าต่างนี้
Ram G.

เพิ่มการอนุญาต: <ใช้ - การอนุญาต android: name = "android.permission.SYSTEM_ALERT_WINDOW" />
codezjx

3
ดูเหมือนว่าคุณไม่สามารถเปิดใช้งานการอนุญาตนี้ใน API 23 เป็นต้นไปcode.google.com/p/android-developer-preview/issues/…
roy zhang

1
คุณสามารถใช้สำหรับ API 23 เป็นต้นไปอย่างไรก็ตามคุณต้องแจ้งผู้ใช้: startActivityForResult (เจตนาใหม่ (การตั้งค่า .ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse ("แพ็คเกจ:" + getPackageName ()), OVERLAY_PERMISSION_REQ_CODE อย่างไรก็ตามไม่ว่าคุณจะใช้มันเป็นอีกเรื่องหนึ่งก็ตาม ...
เบ็นโอนีล

2
สิ่งนี้มีประโยชน์เมื่อคุณแสดงกล่องโต้ตอบความคืบหน้าภายในบริการ
Anand Savjani

37

คุณระบุปัญหาได้อย่างถูกต้องเมื่อคุณพูดว่า "... สำหรับ AlertDialog () ไม่ได้รับ getApplicationContext () หรือ getApplication () เป็นบริบทเนื่องจากมันมีข้อยกเว้น: 'ไม่สามารถเพิ่มหน้าต่าง - โทเค็น null ไม่ได้สำหรับ ใบสมัคร'"

ในการสร้างไดอะล็อกคุณต้องมีบริบทของกิจกรรมหรือบริบทบริการไม่ใช่บริบทแอปพลิเคชัน (ทั้ง getApplicationContext () และ getApplication () ส่งคืนบริบทแอปพลิเคชัน)

นี่คือวิธีที่คุณได้รับบริบทของกิจกรรม :

(1) ในกิจกรรมหรือบริการ:

AlertDialog.Builder builder = new AlertDialog.Builder(this);

(2) ในส่วน: AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());

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

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

เพื่อป้องกันการรั่วไหลของหน่วยความจำให้ตรวจสอบรหัสของคุณเพื่อหาข้อผิดพลาดเชิงตรรกะที่ทำให้หน่วยความจำที่จัดสรรถูกอ้างอิงโดยไม่มีกำหนดโดย "this" (หรือการอ้างอิงอื่น ๆ ) อย่าลืมตรวจสอบการอ้างอิงลูกโซ่ด้วย นี่คือเครื่องมือบางอย่างที่คุณสามารถใช้เพื่อช่วยคุณวิเคราะห์การใช้หน่วยความจำและค้นหาการรั่วไหลของหน่วยความจำที่น่ารำคาญ:


สำหรับกิจกรรมคุณสามารถใช้ActivityName.thisซึ่งActivityNameคือ (เห็นได้ชัด) ชื่อกิจกรรมของคุณ (เช่น MainActivity)
Luis Cabrera Benito

34

กล่องโต้ตอบของคุณไม่ควรเป็น "วัตถุที่มีอายุยืนยาวซึ่งต้องการบริบท" เอกสารกำลังสับสน โดยทั่วไปถ้าคุณทำสิ่งที่ชอบ:

static Dialog sDialog;

(หมายเหตุคงที่ )

จากนั้นในกิจกรรมที่คุณทำ

 sDialog = new Dialog(this);

คุณอาจจะรั่วกิจกรรมเดิมในระหว่างการหมุนเวียนหรือคล้ายกันที่จะทำลายกิจกรรม (เว้นแต่คุณจะล้างข้อมูลใน onDestroy แต่ในกรณีนั้นคุณอาจไม่ทำให้วัตถุไดอะล็อกคงที่)

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

Dialog mDialog;

...

mDialog = new Dialog(this);

เป็นเรื่องปกติและไม่ควรรั่วกิจกรรมเนื่องจาก mDialog จะถูกปลดปล่อยด้วยกิจกรรมเนื่องจากมันไม่คงที่


ฉันกำลังเรียกมันจาก asynctask สิ่งนี้ได้ผลสำหรับฉันขอบคุณเพื่อน ๆ
MemLeak

กล่องโต้ตอบของฉันคงที่เมื่อฉันลบการประกาศคงที่มันทำงานได้
Ceddy Muhoza

25

ฉันต้องส่งบริบทของฉันผ่านคอนสตรัคเตอร์บนอแด็ปเตอร์ที่กำหนดเองซึ่งแสดงเป็นส่วน ๆ และมีปัญหากับ getApplicationContext () ฉันแก้ไขด้วย:

this.getActivity().getWindow().getContext()ในการonCreateเรียกกลับของแฟรกเมนต์


4
สิ่งนี้ใช้งานได้สำหรับฉันเช่นกันฉันส่งต่อไปยังตัวสร้างของ AsyncTask ภายนอกที่ฉันใช้อยู่ (มันแสดงกล่องโต้ตอบความคืบหน้า)
Rohan Kandwal

3
นี่คือคำตอบที่แท้จริงสำหรับงานที่ซับซ้อนมากขึ้น :)
teejay

1
ฉันเห็นด้วยกับ @teejay
Erdi İzgi


20

ในActivityเมื่อคลิกปุ่มแสดงกล่องโต้ตอบ

Dialog dialog = new Dialog(MyActivity.this);

ทำงานให้ฉัน



18

สับน้อย: คุณสามารถป้องกันการทำลายกิจกรรมของคุณด้วย GC (คุณไม่ควรทำ แต่ก็สามารถช่วยในบางสถานการณ์อย่าลืมให้ชุด. contextForDialogไปnullเมื่อมันไม่จำเป็น):

public class PostActivity extends Activity  {
    ...
    private Context contextForDialog = null;
    ...
    public void onCreate(Bundle savedInstanceState) {
        ...
        contextForDialog = this;
    }
    ...
    private void showAnimatedDialog() {
        mSpinner = new Dialog(contextForDialog);
        mSpinner.setContentView(new MySpinner(contextForDialog));
        mSpinner.show();
    }
    ...
}

@MurtuzaKabul มันใช้งานได้เพราะนี่ == PostActivity ซึ่งสืบทอดมาจากกิจกรรม -> ซึ่งสืบทอดมาจากบริบทดังนั้นเมื่อคุณผ่านกล่องโต้ตอบบริบทของคุณคุณกำลังผ่านกิจกรรมจริง ๆ
Elad Gelman

13

หากคุณใช้แฟรกเมนต์และใช้ข้อความ AlertDialog / Toast ให้ใช้ getActivity () ในพารามิเตอร์บริบท

แบบนี้

ProgressDialog pdialog;
pdialog = new ProgressDialog(getActivity());
pdialog.setCancelable(true);
pdialog.setMessage("Loading ....");
pdialog.show();

12

เพียงใช้ต่อไปนี้:

สำหรับผู้ใช้ JAVA

ในกรณีที่คุณใช้กิจกรรม -> AlertDialog.Builder builder = new AlertDialog.Builder(this);

หรือ

AlertDialog.Builder builder = new AlertDialog.Builder(your_activity.this);

ในกรณีที่คุณใช้แฟรกเมนต์ -> AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());

สำหรับผู้ใช้ KOTLIN

ในกรณีที่คุณใช้กิจกรรม -> val builder = AlertDialog.Builder(this)

หรือ

val builder = AlertDialog.Builder(this@your_activity.this)

ในกรณีที่คุณใช้แฟรกเมนต์ -> val builder = AlertDialog.Builder(activity!!)


9

เพิ่ม

dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);

และ

"android.permission.SYSTEM_ALERT_WINDOW"/> โดยชัดแจ้ง

มันเหมาะกับฉันแล้ว หลังจากปิดและเปิดแอปพลิเคชันขึ้นมาก็ทำให้ฉันมีข้อผิดพลาดในเวลานั้น


9

ฉันใช้ProgressDialogในส่วนและได้รับข้อผิดพลาดนี้เมื่อผ่านgetActivity().getApplicationContext()เป็นพารามิเตอร์ตัวสร้าง เปลี่ยนเป็นgetActivity().getBaseContext()ไม่ทำงาน

ทางออกสำหรับฉันก็คือการผ่านgetActivity(); กล่าวคือ

progressDialog = new ProgressDialog(getActivity());



6

หากคุณอยู่นอกกิจกรรมคุณต้องใช้ฟังก์ชัน "NameOfMyActivity.this" ในฟังก์ชันของคุณเป็นตัวอย่างกิจกรรม:

public static void showDialog(Activity activity) {
        AlertDialog.Builder builder = new AlertDialog.Builder(activity);
        builder.setMessage("Your Message")
        .setPositiveButton("Yes", dialogClickListener)
        .setNegativeButton("No", dialogClickListener).show();
}


//Outside your Activity
showDialog(NameOfMyActivity.this);

5

หากคุณใช้แฟรกเมนต์และใช้AlertDialog / Toastข้อความให้ใช้getActivity()ในพารามิเตอร์บริบท

ทำงานให้ฉัน

ไชโย!


5

ลองใช้บริบทของกิจกรรมที่จะอยู่ในกล่องโต้ตอบ แต่ระวังเมื่อคุณใช้คำหลัก "นี้" เพราะมันจะไม่ทำงานทุกครั้ง

Forex ตัวอย่างถ้าคุณมี TabActivity เป็นโฮสต์ที่มีสองแท็บและแต่ละแท็บเป็นกิจกรรมอื่นและถ้าคุณพยายามสร้างกล่องโต้ตอบจากแท็บใดแท็บหนึ่ง (กิจกรรม) และถ้าคุณใช้ "สิ่งนี้" คุณจะได้รับข้อยกเว้น ไดอะล็อก case ควรเชื่อมต่อกับกิจกรรมโฮสต์ที่โฮสต์ทุกอย่างและมองเห็นได้ (คุณสามารถพูดบริบทของกิจกรรมหลักของผู้ปกครองที่มองเห็นได้)

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


4

สำหรับผู้อ่านในอนาคตสิ่งนี้จะช่วย:

public void show() {
    if(mContext instanceof Activity) {
        Activity activity = (Activity) mContext;
        if (!activity.isFinishing() && !activity.isDestroyed()) {
            dialog.show();
        }
    }
}


2

หรือความเป็นไปได้อื่นคือการสร้างไดอะล็อกดังนี้

final Dialog dialog = new Dialog(new ContextThemeWrapper(
            this, R.style.MyThemeDialog));

2

ฉันคิดว่ามันอาจเกิดขึ้นเช่นกันหากคุณพยายามแสดงข้อความโต้ตอบจากเธรดที่ไม่ใช่เธรด UI หลัก

ใช้runOnUiThread()ในกรณีนั้น


2

ลองใช้getParent()สถานที่ของการโต้แย้งเช่นความAlertDialog.Builder(getParent());หวังใหม่มันจะได้ผลมันใช้ได้ผลสำหรับฉัน


1

หลังจากดูที่ API คุณสามารถส่งผ่านกล่องโต้ตอบกิจกรรมของคุณหรือ getActivity หากคุณอยู่ในส่วนแล้วล้างข้อมูลด้วย dialog.dismiss () ในวิธีการส่งคืนเพื่อป้องกันการรั่วไหล

แม้ว่าจะไม่ได้ระบุอย่างชัดเจนทุกที่ที่ฉันรู้ดูเหมือนว่าคุณจะถูกส่งกลับโต้ตอบใน OnClickHandlers เพียงเพื่อทำเช่นนี้


0

หากกล่องโต้ตอบของคุณกำลังสร้างบนอะแดปเตอร์:

ผ่านกิจกรรมไปยังตัวสร้างอะแดปเตอร์:

adapter = new MyAdapter(getActivity(),data);

รับอะแดปเตอร์:

 public MyAdapter(Activity activity, List<Data> dataList){
       this.activity = activity;
    }

ตอนนี้คุณสามารถใช้กับเครื่องมือสร้างของคุณ

            AlertDialog.Builder alert = new AlertDialog.Builder(activity);

-1

นี่คือวิธีที่ฉันแก้ไขข้อผิดพลาดเดียวกันสำหรับแอปพลิเคชันของฉัน: การ
เพิ่มบรรทัดต่อไปนี้หลังจากสร้างไดอะล็อก:

dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);  

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

หวังว่านี่จะช่วยคุณในการพัฒนาแอพของคุณ

เดวิด


-1
android.support.v7.app.AlertDialog.Builder builder = new android.support.v7.app.AlertDialog.Builder(getWindow().getDecorView().getRootView().getContext());

builder.setTitle("Confirm");
builder.setMessage("Are you sure you want delete your old account?");

builder.setPositiveButton("YES", new DialogInterface.OnClickListener() {

    public void onClick(DialogInterface dialog, int which) {
        //Do nothing but close the dialog



        dialog.dismiss();

    }
});

builder.setNegativeButton("NO", new DialogInterface.OnClickListener() {

    @Override
    public void onClick(DialogInterface dialog, int which) {

        //Do nothing
        dialog.dismiss();
    }
});

android.support.v7.app.AlertDialog alert = builder.create();
alert.show();
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.