详解Handler


本来按照《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() 方法才会被唤醒并继续执行。

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6