发现自己讲的东西都是UI相关的,这一篇就来讲讲Android很重要的知识点:Handler Looper Message。
废话少说,直接入正题。
(1)存在的意义:
我一直把Handler Looper Message 这几个类当成几个可以搭配使用的工具类,特别之处在于系统提供了这些工具类,并且系统自己也使用了这些类。
既然是工具类,那么其功能是什么呢?
答:在当前线程建立一个唯一的消息队列,通过Handler可以向消息队列添加消息,Looper不断从消息队列取出消息,再转发给发送该消息的Handler。感觉就是Handler发送消息,然后又回到了Handler,很无聊对不对。
那么做这么无聊的事有什么作用呢?
答:解决多线程并发问题。
先说下什么是多线程并发问题:
多个线程同时对同一块内存区域进行操作,因为无法确定线程执行的先后顺序,将导致不可预计的结果。
快要过年了,举个经典栗子:
两个出售火车票的窗口A和B,当还有1张火车票时,A和B同时来了人来买票,A和B一查,还剩1张票,于是就把票卖给了这两个人,结果可能是两人都拿到了票,但总票数多了一张,并且剩余票数为-1这个不符合现实逻辑的数。
下面说说是怎么解决的:
一般的解决多线程并发问题办法就是加上各种类型的锁,这些锁的作用就是保证在某个时刻,只有一条线程可以进行访问修改,但Android并不是这么处理的。
Android是基于单线程模型,什么是单线程模型?
答:单线程模型并不是说只有一条线程(起码在Android里不是这个意思),而是该线程内的变量只允许自己访问,不允许其它线程访问。说白了还是为了解决多线程并发问题,用上面火车票的例子就是A和B都可以卖票,但是只有A可以出票,B必须到A那里拿票。
具体在Android系统上的表现就是:UI控件只有在UI线程(主线程)才可以访问,在其它线程不可以直接访问。
(怎么又跟UI相关了,我去)
这样虽然解决了多线程并发问题,防止了多个线程同时对一个UI控件进行修改造成状态混乱。但是又引发了另外一个问题,就是其它线程需要访问控件时怎么办,比如我有一条下载线程要更新下载进度到ProgressBar上要怎么办?
(这个例子举得不好,ProgressBar做了特殊处理,可以在非UI线程中直接操作,实际上也是用了Handler,只是自动帮我们做了而已,大家这里明白意思就行了)
答:于是呢产生了我们今天的主角,Handler
Looper Message!当其它线程想访问修改UI控件时,它不能直接访问,但它可以通过Handler发送一个消息给UI线程,让UI线程去帮它修改。
下面模拟一下在Handler
Looper Message这种机制下有多条线程想同时修改一个UI控件时会是什么情况:
首先,线程A想修改控件K,于是它通过Handler向UI线程的消息队列发送了一个消息,该消息被添加进消息队列,同时线程B也做了同样的事。这时两个修改控件K的消息被添加进了消息队列,然后Looper不停歇的从消息队列中取出消息进行处理,但Looper每次只取一个消息,所以这两个消息不会被同时处理执行。
上面说Handler Looper Message的作用是解决多线程并发问题不算正确,确切得说它们的作用是让UI线程可以与其它线程进行安全的通信。
Handler Looper Message下的火车票:
还是两个人到A和B两个窗口买票,因为只有A可以出票,于是B对A说:“我这里需要一张票”(相当于用Handler发送了一个消息进队列里)。这时A也同样说我也需要一张票(又添加了一个消息),这时另外一个人C(Looper)从消息队列中取出消息交给A处理(先取出那个取决于那个消息先入队),A处理第一个消息的时候还有1张票,于是A出了一张票,处理第二个消息的时候没票了,于是它幸灾乐祸地说:没票了,那凉快那呆着去。
因为都是A在处理,所以两个消息不会被同时处理,也就不会出现两次检查时都以为还剩一张票的情况。
(2)内部机制:
先上图,再看码:
(注意从handler开始看起)
高清源码:
(1)Looper和MessageQueue的建立
前面说了Looper和Message这些类系统自己也用了,那么是什么时候用的?
答:在应用开启,创建第一个Activity的时候。
具体分析:
从最简单的HelloWorld开始,我们就习惯了main方法作为程序的入口。还记得书上说过必须有一个mian方法才称之为一个Java程序,但我们开发Android应用时从没写过main方法,那么我们的程序入口在哪?
其实是有main方法的,只是这个类是系统提供的,不是我们写的而已,它就是ActivityThread(注意虽然以Thread结尾,但ActivityThread不是继承于Thread类,实际上它没有继承任何类),下面是ActivityThread的main方法:
public static final void main(String[] args) { Looper.prepareMainLooper();//准备Looper //。。。 //做一些应用启动时需要做的事 //。。。 Looper.loop();//开始循环 //。。。 //做一些应用销毁时需要做的事 //。。。 }
准备Looper是准备了什么呢?
答:首先为当前线程新建一个Looper:
prepareMainLooper()->prepare(boolean)->new Looper(boolean)
然后在Looper的构造方法里新建了一个MessageQueue:
private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); }
(prepareMainLooper()和prepare()方法的区别在于prepareMainLooper()新建的Looper是不可以手动终结的)
这里还有一个知识点就是Looper被存放在ThreadLocal中,简单点说就是ThreadLocal可以保证各条线程都有自己专属的Looper和MessageQueue。详情自己上网查。
建立完成后开始消息循环:
public static void loop() { final Looper me = myLooper();//得到自己 final MessageQueue queue = me.mQueue;//得到消息队列 //开始消息循环 for (;;) { Message msg = queue.next(); if (msg == null) { return; } //消息不为空,把消息发回给handler msg.target.dispatchMessage(msg); } }
在消息发回来的时候有3种方式进行处理,dispatchMessage就是通知这3种方式:
public void dispatchMessage(Message msg) { if (msg.callback != null) { //第一种,为Message设置回调(CallBack) handleCallback(msg); } else { //第二种,为Handler设置回调(个人比较喜欢用这种) if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } //第三种,重写Handler的handleMessage方法 handleMessage(msg); } }
(2)Handler的一般用法:
上面说了我比较喜欢第二种方式,所以我一般这么新建Handler:
Handler mHandler = new Handler(new Handler.Callback() { @Override public boolean handleMessage(Message msg) { // doSomething return false; } });
然后呢就可以开始调用mHandler.sendMessage(msg)向消息队列添加消息了:
各个sendMessage方法经几次重载最后会去调用sendMessageAtTime方法,sendMessageAtTime又会去调用enqueueMessage方法:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { //注意这里把message的target设置为handler自己 //这样message才找得到回家的路 msg.target = this; if (mAsynchronous) { msg.setAsynchronous(true); } //MessageQueue的enqueueMessage方法就是一个入队操作,这里就不再深入了 return queue.enqueueMessage(msg, uptimeMillis); }
就这样我的Handler添加了一个消息到MessageQueue中,等Looper将消息取回给我的mHandler时,我设置的CallBack就会被执行了。
(3)随便再说点凑字数
<1>Looper和MessageQueue是一一对应的,MessageQueue在Looper的构造方法中创建。
<2>每个线程最多只能有一个Looper和其对应的MessageQueue,UI线程(主线程)的Looper和MessageQueue由系统创建,在没有Looper和MessageQueue的线程里创建的handler发送消息将会产生异常(消息队列都没有你想发到哪去)。
<3>注意handler发送的消息是发送到那个线程的消息队列。你可以在new的时候指定Looper,消息就发送到Looper对应的MessageQueue。如果没有指定Looper的话,就会默认指定当前线程的Looper给Handler(当然前提是当前线程有Looper),至于是怎么获得当前线程的Looper的,就是ThreadLocal的功劳。再一次,对ThreadLocal有兴趣的请自己上网查。
求赞求评论指点