Android Handler的源码分析

1、简述

由于安卓中为了确保界面的更新同步,限制了只有UI(主)线程可以更新界面,但是UI线程不能执行耗时操作,相关的文件读取、数据加载和网络请求等操作,需要放置在子线程进行处理,但处理完成后怎样将子线程的处理结果及时的发送至主线程呢?
此时就需要引入Handler的运行机制,通过Hnadler的创建、发送和处理方式,实现跨线程的数据通信。
其实Handelr主要用于线程间通信的,但在安卓开发中一般用于子线程发送至主线程的操作。

2、Handelr的简单使用

2.1子线程发送至主线程

private final int TEST_HANDLER_NUM = 100;
    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case TEST_HANDLER_NUM:
                    Log.i(TAG, "handleMessage: 接收到处理结果");
                    break;
            }
        }
    };
  
  new Thread(new Runnable() {
            @Override
            public void run() {
                SystemClock.sleep(5 * 1000);
                Message message = mHandler.obtainMessage();
                message.what = TEST_HANDLER_NUM;
                mHandler.sendMessage(message);
                Log.i(TAG, "run: 消息处理完成,开始发送");
            }
        }).start();

2.2 主线程发送至子线程

//在子线程中创建Handler
new Thread(new Runnable() {
            @Override
            public void run() {
                Looper.prepare();
                subHandler = new Handler() {
                    @Override
                    public void handleMessage(Message msg) {
                        super.handleMessage(msg);
                        switch (msg.what) {
                            case TEST_SUB_HANDLER_NUM:
                                Log.i(TAG, "handleMessage: 子线程接收到消息");
                                break;
                        }
                    }
                };
                Looper.loop();

            }
        }).start();
//主线程发送消息
findViewById(R.id.TestA).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
				subHandler.sendEmptyMessageAtTime(TEST_SUB_HANDLER_NUM,5*1000);
                Log.i(TAG, "onCreate: 发送消息至子线程");
}

2.3 采用post形式

UIHandler.post(new Runnable() {
                @Override
                public void run() {
                    Log.i(TAG, "run: "+currentThread().getName());
                    mTextView.setText("hahha");
                }
            });

2.4 小结

Handler的使用中,除了上述使用的sendMessage/sendEmptyMessageAtTime,还包括post/postAtTime等,但是通过查看源码可以发现,其最终调用的还是sendMessage相关的方法,因此本章中主要是对sendMessage相关的源码分析。

3. Handler运行机制

3.1执行流程
在这里插入图片描述针对上图中相关的类简介如下:

Handler相关的流程包括如下:创建Handler、发送消息、存储消息、取出消息、处理消息五个过程,其中包含的类如下:

  • Handler:负责发送和处理消息;

  • Looper:负责不断的轮询消息队列,并将消息 分发至Handler处理;

  • MessageQueue:存储消息的队列;

  • Message:消息载体;

3.1.1创建Handler

在创建Handler时,其执行的构造方法如下:

public Handler(Callback callback, boolean async) {
...
		mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
...
}

在创建Handler时,通过myLooper()获取到Looper对象,否则会抛出RuntimeException异常,而myLooper()的获取方式为通过ThreadLocal的get()方法。
既然时通过ge()方式获取,那么是什么时候调用的set()方法呢?

public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

查看2.2 主线程发送至子线程,在创建Handler前,先调用了Looper.prepare()方法,然后调用了 Looper.loop()方法,至于为何需要调用这个两个方法?2.1部分为何不需要调用?
为了回答以上问题,需要查看prepare()和loop()到底是处理了什么?

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));
    }

调用prepare方法后,先通过ThreadLocal.get()查看是否为空,如果不为空则抛出异常,表明一个线程中只能绑定一个Looper对象,如果为空,则新建一个Looper对象,并通过set方法赋值给ThreadLocal。
而loop()是消息的轮询机制,该部分在下面消息的轮询中会详细讲解。

为何2.1部分子线程发送消息至主线程,不需要调用prepare()方法?
查看源码注释,可以看出,在应用启动时,就调用了getMainLooper() 方法,为主线程创建了Looper对象。

 /**
     * Returns the application's main looper, which lives in the main thread of the application.
     */
    public static Looper getMainLooper() {
        synchronized (Looper.class) {
            return sMainLooper;
        }
    }

其中ThreadLocal用于线程的存储类,主要包含set和get方法,其内部维护这一个Map集合,其中key是当前的线程,value为需要存储的值。

  public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }
3.1.2 发送消息

如第2部分所示为Handler的简单使用,无论是发送Message 还是Runable,但最终运行至sendMessageAtTime中,如下代码所示:

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);
    }
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
	//设置消息的tagrget
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

执行至Handler的enqueueMessage后,开始调用MessageQueue 的enqueueMessage,开始消息的存储操作。

3.1.3 存储消息

MessageQueue主要用于消息的存取,通过next()获取消息信息,enqueueMessage()存储消息,本节主要讲解消息的添加,消息的分发处理在下面讲解。

Handler中的enqueueMessage处理:

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
		//在发送至MessageQueue 前,将当前的Handler赋值给Message 的target变量
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

MessageQueue 中的enqueueMessage处理:

boolean enqueueMessage(Message msg, long when) {
	//1、消息属性的判断
	   if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        if (msg.isInUse()) {
            throw new IllegalStateException(msg + " This message is already in use.");
        }
	//2、更改消息属性 
			msg.markInUse();
            msg.when = when;
            //上次添加的消息信息
            Message p = mMessages;
            boolean needWake;
      //3、查看消息是否为空,上次消息是否添加成功
            if (p == null || when == 0 || when < p.when) {
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                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;
}
3.1.4 消息分发
3.1.4.1 消息轮询

在2.2 主线程发送至子线程,在创建Handler后,需要手动调用 Looper.loop()方法,如果注释该代码,会发现及时发送了消息,但是在handleMessage接收不到消息的回调信息,下面看下loop()主要执行了那些操作?

public static void loop() {
		//1、 查看当前线程的Looper是否为空
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        //2、获取当前线程对应的 MessageQueue
        final MessageQueue queue = me.mQueue;
        ....
        //3、死循环获取消息信息
         for (;;) {
            Message msg = queue.next(); 
            //4、如果去除的消息为null ,则跳出循环
            if (msg == null) {
                return;
            }
            ...
            //5、根据消息的target分发至对应的dispatchMessage方法中
             msg.target.dispatchMessage(msg);
		   ...
		  }        
 }
3.1.4.2 消息获取

以上完成消息的遍历取出和分发操作,其中消息的取出通过MessageQueue 的next()方法实现。

Message next(){
...
//1、死循环遍历
for (;;) {
...
synchronized (this) {
//2、获取当前时间戳;初始化上次消息;当前消息
				final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null && msg.target == null) {
  // 3、查找消息队列中的下一个消息
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
 //	4、判断当前消息是否准备完成
						if (now < msg.when) {
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
//5、消息属性变更和发送消息
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        return msg;
                    }
				}
}

...

}
}
3.1.4.3 消息类型

在上述讨论中一直围绕着Message的处理来梳理,下面查看一下Message的类,其中最常用的属性如下,对于what、arg1、arg2和obj在Handler的应用处理中使用较多,而target属性用于标注消息,在消息的存储和分发中便于标注是基于那个Handler 进行处理的,这是由于针对一个Looper对象,我们可以创建多个Handler实例。

public final class Message implements Parcelable {
			public int what;
			public int arg1;
			public int arg2;
			public Object obj;
			...
			Handler target;
			...
}

3.1.5 消息分发

在3.1.4.1 消息轮询中,完成消息的分发处理,分发中通过Message的target判断所属的Handler,其实target就是Handler类型。

public void dispatchMessage(Message msg) {
//1、msg是否为包含callback(Runable形式)
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
//2、是否重写了Callback方法        
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
//3、如果上述都为空,默认执行至重写的 handleMessage中,也就是常见的创建Handler的形式
            handleMessage(msg);
        }
    }
3.2 常见问题分析

1、Handler是怎么实现切换线程的?
2、handler.sendMessage()与handler.post()的区别?
3、MessageQueue是怎么增删消息的?
4、一个线程可以有几个Handler?几个Looper?几个MessageQueue?
5、A Handler发送的消息为什么不会跑到B Handler的handleMessage()方法中?
6、简述ThreadLoacal的原理?
7、Handler引起的内存泄漏?
8、在UI中创建的Handler,通过post方式发送的消息在run方法中可以进行UI更新吗?

1、Handler是怎么实现切换线程的?

  • 创建Handler:在创建时需要先获取该线程(一般为UI线程)对应的Looper信息;
  • 发送消息:在子线程数据处理完成后,发送消息,在发送消息前将该Handler作为target,将消息发送至MessageQueue中;
  • 消息处理:通过Looper的loop方法,不断获取消息信息,根据消息属性中的target分发消息,从而完成了子线程至主线程的消息处理。
    2、handler.sendMessage()与handler.post()的区别?
    查看Handler可以发现,最终发送至MessageQueue的方法都是Handler中的enqueueMessage(),但是对于Message的处理,在分发时,会将消息的结果返回至其callback 中(如果不为空)。
public final boolean post(Runnable r)
    {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }
 private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }

3、MessageQueue是怎么增删消息的?
增加:通过enqueueMessage方法,通过对消息添加时间和之前消息的判断,将新添加的消息放置在栈顶

msg.next = p; // invariant: p == prev.next
prev.next = msg;

删除:通过next方法取出消息,在loop()中进行消息的分发处理,在next返回消息前对消息属性进行修改

// MessageQueue中的next()方法
msg.next = null;
msg.markInUse();
// Message中的markInUse()方法,表明消息已经使用
void markInUse() {
        flags |= FLAG_IN_USE;
    }

4、一个线程可以有几个Handler?几个Looper?几个MessageQueue?
多个Handler?
**多个:**上述描述中,知道之所以消息可以准确的分发处理,是由于通过Message的target方法在添加和分发时标注Handler,实现准确的分发,因此同一个线程可以创建多个Handler对象,例如常见的UI线程中:

 private Handler mHandler= new Handler(Looper.myLooper()){...}

几个Looper?
一个:在2.2中主线程发送至子线程中,需要先调用Looper.prepare()方法,否则会抛出异常,那如果一个线程中包含多个Looper会怎样?
查看源码可以发现,如果包含多个Looper对象会抛出异常。

 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));
    }

几个MessageQueue?
一个:在调用Looper.prepare()时,会判断该线程的Looper是否为空,只有为空的情况才会调用Looper的构造方法,创建MessageQueue,因此只有一个。

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

5、A Handler发送的消息为什么不会跑到B Handler的handleMessage()方法中?
参考问题1和4可知,一个线程中包含多个Handelr对象,而在消息的添加分发时,通过Message的target(Handler)标注。
6、简述ThreadLoacal的原理?
在3.1中新建Handler时,已对ThreadLoacal简述,通过set和get方法,其内部维护这一个Map集合,其中key是当前的线程,value为需要存储的值。
7、Handler引起的内存泄漏?
可参考Android中使用Handler为何造成内存泄漏?中相关的介绍,主要是由于在消息处理时,当前Activty已经关闭,但是仍有消息处理,导致当前Activty无法正常消耗出现内存泄漏的情况,解决方案:

  1. Activity销毁时及时清理消息队列;
  2. 自定义静态Handler类+软引用。

参考:Android Handler之原理解析
8、在UI中创建的Handler,通过post方式发送的消息在run方法中可以进行UI更新吗?
可以:通过查看源码可以发现,其最终调用的还是sendMessage相关的方法,但是如果传入的Runnable形式,其内部会对其进行了封住成Message类。

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

在消息处理结束后,其回调至Runnable 的run方法中,需要注意的是这里的仍然是在UI线程中,因为我们创建的Handler是在UI线程中,且Handler将Runnable内部封住成Message的形式,在消息分发时首先检测callback是否为空,如下所示:

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

如果不为空则调用handleCallback方法,但是该方法实际上是调用message.callback.run(),而message.callbac是我们在添加消息时赋值的Runable,所以最终调用的是Runable中的run()方法,因此还是回到UI线程中,可以更新UI界面。

private static void handleCallback(Message message) {
        message.callback.run();
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值