详解Handler

详解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.rhkb.cn/news/21784.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

AI_News周刊:第四期

CV - 计算机视觉 | ML - 机器学习 | RL - 强化学习 | NLP 自然语言处理 News 1.对抗“唤醒人工智能”马斯克招募团队开发 OpenAI 竞争对手 据两位直接了解这项工作的人士和另一位了解情况的人士透露&#xff0c;埃隆马斯克最近几周与人工智能研究人员接洽&#xff0c;商讨成…

tasker配置文件_如何在Android上管理Tasker配置文件和自动执行功能

tasker配置文件 Previously mentioned Tasker is an Android automation powerhouse. We’ll show you how to export and import profiles so you don’t have to create them from scratch. We also have some for you to download and tweak to your heart’s extent. 前面提…

大家都在卷ChatGPT的时候,我已经...

最近因ChatGPT爆火&#xff0c;使得很多人非常的焦虑&#xff0c;下面是我看到的一位好朋友心情&#xff01;不知道大家是否也有同样的心情&#xff01;&#xff5e; 感觉很多人在看到ChatGPT时都有这样的担忧&#xff0c;这样的担忧是不是源自于神经网络如果参数量足够大&…

渗透测试|网络安全常用靶场

搭建教程网络上随便一搜一大堆&#xff0c;这里就不再赘述 vulhub 免费开源漏洞靶场 www.vulhub.org vulnhub 国外的一个靶场&#xff0c;难度中上&#xff08;类似于真实渗透环境&#xff09; https://www.vulnhub.com/ pikachu 综合靶场 https://github.com/zhuifengs…

关于渗透测试

今天看到某安全公司网站上有关渗透测试的内容&#xff0c;感觉不错&#xff0c;转了过来 原文&#xff1a;http://cimersec.w92.mc-test.com/index.php/Profession/view/id/9 渗透测试&#xff0c;是指为了对客户目标网络的安全性进行实际检查&#xff0c;进行带有攻击性行为的…

实战渗透--一次对后台登录系统的简单渗透测试

某网站后台登录界面 发现有验证码框 猜想会不会存在验证码绕过的漏洞 首先随意输入用户名密码&#xff08;用于抓包&#xff09; 打开burp抓包 分析数据包后 找到对应的传参点 即输入的账号密码还有验证码 这里可以看到 账号和密码全都是明文传输 并没有进行加密 所以更改起来还…

网络渗透测试

1.5指纹识别 指纹由于其终身不变性唯一性和方便性 ,几乎已成为生物特征识别的代名词.通常我们说的指纹就是人的手指末端正面皮肤凹凸不平纹线&#xff0c;纹线规律的排列形成不同纹型。 内容 1.6查找真实IP 在渗透测试过程中&#xff0c;目标服务器可能只有一个域名。 1…

渗透安全测试

渗透安全测试 PTES&#xff08;渗透测试执行标准&#xff09;&#xff0c;渗透测试的过程包括交互&#xff0c;信息收集建模&#xff0c;Vul-可行性分析&#xff0c;开发&#xff0c;后期开发等。渗透测试旨在提高系统的安全性&#xff0c;而不是为了破坏&#xff0c;不会影响…

【渗透测试基础】越权攻击讲解

01 什么是越权 越权&#xff0c;是攻击者在获得低权限账号后&#xff0c;利用一些方式绕过权限检查&#xff0c;访问或者操作到原本无权访问的高权限功能。在实际的代码安全审查中&#xff0c;这类漏洞很难通过工具进行自动化检测&#xff0c;因此危害很大。越权有两种类型&am…

渗透测试工具

前言 本篇文章总结了很好用的渗透测试工具&#xff0c;会不断更新&#xff01;&#xff01;&#xff01; 供大家学习使用&#xff01;&#xff01;&#xff01; 正文 01 信息收集 1.1 dirsearch 1.1.1介绍&#xff1a; 类似御剑扫描的一款网站目录扫描器&#xff0c;由pyt…

PentestGPT:一款由ChatGPT驱动的强大渗透测试工具

关于PentestGPT PentestGPT是一款由ChatGPT驱动的强大渗透测试工具,该工具旨在实现渗透测试任务执行过程的自动化。该工具基于ChatGPT实现其功能,允许广大研究人员以交互式的方式使用,并指导渗透测试人员进行渗透测试任务的总体进度调控并执行指定操作。 除此之外,Pentes…

利用ChatGPT进行内网域渗透学习

ChatGPT可以直接模拟在域内环境中的命令执行结果 1、查看共享 2、定位域控

考研政治考题分布、单选多选技巧Keywords、大题点默析

文章目录 一、考题分布(一) 选择33道 (16道单选&#xff0c;17道多选:16117250分)(二) 大题5道 (51050分) 二、选择技巧、选择题规律Keywords选择题规律 三、大题点默析结构 一、考题分布 (一) 选择33道 (16道单选&#xff0c;17道多选:16117250分) 1-4&#xff1a;马原4道单…

chatgpt-4它的未来是什么?该如何应用起来?

在当今快节奏的数字通信世界中&#xff0c;ChatGPT已成为一个强大的在线聊天平台&#xff0c;改变了人们互动和沟通的方式。凭借其先进的AI功能、用户友好的界面和创新技术&#xff0c;ChatGPT已成为个人和企业的热门选择。 然而&#xff0c;ChatGPT的未来有望更加激动人心和具…

万字干货!ChatGPT 从零完全上手实操指南!【二】

2.调教 GPT之奖惩指令 其实这个所谓“奖惩指令&#xff08;有监督学习&#xff09;”的作用和调教原理很好理解&#xff0c;它就像是我们教育孩子一样。 如果你希望孩子达到你理想的行为标准&#xff0c;那么你就需要对他进行教育&#xff0c;如果孩子做得好&#xff0c;我们就…

分享一些程序员接私活、兼职的平台

跟大家分享一下如何判断一个外包项目是否靠谱&#xff0c;有哪些接项目的渠道&#xff0c;以及其他接私活的经验。 判断项目是否靠谱&#xff0c;上来不说需求没有文档直接问你多少需要多少钱&#xff0c;说话不靠谱&#xff0c;可能就是打听价的&#xff0c;这样的项目无需太…

chatgpt赋能python:Python如何成为一名兼职SEO?

Python如何成为一名兼职SEO&#xff1f; 简介 SEO&#xff0c;即搜索引擎优化&#xff0c;是一种很有前途的职业。随着互联网的发展&#xff0c;越来越多的公司意识到网站能为自己带来的价值&#xff0c;从而开始注重SEO。如果你想在这个领域探索机会&#xff0c;那么Python就…

【大虎与二狗】预算减半KPI不变,你该怎么办?

“哎呀&#xff0c;今天又没抢到&#xff0c;名额太少了&#xff0c;下次你们帮我一起哈”。 不需要抬头看&#xff0c;陈虎就知道说话的是楠楠。作为部门里自诩的“文青“&#xff0c;楠楠最爱的就是穷游&#xff0c;而最近每天10点她都会守在电脑前抢购某旅游平台“3999五一…

AI绘画大全 Midjourney13000+gpt最新注册+使用教程+关键词描述词 软件+教程

AI绘画大全 Midjourney13000gpt最新注册使用教程关键词描述词 软件教程 AI绘画大全&#xff1a;Midjourneygpt最新注册和使用教程&#xff0c;Midjourney13000AI绘画关键词描述词等&#xff08;教程软件&#xff09; ai美术馆-第一周 3.Ai绘图变现渠道 4.Ai生成图 2.30AI绘…

遥感云大数据在灾害、水体与湿地领域典型案例实践及GPT模型

近年来遥感技术得到了突飞猛进的发展&#xff0c;航天、航空、临近空间等多遥感平台不断增加&#xff0c;数据的空间、时间、光谱分辨率不断提高&#xff0c;数据量猛增&#xff0c;遥感数据已经越来越具有大数据特征。遥感大数据的出现为相关研究提供了前所未有的机遇&#xf…