คำเตือน: อย่าวางคลาสบริบทของ Android ในช่องคงที่ นี่คือการรั่วไหลของหน่วยความจำ (และหยุดการรันทันทีด้วย)


84

Android Studio:

อย่าวางคลาสบริบทของ Android ในช่องคงที่ นี่คือการรั่วไหลของหน่วยความจำ (และหยุดการรันทันทีด้วย)

ดังนั้น 2 คำถาม:

# 1 คุณจะเรียก a startServiceจากวิธีการคงที่โดยไม่มีตัวแปรคงที่สำหรับบริบทได้อย่างไร?
# 2 คุณส่ง localBroadcast จากวิธีการคงที่ (เหมือนกัน) ได้อย่างไร?

ตัวอย่าง:

public static void log(int iLogLevel, String sRequest, String sData) {
    if(iLogLevel > 0) {

        Intent intent = new Intent(mContext, LogService.class);
        intent.putExtra("UPDATE_MAIN_ACTIVITY_VIEW", "UPDATE_MAIN_ACTIVITY_VIEW");
        mContext.startService(intent);
    }
}

หรือ

        Intent intent = new Intent(MAIN_ACTIVITY_RECEIVER_INTENT);
        intent.putExtra(MAIN_ACTIVITY_REQUEST_FOR_UPDATE, sRequest));
        intent.putExtra(MAIN_ACTIVITY_DATA_FOR_VIEW, sData);
        intent.putExtra(MAIN_ACTIVITY_LOG_LEVEL, iLogLevel);
        LocalBroadcastManager.getInstance(mContext).sendBroadcast(intent);

อะไรคือวิธีที่ถูกต้องในการทำเช่นนี้โดยไม่ใช้mContext?

หมายเหตุ: ฉันคิดว่าคำถามหลักของฉันอาจเป็นวิธีส่งบริบทไปยังคลาสที่เมธอดการเรียกใช้อยู่


คุณไม่สามารถส่งบริบทเป็นพารามิเตอร์ในเมธอดได้หรือไม่?
Juan Cruz Soler

ฉันจะเรียกกิจวัตรนี้ในสถานที่ที่ไม่มีบริบทเช่นกัน
John Smith

# 1 ส่งผ่านเป็นพารามิเตอร์ # 2 เหมือนกัน
njzk2

จากนั้นคุณต้องส่งบริบทไปยังวิธีการโทรด้วย ปัญหาคือเขตข้อมูลแบบคงที่ไม่ได้เก็บรวบรวมขยะดังนั้นคุณอาจรั่วไหลของกิจกรรมด้วยมุมมองทั้งหมด
Juan Cruz Soler

1
@JohnSmith Cascade จากกิจกรรมเริ่มต้น (ผ่านพารามิเตอร์ตัวสร้างหรือพารามิเตอร์วิธีการ) จนถึงจุดที่คุณต้องการ
AndroidMechanic - Viral Patel

คำตอบ:


56

เพียงแค่ส่งเป็นพารามิเตอร์ให้กับวิธีการของคุณ ไม่มีความรู้สึกในการสร้างอินสแตนซ์แบบคงที่Contextเพียงเพื่อจุดประสงค์ในการเริ่มต้นIntentไฟล์.

นี่คือลักษณะวิธีการของคุณ:

public static void log(int iLogLevel, String sRequest, String sData, Context ctx) {
    if(iLogLevel > 0) {

        Intent intent = new Intent(ctx, LogService.class);
        intent1.putExtra("UPDATE_MAIN_ACTIVITY_VIEW", "UPDATE_MAIN_ACTIVITY_VIEW");
        ctx.startService(intent);
    }
}

อัปเดตจากความคิดเห็นเกี่ยวกับคำถาม: เรียงลำดับบริบทจากกิจกรรมที่เริ่มต้น (ผ่านพารามิเตอร์ตัวสร้างหรือพารามิเตอร์วิธีการ) จนถึงจุดที่คุณต้องการ


คุณสามารถให้ตัวอย่างตัวสร้างได้หรือไม่?
John Smith

หากชื่อคลาสของคุณMyClassเพิ่มตัวสร้างสาธารณะเช่นเดียวกับวิธีการpublic MyClass(Context ctx) { // put this ctx somewhere to use later }(นี่คือตัวสร้างของคุณ) ตอนนี้สร้างอินสแตนซ์ใหม่ของการMyClassใช้ตัวสร้างนี้เช่นMyClass mc = new MyClass(ctx);
AndroidMechanic - Viral Patel

ฉันไม่คิดว่าการส่งต่อตามความต้องการมันง่ายอย่างที่คิด แม้ว่าจะมีประโยชน์ที่ชัดเจนเช่นไม่ต้องกังวลเกี่ยวกับบริบทเก่าหรือเหมือนที่นี่ แต่เป็นแบบคงที่ สมมติว่าคุณต้องการบริบท [คุณอาจต้องการเขียนถึง prefs] ในการเรียกกลับการตอบกลับซึ่งจะเรียกใช้แบบอะซิงโครนัส ดังนั้นในบางครั้งคุณถูกบังคับให้ใส่ลงในช่องสมาชิก และตอนนี้คุณต้องคิดว่าจะไม่ทำให้มันนิ่งได้อย่างไร stackoverflow.com/a/40235834/2695276ดูเหมือนจะใช้งานได้
Rajat Sharma

1
สามารถใช้ ApplicationContext เป็นฟิลด์แบบคงที่ได้หรือไม่ ไม่เหมือนกิจกรรมวัตถุแอปพลิเคชันจะไม่ถูกทำลายใช่ไหม?
NeoWang

แต่คำถามคือเหตุใดหน่วยความจำจึงรั่วเริ่มต้นด้วย?
juztcode

51

ตรวจสอบให้แน่ใจว่าคุณได้ส่ง context.getApplicationContext () หรือเรียก getApplicationContext () บนบริบทใด ๆ ที่ส่งผ่าน method / constructor ไปยัง singleton ของคุณหากคุณตัดสินใจที่จะจัดเก็บไว้ในฟิลด์สมาชิกใด ๆ

ตัวอย่างการพิสูจน์คนงี่เง่า (แม้ว่าใครบางคนจะผ่านไปในกิจกรรมก็จะคว้าบริบทของแอปและใช้สิ่งนั้นเพื่อสร้างอินสแตนซ์ซิงเกิลตัน)

public static synchronized RestClient getInstance(Context context) {
    if (mInstance == null) {
        mInstance = new RestClient(context.getApplicationContext());
    }
    return mInstance;
}

getApplicationContext () ตามเอกสาร: "ส่งคืนบริบทของอ็อบเจ็กต์แอ็พพลิเคชันส่วนกลางเดียวของกระบวนการปัจจุบัน"

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

เปรียบเทียบกับบริบทภายในของมุมมอง / กิจกรรมที่มีข้อมูลจำนวนมากหากคุณรั่วไหลบริบทที่จัดขึ้นโดยกิจกรรมระบบจะไม่สามารถปลดปล่อยทรัพยากรนั้นซึ่งเห็นได้ชัดว่าไม่ดี

การอ้างอิงถึงกิจกรรมตามบริบทควรใช้วงจรชีวิตเช่นเดียวกับกิจกรรมนั้น ๆ มิฉะนั้นจะจับบริบทเป็นตัวประกันทำให้หน่วยความจำรั่วไหล (ซึ่งเป็นเหตุผลเบื้องหลังคำเตือนผ้าสำลี)

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

    // getApplicationContext() is key, it keeps you from leaking the
    // Activity or BroadcastReceiver if someone passes one in.

8
สำหรับผู้ชายที่ทุบตีคนที่ทุบตัวอย่างข้างต้น: ประเด็นของหัวข้อนี้คือคำเตือน Lint ที่ขัดแย้งกับรูปแบบการสร้างซิงเกิลตันที่แนะนำของ Google
Raphael C

7
อ่าน: "อย่าวางคลาสบริบทของ Android ในช่องแบบคงที่นี่คือหน่วยความจำรั่ว (และหยุดการรันทันทีด้วย)" คุณรู้หรือไม่ว่าคลาสบริบทคืออะไร? กิจกรรมเป็นหนึ่งในนั้นและคุณไม่ควรจัดเก็บกิจกรรมเป็นช่องแบบคงที่ตามที่คุณอธิบายไว้ (มิฉะนั้นจะทำให้หน่วยความจำรั่วไหล) อย่างไรก็ตามคุณสามารถจัดเก็บบริบท (ตราบใดที่เป็นบริบทของแอปพลิเคชัน) เป็นฟิลด์แบบคงที่เนื่องจากมันอยู่ได้นานกว่าทุกอย่าง (และไม่สนใจคำเตือน) ฉันแน่ใจว่าเราเห็นด้วยกับข้อเท็จจริงง่ายๆนี้ใช่ไหม?
Marcus Gruneau

ในฐานะสัตว์แพทย์ iOS ในสัปดาห์แรกของ Android ... คำอธิบายเช่นนี้ช่วยให้ฉันเข้าใจบริบทนี้ไร้สาระ .. ดังนั้นคำเตือนผ้าสำลี (โอ้ฉันไม่ชอบคำเตือนใด ๆ ) จะอยู่เฉยๆ แต่คำตอบของคุณช่วยแก้ปัญหาที่แท้จริงได้ .
eric

1
@Marcus หากชั้นเรียนลูกของคุณไม่ทราบว่าใครเป็นผู้สร้างอินสแตนซ์กับบริบทใดการจัดเก็บเป็นสมาชิกแบบคงที่ ยิ่งไปกว่านั้นบริบทของแอปพลิเคชันเป็นส่วนหนึ่งของวัตถุแอปพลิเคชันของแอปของคุณวัตถุแอปพลิเคชันจะไม่อยู่ในหน่วยความจำตลอดไปมันจะถูกฆ่า ตรงกันข้ามกับความเชื่อที่เป็นที่นิยมแอปจะไม่เริ่มต้นใหม่ตั้งแต่ต้น Android จะสร้างวัตถุแอปพลิเคชันใหม่และเริ่มกิจกรรมที่ผู้ใช้อยู่มาก่อนเพื่อให้ภาพลวงตาว่าแอปพลิเคชันไม่เคยถูกฆ่าตั้งแต่แรก
Raphael C

@RaphaelC คุณมีเอกสารดังกล่าวหรือไม่? ดูเหมือนว่าจะผิดอย่างสิ้นเชิงเพราะ Android รับรองบริบทของแอปพลิเคชันเพียงหนึ่งรายการต่อการรันของแต่ละกระบวนการ
HaydenKai

6

เป็นแค่คำเตือน ไม่ต้องกังวล. หากคุณต้องการใช้บริบทของแอปพลิเคชันคุณสามารถบันทึกในคลาส "singleton" ซึ่งใช้เพื่อบันทึกคลาสซิงเกิลตันทั้งหมดในโครงการของคุณ


2

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

การเรียกใช้ทันทียังทำงานได้ดีที่นี่ ...


ฉันไม่คิดว่าคุณผิดหลักการ แต่คุณต้องระมัดระวังเป็นพิเศษว่ากิจกรรมที่คุณกำลังพูดถึงมีเพียงหนึ่งอินสแตนซ์เดียวในช่วงเวลาใดเวลาหนึ่งก่อนที่จะสามารถใช้ฟิลด์คงที่ได้ หากแอปของคุณลงเอยด้วยกองหลังมากกว่าหนึ่งชุดเนื่องจากสามารถเปิดใช้งานได้จากที่ต่างๆ (การแจ้งเตือนการลิงก์ในรายละเอียด ... ) สิ่งต่างๆจะผิดพลาดเว้นแต่คุณจะใช้แฟล็กบางอย่างเช่น singleInstance ในไฟล์ Manifest ดังนั้นจึงง่ายกว่าเสมอที่จะหลีกเลี่ยงเขตข้อมูลคงที่จากกิจกรรม
BladeCoder

android: launchMode = "singleTask" น่าจะเพียงพอดังนั้นฉันจึงเปลี่ยนไปใช้แบบนั้นฉันใช้ singleTop แต่ไม่รู้ว่ามันไม่เพียงพอเพราะฉันต้องการเพียงอินสแตนซ์เดียวของกิจกรรมหลักนั่นคือวิธีการออกแบบแอปของฉัน
Renetik

2
"singleTask" รับประกันหนึ่งอินสแตนซ์ต่องานเท่านั้น หากแอปของคุณมีจุดเข้าใช้งานหลายจุดเช่นลิงก์ในรายละเอียดหรือเปิดใช้งานจากการแจ้งเตือนคุณอาจต้องทำงานหลายอย่าง
BladeCoder

2

ใช้WeakReferenceเพื่อจัดเก็บบริบทในคลาส Singleton และคำเตือนจะหายไป

private WeakReference<Context> context;

//Private contructor
private WidgetManager(Context context) {
    this.context = new WeakReference<>(context);
}

//Singleton
public static WidgetManager getInstance(Context context) {
    if (null == widgetManager) {
        widgetManager = new WidgetManager(context);
    }
    return widgetManager;
}

ตอนนี้คุณสามารถเข้าถึงบริบทเช่น

  if (context.get() instanceof MainActivity) {
            ((MainActivity) context.get()).startActivityForResult(pickIntent, CODE_REQUEST_PICK_APPWIDGET);
        }

1

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

ตอนนี้มีสองสถานการณ์ที่คุณจะได้รับคำเตือนนี้ สำหรับตัวอย่าง (สิ่งที่ชัดเจนที่สุด):

public static Context ctx;

จากนั้นก็มีสิ่งที่ยุ่งยากกว่าเล็กน้อยซึ่งบริบทถูกรวมไว้ในชั้นเรียน:

public class Example{
    public Context ctx;
    //Constructor omitted for brievety 
}

และคลาสนั้นถูกกำหนดให้เป็นแบบคงที่:

public static Example example;

และคุณจะได้รับคำเตือน

วิธีแก้ปัญหานั้นค่อนข้างง่าย: อย่าวางฟิลด์บริบทในอินสแตนซ์แบบคงที่ไม่ว่าจะเป็นคลาสการตัดหรือประกาศแบบคงที่โดยตรง

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

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


เราจะทำอย่างไรโดยไม่ให้หน่วยความจำรั่ว?
isJulian00

1
คุณทำไม่ได้ หากคุณจำเป็นต้องผ่านบริบทรอบ ๆ อย่างแน่นอนคุณสามารถดูรถบัสเหตุการณ์
Zoe

ตกลงนี่เป็นปัญหาที่ฉันพบถ้าคุณช่วยได้โปรดลองดูมันอาจจะมีวิธีอื่นในการทำมัน btw วิธีนี้ต้องคงที่เพราะฉันเรียกมันจากรหัส c ++ stackoverflow.com/questions/54683863/…
isJulian00

0

หากคุณแน่ใจว่าเป็น Application Context มันสำคัญที่สุด เพิ่มสิ่งนี้

@SuppressLint("StaticFieldLeak")

1
ฉันจะไม่แนะนำให้ทำเช่นนี้ต่อไป หากคุณต้องการบริบทคุณสามารถใช้วิธีการ requireContext () หากคุณใช้ AndroidX libs หรือคุณสามารถส่งบริบทไปยังเมธอดที่ต้องการได้โดยตรง หรือคุณสามารถรับข้อมูลอ้างอิงคลาสของแอพได้ แต่ฉันไม่แนะนำให้ใช้คำแนะนำ SuppressLint ดังกล่าว
Oleksandr Nos
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.