Android Application Development - How to Solve handler's Warning: Handler Class Should be Static or Leaks Occur

Keywords: Android Google Java

Turn from android handler warning Handler Class Should be Static or Leaks Occur

When updating the UI with Handler, I wrote as follows:

public class SampleActivity extends Activity {
    private final Handler mLeakyHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
          // TODO
        }
    }
}

It looks normal, but Android Lint warns:

This Handler class should be static or leaks might occur

This means that the Handler must be static, otherwise it will cause memory leaks.

In fact, Romain Guy, an engineer at the Android Framework, has already explained this question in the Google forum and given his suggestions.

I wrote that debugging code because of a couple of memory leaks I
found in the Android codebase. Like you said, a Message has a
reference to the Handler which, when it's inner and non-static, has a
reference to the outer this (an Activity for instance.) If the Message
lives in the queue for a long time, which happens fairly easily when
posting a delayed message for instance, you keep a reference to the
Activity and "leak" all the views and resources. It gets even worse
when you obtain a Message and don't post it right away but keep it
somewhere (for instance in a static structure) for later use.

His suggestions are as follows:

class OuterClass {
    class InnerClass {
        private final WeakReference<OuterClass> mTarget;

        InnerClass(OuterClass target) {
           mTarget = new WeakReference<OuterClass>(target);
    }

    void doSomething() {
           OuterClass target = mTarget.get();
           if (target != null) {
                target.do();    
           }
     }
}

Next, let's further explain:

  • When Android App starts, the Android Framework creates a Looper object for the main thread, which runs through the entire life cycle of the App, implements a Message Queue, and opens a loop to process the Message object. The main events of Framework contain internal Message objects, which are added to the message queue to execute when these events are triggered.
  • When a Handler is instantiated (as above), it will be associated with the message queue of the main thread Looper object, and the Message object pushed into the message queue will hold a reference to the Handler so that when Looper processes the message, the Framework executes the handleMessage(Message) method of the Handler.
  • In the Java language, non-static anonymous inner classes will hold an implicit reference to the outer classes, while static inner classes will not.

Where exactly did memory leaks occur? Take the following code as an example:

public class SampleActivity extends Activity {              
    private final Handler mLeakyHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
          // ...
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Post a message and delay its execution for 10 minutes.
        mLeakyHandler.postDelayed(new Runnable() {
            @Override
            public void run() { }
        }, 60 * 10 * 1000);

        // Go back to the previous Activity.
        finish();
     }
}

When Activity is finished (), the message will exist in the message queue for up to 10 minutes before it is executed. This Message holds a reference to Handler, and Handler also holds an implicit reference to an external class (SampleActivity), which will remain until Message is executed, thus ensuring that the context of the Activity is not reclaimed by the garbage collection mechanism, and that the application's resources (views and resources) are leaked.

To solve this problem, Handler in the following code is a static anonymous inner class. Statically anonymous inner classes do not hold an implicit reference to external classes, so Activity will not be compromised. If you need to call an external activity method in Handler, let Handler hold a WeakReference to Activity so that the context of the activity is not leaked, as shown below:

public class SampleActivity extends Activity {     
    /**
    * Instances of static inner classes do not hold an implicit
    * reference to their outer class.
    */
    private static class MyHandler extends Handler {
        private final WeakReference<SampleActivity> mActivity;
        public MyHandler(SampleActivity activity) {
        mActivity = new WeakReference<SampleActivity>(activity);
    }

    @Override
    public void handleMessage(Message msg) {
        SampleActivity activity = mActivity.get();
        if (activity != null) {
            // ...
        }
    }
}

private final MyHandler mHandler = new MyHandler(this);      
    /**
    * Instances of anonymous classes do not hold an implicit
    * reference to their outer class when they are "static".
    */
    private static final Runnable sRunnable = new Runnable() {
        @Override
        public void run() { }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Post a message and delay its execution for 10 minutes.
        mHandler.postDelayed(sRunnable, 60 * 10 * 1000);

        // Go back to the previous Activity.
        finish();
    }
}

Conclusion:
In practical development, if the lifecycle of internal classes is not consistent with that of Activeness (for example, Activity finish() waits 10 minutes before an instance of the internal class is executed), then non-static internal classes are avoided in Activity. In this case, a static internal class is used and a WeakReference to Activity is held.

Posted by SCRUBBIE1 on Thu, 10 Jan 2019 15:18:10 -0800