私はサービスを備えたAndroid 2.3.3アプリケーションを開発しています。私はそのサービスの中にMainアクティビティと通信するためにこれを持っています:
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
どういう意味ですか?
IncomingHandler
クラスが静的でない場合は、Service
オブジェクトへの参照があります。
同じスレッドのHandler
オブジェクトはすべて、共通のLooperオブジェクトを共有しています。これらのオブジェクトは、メッセージの投稿と読み取りを行います。
メッセージにターゲットHandler
が含まれているため、メッセージキューにターゲットハンドラを持つメッセージがある限り、ハンドラをガベージコレクションすることはできません。 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);
}
}
}
詳しくは post を参照してください。
他の人が述べているように、Lintの警告は潜在的なメモリリークによるものです。 Handler
を構築するときにHandler.Callback
を渡すことでLintの警告を回避することができます(つまり、Handler
をサブクラス化せず、Handler
非静的内部クラスがない)。
Handler mIncomingHandler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
// todo
return true;
}
});
私が理解しているように、これは潜在的なメモリリークを避けることはできません。 Message
オブジェクトは、参照を保持するmIncomingHandler
オブジェクトへの参照を保持し、Service
オブジェクトへの参照を保持するHandler.Callback
オブジェクトを保持します。 Looper
メッセージキューにメッセージがある限り、Service
はGCにはなりません。ただし、メッセージキューに長い遅延メッセージがない限り、これは深刻な問題にはなりません。
弱い参照と静的ハンドラクラスを使用して問題を解決する一般的な例を示します(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);
}
}
この方法は私にとってはうまくいきました。独自の内部クラスでメッセージを処理する場所を保つことでコードをきれいに保ちます。
使いたいハンドラ
Handler mIncomingHandler = new Handler(new IncomingHandlerCallback());
インナークラス
class IncomingHandlerCallback implements Handler.Callback{
@Override
public boolean handleMessage(Message message) {
// Handle message code
return true;
}
}
@ Soggerの答えを参考にして、私は一般的なHandlerを作成しました。
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;
}
}
...
}
よくわかりませんが、onDestroy()で初期化ハンドラをnullにすることができます。