Android好奇宝宝_09_Handler Looper Message

发现自己讲的东西都是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有兴趣的请自己上网查。

求赞求评论指点

时间: 2024-08-28 07:30:50

Android好奇宝宝_09_Handler Looper Message的相关文章

Android好奇宝宝_04_一个有3个功能的Adapter

感觉Android好奇宝宝这个系列是脱离不了ListView和GridView了... 这一篇呢来分享点好东西 一个自定义Adapter,可以快速实现三个功能: (1)自动缓存处理 好吧,这个功能不是我实现的.我只是照搬鸿洋大大的,我会简单说下,不过还是请先看下他的原文,再来看我添加的两个功能,传送门 (2)支持item的不同布局 提供一个接口来通过position和该position的数据来设置不同的布局 (3)局部刷新 只刷新指定item的某个子View,避免一直调用notifyDataSe

Android好奇宝宝_番外篇_看脸的世界_08

废话少说,先上效果图: (左侧的图片是我用window画图软件1分钟画的,所以就不要嫌丑了,You can you up no bb.) 这是我发过最挫的效果图了,不过这是由于没有图片素材导致的,就不要在意这些细节了,知道实现原理后完全可以发挥你的想象去实现更美观的效果. 这个效果也是有开源库的,不过我又把名字给忘了,不过我记得原理,于是就试着自己写了一下. 其实原理很简单,我在另一篇博客(一个有吃豆人删除动画的ListView)也说过了,这一篇当做兑换那些年少轻狂不更事时许下的诺言(是不是瞬间

Android异步消息处理 Handler Looper Message关系源码分析

# 标签: 读博客 对于Handler Looper Message 之前一直只是知道理论,知其然不知所以然,看了hongyang大神的源码分析,写个总结帖. 一.概念.. Handler . Looper .Message 这三者都与Android异步消息处理线程相关的概念. 异步消息处理线程启动后会进入一个无限的循环体之中,每循环一次,从其内部的消息队列中取出一个消息,然后回调相应的消息处理函数,执行完成一个消息后则继续循环.若消息队列为空,线程则会阻塞等待. 说了这一堆,那么和Handle

Android好奇宝宝_11_SwipeRefreshLayout原理浅析

上一篇文章写了一个RecyclerView的Demo,然后就想加个下拉刷新功能进去试试,由于RecyclerView算比较新的东西,所以暂时找不到什么开源库使用.于是想到了官方提供的SwipeRefreshLayout,号称能为任何View添加下拉刷新功能. SwipeRefreshLayout的使用很简单: (1)将要下拉刷新的View嵌套到SwipeRefreshLayout中: <jjj.demo.newstuffdemo.JJJSwipeRefreshLayout android:id=

Android好奇宝宝_番外篇_看脸的世界_02

一个有吃豆人删除动画的ListView 这是一个无聊的效果,由一个无聊的程序猿,在无聊的情况下写的. 虽然这效果不中看中用,不过就当学习了. 先上图 效果一目了然,主要是: (1)移除item时执行吃豆人动画 (2)滚动时吃豆人也相应移动 (3)应对可见与不可见状态间的切换 简单原理分析: (1)吃豆人.豆.和左边的白色矩形(当然所有颜色都是可以改的,你想换成图片也行)都是用canvas画出来的. (2)问:canvas那里来的?答:ListView的canvas.具体是重写ListView的这

Android好奇宝宝_番外篇_看脸的世界_06

简单实现波纹效果 其实这一篇的效果实现很简单,写这篇博客重点是为了说另一件事,剧透一下:有关内存泄露的. 先说下效果的实现: 原理: 原理只有一个,就是Shader的使用.Shader我看别人翻译成着色器,其实它的作用就是为画笔增加颜色的渐变,画笔默认是一个颜色画到底,但是使用Shader可以实现从一个颜色渐变到另一个颜色. 想了解更多关于Shader的姿势,推荐博客:传送门 有了Shader,就能很简单的画出波纹的效果了,至于动画效果,只是动态改变画的大小而已. 高清源码: (1)初始化 重写

Android好奇宝宝_番外篇_看脸的世界_05

上一篇番外篇讲了一个炒鸡炒鸡简单的自定义ProgressBar,这一篇基于上一篇的基础扩展为SeekBar,没看过上一篇的,请先看一遍:传送门 先上效果图(2G内存的机子运行模拟器,所以有点卡): 这个效果之前不知道在哪里看到过,我也忘了. 下面进入正题: 测量大小和绘制部分沿用上一篇ProgressBar的,不清楚的请走上面的传送门. 对比上一篇的扩展: (1)SeekBar能通过触摸改变刻度 (2)SeekBar上方添加一个显示当前刻度的浮动View(后面用FloatView表示) (1)通

Android好奇宝宝_番外篇_看脸的世界_03

无聊刷帖看到一个求助,试着写了一下. 一个自定义Switch控件,附带动画效果. 说是控件,其实是一个布局容器,先上效果图: 先讲原理,再看高清源码. 原理: 好像没啥原理,汗... 跟其它自定义容器控件一样,一般要注意: (1)计算好大小,宽度和高度 (2)计算好子View的布局位置 不是一般要注意的: (3)动画是用的nineoldandroids (4)遮挡效果是通过控制子View的绘制顺序 高清源码: (1)计算大小: protected void onMeasure(int width

Android好奇宝宝_06_聊一聊Android里的动画

这一篇我们来聊一聊高大上的动画效果. 首先说一个常识,一个对理解动画最重要的概念,亦是动画的本质: 动画的原理是利人眼的视觉暂留的特性,即如果一帧帧图像切换的足够快的话,人眼就察觉不到停顿,看起来就像连续的动画了. 动画的原理很简单,就是让图像进行快速的切换.动画的难点是计算出每两帧之间的差异,比如一个位移动画,对于每一帧你都必须计算出它的位置,如果是直线匀速的.很容易计算,但如果是曲线的而且还是有加速度(即移动的速度是会变化的)的,那么计算就会变的复杂了. 总结一下,动画有两个要素,一个是若干