天天看点

Android好奇宝宝_09_Handler Looper Message

发现自己讲的东西都是UI相关的,这一篇就来讲讲Android很重要的知识点:Handler Looper Message。

废话少说,直接入正题。

(1)存在的意义:

我一直把Handler Looper Message 这几个类当成几个可以搭配使用的工具类,特别之处在于系统提供了这些工具类,并且系统自己也使用了这些类。

既然是工具类,那么其功能是什么呢?

答:在当前线程建立一个唯一的消息队列,通过Handler可以向消息队列添加消息,Looper不断从消息队列取出消息,再转发给发送该消息的Handler。感觉就是Handler发送消息,然后又回到了Handler,很无聊对不对。

那么做这么无聊的事有什么作用呢?

答:解决多线程并发问题。

先说下什么是多线程并发问题:

多个线程同时对同一块内存区域进行操作,因为无法确定线程执行的先后顺序,将导致不可预计的结果。

Android好奇宝宝_09_Handler Looper Message

快要过年了,举个经典栗子:

两个出售火车票的窗口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)内部机制:

先上图,再看码:

Android好奇宝宝_09_Handler Looper Message

(注意从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有兴趣的请自己上网查。

求赞求评论指点