Lớp Handler này phải là tĩnh hoặc có thể xảy ra rò rỉ: In chuẩnHandler


297

Tôi đang phát triển ứng dụng Android 2.3.3 với một dịch vụ. Tôi có dịch vụ này bên trong dịch vụ đó để liên lạc với Hoạt động chính:

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

Và ở đây, final Messenger mMessenger = new Messenger(new IncomingHandler());tôi nhận được cảnh báo Lint sau:

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

Nó có nghĩa là gì?


24
Kiểm tra bài viết trên blog này để biết thêm thông tin về chủ đề này!
Adrian Monk

1
Rò rỉ bộ nhớ gây ra bởi bộ sưu tập rác ... Điều này đủ để chứng minh Java không phù hợp và được thiết kế tồi như thế nào
Gojir4

Câu trả lời:


392

Nếu IncomingHandlerlớp không tĩnh, nó sẽ có một tham chiếu đến Serviceđối tượng của bạn .

Handler tất cả các đối tượng cho cùng một luồng đều chia sẻ một đối tượng Looper chung, chúng gửi thông điệp đến và đọc từ đó.

Vì các tin nhắn chứa mục tiêu Handler, miễn là có các tin nhắn với trình xử lý đích trong hàng đợi tin nhắn, trình xử lý không thể được thu gom rác. Nếu xử lý không tĩnh, Servicehoặc Activitykhông thể là rác được thu thập, ngay cả sau khi bị phá hủy.

Điều này có thể dẫn đến rò rỉ bộ nhớ, ít nhất là trong một thời gian - miễn là các tin nhắn vẫn ở trong hàng đợi. Đây không phải là một vấn đề lớn trừ khi bạn gửi tin nhắn bị trì hoãn lâu.

Bạn có thể thực hiện IncomingHandlertĩnh và có một WeakReferencedịch vụ của bạn:

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

Xem bài đăng này của Romain Guy để tham khảo thêm


3
Romain cho thấy rằng WeakReference cho lớp bên ngoài là tất cả những gì cần thiết - một lớp lồng tĩnh là không cần thiết. Tôi nghĩ rằng tôi sẽ thích cách tiếp cận WeakReference hơn vì nếu không thì toàn bộ lớp bên ngoài thay đổi mạnh mẽ do tất cả các biến 'tĩnh' mà tôi cần.
Ai đó ở đâu đó vào

35
Nếu bạn muốn sử dụng một lớp lồng nhau, nó phải là tĩnh. Mặt khác, WeakReference không thay đổi bất cứ điều gì. Lớp bên trong (lồng nhưng không tĩnh) luôn giữ tham chiếu mạnh đến lớp bên ngoài. Không cần cho bất kỳ biến tĩnh mặc dù.
Tomasz Niedabylski

2
@SomeoneSomewhere mSerivce là một WeakReference. get()sẽ trả về null khi đối tượng được tham chiếu là gc-ed. Trong trường hợp này, khi dịch vụ đã chết.
Tomasz Niedabylski

1
Lưu ý: sau khi làm cho In chuẩnHandler tĩnh, tôi đã nhận được lỗi "Hàm tạo MyActivity.In chuẩnHandler () không xác định." trên dòng "Messenger cuối cùng trongMesbah = Messenger mới (In chuẩnHandler ());" mới. Giải pháp là thay đổi dòng đó thành "Messenger cuối cùng trongMesbah = Messenger mới (In chuẩnHandler mới (này));".
Lance Lefebure

4
@Someone Ở đâu đó Vâng, bài đăng của Romain sai ở chỗ anh ấy đã lỡ khai báo lớp tĩnh bên trong mà bỏ lỡ toàn bộ điểm. Trừ khi anh ta có một số trình biên dịch cực hay, tự động chuyển đổi các lớp bên trong thành các lớp tĩnh khi chúng không sử dụng các biến lớp.
Sogger

67

Như những người khác đã đề cập cảnh báo Lint là do rò rỉ bộ nhớ tiềm năng. Bạn có thể tránh cảnh báo Lint bằng cách chuyển một Handler.Callbackkhi xây dựng Handler(nghĩa là bạn không phân lớp Handlervà không có Handlerlớp bên trong không tĩnh):

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

Theo tôi hiểu, điều này sẽ không tránh được việc rò rỉ bộ nhớ tiềm năng. Messagecác đối tượng giữ một tham chiếu đến mIncomingHandlerđối tượng chứa tham chiếu Handler.Callbackđối tượng giữ tham chiếu đến Serviceđối tượng. Miễn là có tin nhắn trong Looperhàng đợi tin nhắn, Servicesẽ không phải là GC. Tuy nhiên, đây sẽ không phải là vấn đề nghiêm trọng trừ khi bạn có tin nhắn chậm trễ trong hàng đợi tin nhắn.


10
@Braj Tôi không nghĩ tránh cảnh báo lint nhưng vẫn giữ lỗi là một giải pháp tốt. Trừ khi, như cảnh báo xơ xác nếu trình xử lý không được đặt trên bộ xử lý chính của bạn (và bạn có thể đảm bảo tất cả các thông báo đang chờ xử lý trên đó bị hủy khi lớp bị hủy), sau đó rò rỉ tham chiếu được giảm nhẹ.
Sogger

33

Dưới đây là một ví dụ chung về việc sử dụng lớp trình xử lý tĩnh và tham chiếu yếu để giải quyết vấn đề (như được khuyến nghị trong tài liệu 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
Ví dụ Sogger là tuyệt vời. Tuy nhiên, phương thức cuối cùng trong Myclassnên được khai báo public Handler getHandler()thay vìpublic void
Jason Porter

Nó tương tự như câu trả lời của Tomasz Niedabylski
CoolMind

24

Cách này hoạt động tốt với tôi, giữ sạch mã bằng cách giữ nơi bạn xử lý thư trong lớp bên trong của chính nó.

Trình xử lý bạn muốn sử dụng

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

Lớp học bên trong

class IncomingHandlerCallback implements Handler.Callback{

        @Override
        public boolean handleMessage(Message message) {

            // Handle message code

            return true;
        }
}

2
Ở đây phương thức handleMessage trả về true cuối cùng. Bạn có thể giải thích chính xác điều này có nghĩa là gì (giá trị trả về đúng / sai) không? Cảm ơn.
JibW

2
Sự hiểu biết của tôi về việc trả về true là chỉ ra rằng bạn đã xử lý tin nhắn và do đó tin nhắn không nên được chuyển đi bất cứ nơi nào khác, ví dụ như trình xử lý cơ bản. Điều đó nói rằng tôi không thể tìm thấy bất kỳ tài liệu nào và sẽ được sửa chữa một cách vui vẻ.
Stuart Campbell

1
Javadoc nói: Trình xây dựng liên kết trình xử lý này với Looper cho luồng hiện tại và có giao diện gọi lại trong đó bạn có thể xử lý các thông báo. Nếu chủ đề này không có kẻ lừa đảo, trình xử lý này sẽ không thể nhận được tin nhắn nên một ngoại lệ được đưa ra. <- Tôi nghĩ Trình xử lý mới (In chuẩnHandlerCallback ()) mới sẽ không hoạt động khi không có Looper được gắn vào chuỗi và đó có thể là trường hợp. Tôi không nói sai khi làm như vậy trong một số trường hợp, tôi chỉ nói rằng nó không phải lúc nào cũng hoạt động như bạn mong đợi.
dùng504342

1
@StuartCampbell: Bạn đã đúng. Xem: nhóm.google.com/forum/# ! topic/android-developers/L_xYM0yS6z8 .
MDTech.us_MAN

2

Với sự giúp đỡ của câu trả lời của @ Sogger, tôi đã tạo ra một Handler chung:

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

Giao diện:

public interface MessageHandler {

    void handleMessage(Message msg);

}

Tôi đang sử dụng nó như sau. Nhưng tôi không chắc chắn 100% nếu điều này an toàn. Có lẽ ai đó có thể nhận xét về điều này:

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

Tôi không chắc chắn nhưng bạn có thể thử xử lý intialising thành null trong onDestroy ()


1
Tất cả các đối tượng xử lý cho cùng một luồng đều chia sẻ một đối tượng Looper chung, chúng gửi thông điệp đến và đọc từ đó. Vì các thông điệp chứa Trình xử lý đích, miễn là có các thông báo có trình xử lý đích trong hàng đợi thư, trình xử lý không thể được thu gom rác.
msysmilu

0

Tôi bối rối. Ví dụ tôi tìm thấy tránh hoàn toàn thuộc tính tĩnh và sử dụng luồng 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();
                }
            }
        };
    }

Điều tôi thích về giải pháp này là không có vấn đề gì khi cố gắng trộn các biến lớp và phương thức.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.