当前位置:网站首页>Explain the possible memory leaks caused by the handler at one time and the solutions | the developer said · dtalk

Explain the possible memory leaks caused by the handler at one time and the solutions | the developer said · dtalk

2022-06-25 13:42:00 Android Developer

8b2fe08f0d23ff7eab059af01eeb1b7d.png

The original author of this article : Minnow rice Jun , original text Published on : TechMerger

This paper reproduces and supplements several diagrams and chapters , I hope I can make it clear for you at one time  Handler Possible memory leaks and solutions !

  1. Handler Improper use ?

  2. Why memory leaks ?

  3. Sub thread Looper Will it lead to a memory leak ?

  4. Non inner class Handler Is there a memory leak ?

  5. It's online Callback Can the interface really be solved ?

  6. Use... Correctly Handler?

  7. Conclusion

eddb6a07e45664d420ff11a315fdc135.png

7147b1fd62c8dd81d795fc6b6b9f5cc5.png

Handler Improper use ?

Find out what it's called first Handler Improper use

It generally has these characteristics :

1. Handler use Anonymous inner class or Inner class Expand , External classes are held by default Activity References to :

//  Anonymous inner class 
override fun onCreate(savedInstanceState: Bundle?) {
    ...
    val innerHandler: Handler = object : Handler(Looper.getMainLooper()) {
        override fun handleMessage(msg: Message) {
            Log.d(
                "MainActivity",
                "Anonymous inner handler message occurred & what:${msg.what}"
            )
        }
    }
}
//  Inner class 
override fun onCreate(savedInstanceState: Bundle?) {
    ...
    val innerHandler: Handler = MyHandler(Looper.getMainLooper())
}


inner class MyHandler(looper: Looper): Handler(looper) {
    override fun handleMessage(msg: Message) {
        Log.d(
            "MainActivity",
            "Inner handler message occurred & what:\${msg.what}"
        )
    }
}

2. Activity When you quit Handler Still reachable , There are two situations :

  • When you quit, there are still Thread In the process , It refers to Handler;

  • When I quit, although Thread It's over , but Message Still waiting in the queue or Processing , Hold indirectly Handler.

override fun onCreate(savedInstanceState: Bundle?) {
    ...
    val elseThread: Thread = object : Thread() {
        override fun run() {
            Log.d(
                "MainActivity",
                "Thread run"
            )


            sleep(2000L)
            innerHandler.sendEmptyMessage(1)
        }
    }.apply { start() }
}

1c89e7a02d353c14dcbdaf684f4e34fc.png

Why memory leaks ?

Aforementioned Thread In the process of execution , If Activity Into the background , Subsequently, due to insufficient memory, it triggered destroy. The virtual machine is marking GC When the object , The following two situations will happen :

  • Thread It's not over yet , In an active state

    Active Thread As GC Root object , It holds Handler example ,Handler It also holds external classes by default Activity Example , This layer of reference chain can still reach :

7c54332b9e188c9453ab33258b90933f.png

  • Thread Although it's over , But sent Message Not finished yet

    Thread Sent Message May still be waiting in the queue , Or just in handleMessage() In the callback of . At the moment Looper adopt MessagQueue Hold the Message,Handler Again target Attributes are Message hold ,Handler And hold Activity, Eventually lead to Looper Hold indirectly Activity.

    You may not have noticed the of the main thread Main Looper Is different from other threads Looper Of .

    In order to make it easy for any thread to obtain the of the main thread Looper example ,Looper Define it as a static attribute sMainLooper.

public final class Looper {
    private static Looper sMainLooper;  // guarded by Looper.class
    ...
    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            sMainLooper = myLooper();
        }
    }
}

Static properties are also GC Root object , It leads to... Through the above application chain Activity Still up to .

0031ea6f0b19d2d5d910260e263c8dc0.png

Both of these situations will lead to Activity Instances will not be marked correctly , until Thread End and Message It's done . Before that Activity Instances will not be recycled .

Inner class Thread It can also lead to Activity Can't recycle ?

In order to focus on Handler Cause memory leaks , Not for Thread The directly generated reference chain is described .

In the code example above Thread It also adopts the form of anonymous inner class , Of course, it also holds Activity example . At this point , Unfinished Thread Will directly occupy Acitvity example , It also leads to Activity A chain of references for memory leaks , Need to pay attention to !

c8420a4aaca559475549dcf2e12e2dd7.png

Sub thread Looper Will it lead to a memory leak ?

In order to make it easy for each thread to get its own Looper example ,Looper Class is static sThreadLocal attribute management Examples .

public final class Looper {
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    ...
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }
}

But as a static attribute ,sThreadLocal It's also GC Root object . From this point of view, will it also indirectly lead to Message It can't be recycled ?

Meeting , But it's not really because ThreadLocal, But because Thread.

Look over ThreadLocal Source code , You will find : The target object is not stored in ThreadLocal in , But its static inner class ThreadLocalMap in . Plus, it's unique to threads , The Map Has been Thread hold , The life cycle of the two is the same .

// TheadLocal.java
public class ThreadLocal<T> {
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }


    //  establish  Map  And put it in  Thread  in 
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }


    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
}
// Thread.java
public class Thread implements Runnable {
    ThreadLocal.ThreadLocalMap threadLocals = null;
}

A further detail is : ThreadLocalMap The elements in Entry use Weak reference Holding as key Of ThreadLocal object , But as a value The target object of is Strong citation the .

This leads to Thread Indirectly holds the target object , For example, this time Looper example . This ensures that Looper The life cycle of Thread bring into correspondence with , but Looper There is a risk of memory leakage if the life cycle is too long ( Of course it's not Looper Designer's pot ).

// TheadLocal.java
public class ThreadLocal<T> {
    ...
    static class ThreadLocalMap {
        private Entry[] table;


        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;


            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
    }
}

When weakly referenced key Instance because GC The occurrence will be cut off and Map Reference relation of , But until the next manual execution Entry Of set、get or remove front ,value Are not set to null. During this period value Are strongly quoted , Cause hidden danger .

The entries in this hash map extend WeakReference, using its main ref field as the key (which is always a ThreadLocal object).

Note that null keys (i.e. entry.get() == null) mean that the key is no longer referenced, so the entry can be expunged from table.

Such entries are referred to as "stale entries" in the code that follows.

But it should be noted that : Office Looper Examples held sThreadLocal Is static , It will not be recycled until the process ends , It is unlikely that the above mentioned key Being recycled value An isolated situation .

in short ,Looper Of ThreadLocal No memory leaks .

go back to Thread, Because of its strong reference Looper, therefore Thread It will still lead to Message There is a memory leak . such as : To the child thread Handler Sent the inner class writing Runnable, When Activity It's time to end Runnable Not yet arrived or in the process of implementation , that Activity Because the following reference chain can always reach , That is, the possibility of memory leakage .

dadabaab4832ee044551f3317e2a3cc5.png

The solution is not complicated :

  • Activity Remove child threads at the end Handler All unprocessed in Message, such as Handler#removeCallbacksAndMessages(). This will cut off MessageQueue To Message, as well as Message To Runnable Reference relation of ;

  • A better way is to call Looper#quit() or quitSafely(), It will empty all Message Or the future Message, And promote loop() End of polling . The end of the child thread means the reference starting point GC Root No longer exists .

Last , Clear a few points of consensus :

  1. management Looper Static properties of the instance sThreadLocal, Does not hold actual storage Looper Of ThreadLocalMap, But through Thread Read and write . This makes sThreadLocal Although expensive  GC Root, But we can't achieve Looper Reference chain of , Further, this path does not constitute a memory leak !

  2. in addition , Because it is a static property , It won't happen key Being recycled value Risk of isolated memory leaks

  3. Last , because Thread hold Looper value, From this path It is possible that there is a memory leak .

e261a2917d29efaf251296f8fa9e5d9b.png

Non inner class Handler Is there a memory leak ?

As mentioned above, anonymous inner classes or inner classes are Handler A feature that causes memory leaks , Then if Handler Do not use the writing method of internal classes , Will it cause leakage ?

Such as this :

override fun onCreate(...) {
    Handler(Looper.getMainLooper()).apply {
        object : Thread() {
            override fun run() {
                sleep(2000L)
                post { 
                    // Update ui
                }
            }
        }.apply { start() }
    }
}

It may still cause memory leaks .

although Handler Not an inner class , but post Of Runnable It's also an internal class , It will also hold Activity Example . in addition ,post To Handler Of Runnable In the end callback Attributes are Message hold .

5a690ade33f3da3210e3912f22b2e825.png

Based on these two performances , Even if Handler Not an inner class , But because Runnable Is the inner class , The same thing happens Activity By Thread or Main Looper Risk of improper holding .

d635eaa0d8b4c9a83b92f173c4845999.png

It's online Callback Can the interface really be solved ?

There is a saying on the Internet : establish Handler Do not overwrite handleMessage(), It's about designating Callback Interface instance , In this way, memory leakage can be avoided . The reason is that after this writing AndroidStudio The following warning will not pop up again :

This Handler class should be static or leaks might occur.

in fact ,Callback If the instance is still anonymous inner class or the writing method of inner class , Still cause memory leaks , It's just AS It's just that this layer of warning didn't pop up .

private Handler mHandler = new Handler(new Handler.Callback() {
    @Override  
    public boolean handleMessage(Message msg) {  
        return false;  
    }  
});

For example, the above way of writing ,Handler It will pass in Callback example , and Callback As an internal class , External classes are held by default Activity References to .

public class Handler {
    final Callback mCallback;


 public Handler(@NonNull Looper looper, @Nullable Callback callback) {
        this(looper, callback, false);
    }


    public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
        ...
        mCallback = callback;
    }
}

Whether from Thread Active perspective , Or from the Thread It's over, but Message From the perspective of still unfinished implementation , Will lead to Activity Can still be GC Root Risk of memory leakage due to indirect reference .

Essentially the same as the above Runnable The example is the same problem :

6e7d44c5c46de0b24d5dd3897f76e232.png

79b838fc5a7d176b183fc1580398c680.png

Use... Correctly Handler?

GC When marking Thread It's over and Message Once the conditions that have been processed are not met ,Activity The life cycle of will be wrongly extended , This leads to a memory leak !

So how to avoid this situation ? For the above characteristics , In fact, there should be an answer :

  • One will strongly quote Activity Change to weak reference ;

  • Second, cut off the two in time GC Root Reference chain relationship of : Main Looper To Message, And End Sub threads .

58134391fe185bbd4cd68f049d549f6f.png

The code example is briefly described below :

1. take Handler or Callback or Runnable Defined as Static inner class :

class MainActivity : AppCompatActivity() {
    private class MainHandler(looper: Looper?, referencedObject: MainActivity?) :
            WeakReferenceHandler<MainActivity?>(looper, referencedObject) {
        override fun handleMessage(msg: Message) {
            val activity: MainActivity? = referencedObject
            if (activity != null) {
                // ...
            }
        }
    }
}

2. It also needs to be Weak reference An instance of an external class :

open class WeakReferenceHandler<T>(looper: Looper?, referencedObject: T) :     Handler(looper!!) {
    private val mReference: WeakReference<T> = WeakReference(referencedObject)


    protected val referencedObject: T?
        protected get() = mReference.get()
}

3. onDestroy When Cut off the reference chain relationship , Correct the life cycle :

  • Activity At the time of destruction , If the subthread task has not ended , In time interrupt Thread:

override fun onDestroy() {
    ...
    thread.interrupt()
}
  • If... Is created in the child thread Looper And became Looper Thread words , Must be Manual quit. such as HandlerThread:

override fun onDestroy() {
    ...
    handlerThread.quitSafely()
}
  • Main thread Looper Cannot manually quit, So you need to manually Clear the main thread Handler Unprocessed Message:

override fun onDestroy() {
    ...
    mainHandler.removeCallbacksAndMessages(null)
}

※1: Message In execution recycle() It will be cleared after  Main Handler Reference relation of ;

※2: Looper Sub thread call quit It will be empty when Message, Therefore, there is no need to target the sub thread Handler Do it again Message The emptying of .

504bf356655cb8ddd6fbe6aee2b5908c.png

Conclusion

Review the main points of this article :

  • hold Activity Example of Handler Handle , Its Life cycle It should be with Activity bring into correspondence with ;

  • If Activity It should have been destroyed , But asynchrony Thread Still active or sent Message Not yet processed , Will lead to Activity Example of The life cycle has been wrongly extended ;

  • Cause what should have been recycled Activity example Quilt sub thread or   Main Looper  Occupy and cannot be recovered in time .

Simply put, the right thing to do :

  • Use Handler When it comes to mechanisms , Whether it's overwriting Handler Of handleMessage() The way , Or specify callback Callback The way , And send the task Runnable The way , Use as far as possible Static inner class + Weak reference , Avoid its strong references to hold Activity Example .

    Ensure that even if the lifecycle is extended by mistake ,Activity Can also be GC Recycling .

  • At the same time Activity At the end , in due course Empty Message、 End Thread Or quit Looper, In order to recycle Thread or Message.

    Make sure that it can be cut off completely GC Root Arrived in Activity Reference chain of .


Long press the QR code on the right

See more developers share

7482c1c817523a715d09ecdb9673af3e.png

" The developer said ·DTalk" oriented a2300656ef8083d705f699e21318d4e8.png Chinese developers solicit Google Mobile application (apps & games)  Related products / Technical content . Welcome to share your insights or insights on the mobile application industry 、 Experience or new discovery in the process of mobile development 、 As well as the application of the sea experience summary and the use of related products feedback . We sincerely hope to provide these outstanding Chinese developers with better opportunities to show themselves 、 A platform to give full play to one's strong points . We will focus on selecting excellent cases through your technical content Google Development Technologist (GDE)  The recommendation of .

582c65121fdc696a1876153eaaa56a31.gif  Click at the end of the screen  |  Read the original  |  Sign up now  " The developer said ·DTalk" 


5381f2a0f19dfed5b33703f311578526.png

原网站

版权声明
本文为[Android Developer]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/176/202206251309352425.html