Kelas Handler ini harus statis atau kebocoran mungkin terjadi: IncomingHandler

297

Saya sedang mengembangkan aplikasi Android 2.3.3 dengan sebuah layanan. Saya memiliki ini di dalam layanan itu untuk berkomunikasi dengan aktivitas Utama:

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());
    [ ... ]
}

Dan di sini,, final Messenger mMessenger = new Messenger(new IncomingHandler());saya mendapatkan peringatan Lint berikut:

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

Apa artinya?

VansFannel
sumber
24
Lihat posting blog ini untuk informasi lebih lanjut tentang hal ini!
Adrian Monk
1
Kebocoran memori yang disebabkan oleh pengumpulan sampah ... Ini cukup untuk menunjukkan bagaimana Java tidak konsisten dan dirancang dengan buruk
Gojir4

Jawaban:

392

Jika IncomingHandlerkelas tidak statis, itu akan memiliki referensi ke Serviceobjek Anda .

Handler objek untuk utas yang sama semua berbagi objek Looper umum, yang mereka kirimi pesan dan baca.

Karena pesan mengandung target Handler, selama ada pesan dengan target handler dalam antrian pesan, handler tidak dapat dikumpulkan sebagai sampah. Jika pawang tidak statis, Anda Serviceatau Activitytidak dapat mengumpulkan sampah, bahkan setelah dihancurkan.

Hal ini dapat menyebabkan kebocoran memori, setidaknya untuk beberapa waktu - selama pesan tetap dalam antrian. Ini tidak banyak masalah kecuali Anda mengirim pesan yang lama tertunda.

Anda dapat membuat IncomingHandlerstatis dan memiliki WeakReferencelayanan Anda:

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);
         }
    }
}

Lihat posting ini oleh Romain Guy untuk referensi lebih lanjut

Tomasz Niedabylski
sumber
3
Romain menunjukkan bahwa WeakReference ke kelas luar adalah semua yang diperlukan - kelas bersarang statis tidak diperlukan. Saya pikir saya akan lebih suka pendekatan WeakReference karena kalau tidak seluruh kelas luar secara drastis berubah karena semua variabel 'statis' yang saya butuhkan.
Seseorang di suatu tempat
35
Jika Anda ingin menggunakan kelas bersarang, itu harus statis. Kalau tidak, WeakReference tidak mengubah apa pun. Kelas dalam (bersarang tetapi tidak statis) selalu memegang referensi kuat untuk kelas luar. Tidak perlu variabel statis apa pun.
Tomasz Niedabylski
2
@SomeoneSomewhere mSerivce adalah WeakReference. get()akan mengembalikan nol ketika objek yang direferensikan adalah gc-ed. Dalam hal ini, ketika layanan mati.
Tomasz Niedabylski
1
Catatan: setelah membuat IncomingHandler statis, saya mendapatkan kesalahan "Konstruktor MyActivity.IncomingHandler () tidak terdefinisi." on the line "final Messenger inMessenger = messenger baru (IncomingHandler baru ());". Solusinya adalah mengubah baris itu menjadi "final Messenger inMessenger = new Messenger (new IncomingHandler (this));".
Lance Lefebure
4
@ Seseorang di suatu tempat Ya, posting Romain salah karena dia melewatkan mendeklarasikan statis kelas dalam yang melewatkan seluruh poin. Kecuali dia memiliki beberapa kompiler sangat keren yang secara otomatis mengkonversi kelas dalam ke kelas statis ketika mereka tidak menggunakan variabel kelas.
Sogger
67

Seperti orang lain telah menyebutkan peringatan Lint adalah karena potensi kebocoran memori. Anda dapat menghindari peringatan Lint dengan melewatkan Handler.Callbackketika membangun Handler(yaitu Anda tidak subkelas Handlerdan tidak ada Handlerkelas dalam non-statis):

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

Seperti yang saya pahami, ini tidak akan menghindari potensi kebocoran memori. Messageobjek memegang referensi ke mIncomingHandlerobjek yang memegang referensi Handler.Callbackobjek yang memegang referensi ke Serviceobjek. Selama ada pesan dalam Looperantrian pesan, maka Servicetidak akan menjadi GC. Namun, itu tidak akan menjadi masalah serius kecuali jika Anda memiliki pesan lama dalam antrian pesan.

Michael
sumber
10
@Braj Saya tidak berpikir menghindari peringatan serat tetapi masih menyimpan kesalahan adalah solusi yang bagus sama sekali. Kecuali, karena peringatan serat menyatakan jika pawang tidak memakai looper utama Anda (dan Anda dapat memastikan semua pesan yang tertunda di atasnya dihancurkan ketika kelas dihancurkan) maka membocorkan referensi dikurangi.
Sogger
33

Berikut adalah contoh umum menggunakan kelas referensi lemah dan pengendali statis untuk menyelesaikan masalah (seperti yang direkomendasikan dalam dokumentasi 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);
  }
}
Sogger
sumber
2
Contoh Sogger sangat bagus. Namun, metode terakhir Myclassharus dinyatakan sebagai public Handler getHandler()bukanpublic void
Jason Porter
Ini mirip dengan jawaban Tomasz Niedabylski
CoolMind
24

Cara ini bekerja dengan baik untuk saya, menjaga kode tetap bersih dengan menjaga di mana Anda menangani pesan di kelas dalamnya sendiri.

Pawang yang ingin Anda gunakan

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

Kelas batin

class IncomingHandlerCallback implements Handler.Callback{

        @Override
        public boolean handleMessage(Message message) {

            // Handle message code

            return true;
        }
}
Stuart Campbell
sumber
2
Di sini metode handleMessage mengembalikan true pada akhirnya. Bisakah Anda jelaskan apa artinya ini sebenarnya (nilai pengembalian benar / salah)? Terima kasih.
JibW
2
Pemahaman saya tentang mengembalikan true adalah untuk menunjukkan bahwa Anda telah menangani pesan dan oleh karena itu pesan tidak boleh diteruskan ke tempat lain misalnya penangan yang mendasarinya. Yang mengatakan saya tidak dapat menemukan dokumentasi dan akan dengan senang hati diperbaiki.
Stuart Campbell
1
Javadoc mengatakan: Constructor mengaitkan handler ini dengan Looper untuk utas saat ini dan mengambil antarmuka panggilan balik di mana Anda dapat menangani pesan. Jika utas ini tidak memiliki looper, pawang ini tidak akan dapat menerima pesan sehingga pengecualian dilemparkan. <- Saya pikir Handler baru (IncomingHandlerCallback ()) baru tidak akan berfungsi ketika tidak ada Looper yang melekat pada utas, dan itu BISA terjadi. Saya tidak mengatakan itu salah untuk melakukannya dalam beberapa kasus, saya hanya mengatakan itu tidak selalu berfungsi seperti yang Anda harapkan.
user504342
1
@StuartCampbell: Anda benar. Lihat: groups.google.com/forum/#!topic/android-developers/L_xYM0yS6z8 .
MDTech.us_MAN
2

Dengan bantuan jawaban @ Sogger, saya membuat Handler generik:

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);
        }
    }
}

Antarmuka:

public interface MessageHandler {

    void handleMessage(Message msg);

}

Saya menggunakannya sebagai berikut. Tapi saya tidak yakin 100% apakah ini aman bocor. Mungkin seseorang bisa berkomentar tentang ini:

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;
        }
    }

    ...

}
Marius
sumber
0

Saya tidak yakin tetapi Anda dapat mencoba menginternalisasi handler ke null di onDestroy ()

Chaitanya
sumber
1
Objek handler untuk utas yang sama semuanya berbagi objek Looper umum, yang mereka kirimi pesan dan baca. Karena pesan mengandung penangan target, selama ada pesan dengan penangan target dalam antrian pesan, penangan tidak dapat dikumpulkan sebagai sampah.
msysmilu
0

Saya bingung. Contoh yang saya temukan menghindari properti statis sepenuhnya dan menggunakan utas 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();
                }
            }
        };
    }

Hal yang saya sukai dari solusi ini adalah tidak ada masalah dalam mencoba memadukan variabel kelas dan metode.

pengguna2515235
sumber