详解Handler

news/2024/11/23 0:19:16/

详解Handler

文章目录

  • 详解Handler
    • 1.Handler的工作流程
      • 1.1主线程具有如上性质的原因
      • 1.2流程图
    • 2.Handler流程中的重要的几个方法
      • 2.1Message中的属性
        • 2.2.1what
        • 2.2.2replyTo
        • 2.2.3obtain
      • 2.2Handler.post()与Handler.sendMessage()
        • 2.2.1post的源码
          • 2.2.1.1sendMessageDelayed()源码
          • 2.2.1.2sendMessageAtTime()源码
          • 2.2.1.3post的流程总结
      • 2.3enqueueMessage的源码
      • 2.4Handler.postDelay()
        • 2.4.1注意:
          • 1.Handler的延迟消息
          • 2.线程与Handler与Looper
          • 3.一个线程中多个Handler怎么判断用哪一个Handler进行接收
          • 4.Handler如何消耗数据?Message时间怎么判断?
            • 1.如何消耗数据
            • 1.1message.callback
            • 1.2mcallback
            • 1.3mcallback.handleMessage
            • 2Message时间怎么判断
      • 2.5Handler消息机制与时间排序
    • 3.ThreadLocal的相关知识
      • 3.1ThreadLocal的set方法
        • 3.1.1ThreadLocalMap
      • 3.2内存泄漏
    • 4.Looper
      • 4.1为什么一个线程只能创建一个Looper
      • 4.2为什么Looper陷入死循环的时候不会ANR,主线程是阻塞的嘛?
        • 4.2.1什么情况下会导致ANR

本来按照《Android开发艺术探索》的进度我应该现在该看线程和线程池了,但是突然觉得Handler那块还有好多东西没有看,要是一并写到 线程和线程池那块有点头重脚轻,所以还是单独写一篇博客,详细解数一下 Handler

1.Handler的工作流程

Handler的主要作用是将一个任务从它自己的线程切换到某个指定中的线程去执行

我们用它的主要场景就是,在子线程中无法访问UI,我们只能通过但不限于用Handler来将它从子线程切换到UI线程来执行

至于为什么不能在子线程中访问UI,《艺术开发探索》给出过解释,大概就是说,

无法保证非主线程的安全性,多线程的并发操作可能导致UI控件处于不可预期的状态,

解决这种问题本可以用锁,但是使用了锁之后,会导致2个问题

  1. 锁的这种机制会让UI访问的逻辑变复杂
  2. 会降低UI访问效率,因为加上锁之后保证了多线程的原子性。当有一个线程在访问它的时候,其他线程无法访问它,大大降低了它的运行效率

所以我们选择在主线程又称为UI线程进行UI的访问

我们能用UI线程来访问UI,那就说明了UI线程则具备上面的三种性质

1.保证线程的安全

2.访问的逻辑简单

3.不会降低UI访问的效率

我们来了解一下为什么主线程具有上面的性质

1.1主线程具有如上性质的原因

我们清楚Window其实是由ViewViewRootImpl组成的,View就不必说了,而ViewRootImpl

我们在介绍Activity对象创建完成的时候,它会将DecorView添加到Window,Window会创建相应的ViewRootImpl与它关联

足以见得ViewRootImpl的重要了,

在主线程中,当需要更新 UI 的时候,ViewRootImpl 会确保更新操作在主线程中执行,通过线程检查来保证线程安全性。当其他线程尝试更新 UI(例如直接修改 UI 元素属性)时,ViewRootImpl 会在执行操作之前检查当前线程是否为主线程,如果不是主线程,就会抛出 CalledFromWrongThreadException 异常,阻止非主线程更新 UI。

这样就保证了UI线程的安全性与访问的逻辑的简单


至于不会降低UI访问的效率很简单就是因为没加锁不会让某些线程处于停滞状态


前面说的有点多了,那么Handler的工作流程到底是什么呢?

我画了一张流程图

1.2流程图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gjCQLOa9-1685708831933)(../../assets/流程图-导出 (6)].png)

根据这张图我们来了解几个重要的方法

2.Handler流程中的重要的几个方法

2.1Message中的属性

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uBi9Hxrx-1685708831934)(../../assets/image-20230601173946708.png)]

可以看下这张图,里面有obj,what,replyTo,arg1,arg2,sendingUid

objObject可以用来携带任意类型的数据。它通常用于传递消息中需要携带的额外数据。你可以将任何对象赋值给 obj 属性,并在消息处理时获取和使用这些数据。
whatint这是一个整型数值,用于标识消息的类型或目的
replyToMessenger这是一个 Messenger 对象,用于指定接收回复消息的目标
arg1int它们可以用来携带与消息相关的整型数据。
arg2int它们可以用来携带与消息相关的整型数据。
sendingUidint这是一个整型数值,表示发送该消息的应用程序的用户标识符
obtainMessageMessage.obtain() 是一个静态方法,用于获取可重用的 Message 对象。它可以避免频繁地创建新的 Message 对象,从而提高性能和效率。

我们这里面主要说3个

2.2.1what

一般我们在一个线程中写

Message  message = new Message();
message.what = 1;
message.obj = "2";
mHandler.sendMessage(message);

然后在主线程中

 Handler mHandler = new Handler(new Handler.Callback() {@Overridepublic boolean handleMessage(@NonNull Message msg) {switch (msg.what){case 1:mTextView.setText(""+message.obj);}return true;}});

这段代码很简单的说明了Handler中的obj与what的功能

obj主要用来携带值,what是一个标志,在handleMessage()中我们通过what这个标志进行处理

2.2.2replyTo

replyTo这个标志在IPC通讯的Messenger中我们用过,

在另一个进程中

public class MyService extends Service {
private static class MessengerHandler extends Handler{@Overridepublic void handleMessage(@NonNull Message msg) {super.handleMessage(msg);switch (msg.what){case 1:Log.d("TAG",msg.getData().getString("data"));Messenger client = msg.replyTo;Message replyMessage = new Message();replyMessage.what = 2;Bundle bundle = new Bundle();bundle.putString("TAG","reply"+"我大后台收到你的来信了");replyMessage.setData(bundle);try {client.send(replyMessage);} catch (RemoteException e) {e.printStackTrace();}break;default:break;}}
}
private final Messenger mMessenger = new Messenger(new MessengerHandler());
@Nullable
@Override
public IBinder onBind(Intent intent) {return mMessenger.getBinder();
}
}

我们通过

Messenger client = msg.replyTo;

获得了MainActivity传递过来的Messenger,然后接着获取MainActivity给我们传递的消息,同时我们又用刚才获得的MessengerMainActivity发送消息

在MainActivity中

public class MainActivity extends AppCompatActivity {// 服务端Messengerprivate Messenger mServerMessenger;// 服务端连接状态private boolean mIsBound = false;// 绑定服务端private Button message_0;private ServiceConnection mConnection = new ServiceConnection() {@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {mServerMessenger = new Messenger(service);Message message = new Message();message.what = 1;Bundle bundle = new Bundle();bundle.putString("data","你好啊");message.setData(bundle);message.replyTo = mGetReplyMessenger;try {mServerMessenger.send(message);} catch (RemoteException e) {e.printStackTrace();}}@Overridepublic void onServiceDisconnected(ComponentName name) {}};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);message_0 = findViewById(R.id.message_0);// 绑定服务端if(!mIsBound) {message_0.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {mIsBound = true;Intent intent = new Intent(MainActivity.this, MyService.class);bindService(intent, mConnection, Context.BIND_AUTO_CREATE);}});}}@Overrideprotected void onDestroy() {super.onDestroy();// 解绑服务端unbindService(mConnection);}private Messenger mGetReplyMessenger = new Messenger(new MessengerHandle());private static class MessengerHandle extends Handler{@Overridepublic void handleMessage(@NonNull Message msg) {super.handleMessage(msg);switch (msg.what){case 2:Log.d("TAG",msg.getData().getString("TAG").toString());}}}
}

我们主要看最后面的这个mGetReplyMessengermConnection中被初始化

message.replyTo = mGetReplyMessenger;

然后获得那个进程给我们传递过来的值

2.2.3obtain

2.2Handler.post()与Handler.sendMessage()

Handler.post()Handler.sendMessage() 都是 Handler 类提供的方法,用于向消息队列发送消息并在指定的时间后处理消息。它们的主要区别在于消息的发送方式和处理机制。

  1. Handler.post():该方法用于将一个 Runnable 对象提交到消息队列中,以便在主线程中执行。它不需要创建 Message 对象,而是直接将 Runnable 对象封装成消息并发送到消息队列。当消息处理时,Handler 会将 Runnable 对象的 run() 方法执行在主线程中。

    handler.post(new Runnable() {@Overridepublic void run() {// 在主线程中执行的操作}
    });
    

    2.Handler.sendMessage():该方法用于发送一个 Message 对象到消息队列中,在指定的时间后处理消息。它需要创建一个 Message 对象,并使用 Handler.sendMessage() 将消息发送到消息队列中。当消息处理时,Handler 会回调 Handler.handleMessage() 方法来处理消息。

    // 在主线程中创建一个 Handler 对象
    Handler handler = new Handler(Looper.getMainLooper()) {@Overridepublic void handleMessage(Message msg) {// 在主线程中处理消息switch (msg.what) {case MESSAGE_ID:// 处理特定的消息Object obj = msg.obj;// 执行相应的操作break;// 处理其他消息// ...}}
    };// 创建一个 Message 对象,并发送到消息队列中
    Message message = handler.obtainMessage();
    message.what = MESSAGE_ID;
    message.obj = someObject;
    handler.sendMessage(message);
    

    总的来说,Handler.post() 适用于在主线程中执行简单的代码块或任务,而 Handler.sendMessage() 更适用于发送包含更多信息的消息,并需要在消息处理中进行更复杂的操作。

其他并没有其他什么区别

我们之前看过**sendMessage()**的源码

现在看看post的源码

2.2.1post的源码

public final boolean post(@NonNull Runnable r) {return  sendMessageDelayed(getPostMessage(r), 0);
}

我们会发现post内部调用了sendMessageDelayed(),其中传递的参数分别是Runnable延时时间

我们再点击sendMessageDelayed()的源码看看

2.2.1.1sendMessageDelayed()源码
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {if (delayMillis < 0) {delayMillis = 0;}return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

它先进行了一个判断,判断延时时间是否小于0,小于0则给它赋值为0,然后返回sendMessageAtTime()

2.2.1.2sendMessageAtTime()源码
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {MessageQueue queue = mQueue;if (queue == null) {RuntimeException e = new RuntimeException(this + " sendMessageAtTime() called with no mQueue");Log.w("Looper", e.getMessage(), e);return false;}return enqueueMessage(queue, msg, uptimeMillis);
}

sendMessageAtTime中先判断MessageQueue为不为空,为空的话返回一个异常

否则的话进行enqueueMessage(),这个方法应该很眼熟,在MessaageQueue中这个是用来添加消息的

2.2.1.3post的流程总结

post()传递的是一个runnable,然后进入sendMessageDelayed方法,它会让你把runnable进行message化与delayMillis一起传进去

然后进入了sendMessageAtTime方法,它会让你把messageSystemClock.uptimeMillis() + delayMillis一笔给传进去,后面的这个是什么呢?给出的解释是:uptimeMills

然后传递enqueueMessage(),把MessageQueue,msguptimeMills三个参数一起传进去,进行MessageQueue的插入

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yDV68Xmm-1685708831934)(../../assets/流程图-导出 (8)]-1685611770598-1.png)

我们再进来看看enqueueMessage()是怎么把msg插入到MessageQueue里面的

2.3enqueueMessage的源码

boolean enqueueMessage(Message msg, long when) {if (msg.target == null) {throw new IllegalArgumentException("Message must have a target.");}synchronized (this) {if (msg.isInUse()) {throw new IllegalStateException(msg + " This message is already in use.");}if (mQuitting) {IllegalStateException e = new IllegalStateException(msg.target + " sending message to a Handler on a dead thread");Log.w(TAG, e.getMessage(), e);msg.recycle();return false;}msg.markInUse();msg.when = when;Message p = mMessages;boolean needWake;if (p == null || when == 0 || when < p.when) {// New head, wake up the event queue if blocked.msg.next = p;mMessages = msg;needWake = mBlocked;} else {// Inserted within the middle of the queue.  Usually we don't have to wake// up the event queue unless there is a barrier at the head of the queue// and the message is the earliest asynchronous message in the queue.needWake = mBlocked && p.target == null && msg.isAsynchronous();Message prev;for (;;) {prev = p;p = p.next;if (p == null || when < p.when) {break;}if (needWake && p.isAsynchronous()) {needWake = false;}}msg.next = p; // invariant: p == prev.nextprev.next = msg;}// We can assume mPtr != 0 because mQuitting is false.if (needWake) {nativeWake(mPtr);}}return true;
}

 if (p == null || when == 0 || when < p.when)

这个if判断中

如果消息队列为空(即没有已存在的消息),

如果当前消息的触发时间为 0(即立即触发),

如果当前消息的触发时间早于消息队列中已有消息的触发时间

那么就将当前消息的 next 属性指向原先的队头 p,即将当前消息插入到原先的队头之前。

将队列的头部指针 mMessages 更新为当前消息,使其成为新的队头。

根据当前线程的阻塞状态来设置 needWake 变量。如果当前线程被阻塞(即等待消息队列),则需要唤醒线程,以便立即处理新插入的消息。

然后在else中

 for (;;) {prev = p;p = p.next;if (p == null || when < p.when) {break;}if (needWake && p.isAsynchronous()) {needWake = false;}}msg.next = p; // invariant: p == prev.nextprev.next = msg;

如果在needWake==true当前message的插入为异步操作,则取消唤醒needwake

如果没有插入的东西或者当前消息的触发时间早于消息队列中已有消息的触发时间则退出for循环

否则的话就一直进入for循环进行插入操作

2.4Handler.postDelay()

我们点击postDelay()的源码

public final boolean postDelayed(@NonNull Runnable r, long delayMillis) {return sendMessageDelayed(getPostMessage(r), delayMillis);
}/** @hide */
public final boolean postDelayed(Runnable r, int what, long delayMillis) {return sendMessageDelayed(getPostMessage(r).setWhat(what), delayMillis);
}

我们会发现其实post()和postDelay()内部是一样的,都调用的sendMessageDelay(),所以两个唯一的不同就是,post里面传递的delayMillis为0,而postDelay()传递的delayMillis不一定为0

2.4.1注意:

1.Handler的延迟消息

Handler的延迟消息是确定的吗?postDelay 2000ms,后续修改系统时间会影响延迟消息吗?

这个回答我们可以看看

sendMessageDelayed()中的源码

 return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);

中注意:

SystemClock.uptimeMillis() + delayMillis

SystemClock.uptimeMillis() + delayMillis 是用来计算相对时间的表达式。它的目的是计算出延迟触发的时间点。

SystemClock.uptimeMillis() 是一个用于获取系统启动时间的方法,它返回自系统启动以来经过的毫秒数。它不受系统时间的修改影响。

delayMillis 是延迟的时间间隔,以毫秒为单位。

通过将当前的系统启动时间(SystemClock.uptimeMillis())与延迟的时间间隔(delayMillis)相加,可以得到延迟触发的时间点。

如果我postDelay(2000)的话,就有可能延迟超过了2000ms。因为delayMillis的时间为SystemClock.uptimeMillis() + delayMillis

但是消息延迟不单单因为这个

在Android系统中,处理消息和执行任务的机制是基于消息循环(Message Loop)和系统调度。延迟消息的触发时间取决于消息队列中的其他消息、正在执行的任务、系统负载等因素。

因此,尽管你指定了2000毫秒的延迟,但实际触发时间可能会稍有偏差,可能略早或略晚于2000毫秒

另外,需要注意的是,系统事件(例如屏幕休眠、设备进入深度睡眠模式等)也可能会影响到延迟消息的触发时间,因为在这些事件发生时,消息循环可能会被暂停或受到影响

后续修改系统时间并不会影响延迟消息,因为我延迟消息本来就和系统时间没有关系,

SystemClock.uptimeMillis() 是一个用于获取系统启动时间的方法,它返回自系统启动以来经过的毫秒数。它不受系统时间的修改影响。

简单总结一下上面的话:Handler的延迟消息不是确定的,postDelay 2000ms可能时间超过或小于2000ms

因为2点:

  1. 处理消息和执行任务的机制是基于消息循环(Message Loop)和系统调度。延迟消息的触发时间取决于消息队列中的其他消息、正在执行的任务、系统负载等因素。
  2. 系统事件(例如屏幕休眠、设备进入深度睡眠模式等)也可能会影响到延迟消息的触发时间,因为在这些事件发生时,消息循环可能会被暂停或受到影响

与后续修改系统时间无关。


2.线程与Handler与Looper

一般会问一个线程中允许创建多个Handler嘛?允许创建多个Looper嘛?

在一个线程中,你可以创建多个 Handler 对象,并且每个 Handler 对象都需要关联一个 Looper 对象。所以说,一个线程可以创建多个 Handler,但每个 Handler 都需要有一个关联的 Looper

3.一个线程中多个Handler怎么判断用哪一个Handler进行接收

我们继续搬上之前那段经典代码

Message  message = new Message();
message.what = 1;
message.obj = "2";
mHandler.sendMessage(message);
 Handler mHandler = new Handler(new Handler.Callback() {@Overridepublic boolean handleMessage(@NonNull Message msg) {switch (msg.what){case 1:mTextView.setText(""+message.obj);}return true;}});

答案很明显了吧,我们就是通过message的what属性来确定后面再handleMessage里面怎么进行处理

4.Handler如何消耗数据?Message时间怎么判断?
1.如何消耗数据

我们重新回顾一下Handler的流程

Handler通过sendMessage/post/postDelay这几种方法将Message/Runnable对象传到sendMessageAtTime()然后sendMessageAtTime会调用enqueueMessage()将Message对象加入MessageQueue里面,然后Looper进行初始化prepare方法会调用ThreadLocal的set方法然后调用loop进行查找,查找到后调用Handler.dispatchMessage进行消息的处理

其中**Handler.dispatchMessage()**的这个流程的操作就是如何消耗数据

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-slaEyjIH-1685708831935)(../../assets/QQ图片20230601211615.png)]

先判断message.callback为不为null,如果不为null的话,直接handlercallback()

如果为null的话判断mcallback为不为null,这个mcallback为一个全局变量,如果它不为null的话则再判断它的mcallback.handleMessage为不为true,如果为true的话就结束

如果mcallback为null或者mcallback.handleMessage不为true的话则调用**handleMessage()**方法

这就是handler的处理

你有没有发现那个handleMessage()特别眼熟

 Handler mHandler = new Handler(new Handler.Callback() {@Overridepublic boolean handleMessage(@NonNull Message msg) {switch (msg.what){case 1:mTextView.setText(""+message.obj);}return true;}});

Handler.callback()中重写的就是handleMessage()

所以我们遇到的大部分情况就是message.callback==null或者mcallback.handleMessage!=true

所以我们需要重写

handleMessage方法

我们来看看message.callback,mcallback,mcallback.handleMessage分别表示什么

1.1message.callback

message.callbackMessage类中的一个字段,它允许你在发送消息时指定一个Runnable对象作为回调函数。当消息被处理时,如果message.callback不为null,将直接执行该回调函数,而不会经过Handler的处理逻辑。

1.2mcallback

我们可以点击mcallback的源码发现它指向

final Callback mCallback;
1.3mcallback.handleMessage

我们点击handlerMessage()的源码

public interface Callback {/*** @param msg A {@link android.os.Message Message} object* @return True if no further handling is desired*/boolean handleMessage(@NonNull Message msg);
}
2Message时间怎么判断
handler.sendMessageDelayed(message,2000);

表示handler将在系统启动后约2ms之后进行该操作

2.5Handler消息机制与时间排序

chatGPT给出的解释是:

Handler的消息机制和时间排序基于消息队列(MessageQueue)和消息循环(MessageLoop)。

消息队列是用来存储和管理待处理的消息的数据结构,它按照消息的触发时间进行排序。当使用Handler发送消息时,消息会被添加到消息队列中,并按照触发时间的顺序插入到合适的位置。

消息循环是一个无限循环,它从消息队列中取出消息并将其交给对应的Handler进行处理。在每次循环迭代中,消息循环会检查消息队列中是否有消息待处理。如果有消息,则根据消息的触发时间和优先级依次处理消息,直到消息队列为空。

通过消息队列和消息循环的配合,Handler能够按照正确的顺序处理消息。消息的触发时间决定了消息在队列中的位置,而消息循环负责按照队列顺序逐个取出消息进行处理。

这种基于消息队列和消息循环的机制可以保证消息的顺序和准确性。较早触发的消息会先被处理,而较晚触发的消息会在之后的时刻被处理,确保了消息处理的有序性。同时,通过消息队列的排序,可以优先处理优先级较高的消息。

3.ThreadLocal的相关知识

先说一下ThreadLocal的作用,我们在进行

Looper.prepare();

的时候点击源码进去会发现里面进行了ThreadLocal的set方法,

ThreadLocal它的作用在我理解就是在多个线程中虽然调用的是同一个ThreadLocal,但是它们的值不一样,根本原因是因为ThreadLocal内部有一个ThreadLocalMap

我们看看ThreadLocal内部的set方法

3.1ThreadLocal的set方法

public void set(T value) {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);
}

我们会发现ThreadLocalset方法会先获得当前的线程,然后获得ThreadLocalMap,判断map为不为空,如果它不为空,就直接把当前的线程和value值传给map,如果map为null的话则创建map

这里面我们就可以明白为什么每个Thread的ThreadLocalMap不一样了

因为

 Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);

我们会发现它是先获得当前的线程,然后再用获得的线程来创建ThreadLocalMap

创建的话很简单

void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocalMap(this, firstValue);
}

就new一个ThreadLocalMap

我们看看ThreadLocalMap内部

3.1.1ThreadLocalMap

 ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {table = new Entry[INITIAL_CAPACITY];int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);table[i] = new Entry(firstKey, firstValue);size = 1;setThreshold(INITIAL_CAPACITY);}/*** Construct a new map including all Inheritable ThreadLocals* from given parent map. Called only by createInheritedMap.** @param parentMap the map associated with parent thread.*/private ThreadLocalMap(ThreadLocalMap parentMap) {Entry[] parentTable = parentMap.table;int len = parentTable.length;setThreshold(len);table = new Entry[len];for (int j = 0; j < len; j++) {Entry e = parentTable[j];if (e != null) {@SuppressWarnings("unchecked")ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();if (key != null) {Object value = key.childValue(e.value);Entry c = new Entry(key, value);int h = key.threadLocalHashCode & (len - 1);while (table[h] != null)h = nextIndex(h, len);table[h] = c;size++;}}}}

其实别看这么多的代码,我的理解就是ThreadLocalMap就是用一个Entry数组存储,并结合了哈希表的概念

为什么这么说呢,因为它是一个二维数组,其中第一个为索引值,第二个为存储对象。

存储的是什么呢?Entry 的 key 就是线程的本地化对象 ThreadLocal,而 value 则存放了当前线程所操作的变量副本。

其中ThreadLocal最容易被问到的还有就是内存泄漏

我们先了解一下什么是内存泄漏

3.2内存泄漏

在Android中,内存泄漏指的是应用程序在运行过程中,由于不正确的内存管理导致一些对象无法被垃圾回收器正确释放,从而造成内存资源的浪费和持续占用。这些未释放的对象会继续占用内存空间,导致应用程序的内存占用逐渐增加,最终可能导致内存溢出或导致应用程序运行缓慢、卡顿甚至崩溃。

在ThreadLocal中内存泄露的根本原因在于 ThreadLocalMap 的生命周期与当前线程 CurrentThread 的生命周期相同,且 ThreadLocal 使用完没有进行手动删除导致的

所以我们如果Looper进行**prepare()方法后不进行ThreadLocalMap.remove()**方法就会导致内存泄漏

4.Looper

4.1为什么一个线程只能创建一个Looper

我们刚才其实将ThreadLocal的时候讲过了

Looper进行初始化的时候会调用ThreadLocalset方法,set方法在内部会获得当前的线程,并根据当前的线程创建ThreadLocalMap,每个线程都有自己的 ThreadLocalMap,而 ThreadLocalMap 中只能保存一个 Looper 实例。

4.2为什么Looper陷入死循环的时候不会ANR,主线程是阻塞的嘛?

我们回顾一下Looper的流程,当它被prepare之后,调用Looper 的 loop() 方法,它在执行过程中会不断从消息队列中获取消息,并将消息分发给对应的 Handler 进行处理。

Looper的loop是个无限循环的方法,但是不会阻塞主线程,更不会ANR

我们先了解一下什么情况会导致ANR

4.2.1什么情况下会导致ANR

我们一般都知道如果一个界面如果长时间没有反应则是因为它陷入了ANR

但是比较官方的说法是:

在 Android 中,主线程负责处理 UI 相关的操作,包括用户输入、界面更新等。为了保证主线程的响应性,Android 系统对主线程的响应时间有一定的限制,通常为 5 秒钟。如果主线程在这个时间内没有响应,就会被认为发生了 ANR,并弹出 ANR 对话框


但是我们在进行Looperloop方法的时候,它会判断当前的MessageQueue中是否有新消息,如果没有新消息,loop() 方法会进入等待状态,不会占用主线程的执行时间片。只有当有新的消息到达时,loop() 方法才会被唤醒并继续执行。


http://www.ppmy.cn/news/172420.html

相关文章

《操作系统》—— 处理机调度算法

前言&#xff1a; 在之前的文章中&#xff0c;我们已经了解了进程和线程相关的基本概念&#xff0c;今天我们将要了解的是关于处理机调度相关的知识。 目录 &#xff08;一&#xff09;调度的概念 1、调度的基本概念 2、调度的层次 3、三级调度的关系 &#xff08;二&…

《计算机网络——自顶向下方法》精炼——3.7(1)

少而好学,如日出之阳;壮而好学,如日中之光;志而好学,如炳烛之光。——刘向 文章目录 拥塞控制方法ATM ABR拥塞控制 TCP拥塞控制TCP拥塞控制算法的实现慢启动拥塞避免快速恢复总结 拥塞控制方法 在上一篇文章中&#xff0c;我们介绍了在数据传输过程中出现的问题。本节将简要介…

购买ipad学习软件全套平板苹果付费应用app笔记iso绘画画下载

购买ipad学习软件全套平板苹果付费应用app笔记iso绘画画下载&#xff1a;备忘链接 或者在这里留言&#xff0c;我看到就帮你们下载啦。

小米平板2驱动程序

刷机有风险&#xff0c;操作需谨慎&#xff0c;出现任何问题&#xff0c;楼主概不负责 刷入程序 Dism 进入软件选择驱动管理&#xff0c;添加驱动选择已解压的文件进行刷入。 点击获取驱动 提取码:6273

电脑手机ipad平板百度网盘下载文件保存位置

百度网盘是我们使用最多的一个网盘&#xff0c;一些新手小白不知道百度网盘下载的文件在哪&#xff0c;下面就和大家分享电脑手机ipad平板百度网盘下载文件保存位置。 1.电脑百度网盘下载的文件保存位置 我们在登录电脑端的百度网盘客户端后&#xff0c;点击右上角设置按钮&am…

在读书郎平板上安装第三方应用

读书郎平板上的限制诸多&#xff0c;应用商店的东西也是少的可怜。 最近更新了系统&#xff0c;用下载下来的apk安装已经不允许了。 此篇文章分享一下如何在读书郎平板上安装第三方应用。 材料 读书郎平板一台能上网的电脑USB连接线 在 Downloads - ADB Shell 处下载adb。…

Android 系统下载 DownloadManager

一、所需权限 <uses-permission android:name"android.permission.INTERNET" /> <!--如果下载的文件是apk&#xff0c;下载完安装需要该权限--> <uses-permission android:name"android.permission.REQUEST_INSTALL_PACKAGES"/> 二、简…

安卓平板排行榜_shopee虾皮台湾安卓市场, shopee虾皮直播下载

(将此文转发到朋友圈并截图发给小编微信&#xff1a;45677606 获取跨境电商免费学习大礼包。) 我做跨境电商也有六年的时间了&#xff0c;在电商这个行业也有自己的一些经验。经验也许没有其他大卖家丰富&#xff0c;但会将我知道的都进行分享。如果有不懂得亚马逊问题可以我(…