详解Handler

news2025/1/10 2:16:50

详解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() {
        @Override
        public 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{
    @Override
    public 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 {
    // 服务端Messenger
    private Messenger mServerMessenger;
    // 服务端连接状态
    private boolean mIsBound = false;
    // 绑定服务端
    private Button message_0;
    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public 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();
            }
        }
        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    };
    @Override
    protected 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() {
                @Override
                public void onClick(View v) {
                    mIsBound = true;
                    Intent intent = new Intent(MainActivity.this, MyService.class);
                    bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
                }
            });
        }
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 解绑服务端
        unbindService(mConnection);
    }
    private Messenger mGetReplyMessenger = new Messenger(new MessengerHandle());
    private static class MessengerHandle extends Handler{
        @Override
        public 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() {
        @Override
        public void run() {
            // 在主线程中执行的操作
        }
    });
    

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

    // 在主线程中创建一个 Handler 对象
    Handler handler = new Handler(Looper.getMainLooper()) {
        @Override
        public 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.next
            prev.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.next
            prev.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() {
        @Override
        public 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() {
        @Override
        public 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);
    else
        createMap(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.coloradmin.cn/o/602560.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

centos6离线安装docker

参考 RedHat 6.8 离线安装Docker &#xff08;rpm包安装&#xff09; - 神奇二进制 - 博客园 (cnblogs.com) 可参考&#xff0c;但本次安装未参考 CentOS6 完全离线安装Docker - 简书 (jianshu.com) 走了一遍&#xff0c;大雾 (1条消息) 离线安装Docker_洒家肉山大魔王的博客…

萌啦科技参加ICBE跨境电商博览会完美落幕,期待再相会!

“ 萌啦科技联合DNY123、喜运达物流共同亮相2023 ICBE跨境电商博览会&#xff0c;更全面地服务东南亚电商卖家&#xff0c;把握新兴市场电商发展商机&#xff01;” 跨境电商“万人”博览会 5月15日-5月17日&#xff0c;ICBE国际跨境电商交易博览会在广州琶洲保利世贸博览馆隆重…

《商用密码应用与安全性评估》第四章密码应用安全性评估实施要点4.3密码测评要求与测评方法

总体要求测评方法 1.密码算法核查 测评人员应当首先了解信息系统使用的算法名称、用途、位置、执行算法的设备及其实现方式&#xff08;软件、硬件或固件等&#xff09;。针对信息系统使用的每个密码算法&#xff0c;测评人员应当核查密码算法是否以国家标准或行业标准形式发布…

数据结构与算法-二分查找

1.1 什么是算法&#xff1f; 定义 在数学和计算机科学领域&#xff0c;算法是一系列有限的严谨指令&#xff0c;通常用于解决一类特定问题或执行计算 In mathematics and computer science, an algorithm (/ˈlɡərɪəm/) is a finite sequence of rigorous instructions, …

IO读写的基础原理

read系统调用write系统调用read系统调用&#xff0c;并不是直接从物理设备把数据读取到内存中&#xff0c;write系统调用&#xff0c;也不是直接把数据写入到物理设备。调用操作系统的read&#xff0c;是把数据从内核缓冲区复制到进程缓冲区&#xff1b;而write系统调用&#x…

健康医疗类APP开发 满足民众在线医疗需求

生活水平和社会大环境的变化让人们对于医疗服务的要求也随之提高&#xff0c;传统的到医院就诊已经无法更好的满足现代人多元化的医疗服务需求了。于是很多医院诊所等都考虑通过互联网技术来实现诊疗和科普健康知识的目的&#xff0c;为用户提供更加便捷化多元化的健康诊疗服务…

Python魔法属性和方法

1.魔法属性 __doc__ 获取类或方法的描述信息 class Foo:""" 类对象__doc__的属性值"""def func(self):""" 类方法的__doc__属性值 """passfoo Foo()print("类对象的__doc__&#xff1a;", Foo.__do…

电力需求侧管理是什么及意义

安科瑞虞佳豪 电力需求侧管理是指综合采取合理可行的技术、经济和管理措施&#xff0c;在用电环节实施需求响应、节约用电、电能替代、绿色用电、智能用电、有序用电&#xff0c;推动电力系统安全降碳、提效降耗。 我国分别于2010年和2017年发布了两版电力需求侧管理办法。国…

元”启长三角 共享新未来!长三角数字干线元宇宙创新发展论坛暨第一届长三角元宇宙日在长三角绿洲智谷·赵巷成功举办

5月30日下午&#xff0c;由工信部网络安全产业发展中心&#xff08;工信部信息中心&#xff09;、长三角投资&#xff08;上海&#xff09;有限公司、青浦区经济委员会、青浦区科学技术委员会、青浦区科学技术协会指导&#xff0c;北京大数据协会元宇宙专委会主办&#xff0c;长…

ISO21434 项目网络安全管理(三)

目录 一、概述 二、目标 三、输入 3.1 先决条件 3.2 进一步支持信息 四、要求和建议 4.1 网络安全责任 4.2 网络安全规划 4.3 裁剪 4.4 重用 4.5 非上下文组件 4.6 现成组件 4.7 网络安全案例&#xff08;Cybersecurity case&#xff09; 4.8 网络安全评估&#…

网店系统如何建设?如何搭建网店?

互联网的不断发展&#xff0c;越来越多的商家开始意识到建设自己的网店是非常必要和重要的。通过搭建网店系统&#xff0c;商家无需承担大量的租赁、装修等成本&#xff0c;同时可以将商品推广到更广阔的市场&#xff0c;提高销售额。那么&#xff0c;网店系统如何建设呢&#…

[QCA6174]QCA6174 DFS认证4.6.2.3 Channel Shutdown出现跳转之后在原始信道上有弱信号问题分析及解决方案

WIFI DFS测试要求 Master设备需要测试的项目 4.6.2.1 Channel Availability Check ---信道可用性检查 定义其作为雷达脉冲检测机制,当雷达脉冲出现时所占用的信道需要能被设备检测到已经被占用。当相关信道未被占用时,这些信道被称为Avaliable Channel可用信道 4.6.2.2 In…

【TA 100】Flow Map实现水体流动效果

最近刚好学到Shader Graph水体流动&#xff0c;看下其他实现方式记录下 1 什么是flow map 1 什么是Flow map? flowmap的实质:一张记录了2D向量信息的纹理Flow map上的颜色(通常为RG通道) 记录该处向量场的方向&#xff0c;让模型上某一点表现出定量流动的特征。通过在shader中…

Python接口自动化—接口测试用例和接口测试报告模板

简介 当今社会在测试领域&#xff0c;接口测试已经越来越多的被提及&#xff0c;被重视&#xff0c;而且现在好多招聘信息要对接口测试提出要求。区别于传统意义上的系统级别测试&#xff0c;很多测试人员在接触到接口测试的时候&#xff0c;也许对测试执行还可以比较顺利的上…

vue高频面试题(一)

大厂面试题分享 面试题库 前后端面试题库 &#xff08;面试必备&#xff09; 推荐&#xff1a;★★★★★ 地址&#xff1a;前端面试题库 web前端面试题库 VS java后端面试题库大全 VUE Vue2和3对比 脚手架创建项目 之前有个国企&#xff0c;问到了怎么用脚手架创建vue项…

真空热压烧结炉JZM-1200技术参数一览表

真空热压烧结炉是将真空、气氛、热压成型、高温烧结结合在一起设备&#xff0c;适用于粉末冶金、功能陶瓷等新材料的高温热成型。如应用于透明陶瓷、工业陶瓷等金属以及由难容金属组成的合金材料的真空烧结以及陶瓷材料碳化硅和氮化硅的高温烧结&#xff0c;也可用于粉末和压坯…

2. 量化多因子数据清洗——去极值、标准化、正交化、中性化

一、去极值 1. MAD MAD&#xff08;mean absolute deviation&#xff09;又称为绝对值差中位数法&#xff0c;是一种先需计算所有因子与平均值之间的距离总和来检测离群值的方法. def extreme_MAD(rawdata, n): median rawdata.quantile(0.5) # 找出中位数 new_median (abs(…

Mybatais-plus超详细教程

文章目录 前言什么是Mybatis-plus特性引入依赖配置日志Service CRUD 接口SaveSaveOrUpdateRemoveUpdateGetListPageCount Chainqueryupdate Mapper CRUD 接口InsertDeleteUpdateSelect 赠送 前言 在学习Mybatis-plus之前&#xff0c;这里默认大家都已经对mybatis使用有了一定的…

召回评价指标NDCG、MAP

【MAP】 1、AP A P ∑ i 1 n r e l ( i ) p i AP \sum_{i1}^{n}\frac{rel(i)}{p_i} APi1∑n​pi​rel(i)​ 其中 n 表示候选序列长度&#xff0c; p i p_i pi​表示第 i 个 item 的位置 本质是对每个位置item的分数加一个基于位置的筛选.简单粗暴&#xff0c;直接除以位置…

NeRF与三维重建专栏(三)nerf_pl源码部分解读与colmap、cuda算子使用

前言 上一章中我们介绍了NeRF原理、传统体渲染方法以及两者之间的联系&#xff0c;本章中我们将讲解colmap的安装以及使用&#xff0c;部分nerf_pl源码&#xff0c;同时在开发过程中&#xff0c;由于部分操作python/torch不支持&#xff0c;我们需要自己造轮子&#xff0c;且在…