当前位置:网站首页>Find out the possible memory leaks caused by the handler and the solutions
Find out the possible memory leaks caused by the handler and the solutions
2022-06-25 08:14:00 【Cattle within yards】
author : Minnow rice Jun
Reprinted address :https://mp.weixin.qq.com/s/kzExhgdm4fIEk882w8cEkQ

I wrote an article on this topic last year , But several important chapters are missing , And the subthreads Looper The conclusion is not very clear . Several schematic diagrams and chapters have been reproduced and supplemented this time , Expect to make it clear at one time !
- 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?
- Conclusion
1. Handler Improper use ?
Find out what it's called first Handler Improper use ?
It generally has these characteristics :
- Handler use Anonymous inner class or Inner class Expand , External classes are held by default
ActivityReferences 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}"
)
}
}
- 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() }
}
2. 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 :

- Thread Although it's over , But sent
MessageNot 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 .

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 !
3. 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'll find that : 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 .

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()orquitSafely(), It will empty all Message Or the future Message, And promoteloop()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 :
- 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 !
- in addition , Because it is a static property , It won't happen key Being recycled value Risk of isolated memory leaks ~
- Last , because Thread hold Looper value, From this path It is possible that there is a memory leak
4. 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 .

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 .
5. 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 :

6. 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

The code example is briefly described below :
- 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) {
// ...
}
}
}
}
- 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()
}
- 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() And will be cleared 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
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 callbackCallbackThe way , And send the taskRunnableThe 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
边栏推荐
- STM32CubeMX 學習(5)輸入捕獲實驗
- Logu P2486 [sdoi2011] coloring (tree chain + segment tree + merging of intervals on the tree)
- Electronics: Lesson 011 - experiment 10: transistor switches
- c#搭建ftp服务器并实现文件上传和下载
- FFT [template]
- 不怕百战失利,就怕灰心丧气
- Can bus working condition and signal quality "physical examination"
- What is the difference between agreement and service?
- Functions should not specify operation types through variables
- Black dot = = white dot (MST)
猜你喜欢
![[thesis study] vqmivc](/img/38/a97ac763a7d6e71d4c7340c7abb6e7.png)
[thesis study] vqmivc

PH neutralization process modeling

Anaconda based module installation and precautions

Electronics: Lesson 013 - Experiment 14: Wearable pulsed luminaries

Authority design of SaaS system based on RBAC

自制坡道,可是真的很香

Looking for b-end product manager after years? I almost ruined myself

C disk drives, folders and file operations

Sword finger offer (simple level)

Biweekly investment and financial report: capital ambush Web3 infrastructure
随机推荐
Matlab code format one click beautification artifact
Anaconda based module installation and precautions
测一测现在的温度
TCP and UDP
Set the textalign property of the label control in C to control the method of text centering
Cloud computing exam version 1 0
Electronics: Lesson 009 - Experiment 7: study relays
剑指offer刷题(简单等级)
TCP 加速小记
电子学:第010课——实验 8:继电振荡器
【补题】2021牛客暑期多校训练营1-3
C # set up FTP server and realize file uploading and downloading
飞机引气系统的建模与故障仿真
什么是SKU和SPU,SKU,SPU的区别是什么
c#ColorDialog更改文本颜色和FontDialog更改文本字体的使用示例
静态网页服务器
剑指offer刷题(中等等级)
电子学:第012课——实验 11:光和声
FFT [template]
To understand the difference between Gram-positive and Gram-negative bacteria and the difference in pathogenicity