源码分析之Handler
Handler是Android中的消息机制实现,可以实现UI线程和子线程的消息传递,这里就来深入了解Android的消息机制,来分析Handler的源代码
入手实例
在Android开发中,子线程与主线程通信是再寻常不过的事情了,那么为何需要子线程和主线程通信呢,相信只要是做过Android开发都知道这是为啥,为了博客的尽可能详尽,这里还是说说原因
举个简单例子,以前刚做Android开发的时候,啥也不懂,记得有一次,在主线程中做了耗时操作,具体记不得啥操作了,然后一运行,报了个无响应,也就是常说的ANR,然后就知道了主线程阻塞,耗时的操作放在子线程,主线程一旦阻塞超过5s就会报ANR,既然主线程不做耗时操作,那么子线程里面执行怎么和主线程互动呢,由此便引生出了Handler,当然AsyncTask也是基于同样的道理,AsyncTask的源代码在之前已经分析过了,这里就不再赘述
那么在开发中Handler是怎么使用的呢,这里结合一个例子作为讲解
public class MainActivity extends Activity {
    private static final String TAG = "cj5785";
    private TextView textView;
    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            Log.d(TAG, "handleMessage: " + Thread.currentThread().getName());
            String str = (String) msg.obj;
            textView.setText(str);
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView = (TextView) findViewById(R.id.text_view);
    }
    public void buttonClick(View view) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                Log.d(TAG, "run: " + Thread.currentThread().getName());
                Message message = Message.obtain();
                message.obj = "更新UI";
                handler.sendMessage(message);
            }
        }).start();
    }
}
这是一个简单的更新UI的例子,通过log可以清楚的看到,两个打印语句是处在不同的线程中的,然而却实现了主线程和子线程的消息传递
D/cj5785: run: Thread-13741
D/cj5785: handleMessage: main
如果直接在子线程中更新UI则会报出以下错误
Only the original thread that created a view hierarchy can touch its views.
那么这样的线程通信是怎么实现的呢,下面就来分析一下源代码
源代码分析
分析源代码,尤其是涉及到几个类的这种代码,找准切入点很关键,那么这个分析的切入点在哪呢,子线程发出消息,主线程接收,那么很明显是子线程的发出点,那么就应该先看子线程里面代码的处理
Message message = Message.obtain();
message.obj = "更新UI";
handler.sendMessage(message);
Message部分
首先是生成了一个Message对象,这里有两种方式,一种是直接new一个出来,一种是使用obtain(),那就去看看这个对象是怎么生成的
public static Message obtain() {
    synchronized (sPoolSync) {
        if (sPool != null) {
            Message m = sPool;
            sPool = m.next;
            m.next = null;
            m.flags = 0; // clear in-use flag
            sPoolSize--;
            return m;
        }
    }
    return new Message();
}
这里主要关注的是next参数,那么next是啥呢,有C语言基础的一看就知道是啥,没有也没关系,我们继续往下看
// sometimes we store linked lists of these things
/*package*/ Message next;
private static final Object sPoolSync = new Object();
private static Message sPool;
private static int sPoolSize = 0;
相信这句注释已经很明显了,这不就是链表么,Java中的链表使用对象引用实现,那么也就是说这里判断有没有Message有的话就实现复用,没有的话就new一个,而sPoolSync就是一个同步锁而已,到这里,并没有给出主线程和子线程消息传递的理由,那么继续往下看,message.obj = "更新UI";这里只是设置了变量,也没有参考价值,那么只剩handler.sendMessage(message);了,到此貌似Message的任务就完成了,那么来看看Message里面的一些值得注意的地方吧
照例先从类开始
public final class Message implements Parcelable {
	···
}
Message实现了Parcelable接口,Parcelable和Serializable都是实现序列化,用于消息的传输,Message是一个final类,简而言之无法继承
再看看Message的成员变量,常用那几个就不说了,这里谈一谈重要的一些
/*package*/ Bundle data;
/*package*/ Handler target;
/*package*/ Runnable callback;
private static final int MAX_POOL_SIZE = 50;
private static boolean gCheckRecycle = true;
首先是Bundle,这也就是Message为何可以设置Bundle的原因所在了,这里出现了Handler,也就是说在这里涉及到了Handler,这里简单来说就是Message会持有Handler的引用,这里注意到似乎只用带参的obtain()以及setTarget()才会初始化Handler,MAX_POOL_SIZE说明了Message的大小,gCheckRecycle则是清除信息的标志位
Handler部分
那么到这里已经没啥线索了,可以猜测在Handler中对target进行了初始化,那看看sendMessage()做了啥吧
public final boolean sendMessage(Message msg){
    return sendMessageDelayed(msg, 0);
}
这里意外发现了常用的延迟设置,再继续看
public final boolean sendMessageDelayed(Message msg, long delayMillis){
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
所以从这就可以看出,当延迟小于零时会将其设为0,这里注意一下,SystemClock.uptimeMillis()是一个native方法,返回的是boot时间,再继续追寻
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);
}
到这里,出现了一个新东西,这便是Handler组成的重要部分,那么MessageQueue是啥呢,熟悉数据结构的一眼就看出来了,这其实是个队列,稍后再看这个队列做了啥,队列不为空就要调enqueueMessage()了,那么继续
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}
这里,对target赋值了,然后设置了异步状态标识,然后回到了MessageQueue的enqueueMessage()方法,至此,有必要暂停Handler的分析,去看一看MessageQueue了
MessageQueue部分
在MessageQueue中找到handler调用的方法,由于方法比较长,不便于描述,将其描述用注释形式写在代码中
boolean enqueueMessage(Message msg, long when) {
	//target不能为空
    if (msg.target == null) {
        throw new IllegalArgumentException("Message must have a target.");
    }
	//msg不能正在被使用
    if (msg.isInUse()) {
        throw new IllegalStateException(msg + " This message is already in use.");
    }
	//同步代码块,保证线程安全
    synchronized (this) {
		//未退出
        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(); //清除Message
            return false;
        }
        msg.markInUse(); //标记正在使用
        msg.when = when; //得到延迟
        Message p = mMessages; //赋值,在第一次调用此方法前,mMessages为null
        boolean needWake;
        if (p == null || when == 0 || when < p.when) { //由于是短路或,不会执行到第三个条件
            // New head, wake up the event queue if blocked.
            msg.next = p; //将msg的next指向p
            mMessages = msg; //将msg赋值给mMessages
            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 = p.next; //p的后节点赋值给p
                if (p == null || when < p.when) { //循环终止条件(到达尾节点或者when值小于后面的节点)
                    break;
                }
                if (needWake && p.isAsynchronous()) {
                    needWake = false;
                }
            }
            msg.next = p; //msg的下一节点指向p
            prev.next = msg; //prev的下一节点指向msg
        }
        // We can assume mPtr != 0 because mQuitting is false.
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}
如果只是一个msg,那么就只形成一个节点,如果多个msg,则会按时间顺序形成一条链表,虽然形成了链表,那么我们最关心的主线程与子线程的通信去哪了,或者说我们的分析过程问题出在哪里,注意到前面的mQueue,这个东西是哪里来的
final MessageQueue mQueue;
那么是在那里初始化的,注意到在MainActivity中,调用前生成了Handler对象,使用的是无参的构造方法,那么来看看这个方法
public Handler() {
    this(null, false);
}
额,好像没啥用,继续
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;
}
关于第一个if判断,这里说明一下:FIND_POTENTIAL_LEAKS标志设置为true以检测扩展的Handler类,如果不是静态的匿名,本地或成员类, 这类可能会产生泄漏。也就是我们常见构造时的警告说明!至于消除警告方法一般是设置成静态或弱引用
接下来发现有个mLooper,这是一个循环器,MessageQueue和Handler之间桥梁,那么这里就跳转到Looper吧
Looper
首先看看Looper.myLooper()干了啥
/**
 * Return the Looper object associated with the current thread.  Returns
 * null if the calling thread is not associated with a Looper.
 */
public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}
结合注释知道了,这里返回带有当前线程的Looper,下面是get啥呢,继续来看get()
/**
 * Returns the value in the current thread's copy of this
 * thread-local variable.  If the variable has no value for the
 * current thread, it is first initialized to the value returned
 * by an invocation of the {@link #initialValue} method.
 *
 * @return the current thread's value of this thread-local
 */
public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null)
            return (T)e.value;
    }
    return setInitialValue();
}
/**
 * Variant of set() to establish initialValue. Used instead
 * of set() in case user has overridden the set() method.
 *
 * @return the initial value
 */
private T setInitialValue() {
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
    return value;
}
结合注释可知,这里返回的是线程值,注意sThreadLocal的生成
// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
嗯,也就是说在get前要调用prepare(),从而可以推导出在ActivityThread里面调用了这个方法,查看一下
public static void main(String[] args) {
    ···
    Looper.prepareMainLooper();
	···
    Looper.loop();
	···
}
看来没错,那么再看看Looper.prepareMainLooper()
/**
 * Initialize the current thread as a looper, marking it as an
 * application's main looper. The main looper for your application
 * is created by the Android environment, so you should never need
 * to call this function yourself.  See also: {@link #prepare()}
 */
public static void prepareMainLooper() {
    prepare(false);
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();
    }
}
重点关注第一个方法,那么这个方法一定就是关键了
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,使用的是当前线程,也就保证了一个Thread只对应一个Looper
好像还差一点,再来,感觉到曙光了
private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}
到这里,终于找到了关键点,也就是说主线程和子线程通过MeaasgeQueue建立起了连接,这个Queue是同一个对象,那么具体是怎么执行的,前面执行了Looper.prepareMainLooper();,后面还有Looper.loop();,那么就来看看这里面做了什么吧
/**
 * Run the message queue in this thread. Be sure to call
 * {@link #quit()} to end the loop.
 */
public static void loop() {
	得到当前线程Looper
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
	//得到当前线程Queue
    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 (;;) { //条件循环
		//执行queue的next()方法,不断从queue中取出Message
        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.isTagEnabled(traceTag)) {
            Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
        }
        try {
			//之前设置了target,这里调用Handler的dispatchMessage()方法
            msg.target.dispatchMessage(msg);
        } 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();
    }
}
这里就是不断循环得到Message,然后分发消息,这里的重点在于dispatchMessage()做了啥
/**
 * Handle system messages here.
 */
public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}
可以看到,这里由于前面没有设置callback,所以执行的是handleMessage(),那么再继续看看
/**
 * Subclasses must implement this to receive messages.
 */
public void handleMessage(Message msg) {
}
这里并没有实现这个方法,注明了要实现这个方法才能收到消息,到此,在子线程发出的消息在主线程就能够得到了
那么到这里,消息机制的源代码分析也就完成了
思路整理
这里画个图就简单明了

其工作原理就是通过Handler将消息发送给MessageQueue,而MessageQueue又是由Looper创建的,这里Handler和Looper使用的是同一个Queue,此时在Looper中通过loop()会不断从MessageQueue中读取消息,当msg全部都发送以后,回收消息。
Message: 消息对象
MessageQueen: 存储消息对象的队列
Looper: 读取MessageQueue里面的消息,交由Handler处理
Handler:发送消息和处理消息
handler一定要记得在页面退出时remove
至此,Handler消息机制就分析完毕了
源码分析之Handler的更多相关文章
- 源码分析Android Handler是如何实现线程间通信的
		
源码分析Android Handler是如何实现线程间通信的 Handler作为Android消息通信的基础,它的使用是每一个开发者都必须掌握的.开发者从一开始就被告知必须在主线程中进行UI操作.但H ...
 - Android Handler处理机制 ( 一 )(图+源码分析)——Handler,Message,Looper,MessageQueue
		
android的消息处理机制(图+源码分析)——Looper,Handler,Message 作为一个大三的预备程序员,我学习android的一大乐趣是可以通过源码学习 google大牛们的设计思想. ...
 - Android源码分析笔记--Handler机制
		
#Handler机制# Handler机制实际就是实现一个 异步消息循环处理器 Handler的真正意义: 异步处理 Handler机制的整体表述: 消息处理线程: 在Handler机制中,异步消息处 ...
 - Android源码分析之Handler
		
接上一篇分析,正如Android doc所说,Handler主要有2方面用处: 1. delay执行同一线程中的某个操作,也就是schedule message.runnable在未来的某一时刻执行: ...
 - android的消息处理机制(图文+源码分析)—Looper/Handler/Message[转]
		
from:http://www.jb51.net/article/33514.htm 作为一个大三的预备程序员,我学习android的一大乐趣是可以通过源码学习google大牛们的设计思想.andro ...
 - Android -- 消息处理机制源码分析(Looper,Handler,Message)
		
android的消息处理有三个核心类:Looper,Handler和Message.其实还有一个Message Queue(消息队列),但是MQ被封装到Looper里面了,我们不会直接与MQ打交道,因 ...
 - spring websocket源码分析续Handler的使用
		
1. handler的定义 spring websocket支持的消息有以下几种: 对消息的处理就使用了Handler模式,抽象handler类AbstractWebSocketHandler.jav ...
 - Android源码分析-消息队列和Looper
		
转载请注明出处:http://blog.csdn.net/singwhatiwanna/article/details/17361775 前言 上周对Android中的事件派发机制进行了分析,这次博主 ...
 - Spring源码分析之`BeanFactoryPostProcessor`调用过程
		
前文传送门: Spring源码分析之预启动流程 Spring源码分析之BeanFactory体系结构 本文内容: AbstractApplicationContext#refresh前部分的一点小内容 ...
 
随机推荐
- Python使用pip安装TensorFlow模块
			
1.首先确保已经安装python,然后用pip来安装matplotlib模块. 2.进入到cmd窗口下,建议执行python -m pip install -U pip setuptools进行升级. ...
 - springboot无法识别配置文件级解决办法
			
eclipse中右键项目bulid path 之后找到 后点击完成后点击运用 修改完成
 - 《exception》第九次团队作业:Beta冲刺与验收准备(第三天)
			
一.项目基本介绍 项目 内容 这个作业属于哪个课程 任课教师博客主页链接 这个作业的要求在哪里 作业链接地址 团队名称 Exception 作业学习目标 1.掌握软件黑盒测试技术:2.学会编制软件项目 ...
 - 项目Alpha冲刺--6/10
			
项目Alpha冲刺--6/10 作业要求 这个作业属于哪个课程 软件工程1916-W(福州大学) 这个作业要求在哪里 项目Alpha冲刺 团队名称 基于云的胜利冲锋队 项目名称 云评:高校学生成绩综合 ...
 - git merge与git rebase区别(转载)
			
这是最近看的讲的比较通俗易懂的rebase与merge的区别了 https://www.jianshu.com/p/4079284dd970
 - npm run dev 报错 iview TypeError [ERR_INVALID_CALLBACK]: Callback must be a function
			
运行npm run dev报这个错 然后找到 D:\text\vue\iview-admin\build\webpack.dev.config.js打开 将这一行代码: fs.write(fd, bu ...
 - Access数据库连接封装类
			
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.D ...
 - java 数组逆序输出(方法内部的代码)
			
//现在数组中有1, 2, 4, 5, 6, 7, 8 请逆序输出 int [] arrs={1,2,3,4,5,6,7,8}; for(int i=arrs.length-1;i>-1;i-- ...
 - 验证和交叉验证(Validation & Cross Validation)
			
之前在<训练集,验证集,测试集(以及为什么要使用验证集?)(Training Set, Validation Set, Test Set)>一文中已经提过对模型进行验证(评估)的几种方式. ...
 - Android入门教程(三)
			
对Android五大布局的描述,分别是 FrameLayout (框架布局),LinearLayout (线性布局),AbsoluteLayout (绝对布局),RelativeLayout (相对布 ...