คำเตือน: คลาส AsyncTask นี้ควรเป็นแบบคงที่หรืออาจเกิดการรั่วไหล


270

ฉันได้รับคำเตือนในรหัสของฉันที่ระบุว่า:

คลาส AsyncTask นี้ควรเป็นแบบคงที่หรืออาจเกิดการรั่วไหล (android.os.As.TyncTask แบบไม่ระบุชื่อ)

คำเตือนที่สมบูรณ์คือ:

คลาส AsyncTask นี้ควรเป็นแบบสแตติกหรืออาจเกิดการรั่วไหล (android.os.As.TyncTask แบบไม่ระบุชื่อ) ฟิลด์แบบสแตติกจะรั่วบริบท ชั้นในแบบไม่คงที่มีการอ้างอิงโดยนัยถึงชั้นนอกของพวกเขา หากคลาสภายนอกนั้นเป็นตัวอย่าง Fragment หรือ Activity การอ้างอิงนี้หมายความว่าตัวจัดการ / ตัวโหลด / ภารกิจที่ใช้เวลานานจะมีการอ้างอิงถึงกิจกรรมที่ป้องกันไม่ให้ถูกรวบรวมขยะ การอ้างอิงฟิลด์โดยตรงกับกิจกรรมและแฟรกเมนต์จากอินสแตนซ์ที่ใช้เวลานานเหล่านี้อาจทำให้เกิดรอยรั่วได้ คลาส ViewModel ไม่ควรชี้ไปที่วิวหรือบริบทที่ไม่ใช่แอปพลิเคชัน

นี่คือรหัสของฉัน:

 new AsyncTask<Void,Void,Void>(){

        @Override
        protected Void doInBackground(Void... params) {
            runOnUiThread(new Runnable() {

                @Override
                public void run() {
                    mAdapter.notifyDataSetChanged();
                }
            });

            return null;
        }
    }.execute();

ฉันจะแก้ไขสิ่งนี้ได้อย่างไร


2
การอ่านandroiddesignpatterns.com/2013/01/นี้ควรให้คำแนะนำว่าทำไมมันจึงควรจะคงที่
Raghunandan

จนถึงตอนนี้ฉันสามารถแทนที่ AsyncTask ด้วย Thread ใหม่ (... ). statr () ร่วมกับ runOnUiThread (... ) ได้ถ้าจำเป็นดังนั้นฉันจึงไม่ต้องจัดการกับคำเตือนนี้อีกต่อไป
ฮ่องกง

1
การแก้ปัญหาใน kotlin สำหรับปัญหานี้คืออะไร?
TapanHP

โปรดพิจารณาอีกครั้งว่าคำตอบใดควรเป็นคำตอบที่ได้รับการยอมรับ ดูคำตอบด้านล่าง
Ωmega

ในกรณีของฉันฉันได้รับคำเตือนนี้จาก Singleton ที่ไม่มีการอ้างอิงโดยตรงกับกิจกรรม (ได้รับผลลัพธ์จากการmyActivity.getApplication()เข้าไปใน Constructor ส่วนตัวสำหรับ Singleton เพื่อเริ่มต้นคลาส RoomDB และคลาสอื่น ๆ ) My ViewModels รับอินสแตนซ์ Singleton เป็นการอ้างอิงส่วนตัวเพื่อดำเนินการบางอย่างบนฐานข้อมูล ดังนั้น ViewModels การนำเข้าแพคเกจเดี่ยวเช่นเดียวกับหนึ่งของพวกเขาแม้กระทั่งandroid.app.Application android.app.Activityเนื่องจาก "ซิงเกิลตัน" ไม่จำเป็นต้องนำเข้า ViewModels เหล่านั้นให้ทำงานดังนั้นอาจมีหน่วยความจำรั่วเกิดขึ้นบ้าง
SebasSBM

คำตอบ:


64

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

ในการแก้ปัญหาของคุณให้ใช้คลาสที่ซ้อนกันแบบคงที่แทนคลาสแบบไม่ระบุชื่อโลคัลและชั้นในหรือใช้คลาสระดับบนสุด


1
วิธีการแก้ปัญหาอยู่ในคำเตือนของตัวเอง ใช้คลาสซ้อนกันแบบคงที่หรือคลาสระดับบนสุด
Anand

3
@KeyurNimavat ฉันคิดว่าคุณสามารถผ่านการอ้างอิงที่อ่อนแอกับกิจกรรมของคุณ
peterchaula

42
ดังนั้นจุดประสงค์ของการใช้ AsyncTask คืออะไร? ถ้ามันง่ายกว่าที่จะเรียกใช้เธรดใหม่และ handler.post หรือ view.post (เพื่ออัปเดต UI) ที่ส่วนท้ายในวิธีการเรียกใช้ของเธรด ถ้า AsyncTask เป็นแบบสแตติกหรือระดับบนสุดจะเป็นการยากที่จะเข้าถึงตัวแปร / วิธีการที่จำเป็นจากมัน
user924

8
ไม่มีรหัสที่ระบุว่ามีวิธีการใช้งานที่ถูกต้องอย่างไร ฉันเคยลองใส่แบบคงที่ แต่จะมีการเตือนและข้อผิดพลาดมากขึ้น
Kasnady

19
@Anand โปรดลบคำตอบนี้เพื่อให้ได้คำตอบที่เป็นประโยชน์มากขึ้นที่stackoverflow.com/a/46166223/145119ที่ด้านบน
Mithaldu

556

วิธีการใช้คลาส AsyncTask ภายในคงที่

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

public class MyActivity extends AppCompatActivity {

    int mSomeMemberVariable = 123;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // start the AsyncTask, passing the Activity context
        // in to a custom constructor 
        new MyTask(this).execute();
    }

    private static class MyTask extends AsyncTask<Void, Void, String> {

        private WeakReference<MyActivity> activityReference;

        // only retain a weak reference to the activity 
        MyTask(MyActivity context) {
            activityReference = new WeakReference<>(context);
        }

        @Override
        protected String doInBackground(Void... params) {

            // do some long running task...

            return "task finished";
        }

        @Override
        protected void onPostExecute(String result) {

            // get a reference to the activity if it is still there
            MyActivity activity = activityReference.get();
            if (activity == null || activity.isFinishing()) return;

            // modify the activity's UI
            TextView textView = activity.findViewById(R.id.textview);
            textView.setText(result);

            // access Activity member variables
            activity.mSomeMemberVariable = 321;
        }
    }
}

หมายเหตุ

  • เท่าที่ฉันรู้อันตรายจากการรั่วไหลของหน่วยความจำประเภทนี้เป็นจริงเสมอ แต่ฉันเริ่มเห็นคำเตือนใน Android Studio 3.0 เท่านั้น จำนวนมากของหลักAsyncTaskบทเรียนออกมียังไม่จัดการกับมัน (ดูที่นี่ , ที่นี่ , ที่นี่และที่นี่ )
  • คุณจะทำตามขั้นตอนที่คล้ายกันหากคุณAsyncTaskอยู่ในระดับบนสุด คลาสภายในแบบสแตติกนั้นเหมือนกับคลาสระดับบนสุดใน Java
  • หากคุณไม่ต้องการกิจกรรม แต่ยังต้องการบริบท (เช่นเพื่อแสดง a Toast) คุณสามารถส่งผ่านการอ้างอิงถึงบริบทแอป ในกรณีนี้ตัวAsyncTaskสร้างจะมีลักษณะเช่นนี้:

    private WeakReference<Application> appReference;
    
    MyTask(Application context) {
        appReference = new WeakReference<>(context);
    }
  • มีข้อโต้แย้งบางอย่างที่มีสำหรับการละเว้นคำเตือนนี้และเพียงแค่ใช้คลาสที่ไม่คงที่ ท้ายที่สุด, AsyncTask ตั้งใจจะสั้นมาก (สองสามวินาทีที่ยาวที่สุด), และมันจะปล่อยการอ้างอิงถึงกิจกรรมเมื่อเสร็จสิ้นต่อไป. ดูนี้และนี้
  • บทความที่ยอดเยี่ยม: วิธีการรั่วบริบท: ตัวจัดการ & ชั้นใน

Kotlin

ใน Kotlin ไม่ได้รวมinnerคำหลักสำหรับชั้นใน สิ่งนี้ทำให้คงที่โดยค่าเริ่มต้น

class MyActivity : AppCompatActivity() {

    internal var mSomeMemberVariable = 123

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // start the AsyncTask, passing the Activity context
        // in to a custom constructor
        MyTask(this).execute()
    }

    private class MyTask
    internal constructor(context: MyActivity) : AsyncTask<Void, Void, String>() {

        private val activityReference: WeakReference<MyActivity> = WeakReference(context)

        override fun doInBackground(vararg params: Void): String {

            // do some long running task...

            return "task finished"
        }

        override fun onPostExecute(result: String) {

            // get a reference to the activity if it is still there
            val activity = activityReference.get()
            if (activity == null || activity.isFinishing) return

            // modify the activity's UI
            val textView = activity.findViewById(R.id.textview)
            textView.setText(result)

            // access Activity member variables
            activity.mSomeMemberVariable = 321
        }
    }
}

1
@ManojFrekzz, ไม่จริง ๆ แล้วคุณสามารถอัปเดต UI ได้โดยใช้การอ้างอิงอ่อน ๆ กับกิจกรรมที่ส่งผ่านตรวจสอบonPostExecuteวิธีการของฉันอีกครั้งในรหัสด้านบน คุณจะเห็นว่าฉันอัปเดต UI ที่TextViewนั่น เพียงใช้activity.findViewByIdเพื่อรับการอ้างอิงถึงองค์ประกอบ UI ที่คุณต้องการอัปเดต
Suragch

7
+1 นี่คือทางออกที่ดีที่สุดและสะอาดที่สุดที่ฉันเคยเห็น! เฉพาะในกรณีที่คุณต้องการแก้ไข UI ในเมธอด onPostExecute คุณควรตรวจสอบว่ากิจกรรมนั้นถูกทำลายหรือไม่: activity.isFinishing ()
zapotec

1
บันทึก! เมื่อใช้คำตอบนี้ฉันยังคงทำงานในข้อยกเว้นตัวชี้ Null เนื่องจากการดำเนินการ doInBackground นั้นใช้หน่วยความจำมากเรียกใช้คอลเลกชันขยะเรียกจุดอ่อนเก็บข้อมูลอ้างอิงอ่อนและทำลาย asynctask คุณอาจต้องการใช้ SoftReference แทนที่จะเป็นจุดอ่อนหากคุณรู้ว่าการใช้งานพื้นหลังของคุณมีหน่วยความจำมาก
PGMacDesign

2
@Sunny ผ่านการอ้างอิงไปยัง Fragment แทนที่จะเป็น Activity คุณจะนำactivity.isFinishing()เช็คออกและอาจแทนที่ด้วยfragment.isRemoving()เช็ค อย่างไรก็ตามฉันไม่ได้ทำงานกับเศษเล็กเศษน้อยเมื่อเร็ว ๆ นี้
Suragch

1
@bashan, (1) ถ้าคลาสภายนอกไม่ใช่กิจกรรมจากนั้นในAsyncTaskConstructor ของคุณคุณจะส่งผ่านการอ้างอิงไปยังคลาสภายนอกของคุณ และคุณจะได้รับการอ้างอิงไปยังชั้นนอกด้วยdoInBackground() ตรวจสอบMyOuterClass ref = classReference.get() null(2) ในonPostExecute()คุณเป็นเพียงการปรับปรุง UI กับผลลัพธ์จากงานพื้นหลัง มันเหมือนกับเวลาอื่น ๆ ที่คุณอัปเดต UI การตรวจสอบactivity.isFinishing()คือเพื่อให้แน่ใจว่ากิจกรรมยังไม่เสร็จสิ้นซึ่งในกรณีนี้จะไม่มีประโยชน์ในการอัปเดต UI
Suragch

23

AsyncTaskคลาสนี้ควรเป็นแบบคงที่หรืออาจเกิดการรั่วไหลได้

  • เมื่อActivityถูกทำลายAsyncTask(ทั้งสองstaticหรือnon-static) ยังคงทำงานอยู่
  • หากคลาสภายในเป็นnon-static( AsyncTask) คลาสจะมีการอ้างอิงไปยังคลาสภายนอก ( Activity)
  • หากวัตถุไม่มีการอ้างอิงชี้ไปที่มันGarbage Collectedจะปล่อยมัน หากวัตถุไม่ได้ใช้งานและGarbage Collected ไม่สามารถปล่อยมันได้ => หน่วยความจำรั่ว

=> ถ้าAsyncTaskเป็นnon-static, Activityจะไม่ปล่อยเหตุการณ์จะถูกทำลาย => การรั่วไหล

โซลูชั่นสำหรับการอัปเดต UI หลังจากทำให้ AsyncTask เป็นคลาสแบบสแตติก

1) ใช้WeakReferenceเช่น @Suragch คำตอบ
2) ส่งและลบActivityการอ้างอิงถึง (จาก)AsyncTask

public class NoLeakAsyncTaskActivity extends AppCompatActivity {
    private ExampleAsyncTask asyncTask;

    @Override 
    protected void onCreate(Bundle savedInstanceState) {
        ...

        // START AsyncTask
        asyncTask = new ExampleAsyncTask();
        asyncTask.setListener(new ExampleAsyncTask.ExampleAsyncTaskListener() {
            @Override
            public void onExampleAsyncTaskFinished(Integer value) {
                // update UI in Activity here
            }
        });
        asyncTask.execute();
    }

    @Override
    protected void onDestroy() {
        asyncTask.setListener(null); // PREVENT LEAK AFTER ACTIVITY DESTROYED
        super.onDestroy();
    }

    static class ExampleAsyncTask extends AsyncTask<Void, Void, Integer> {
        private ExampleAsyncTaskListener listener;

        @Override
        protected Integer doInBackground(Void... voids) {
            ...
            return null;
        }

        @Override
        protected void onPostExecute(Integer value) {
            super.onPostExecute(value);
            if (listener != null) {
                listener.onExampleAsyncTaskFinished(value);
            }
        }

        public void setListener(ExampleAsyncTaskListener listener) {
            this.listener = listener;
        }

        public interface ExampleAsyncTaskListener {
            void onExampleAsyncTaskFinished(Integer value);
        }
    }
}


5
@Suragch ลิงก์ของคุณระบุว่าในขณะที่ onDestroy ไม่รับประกันว่าจะถูกเรียกว่าสถานการณ์เดียวที่มันไม่ได้เป็นเมื่อระบบฆ่ากระบวนการดังนั้นทรัพยากรทั้งหมดจะเป็นอิสระอยู่แล้ว ดังนั้นอย่าบันทึกที่นี่ แต่คุณสามารถปล่อยทรัพยากรได้ที่นี่
Angelo Fuchs

2
ในกรณีที่ AsyncTask ใช้แบบไม่คงที่ทำไมเราไม่สามารถตั้งค่าตัวแปรอินสแตนซ์ AsyncTask เป็น NULL คล้ายกับสิ่งนี้ สิ่งนี้จะบอก GC ให้เพิ่มกิจกรรมฟรีแม้ว่า AsyncTask กำลังทำงานอยู่หรือไม่
Hanif

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