笔记(十六)——安卓消息机制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 mThreadLocal = new ThreadLocal();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 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); 

}

.

你可能感兴趣的:(笔记(十六)——安卓消息机制Handler)