Android消息机制

腾讯Android面试:Handler中有Loop死循环,为什么没有阻塞主线程,原理是什么
Android中为什么主线程不会因为Looper.loop()里的死循环卡死?
Android之消息机制问题
每个 Android 开发者必须知道的消息机制问题总结
腾讯Android面试:Handler中有Loop死循环,为什么没有阻塞
Android中为什么主线程不会因为Looper.loop的死循环卡死
子线程 真的不能更新UI ?
linux 高并发网络编程之epoll详解

总结:
A. Android应用程序的消息处理机制由消息循环、消息发送和消息处理三个部分组成的。
B. Android应用程序的主线程在进入消息循环过程前,会在内部创建一个Linux管道(Pipe),这个管道的作用是使得Android应用程序主线程在消息队列为空时可以进入空闲等待状态,并且使得当应用程序的消息队列有消息需要处理时唤醒应用程序的主线程。
C. Android应用程序的主线程进入空闲等待状态的方式实际上就是在管道的读端等待管道中有新的内容可读,具体来说就是是通过Linux系统的Epoll机制中的epoll_wait函数进行的。
D. 当往Android应用程序的消息队列中加入新的消息时,会同时往管道中的写端写入内容,通过这种方式就可以唤醒正在等待消息到来的应用程序主线程。
E. 当应用程序主线程在进入空闲等待前,会认为当前线程处理空闲状态,于是就会调用那些已经注册了的IdleHandler接口,使得应用程序有机会在空闲的时候处理一些事情。

注:epoll是Linux内核为处理大批量文件描述符而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。另一点原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。epoll除了提供select/poll那种IO事件的水平触发(Level Triggered)外,还提供了边缘触发(Edge Triggered),这就使得用户空间程序有可能缓存IO状态,减少epoll_wait/epoll_pwait的调用,提高应用程序效率。

为什么Handler对象能够子线程和主线程都能访问了
对象是存在java虚拟机的内存模型中的堆区域,这个区域是所有线程都能够访问的

为什么这个死循环不会造成ANR异常呢?

因为Android 的是由事件驱动的,looper.loop() 不断地接收事件、处理事件,每一个点击触摸或者说Activity的生命周期都是运行在 Looper.loop() 的控制之下,如果它停止了,应用也就停止了。只能是某一个消息或者说对消息的处理阻塞了 Looper.loop(),而不是 Looper.loop() 阻塞它。

主线程如何自动调用Looper.prepare()。那就是ActivityThread,并且在main方法中我们会看到主线程也是通过Looper方式来维持一个消息循环。那么这个死循环会不会导致应用卡死,即使不会的话,它会慢慢的消耗越来越多的资源吗?

对于线程即是一段可执行的代码,当可执行代码执行完成后,线程生命周期便该终止了,线程退出。而对于主线程,我们是绝不希望会被运行一段时间,自己就退出,那么如何保证能一直存活呢?简单做法就是可执行代码是能一直执行下去的,死循环便能保证不会被退出。

例如,binder线程也是采用死循环的方法,通过循环方式不同与Binder驱动进行读写操作,当然并非简单地死循环,无消息时会休眠。但这里可能又引发了另一个问题,既然是死循环又如何去处理其他事务呢?通过创建新线程的方式。真正会卡死主线程的操作是在回调方法onCreate/onStart/onResume等操作时间过长,会导致掉帧,甚至发生ANR,looper.loop本身不会导致应用卡死。

为什么说子线程不能更新UI?

子线程是不能直接更新UI的。Android实现View更新有两组方法,分别是invalidate和postInvalidate。前者在UI线程中使用,后者在非UI线程即子线程中使用。换句话说,在子线程调用 invalidate 方法会导致线程不安全。熟悉View工作原理的人都知道,invalidate 方法会通知 view 立即重绘,刷新界面。作一个假设,现在用 invalidate 在子线程中刷新界面,同时UI线程也在用 invalidate 刷新界面,这样会不会导致界面的刷新不能同步?这就是invalidate不能在子线程中使用的原因。

loop的循环消耗性能吗?

主线程Looper从消息队列读取消息,当读完所有消息时,主线程阻塞。子线程往消息队列发送消息,并且往管道文件写数据,主线程即被唤醒,从管道文件读取数据,主线程被唤醒只是为了读取消息,当消息读取完毕,再次睡眠。因此loop的循环并不会对CPU性能有过多的消耗。

loop的循环消耗性能吗?

主线程Looper从消息队列读取消息,当读完所有消息时,主线程阻塞。子线程往消息队列发送消息,并且往管道文件写数据,主线程即被唤醒,从管道文件读取数据,主线程被唤醒只是为了读取消息,当消息读取完毕,再次睡眠。因此loop的循环并不会对CPU性能有过多的消耗。

简单的来说:ActivityThread的main方法主要就是做消息循环,一旦退出消息循环,那么你的程序也就可以退出了。

6.0.0.9 Message可以如何创建?哪种效果更好,为什么?runOnUiThread如何实现子线程更新UI?
创建Message对象的几种方式:技术博客大总结

Message msg = new Message();

Message msg = Message.obtain();

Message msg = handler1.obtainMessage();

后两种方法都是从整个Messge池中返回一个新的Message实例,能有效避免重复Message创建对象,因此更鼓励这种方式创建Message

runOnUiThread如何实现子线程更新UI

看看源码,如下所示

如果msg.callback为空的话,会直接调用我们的mCallback.handleMessage(msg),即handler的handlerMessage方法。由于Handler对象是在主线程中创建的,所以handler的handlerMessage方法的执行也会在主线程中。

在runOnUiThread程序首先会判断当前线程是否是UI线程,如果是就直接运行,如果不是则post,这时其实质还是使用的Handler机制来处理线程与UI通讯。

public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}

@Override
public final void runOnUiThread(Runnable action) {
    if (Thread.currentThread() != mUiThread) {
        mHandler.post(action);
    } else {
        action.run();
    }
}

MessageQueue作用是干什么的
MessageQueue,主要包含2个操作:插入和读取。
读取操作会伴随着删除操作,插入和读取对应的方法分别为enqueueMessage和next,其中enqueueMessage的作用是往消息队列中插入一条消息,而next的作用是从消息队列中取出一条消息并将其从消息队列中移除。

虽然MessageQueue叫消息队列,但是它的内部实现并不是用的队列。

实际上它是通过一个单链表的数据结构来维护消息列表,单链表在插入和删除上比较有优势。

MessageQueue的定义是什么

通过源码我们可以知道,MessageQueue维护了一个消息列表。Messgae并不是直接添加到MessageQueue中,而是通过和Looper相关联的Handler来添加的。在当前线程中可以通过调用Looper.myQueue()方法来获取当前线程的MessageQueue。

next()方法源码分析
在 MessageQueue 中消息的读取其实是通过内部的 next() 方法进行的,next() 方法是一个无限循环的方法。博客
如果消息队列中没有消息,则该方法会一直阻塞,
当有新消息来的时候 next() 方法会返回这条消息并将其从单链表中删除。

子线程更新UI总结概括

handler.post(Runnable r)、 view.post(Runnable r)、activity.runOnUIThread(Runnable r)等方法。跟进去看源码,发现其实它们的实现原理都还是一样,最终都是通过Handler发送消息来实现的。

6.0.1.3 使用Hanlder的postDealy()后消息队列会发生什么变化?
post delay的Message并不是先等待一定时间再放入到MessageQueue中,而是直接进入并阻塞当前线程,然后将其delay的时间和队头的进行比较,按照触发时间进行排序,如果触发时间更近则放入队头,保证队头的时间最小、队尾的时间最大。此时,如果队头的Message正是被delay的,则将当前线程堵塞一段时间,直到等待足够时间再唤醒执行该Message,否则唤醒后直接执行。

为什么一个线程只有一个Looper?

需使用Looper的prepare方法,Looper.prepare()。可以看下源代码,Android中一个线程最多仅仅能有一个Looper,若在已有Looper的线程中调用Looper.prepare()会抛出RuntimeException(“Only one Looper may be created per thread”)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值