Handler面试那些事

1、能讲讲Android的handler机制吗?

消息处理机制本质:<strong>一个线程开启循环模式持续监听并依次处理其它线程发送给它的消息.</strong>
简单的说:Android应用程序是通过消息来驱动的,系统为每个应用程序维护一个消息队列(MessageQueue),应用程序的主线程不断地(Looper)从这个消息队列中获取消息(Message),然后对这些消息进行处理(Handler),这样就实现了通过消息来驱动应用程序的执行.

2、Android消息处理机制的工作原理

3、Handler、Looper、MessageQueue、Message之间的关系.

Handler:

消息的发送和处理者,一般使用sendMessage()和handleMessage()方法来发送和处理消息。

Message:

线程之间传递的消息,它可以在内部携带少量的信息,用于在不同线程之间交换数据。

MessageQueue:

用于存放所有的Handler发送的消息的队列(单链表),这些消息会一直存在于消息队列中等待被处理,消息的处理遵循先进先出原则。
每个线程中只有一个MessageQueue对象。

Looper:

循环监听MessageQueue中是否存在消息,如果存在就会将消息取出并传递给Handler处理。
每个线程中只有一个Looper对象。

4、ThreadLocal是什么?Android如何保证一个线程中最多只有一个Looper,一个MessageQueue?

ThreadLocal可以在不同的线程之中互不干扰的存储并提供数据。
工作原理:每个Thread维护一个ThreadLocalMap映射表,这个映射表的key是ThreadLocal实例本身,value是真正要存储的Object。每次获取或者设置value都是对该ThreadLocalMap映射表进行的操作,是与其他线程分开的。

在Handler的实现机制中,默认通过Looper类的prepare()方法来创建Looper对象并将其存储在ThreadLocal中(实际存储在了当前Thread所维护的ThreadLocalMap中,key为sThreadLocal,值为新建的Looper对象),这就保证了当前线程中有且仅有一个Looper对象。而Looper对象的创建同时也伴随着MessageQueeu对象的创建,自然而然也确定了其唯一性。具体代码如下:

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

private static void prepare(boolean quitAllowed) {
    //如果线程的TLS已有数据,则会抛出异常,一个线程只能有一个Looper,prepare不能重复调用。
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    //往线程的TLS插入数据,简单理解相当于map.put(sThreadLocal,new Looper(quitAllowed));
    sThreadLocal.set(new Looper(quitAllowed));
}

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

5、怎样实现一个带有消息循环(Looper)的线程?

Android系统的UI线程就是一种带有消息循环(Looper)机制的线程,这种线程可以绑定Handler对象,并通过Handler的sendMessage()函数向线程发送消息。

private Handler mHandler;
private Looper mLooper;

private void createLooperThread(){
    new Thread(new Runnable() {
        @Override
        public void run() {
            // Log.e("MainActivity",Thread.currentThread().getName());
            Looper.prepare(); //创建Looper和MessageQueue对象
            mLooper = Looper.myLooper(); // 获取当前线程下的Looper对象
            createHandler();
            Looper.loop(); // 开启Looper循环 由于loop()里面是个死循环,有消息就处理,没消息就挂起休眠,因此此行代码之后的代码是无法运行的。只有调用mLooper.quit()方法后,loop才会中止,其后的代码才能得以运行。
        }
    }).start();
}

// 创建属于 mLooper对象所在线程 的Handler对象
private void createHandler(){
    mHandler = new Handler(mLooper){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            // Log.e("MainActivity",Thread.currentThread().getName());     
        };
    }
}

6、谈谈对HandlerThread的理解

HandlerThread本质上就是一个普通Thread,只不过在内部建立了Looper循环,它的实现很简单,就是在run方法中通过Loop.prepare()来创建消息队列,并通过Loop.loop()来开启消息循环,这样在实际的使用中就允许在HandlerThread中创建Handler。
HandlerThread比较适用于单线程+异步队列的场景,比如一个长时间运行且没有 UI 交互的任务,就像在将用户数据上传到服务器前进行的数据压缩的操作就适合用HandlerThread。
另:IntentService内部就是通过HandlerThread来实现的。
HandlerThread的使用方法如下:

private HandlerThread mHandlerThread;
private Handler mHandler;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    // 创建一个线程
    mHandlerThread = new HandlerThread("handlerthread");
    // 开启一个线程
    mHandlerThread.start();
    mHandler = new Handler(mHandlerThread.getLooper()){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            // 这个方法运行在mHandlerThread线程中,可执行耗时操作
            Log.d("handler","消息:" + msg.what + " 线程:" + Thread.currentThread().getName());
        }
    };

    //在主线程给handler发送消息
    mHandler.sendEmptyMessage(1);

    new Thread(new Runnable() {
        @Override
        public void run() {
            //在子线程给handler发送数据
            mHandler.sendEmptyMessage( 2 ) ;
        }
    }).start() ;
}

@Override
protected void onDestroy() {
    super.onDestroy();
    mHandlerThread.quit();
}

7、发送消息有哪些方法?

Handler类 - sendxxx()方法:
public final boolean sendMessage(Message msg) {
    return sendMessageDelayed(msg, 0);
}

public final boolean sendEmptyMessage(int what) {
    return sendEmptyMessageDelayed(what, 0);
}

public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
    Message msg = Message.obtain();
    msg.what = what;
    return sendMessageDelayed(msg, delayMillis);
}

public final boolean sendMessageDelayed(Message msg, long delayMillis) {
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis) {
    Message msg = Message.obtain();
    msg.what = what;
    return sendMessageAtTime(msg, uptimeMillis);
}

public boolean sendMessageAtTime(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);
}

public final boolean sendMessageAtFrontOfQueue(Message msg) {
    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, 0);
}

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}
Handler类 - postxxx()方法:
public final boolean post(Runnable r) {
   return  sendMessageDelayed(getPostMessage(r), 0);
}

public final boolean postAtTime(Runnable r, long uptimeMillis) {
    return sendMessageAtTime(getPostMessage(r), uptimeMillis);
}

public final boolean postAtTime(Runnable r, Object token, long uptimeMillis) {
    return sendMessageAtTime(getPostMessage(r, token), uptimeMillis);
}

public final boolean postDelayed(Runnable r, long delayMillis) {
    return sendMessageDelayed(getPostMessage(r), delayMillis);
}

public final boolean postAtFrontOfQueue(Runnable r) {
    return sendMessageAtFrontOfQueue(getPostMessage(r));
}

private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
}

private static Message getPostMessage(Runnable r, Object token) {
    Message m = Message.obtain();
    m.obj = token;
    m.callback = r;
    return m;
}
Activity类 - runOnUiThread()方法:
public final void runOnUiThread(Runnable action) {
    if (Thread.currentThread() != mUiThread) {
        mHandler.post(action);
    } else {
        action.run();
    }
}
View类 - postxxx()方法:
public boolean post(Runnable action) {
    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null) {
        return attachInfo.mHandler.post(action);
    }

    // Postpone the runnable until we know on which thread it needs to run.
    // Assume that the runnable will be successfully placed after attach.
    getRunQueue().post(action);
    return true;
}

public boolean postDelayed(Runnable action, long delayMillis) {
    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null) {
        return attachInfo.mHandler.postDelayed(action, delayMillis);
    }

    // Postpone the runnable until we know on which thread it needs to run.
    // Assume that the runnable will be successfully placed after attach.
    getRunQueue().postDelayed(action, delayMillis);
    return true;
}

8、插入消息的流程?

分析上述发送消息的方法,不难发现消息的发送最终都会到enqueueMessage()方法:

// 向消息队列中插入消息
boolean enqueueMessage(Message msg, long when) {
     // 判断消息接收者handler是否为null
    if (msg.target == null) {
        throw new IllegalArgumentException("Message must have a target.");
    }
    if (msg.isInUse()) { // 判断msg是否正在被使用
        throw new IllegalStateException(msg + " This message is already in use.");
    }

    synchronized (this) {
        if (mQuitting) { // MessageQueue处于quit状态
            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为使用状态
        msg.when = when;
        Message p = mMessages;
        boolean needWake;
        if (p == null || when == 0 || when < p.when) { // 若:消息队列为空 or 欲在消息头插入消息 or 欲插入的消息先于消息头的消息执行,则:插入消息
            // 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) { // 若已轮询到消息队列尾 or 欲插入的消息先于轮询到的消息执行 则:跳出循环 -> 插入消息 
                    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;
}

9、取出消息->处理消息的流程?

// 消息队列的循环遍历
public static void loop() {
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    final MessageQueue queue = me.mQueue;

    // Make sure the identity of this thread is that of the local process,
    // and keep track of what that identity token actually is.
    Binder.clearCallingIdentity();
    final long ident = Binder.clearCallingIdentity();

    for (;;) {
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }

        // This must be in a local variable, in case a UI event sets the logger
        final Printer logging = me.mLogging;
        if (logging != null) {
            logging.println(">>>>> Dispatching to " + msg.target + " " +
                    msg.callback + ": " + msg.what);
        }

        final long traceTag = me.mTraceTag;
        if (traceTag != 0) {
            Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
        }
        try {
            msg.target.dispatchMessage(msg); // 关键语句 调用handler的dispatchMessage()方法
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }

        if (logging != null) {
            logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
        }

        // Make sure that during the course of dispatching the
        // identity of the thread wasn't corrupted.
        final long newIdent = Binder.clearCallingIdentity();
        if (ident != newIdent) {
            Log.wtf(TAG, "Thread identity changed from 0x"
                    + Long.toHexString(ident) + " to 0x"
                    + Long.toHexString(newIdent) + " while dispatching to "
                    + msg.target.getClass().getName() + " "
                    + msg.callback + " what=" + msg.what);
        }

        msg.recycleUnchecked();
    }
}

public void dispatchMessage(Message msg) {
    if (msg.callback != null) { // 发送消息 如调用了post(Runnable r)等系列方法(会执行 msg.callback = runnable; 操作),则此处不为null
        handleCallback(msg);
    } else {
        if (mCallback != null) { // 创建Handler对象时作为参数传递进来,可以用来实现消息的拦截
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}

private static void handleCallback(Message message) {
    message.callback.run();
}

10、如何实现Handler消息拦截(不接收消息)

private Handler handler = new Handler(new Callback(){
  @Override        
  public boolean handleMessage(Message msg) {            
     return true; // 设置true拦截消息
  }   
 }){        
  @Override        
  public void handleMessage(Message msg) {                
     // 根据消息类型对消息进行处理
  }    
}; 

相信聪明的读者熟悉问题9中的代码后自然会一目了然此间原理

11、消息机制中的消息池实现原理,消息池为什么不会引起OOM?

通常,我们使用Message.obtain()从消息池中获取Message,避免直接构造Message

public static Message obtain() {
    synchronized (sPoolSync) {
        if (sPool != null) { // 消息池链表头部Message不为空  出池
            Message m = sPool; // 取出链表头部Message
            sPool = m.next; // 链表的下一个Message为新的链表头
            m.next = null;
            m.flags = 0; // clear in-use flag
            sPoolSize--; // 链表池大小 -1
            return m;
        }
    }
    return new Message(); // 消息池中没有Message则重新构造
}

那么消息池中的消息哪里来的呢?我们知道,消息池的主要作用是消息的复用,那就只有当一个消息被new出来并使用结束后,才会进入消息池,也就是这个消息被回收到池中,等待复用,我们找找消息使用结束被回收的函数recycle()

public void recycle() {
    if (isInUse()) { // 消息处于正在处于使用状态
        if (gCheckRecycle) {
            throw new IllegalStateException("This message cannot be recycled because it "
                    + "is still in use.");
        }
        return;
    }
    recycleUnchecked();
}

void recycleUnchecked() {
    // Mark the message as in use while it remains in the recycled object pool.
    // 把这个Message所有成员赋值成最初的状态
    flags = FLAG_IN_USE;
    what = 0;
    arg1 = 0;
    arg2 = 0;
    obj = null;
    replyTo = null;
    sendingUid = -1;
    when = 0;
    target = null;
    callback = null;
    data = null;

    synchronized (sPoolSync) { // 入池
        if (sPoolSize < MAX_POOL_SIZE) {
            next = sPool;
            sPool = this;
            sPoolSize++;
        }
    }
}

看到这,相信你已经知道了消息是怎样加到消息池和怎样从消息池中取出:消息在使用结束recycle的时候入池,在下次obtain消息的时候从消息池中取出。

那么Android会因为Message Pool缓存的Message对象而造成OOM吗?

对于这个问题,我可以明确的说APP不会因Message Pool而OOM
我们知道消息池中保存的最大消息数为MAX_POOL_SIZE表示链表的最大长度为50,当我们执行入池操作时:

  • 将待回收的Message对象字段置空(避免因Message过大,使静态的消息池内存泄漏)。因此无论原先的Message对象有多大,最终被缓存进Message Pool前都被置空,那么这些缓存的Message对象所占内存大小对于一个app内存来说基本可以忽略。所以说,Message Pool并不会造成OOM。
  • 以内置锁的方式(线程安全),判断当前线程池的大小是否小于50。若小于50,直接将Mesaage插入到消息池链表尾部;若大于等于50,则直接丢弃掉,那么这些被丢弃的Message将交由GC处理。

12、主线程中的Looper.loop()一直无限循环为什么不会造成ANR?

造成ANR的原因一般有两种:
  1. 当前的事件没有机会得到处理(即主线程正在处理前一个事件,没有及时的完成或者looper被某种原因阻塞住了);
  2. 当前的事件正在处理,但没有及时完成。

因为Android 的是由事件驱动的,looper.loop() 不断地接收事件、处理事件,每一个点击触摸或者说Activity的生命周期都是运行在 Looper.loop() 的控制之下,如果它停止了,应用也就停止了。只能是某一个消息或者说对消息的处理阻塞了 Looper.loop(),而不是 Looper.loop() 阻塞它。
而且主线程Looper从消息队列读取消息,当读完所有消息时,主线程阻塞。子线程往消息队列发送消息,并且往管道文件写数据,主线程即被唤醒,从管道文件读取数据,主线程被唤醒只是为了读取消息,当消息读取完毕,再次睡眠。因此loop的循环并不会对CPU性能有过多的消耗。

<b>也就说我们的代码其实就是在这个循环里面去执行的,当然不会阻塞了。</b>

13、Handler内存泄漏的问题

造成内存泄漏的原因:
  1. Handler的生命周期与Activity不一致;
  2. Handler引用Activity阻止了GC对Activity的回收。
解决方案:

使用显式引用:1.静态内部类。 2. 外部类
使用弱引用:WeakReference

具体代码:
private static class MyHandler extends Handler {

    private final WeakReference<MainActivity> mActivity;  

    public MyHandler(MainActivity activity) {  
        mActivity = new WeakReference< MainActivity >(activity);  
    }  

    @Override  
    public void handleMessage(Message msg) {  
        System.out.println(msg);  
        if (mActivity.get() == null) {  
            return;  
        }  
        mActivity.get().todo();  
    }  
}  

@Override  
public void onDestroy() {  
    //  If null, all callbacks and messages will be removed.  
    mHandler.removeCallbacksAndMessages(null);  
}  

未完待续...

参考文集:
Android 消息处理机制(Looper、Handler、MessageQueue,Message)
Android的消息机制之ThreadLocal的工作原理
Android面试:主线程中的Looper.loop()一直无限循环为什么不会造成ANR?

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,240评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,328评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,182评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,121评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,135评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,093评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,013评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,854评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,295评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,513评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,398评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,989评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,636评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,657评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352

推荐阅读更多精彩内容

  • 一、提出问题 面试时常被问到的问题: 简述 Android 消息机制 Android 中 Handler,Loop...
    崽子猪阅读 1,520评论 0 10
  • 异步消息处理线程启动后会进入一个无限的循环体之中,每循环一次,从其内部的消息队列中取出一个消息,然后回调相应的消息...
    cxm11阅读 6,424评论 2 39
  • 前言 在Android开发的多线程应用场景中,Handler机制十分常用 今天,我将手把手带你深入分析Handle...
    BrotherChen阅读 472评论 0 0
  • 系列文章Android面试攻略(1)——Android基础Android面试攻略(2)——异步消息处理机制Andr...
    黎清海阅读 1,347评论 0 10
  • 这一夜我睡得很不安稳,醒来时曙光初现,我只歇了两个时辰,却再也睡不着。许久未曾做梦,昨夜竟陷入了一个奇异的梦境。 ...
    栖云歌行阅读 279评论 5 3