笔记(十六)——安卓消息机制Handler

——个人平时笔记,看到的同学欢迎指正错误,文中多处摘录于各大博主与书籍精华

1、定义:Android的消息机制主要是指Handler的运行机制,Handler是同一个进程中线程间的通信机制,主要作用就是将一个任务切换到指定的线程中去执行,Handler并不是专门用于更新UI的,只是这样的特性正好可以用来解决在子线程中无法访问UI的矛盾,才常被开发者用来更新UI。

Handler消息机制流程(深入探讨Android异步精髓Handler):每一个handler的创建都必须有Looper.prepare()->new Handler()-> sendMessage()->MessageQueue->Looper.loop()->handlerMessage(),但是在UI线程即主线程中,系统会自动调用Looper.prepareMainLooper()方法创建主线程的Looper(Looper.prepare()与Looper.loop())和消息队列MessageQueue。

>1、Looper.prepare():做准备工作,为当前线程创建一个Looper。在其内部源代码中,每一个Looper.prepare()创建一个Looper,Looper构造方法内又会初始化一个MessageQueue消息队列和一个线程Thread(当前线程),这也是为什么很多人说的一个Handler只能持有一个MessageQueue的原因。并且由源码可知一个线程对应一个Looper也只有一个Looper.prepare(),否则会抛出异常。在Looper.prepare()内会调用sThreadLocal.set(new Looper(quitAllowed)),这个操作会创建一个新的Looper并压入sThreadLocal数组中。至于Looper,它在Android的消息机制中担负着消息轮询的角色,它会不间断地查看MessageQueue中是否有新的未处理的消息,若有则立刻处理,若无则进入阻塞等待

>2、handler.sendMessage():调用handler.sendMessage()、handler.sendMessageAtTime()等方法发送消息时,在其内部源码中都会调用enqueueMessage(MessageQueue queue, Message msg,long uptimeMillis)方法,并在其方法内部处理后调用queue.enqueueMessage(msg,uptimeMillis)方法将消息插入到消息集MessageQueue队列中。MessageQueue队列是遵从先进先出的原则,然而有个例外,如果调用handler.sendMessageAtFrontOfQueue()方法会直接将uptimeMillis入队列的延迟时间参数设置为0,所以msg消息会直接被插入到消息队列最顶部,待取出消息时又会按优先顶部取出原则取出。在enqueueMessage(MessageQueue queue, Message msg,long uptimeMillis)方法内部已经为每一个msg指定了target标签,原文“msg.target =this;”这里的this就是当前handler,所以为什么调用sendMessage()能够准确的发送到对应的handlerMessage()接收。

>3、queue.enqueueMessage(msg, uptimeMillis):将消息发送并插入到MessageQueue消息队列中,uptimeMillis是发送的延迟时间参数。

Handler可以通过post()、postAtTime()、postDelayed()、postAtFrontOfQueue()等方法发送消息,这几个方法均会执行到sendMessageAtTime(Message msg, long uptimeMillis)方法,除了postAtFrontOfQueue()之外会执行sendMessageAtFrontOfQueue(getPostMessage(r))方法。但是sendMessageAtTime方法与sendMessageAtFrontOfQueue方法在源码里最终均是调用queue.enqueueMessage(msg, uptimeMillis)方法。

MessageQueue消息队列它的内部存储结构并不是真正的队列,而是采用单链表的数据结构来存储消息列表(单链表在插入和删除操作上效率比较高)。

>4、Looper.loop():轮询读取消息。消息的出队执行者,在loop()内部会发起一个死循环不断遍历MessageQueue内部轮询,取出消息Message msg =queue.next()next()取出一条消息并将其从消息队列中移除,直到取出的消息不为空时,才调用msg.target.dispatchMessage(msg)将消息发送到熟悉的handleMessage(msg)中接收(而msg.target=this;this即Handler本身),Handler类中的handleMessage()实现了消息的回调,使用回调彻底完成线程切换。在需要的时候或事情完成后可以调用quit()方法停止消息的轮询,此时next()会返回null,loop()方法会结束,Looper也跟着退出,Looper退出后线程也会跟着终止。

补充:在主线程ActivityThread中也是会有一个Looper.loop()不断循环,但是queu.next()也是阻塞和休眠保证main()不会执行完毕切在等待的时间能够给GC时间回收,因为main()执行完毕会抛出 thrownewRuntimeException("Main thread loop unexpectedly exited");异常


>5、Handler在哪个线程创建,也就与哪个线程绑定,一个线程可以持有多个Handler。Handler的主要作用是将一个任务切换到某个指定的线程中去执行;当handler通过一系列的post或send方法发送消息到达目标线程的MessageQueue(消息队列是指定的目标线程持有的)则此时也就切换了线程。线程是默认没有Looper的,如果需要使用Handler就必须为线程创建Looper。参考:android之handler切换线程终极篇

在介绍的最后,我对handler机制的全过程的总结为:

1.首先Looper.prepare()会在当前线程保存一个looper对象,并且会维护一个消息队列messageQueue,而且规定了messageQueue在每个线程中只会存在唯一的一个。

2.Looper.loop()方法会使线程进入一个无限循环,不断地从消息队列中获取消息,之后回调msg.target.disPatchMessage方法。

3.我们在实例化handler的过程中,会先得到当前所在线程的looper对象,之后得到与该looper对象相对应的消息队列。

4.当我们发送消息的时候,即handler.sendMessage或者handler.post,会将msg中的target赋值为handler自身,之后加入到消息队列中。

5.在第三步实现实例化handler的过程中,我们一般会重写handlerMessage方法(使用post方法需要实现run方法),而这些方法将会在第二步中的msg.target.disPatchMessage方法中被回调,从而实现了message从一个线程到另外一个线程的传递。

查看源码可以看到如下解释,当我们创建Handler对象时,就与该线程和该线程的消息队列相绑定,如果未与当前线程和线程队列绑定就无法正常执行事件的分发处理。我们在主线程创建Handler,就会与主线程相绑定,Handler对象隐式的持有外部对象的引用,该外部对象通常是指Activity,故要避免内存溢出

>6、ThreadLocal并不是线程,是一个数据存储类,它的作用是可以在每个线程中存储数据。Handler创建的时候会采用当前线程的Looper来构造消息循环系统,而ThreadLocal可以在不同的线程中互不干扰地存储并提供数据,通过ThreadLocal可以轻松获取每个线程的Looper。ThreadLocal<T> mThreadLocal = new ThreadLocal<T>();mThreadLocal能够存储当前自己线程下的值,多个线程间的数据不干扰。不同线程访问同一个ThreadLocal的get方法,ThreadLocal内部会从各自的线程中取出一个数组,然后再从数组中根据当前ThreadLocal的索引去查找出对应的value值。很显然,不同线程中的数组是不同的,这就是为什么通过ThreadLocal可以在不同的线程中维护一套数据的副本并且彼此互不干扰。

小结:

1.一个线程对应一个Looper

2.一个Looper对应一个MessageQueue消息队列

3.一个线程对应一个MessageQueue消息队列

4.线程,Looper,MessageQueue消息队列三者一一对应

5.post的一系列方法最终也是通过send的一系列方法来实现的如:handler.postAtTime(),handler.sendMessageAtTime()

6.一个线程可以有多个Handler

handler流程


Handler发送消息的方法流程

>7、避免handler造成内存泄漏:

①先说handler导致activity内存泄露的原因:

handler发送的消息在当前handler的消息队列中,如果此时activity finish掉了,那么消息队列的消息依旧会由handler进行处理,若此时handler声明为内部类(非静态内部类),我们知道内部类天然持有外部类的实例引用,那么就会导致activity无法回收,进而导致activity泄露。

②为何handler要定义为static?

因为静态内部类不持有外部类的引用,所以使用静态的handler不会导致activity的泄露

③为何handler要定义为static的同时,还要用WeakReference 包裹外部类的对象?

这是因为我们需要使用外部类的成员,可以通过"activity. "获取变量方法,更新UI等,如果直接使用强引用,显然会导致activity泄露。

/**

* handler为何会导致内存泄露:

* 如果handler为内部类(非静态内部类),那么会持有外部类的实例,若在handler.sendMessage的时候,activity finish掉了,那么此时activity将无法得到释放,如果申明handler为静态内部类,则不会含有外部类的引用,但是需要在handler中更新UI(注意此时handler为static),则需要引入一个activity引用,显然必须是弱引用,之所以使用弱引用,是因为handler为static,使用activity的弱引用来访问activity对象内的成员

*/

private static class MyHandler extends Handler {

private WeakReference<HandlerOOMActivity> mWeakReference;

public MyHandler(HandlerOOMActivity activity)

{

mWeakReference = new WeakReference<>(activity);

}

@Override

public void handleMessage(Message msg) {

super.handleMessage(msg);

if(mWeakReference!=null)

{

Activity activity = mWeakReference.get();

if(activity!=null){

//handler消息处理

activity.tv.setText("我是更改后的文字");

}

}

}     

在外部Activity释放消息队列,清除Message和Runnable:

@Overrideprotected void onDestroy() {

super.onDestroy();

mHandler.removeCallbacksAndMessages(null); 

}

.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,809评论 6 513
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,189评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 167,290评论 0 359
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,399评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,425评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,116评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,710评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,629评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,155评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,261评论 3 339
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,399评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,068评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,758评论 3 332
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,252评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,381评论 1 271
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,747评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,402评论 2 358

推荐阅读更多精彩内容