详解Handler

这篇具有很好参考价值的文章主要介绍了详解Handler。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

详解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流程图

详解Handler

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

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

2.1Message中的属性

详解Handler

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

obj Object 可以用来携带任意类型的数据。它通常用于传递消息中需要携带的额外数据。你可以将任何对象赋值给 obj 属性,并在消息处理时获取和使用这些数据。
what int 这是一个整型数值,用于标识消息的类型或目的
replyTo Messenger 这是一个 Messenger 对象,用于指定接收回复消息的目标
arg1 int 它们可以用来携带与消息相关的整型数据。
arg2 int 它们可以用来携带与消息相关的整型数据。
sendingUid int 这是一个整型数值,表示发送该消息的应用程序的用户标识符
obtain Message Message.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的插入

详解Handler

我们再进来看看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()**的这个流程的操作就是如何消耗数据

详解Handler

先判断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() 方法才会被唤醒并继续执行。文章来源地址https://www.toymoban.com/news/detail-472961.html

到了这里,关于详解Handler的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 面试谎报了职级,本来是6,谎报成7,已经到HR这一步了,怎么了?

    面试时谎报职级,公司能查出来吗? 一位网友说,自己在业务面时谎报了职级,把6报成7,现在已经到hr这一步了,该怎么办?是继续编吗? 有人不明白,为什么要谎报职级?有什么好处? 网友回答,可以提高面试通过率,定级谈薪的时候可以要更多,晋升天花板也更高。

    2024年02月05日
    浏览(43)
  • ,详解Handler dispatch failed; nested exception is java.lang.NoSuchMethodError异常发生的原因以及排查问题方式和解决办法

    在系统测试的过程中,测试说有个功能报错,可是我们在本地测试都没有问题,报错如下: 看到异常后我们来解读一下这个异常: 日志头部 : 10:35:39.193 [http-nio-18070-exec-70] ERROR c.a.f.w.e.GlobalExceptionHandler :这表示在上午10点35分39秒,应用程序中的一个全局异常处理器( GlobalE

    2024年02月03日
    浏览(60)
  • Handler机制(一)——Handler运行流程分析

    Handler机制是Android的异步消息处理机制,用于在线程间传递消息,主要涉及到四部分:Handler、Looper、Message和MessageQueue。其中Handler是消息的发送者和处理者;Message是消息主体;MessageQueue是消息队列,Handler发送的消息都会放入MessageQueue中等待处理;Looper是MessageQueue的管理者,里

    2024年02月13日
    浏览(39)
  • Android Handler被弃用,那么以后怎么使用Handler,或者类似的功能

    Android API30左右,Android应用在使用传统写法使用Handler类的时候会显示删除线,并提示相关的方法已经被弃用,不建议使用。 Android studio中的显示和建议: 看下官方API关于此处的解释:  简要说就是如果在实例化Handler的时候不提供Looper, 可能导致操作丢失(Handler 没有预估到新

    2023年04月21日
    浏览(40)
  • 我所知道的Handler

    简单讲,handler就是两个功能 插入消息,enqueuemessage,msg,when 从消息队列中遍历所有消息,比对msg.when和当前的when,找到合适的位置插入 处理消息,looper.loop会从messagequeue中调用next。取消息,如果消息还没到时间该执行,就会比对时间,下次轮询就通过binder写入,native函数休

    2024年02月05日
    浏览(29)
  • Handler 同步屏障&异步消息

    简单来说,同步屏障就是一套为了让特殊消息更快执行的一个机制。 这里我们假设一个场景:我们向主线程发送了一个UI绘制操作Message,而此时消息队列中的消息非常多,那么这个Message的处理可能会得到延迟,绘制不及时造成界面卡顿。同步屏障机制的作用,是让这个绘制

    2024年02月09日
    浏览(32)
  • 结合源码拆解Handler机制

    作者:Pingred 当初在讲App启动流程的时候,它的整个流程涉及到的类可以汇总成下面这张图: 那时着重讲了AMS、PMS、Binder这些知识点,有一个是没有对它进行详细讲解的,那就是常见的Handler,它不仅在这个流程里作用在ApplicationThread和ActivityThread进行通信,它在整个安卓体系

    2024年02月11日
    浏览(46)
  • Android Handler机制

    Android Handler 机制是一种用于在 Android 应用中处理多线程和异步操作的机制。 为了解决UI线程(主线程)和后台线程之间的通信和异步处理的问题。在Android应用中,特别是在UI开发中,有一些重要的原则需要遵循: 主线程负责UI更新:  Android UI框架是单线程模型的,也就是说

    2024年02月19日
    浏览(39)
  • Android:Handler

    参考来源 参考来源 参考来源 参考来源 Binder/Socket用于进程间通信,而Handler消息机制用于同进程的线程间通信 handler机制是android系统运行的基础,它采用生产者,消费者模式进行设计。其中生产者和消费者都是handler,多个handler会生产消息message投递到线程共享的messagequeue有序

    2024年02月02日
    浏览(33)
  • 安卓架构核心Handler原理解析

    当使用 handler 去发送一个消息的时候,查看调用栈,发现最终会调用到 MessageQueue.equeueMessage ,那么说既然是个 queue 肯定就有增和删, equeueMessage 就对应它的增,再看看是谁在调用 MessageQueue 。 不妨猜想一下,既然平常写这个 handler 都在主线程,而且主线程的入口就在 main ,

    2023年04月09日
    浏览(35)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包