이 핸들러 클래스는 정적이거나 누수가 발생할 수 있습니다. 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

답변:


391

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는 아무것도 변경하지 않습니다. 내부 (중고하지만 정적이 아닌) 클래스는 항상 외부 클래스에 대한 강력한 참조를 보유합니다. 정적 변수가 필요하지 않습니다.
Tomasz Niedabylski

2
@SomeoneSomewhere mSerivce는 약한 참조입니다. get()참조 된 객체가 gc-ed 일 때 null을 반환합니다. 이 경우 서비스가 종료 된 경우
Tomasz Niedabylski

1
참고 : IncomingHandler를 정적으로 만든 후 "생성자 MyActivity.IncomingHandler ()가 정의되지 않았습니다."라는 오류가 발생했습니다. "최종 메신저 inMessenger = new Messenger (new IncomingHandler ());"행에 있습니다. 해결책은 해당 줄을 "최종 메신저 inMessenger = new Messenger (new IncomingHandler (this));"로 변경하는 것입니다.
랜스 Lefebure

4
@Someone Somewhere 네, Romain의 게시물은 요점을 놓친 내부 클래스 정적 선언을 놓쳤다는 점에서 잘못되었습니다. 클래스 변수를 사용하지 않을 때 내부 클래스를 정적 ​​클래스로 자동 변환하는 매우 멋진 컴파일러가없는 한.
Sogger

67

다른 사람들이 언급했듯이 Lint 경고는 잠재적 인 메모리 누수로 인한 것입니다. Handler.Callback구성 할 때 를 전달하여 Lint 경고를 피할 수 있습니다 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메시지 큐에서,이 ServiceGC되지 않습니다. 그러나 메시지 대기열에 메시지가 오래 지연되지 않는 한 심각한 문제는 아닙니다.


10
@Braj 보푸라기 경고를 피하는 것이 아니라 오류를 유지하는 것이 좋은 해결책이라고 생각합니다. 보푸라기 경고에서 핸들러가 기본 루퍼에 배치되지 않은 경우 (클래스가 소멸 될 때 보류중인 모든 메시지가 소멸되도록 보장 할 수 있음) 참조가 누출되는 경우가 없습니다.
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
제이슨 포터

그것은 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 메소드는 결국 true를 리턴합니다. 이것이 정확히 무엇을 의미하는지 설명해 주시겠습니까 (반환 값 true / false)? 감사.
JibW

2
true를 반환하는 것에 대한 나의 이해는 메시지를 처리 ​​했으므로 메시지를 기본 핸들러와 같은 다른 곳으로 전달해서는 안된다는 것을 나타냅니다. 그것은 문서를 찾을 수 없으며 행복하게 수정 될 것이라고 말했습니다.
스튜어트 캠벨

1
Javadoc의 말 : 생성자는이 핸들러를 현재 스레드의 Looper와 연관시키고 메시지를 처리 ​​할 수있는 콜백 인터페이스를 사용합니다. 이 스레드에 루 퍼가없는 경우이 핸들러는 메시지를 수신 할 수 없으므로 예외가 발생합니다. <-스레드에 연결된 루 퍼가 없으면 새 Handler (new IncomingHandlerCallback ())가 작동하지 않으며 그럴 수 있다고 생각합니다. 나는 어떤 경우에는 그렇게 잘못 말하지 않고 단지 예상대로 항상 작동하지는 않는다고 말하고 있습니다.
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

확실하지 않지만 onDestroy ()에서 처리기를 null로 처리 할 수 ​​있습니다.


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.