android handler面试,Android面试(五):异步消息机制之Handler面试你所需知道的一切...

1. 什么是Handler,为什么要有Handler?

Android中主线程也叫UI线程,主线程主要是用来创建、更新UI的,而其他耗时操作,比如网络访问,文件处理、多媒体处理等都需要在子线程中操作,之所以在子线程中操作是为了保证UI的流畅程度,手机显示的刷新频率是60Hz,也就是一秒钟刷新60次,每16.67毫秒刷新一次,为了不丢失帧,那么主线程处理代码最好不要超过16毫秒。当子线程处理完数据后,为了防止UI处理逻辑的混乱(1.锁机制会让UI处理逻辑变得混乱;2.锁机制会降低UI访问的效率,因为锁机制会阻塞某些线程的执行),Android只允许主线程修改UI,那么这时候就需要Handler来充当子线程和主线程之间的桥梁了。

2. Handler的使用方法:

post(runnable)

sendMessage(message)

其实post(runnable)和sendMessage(message)最终底层都是调用了sendMessageAtTime方法。

3. Handler消息机制的原理:

在主线程中Android默认已经调用了Looper.preperMainLooper方法,调用该方法的目的是在Looper中创建MessageQueue成员变量并把Looper对象绑定到当前线程中(ThreadLocal)。当调用Handler的sendMessage方法的时候,就将Message对象添加到了Looper创建的MessageQueue队列中,同时给Message指定了target对象,其实这个target对象就是Handler对象。主线程默认执行了Looper.looper()方法,该方法从Looper的成员变量MessageQueue队列中调用Message的target对象的dispatchMessage方法(也就是msg.target.dispatchMessage方法)取出Message,然后在dispatchMessage方法中调用Message的target对象的handleMessage()方法(也就是msg.target.handleMessag),这样就完成了整个消息机制。

我们从源码的角度来分析上述原理,首先我们来看Handler的构造方法:

public Handler(Callback callback, boolean async) {

if (FIND_POTENTIAL_LEAKS) {

final Class extends Handler> klass = getClass();

if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&

(klass.getModifiers() & Modifier.STATIC) == 0) {

Log.w(TAG, "The following Handler class should be static or leaks might occur: " +

klass.getCanonicalName());

}

}

mLooper = Looper.myLooper();

if (mLooper == null) {

throw new RuntimeException(

"Can't create handler inside thread that has not called Looper.prepare()");

}

mQueue = mLooper.mQueue;

mCallback = callback;

mAsynchronous = async;

}

我们发现Looper.myLooper()内部是通过sThreadLocal.get()获得Looper的,(关于ThreadLocal:不同的线程访问同一个ThreadLocal,不管是get方法还是set方法对其所做的操作,仅限于各自线程内部。这就是为什么用ThreadLocal来保存Looper,因为这样才能使每一个线程有单独唯一的Looper。)我们可能会想,这是通过get方法获得Looper,那么何时set的呢?

当我们观察Looper这个类,发现有一个方法prepareMainLooper:

public static void prepareMainLooper() {

prepare(false);

synchronized (Looper.class) {

if (sMainLooper != null) {

throw new IllegalStateException("The main Looper has already been prepared.");

}

sMainLooper = myLooper();

}

}

在该方法的prepare中:

private static void prepare(boolean quitAllowed) {

if (sThreadLocal.get() != null) {

throw new RuntimeException("Only one Looper may be created per thread");

}

sThreadLocal.set(new Looper(quitAllowed));

}

哦,原来set方法是在这里调用的哈,接下来我们还剩下一个疑问,那就是prepareMainLooper是在哪里调用的呢?其实Android主线程对应一个类ActivityThread,而每个Android应用程序都是从该类的main函数开始的:

public static void main(String[] args) {

...

Looper.prepareMainLooper();

ActivityThread thread = new ActivityThread();

thread.attach(false);

if (sMainThreadHandler == null) {

sMainThreadHandler = thread.getHandler();

}

if (false) {

Looper.myLooper().setMessageLogging(new

LogPrinter(Log.DEBUG, "ActivityThread"));

}

// End of event ActivityThreadMain.

Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);

Looper.loop();

throw new RuntimeException("Main thread loop unexpectedly exited");

}

我们可以看到,Looper.prepareMainLooper就是在这里调用的,首先程序是从这个main开始的,依次调用了prepareMainLooper ——> prepare ——> sThreadLocal.set,是不是有种茅塞顿开的感觉呢?我们继续看这个main函数,内部调用了Looper.loop,这是Handler机制很重要的一个方法,这也是为什么我们经常说Android主线程默认给我们调用了Looper.prepare和Looper.looper的原因。

接下来我们再来看我们手动调用了handler.sendMessage或者handler.postRunnable方法,默认底层都是调用handler.sendMessageAtTime,该方法内部调用了enqueueMessage:

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {

msg.target = this;

if (mAsynchronous) {

msg.setAsynchronous(true);

}

return queue.enqueueMessage(msg, uptimeMillis);

}

我们可以看到这里给msg.target指定了一个this对象,其实这个this就是Handler对象(因为这是在Handler类中啊,又不是内部类其它的),我们继续看到queue.enqueueMessage方法:

boolean enqueueMessage(Message msg, long when) {

...

boolean needWake;

if (p == null || when == 0 || when < p.when) {

//插入消息到链表的头部:如果当前MessageQueue消息队列为空,或者插入的消息触发时间为0,

//亦或是插入消息的触发时间小于现有头结点的触发时间

// 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;

}

通过调用此方法将消息发送到MessageQueue消息队列中,(这里我一直存在一个问题,这里明明做了触发时间相关的排序,并不符合队列的先进先出的特点,可为什么一直叫做消息队列,就连用的类名翻译过来也是如此,而不是链表呢?还是说这是广义上的队列?如果有知道的大牛,可以跟我说说!!)

刚刚说过Android主线程,也就是ActivityThread的main函数会调用Looper.loop方法:

public static void loop() {

final Looper me = myLooper();

...

final MessageQueue queue = me.mQueue;

...

for (;;) {

Message msg = queue.next(); // might block

if (msg == null) {

// No message indicates that the message queue is quitting.

return;

}

final long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;

...

final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();

final long end;

try {

msg.target.dispatchMessage(msg);

end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();

} finally {

if (traceTag != 0) {

Trace.traceEnd(traceTag);

}

}

...

msg.recycleUnchecked();

}

}

loop方法中调用了queue.next()方法:next()方法是一个无线循环的方法,如果消息队列中没有消息,那么next()方法会一直阻塞,当有新消息到来时,next()方法会将这条消息返回同时也将这条消息从链表中删除。我们主要再来看msg.target.dispatchMessage方法,从上面的分析可以知道msg.target其实就是Handler对象,我们找到dispatchMessage方法:

public void dispatchMessage(Message msg) {

if (msg.callback != null) {

handleCallback(msg);

} else {

if (mCallback != null) {

if (mCallback.handleMessage(msg)) {

return;

}

}

handleMessage(msg);

}

}

其中调用了handleMessage,这也就是我们创建Handler类或其子类,所需要重写的handleMessage方法。至此,整个Handler消息机制就走通了,面试的时候,我们只需要说上面的原理,看源码主要是为了更深入的了解,而不是简单的记忆、背诵,要在理解的基础上去记。

4. Handler引起的内存泄漏以及解决办法

原因:

非静态内部类持有外部类的强引用,导致外部Activity无法释放。

解决办法:

1.handler内部持有外部activity的弱引用

2.把handler改为静态内部类

3.mHandler.removeCallbacksAndMessage(尤其针对延时消息)

5. Handler相关的问题:

(1) Looper死循环为什么不会导致应用卡死?

对于线程即是一段可执行的代码,当可执行代码执行完成后,线程生命周期便该终止了,线程退出。而对于主线程,我们是绝不希望会被运行一段时间,自己就退出,那么如何保证能一直存活呢?简单做法就是可执行代码是能一直执行下去的,死循环便能保证不会被退出,例如,binder 线程也是采用死循环的方法,通过循环方式不同与 Binder 驱动进行读写操作,当然并非简单地死循环,无消息时会休眠。但这里可能又引发了另一个问题,既然是死循环又如何去处理其他事务呢?通过创建新线程的方式。真正会卡死主线程的操作是在回调方法 onCreate/onStart/onResume 等操作时间过长,会导致掉帧,甚至发生 ANR,looper.loop 本身不会导致应用卡死。

(2) 主线程的死循环一直运行是不是特别消耗 CPU 资源呢?

其实不然,这里就涉及到 Linux pipe/epoll机制,简单说就是在主线程的 MessageQueue 没有消息时,便阻塞在 loop 的 queue.next() 中的 nativePollOnce() 方法里,此时主线程会释放 CPU 资源进入休眠状态,直到下个消息到达或者有事务发生,通过往 pipe 管道写端写入数据来唤醒主线程工作。这里采用的 epoll 机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。 所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。

(3) 子线程中Toast、showDialog问题

Toast 本质是通过 window 显示和绘制的(操作的是 window),而子线程不能更新UI 是因为 ViewRootImpl 的 checkThread方法 在 Activity 维护 View树 的行为。

Dialog 亦是如此。

喜欢本篇博客的简友们,就请来一波点赞,您的每一次关注,将成为我前进的动力,谢谢!

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值