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


297

ฉันกำลังพัฒนาแอปพลิเคชัน Android 2.3.3 พร้อมบริการ ฉันมีสิ่งนี้ในบริการที่จะสื่อสารกับกิจกรรมหลัก:

public class UDPListenerService extends Service
{
    private static final String TAG = "UDPListenerService";
    //private ThreadGroup myThreads = new ThreadGroup("UDPListenerServiceWorker");
    private UDPListenerThread myThread;
    /**
     * Handler to communicate from WorkerThread to service.
     */
    private Handler mServiceHandler;

    // Used to receive messages from the Activity
    final Messenger inMessenger = new Messenger(new IncomingHandler());
    // Use to send message to the Activity
    private Messenger outMessenger;

    class IncomingHandler extends Handler
    {
        @Override
        public void handleMessage(Message msg)
        {
        }
    }

    /**
     * Target we publish for clients to send messages to Incoming Handler.
     */
    final Messenger mMessenger = new Messenger(new IncomingHandler());
    [ ... ]
}

และที่นี่final Messenger mMessenger = new Messenger(new IncomingHandler());ฉันได้รับคำเตือนจากผ้าสำลี:

This Handler class should be static or leaks might occur: IncomingHandler

มันหมายความว่าอะไร?


24
ลองอ่านโพสต์บล็อกนี้สำหรับข้อมูลเพิ่มเติมเกี่ยวกับเรื่องนี้!
Adrian Monk

1
การรั่วไหลของหน่วยความจำเกิดจากการรวบรวมขยะ ... นี่เป็นการเพียงพอที่จะแสดงให้เห็นว่า Java มีความไม่สอดคล้องกันและการออกแบบที่ไม่ดีอย่างไร
Gojir4

คำตอบ:


392

ถ้าIncomingHandlerคลาสไม่คงที่จะมีการอ้างอิงถึงServiceวัตถุของคุณ

Handler วัตถุสำหรับชุดข้อความเดียวกันทั้งหมดจะแชร์วัตถุ Looper ทั่วไปซึ่งพวกเขาโพสต์ข้อความไปยังและอ่านจาก

เนื่องจากข้อความมีเป้าหมายHandlerตราบใดที่มีข้อความที่มีตัวจัดการเป้าหมายในคิวข้อความตัวจัดการไม่สามารถรวบรวมขยะได้ หากตัวจัดการไม่คงที่คุณServiceหรือActivityไม่สามารถรวบรวมขยะแม้หลังจากถูกทำลาย

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

คุณสามารถทำให้IncomingHandlerคงที่และมีการWeakReferenceบริการของคุณ:

static class IncomingHandler extends Handler {
    private final WeakReference<UDPListenerService> mService; 

    IncomingHandler(UDPListenerService service) {
        mService = new WeakReference<UDPListenerService>(service);
    }
    @Override
    public void handleMessage(Message msg)
    {
         UDPListenerService service = mService.get();
         if (service != null) {
              service.handleMessage(msg);
         }
    }
}

ดูโพสต์นี้โดย Romain Guy สำหรับการอ้างอิงเพิ่มเติม


3
Romain แสดงให้เห็นว่า WeakReference ไปยังคลาสภายนอกเป็นสิ่งที่จำเป็นทั้งหมด - คลาสที่ซ้อนกันแบบสแตติกไม่จำเป็น ฉันคิดว่าฉันจะชอบวิธี WeakReference เพราะไม่เช่นนั้นชั้นนอกทั้งหมดเปลี่ยนแปลงไปอย่างมากเนื่องจากตัวแปร 'คงที่' ทั้งหมดที่ฉันต้องการ
คนอยู่ที่ไหนสักแห่ง

35
ถ้าคุณต้องการใช้คลาสที่ซ้อนกันมันจะต้องคงที่ มิฉะนั้น WeakReference จะไม่เปลี่ยนแปลงอะไรเลย คลาส Inner (ซ้อนกัน แต่ไม่คงที่) จะมีการอ้างอิงที่แข็งแกร่งกับคลาสภายนอกเสมอ ไม่จำเป็นต้องมีตัวแปรสแตติกใด ๆ
Tomasz Niedabylski

2
@SomeoneSomewhere mSerivce เป็น WeakReference get()จะส่งคืน null เมื่อวัตถุที่อ้างอิงคือ gc-ed ในกรณีนี้เมื่อบริการตาย
Tomasz Niedabylski

1
หมายเหตุ: หลังจากทำ IncomingHandler คงที่ฉันได้รับข้อผิดพลาด "ตัวสร้าง MyActivity.IncomingHandler () ไม่ได้ถูกกำหนด" ในบรรทัด "final Messenger ใน inMessenger = new Messenger (ใหม่ IncomingHandler ());" วิธีแก้ไขคือเปลี่ยนบรรทัดนั้นเป็น "final Messenger ใน Messenger = new Messenger (ใหม่ IncomingHandler (นี่));"
Lance Lefebure

4
@Someone ที่ไหนสักแห่งใช่โพสต์ของ Romain ผิดที่เขาพลาดประกาศการคงที่ของชั้นในซึ่งพลาดจุดทั้งหมด นอกจากว่าเขามีคอมไพเลอร์พิเศษที่ยอดเยี่ยมที่แปลงคลาสภายในเป็นคลาสสแตติกโดยอัตโนมัติเมื่อพวกเขาไม่ใช้ตัวแปรคลาส
Sogger

67

อย่างที่คนอื่น ๆ ได้กล่าวถึง Lint warning นั้นเป็นเพราะหน่วยความจำที่อาจรั่ว คุณสามารถหลีกเลี่ยงคำเตือน Lint ได้โดยการส่งผ่านHandler.Callbackเมื่อสร้างHandler (เช่นคุณไม่ได้คลาสย่อยHandlerและไม่มีHandlerคลาสภายในที่ไม่คงที่):

Handler mIncomingHandler = new Handler(new Handler.Callback() {
    @Override
    public boolean handleMessage(Message msg) {
        // todo
        return true;
    }
});

ตามที่ฉันเข้าใจแล้วนี่จะไม่หลีกเลี่ยงการรั่วไหลของหน่วยความจำที่อาจเกิดขึ้น Messageวัตถุถือการอ้างอิงไปยังmIncomingHandlerวัตถุที่มีการอ้างอิงHandler.Callbackวัตถุที่ถือการอ้างอิงไปยังServiceวัตถุ ตราบใดที่มีข้อความในLooperคิวข้อความServiceจะไม่เป็น GC อย่างไรก็ตามมันจะไม่เป็นปัญหาร้ายแรงเว้นแต่คุณจะมีข้อความล่าช้านานในคิวข้อความ


10
@Braj ฉันไม่คิดว่าการหลีกเลี่ยงคำเตือนขุย แต่ยังคงรักษาข้อผิดพลาดเป็นทางออกที่ดีเลย เว้นแต่ว่าเป็นคำเตือนที่เป็นขุยหากตัวจัดการไม่ได้อยู่ใน looper หลักของคุณ (และคุณสามารถมั่นใจได้ว่าข้อความทั้งหมดที่ค้างอยู่บนมันจะถูกทำลายเมื่อชั้นถูกทำลาย) จากนั้นการรั่วไหลของการอ้างอิงจะลดลง
Sogger

33

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

public class MyClass{

  //static inner class doesn't hold an implicit reference to the outer class
  private static class MyHandler extends Handler {
    //Using a weak reference means you won't prevent garbage collection
    private final WeakReference<MyClass> myClassWeakReference; 

    public MyHandler(MyClass myClassInstance) {
      myClassWeakReference = new WeakReference<MyClass>(myClassInstance);
    }

    @Override
    public void handleMessage(Message msg) {
      MyClass myClass = myClassWeakReference.get();
      if (myClass != null) {
        ...do work here...
      }
    }
  }

  /**
   * An example getter to provide it to some external class
   * or just use 'new MyHandler(this)' if you are using it internally.
   * If you only use it internally you might even want it as final member:
   * private final MyHandler mHandler = new MyHandler(this);
   */
  public Handler getHandler() {
    return new MyHandler(this);
  }
}

2
ตัวอย่าง Sogger นั้นยอดเยี่ยม อย่างไรก็ตามMyclassควรประกาศวิธีสุดท้ายในpublic Handler getHandler()แทนpublic void
Jason Porter

มันคล้ายกับคำตอบของ Tomasz Niedabylski
CoolMind

24

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

ตัวจัดการที่คุณต้องการใช้

Handler mIncomingHandler = new Handler(new IncomingHandlerCallback());

ชั้นใน

class IncomingHandlerCallback implements Handler.Callback{

        @Override
        public boolean handleMessage(Message message) {

            // Handle message code

            return true;
        }
}

2
ที่นี่วิธีการ handleMessage ผลตอบแทนจริงในท้ายที่สุด คุณช่วยอธิบายความหมายของสิ่งนี้ได้อย่างไร (ค่าส่งคืนจริง / เท็จ) ขอบคุณ
JibW

2
ความเข้าใจของฉันเกี่ยวกับการส่งคืนความจริงคือการระบุว่าคุณได้จัดการกับข้อความและดังนั้นจึงไม่ควรส่งต่อข้อความไปที่อื่นเช่นตัวจัดการพื้นฐาน ที่กล่าวว่าฉันไม่สามารถหาเอกสารใด ๆ และจะได้รับการแก้ไขอย่างมีความสุข
Stuart Campbell

1
Javadoc พูดว่า: Constructor เชื่อมโยงตัวจัดการนี้กับ Looper สำหรับเธรดปัจจุบันและใช้อินเทอร์เฟซการเรียกกลับที่คุณสามารถจัดการข้อความได้ หากเธรดนี้ไม่มี looper ตัวจัดการนี้จะไม่สามารถรับข้อความได้ดังนั้นจึงมีข้อผิดพลาดเกิดขึ้น <- ฉันคิดว่า Handler ใหม่ (ใหม่ IncomingHandlerCallback ()) จะไม่ทำงานเมื่อไม่มี Looper แนบกับเธรดและนั่นอาจเป็นกรณี ฉันไม่ได้พูดผิดที่จะทำเช่นนั้นในบางกรณีฉันแค่บอกว่ามันไม่ได้ผลเสมอไปเท่าที่คุณคาดหวัง
user504342

1
@StuartCampbell: คุณถูกต้อง ดู: groups.google.com/forum/#!topic/android-developers/L_xYM0yS6z8
MDTech.us_MAN

2

ด้วยความช่วยเหลือของคำตอบของ @ Sogger ฉันได้สร้างตัวจัดการทั่วไป:

public class MainThreadHandler<T extends MessageHandler> extends Handler {

    private final WeakReference<T> mInstance;

    public MainThreadHandler(T clazz) {
        // Remove the following line to use the current thread.
        super(Looper.getMainLooper());
        mInstance = new WeakReference<>(clazz);
    }

    @Override
    public void handleMessage(Message msg) {
        T clazz = mInstance.get();
        if (clazz != null) {
            clazz.handleMessage(msg);
        }
    }
}

อินเทอร์เฟซ:

public interface MessageHandler {

    void handleMessage(Message msg);

}

ฉันใช้มันดังนี้ แต่ฉันไม่แน่ใจ 100% ว่านี่ปลอดภัยหรือไม่ บางทีใครบางคนสามารถแสดงความคิดเห็นเกี่ยวกับสิ่งนี้:

public class MyClass implements MessageHandler {

    private static final int DO_IT_MSG = 123;

    private MainThreadHandler<MyClass> mHandler = new MainThreadHandler<>(this);

    private void start() {
        // Do it in 5 seconds.
        mHandler.sendEmptyMessageDelayed(DO_IT_MSG, 5 * 1000);
    }

    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case DO_IT_MSG:
                doIt();
                break;
        }
    }

    ...

}

0

ฉันไม่แน่ใจ แต่คุณสามารถลองใช้ intialising handler เป็น null ใน onDestroy ()


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

0

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

    public class example extends Activity {
        final int HANDLE_FIX_SCREEN = 1000;
        public Handler DBthreadHandler = new Handler(Looper.getMainLooper()){
            @Override
            public void handleMessage(Message msg) {
                int imsg;
                imsg = msg.what;
                if (imsg == HANDLE_FIX_SCREEN) {
                    doSomething();
                }
            }
        };
    }

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

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