Android 必懂系列 —— handler机制【由浅及深到源码分析】
发布日期:2021-05-08 16:16:37 浏览次数:21 分类:精选文章

本文共 6060 字,大约阅读时间需要 20 分钟。

【1】handler在Android 开发中到底有什么用?

首先我们要先搞懂handler到底被设计出来有什么用。——一种东西被设计出来肯定就有它存在的意义,而Handler的意义就是切换线程。(线程间通信) 常用的场景就是:网络交互后切换到主线程进行UI更新。

(1) 为什么不直接在子线程更新UI?

Android的UI是线程不安全的,肯定不能同时多个线程操作UI线程。如果加锁又会降低UI的效率,所以通常不能在子线程更新UI。


【2】handler的简单使用如下:

public class MainActivity extends AppCompatActivity {   			//传递的数据			Bundle bundle = new Bundle();			bundle.putString("msg", "传递我这个消息");			    @Override    protected void onCreate(Bundle savedInstanceState) {           super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);         	//数据的接收	final Handler handler = new Handler() {   	        @Override	        public void handleMessage(Message msg) {   	            super.handleMessage(msg);	            if (msg.what == 0x11) {   	            	//这里模拟就获取到闯过来的数据就行了	                Bundle bundle = msg.obj;	                String date = bundle.getString("msg");	            }	        }	};        new Thread(new Runnable() {               @Override            public void run() {                  //发送数据				Message message = Message.obtain();				message.obj=bundle;  				message.what = 0x11;				handler.sendMessage(message);            }        }).start();    }

在主线程中创建handler对象,并且重写handleMessage方法,在子线程中发送message对象。

  • 注意:不建议直接new Message,Message内部保存了一个缓存的消息池,我们可以用obtain从缓存池获得一个消息,Message使用完后系统会调用recycle回收,如果自己new很多Message,每次使用完后系统放入缓存池,会占用很多内存的。

【3】handler 原理分析:

使用Handler方式进行异步消息处理主要由Message,Handler,MessageQueue,Looper四部分组成:

  • (1)Message,线程之间传递的消息,用于不同线程之间的数据交互。Message中的what字段用来标记区分多个消息,arg1、arg2 字段用来传递int类型的数据,obj可以传递任意类型的字段。

  • (2)Handler,用于发送和处理消息。其中的sendMessage()用来发送消息,handleMessage()用于消息处理,进行相应的UI操作。

  • (3)MessageQueue,消息队列(先进先出),用于存放Handler发送的消息,一个线程只有一个消息队列。

  • (4)Looper,可以理解为消息队列的管理者,当发现MessageQueue中存在消息,Looper就会将消息传递到handleMessage()方法中,同样,一个线程只有一个Looper。

Handler实现原理如下图:

在这里插入图片描述
实现逻辑顺序:
在这里插入图片描述
过程模拟:
在这里插入图片描述
messagequeue是一个由单链表实现的优先级队列。由when决定,根据执行时刻决定优先顺序。


【4】源码分析

  • (1)handler 源码分析:

在这里插入图片描述

发现很多sendpost开头函数,都是发送消息的,最终都会调用到sendMessageAtTime这个函数,而这个函数又会调用到handler里面的enqueueMessage函数,这个函数又会调用到MessageQueue里面的enqueueMessage函数。然后我们来分析MessageQueue。

  • (2)MessageQueue 源码分析:

在这里插入图片描述

MessageQueue主要作用就两个:1.存放消息进入MessageQueue。2.从这个消息队列中取出一个Message。

所以来到存放函数:enqueueMessage

boolean enqueueMessage(Message msg, long when) {   ......		if (p == null || when == 0 || when < p.when) {                   // New head, wake up the event queue if blocked.                msg.next = p;                mMessages = msg;                needWake = mBlocked;            } else {                   // Inserted within the middle of the queue.  Usually we don't have to wake                // up the event queue unless there is a barrier at the head of the queue                // and the message is the earliest asynchronous message in the queue.                needWake = mBlocked && p.target == null && msg.isAsynchronous();                Message prev;                for (;;) {                       prev = p;                    p = p.next;                    if (p == null || when < p.when) {                           break;                    }                    if (needWake && p.isAsynchronous()) {                           needWake = false;                    }                }                msg.next = p; // invariant: p == prev.next                prev.next = msg;            }........}

直接看到else里面的for死循环,大概意思就是如果要存放的这个Message的when要比当前的这个Message的when要小的话就直接将要存放的Message放到当前这个Message的前一个,如果不是那么久依次向下找。

说完入队,我们来看看Message如何取出:next函数

Message next() {   		...		...		...	if (msg != null) {                       if (now < msg.when) {                           // Next message is not ready.  Set a timeout to wake up when it is ready.                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);                    } else {                           // Got a message.                        mBlocked = false;                        if (prevMsg != null) {                               prevMsg.next = msg.next;                        } else {                               mMessages = msg.next;                        }                        msg.next = null;                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);                        msg.markInUse();                        return msg;                    }           }           ...           ...           ...   }

就从当前的MessageQueue的头部取出就可以了。

  • (3)Looper源码分析:

首先我们要知道每个线程都只有一个Looper实例,这个是怎么实现的呢?

要想清楚这个问题,我们就必须知道ThreadLocal
下面就具体说说ThreadLocal运行机制。

//ThreadLocal.javapublic T get() {       Thread t = Thread.currentThread();    ThreadLocalMap map = getMap(t);    if (map != null) {           ThreadLocalMap.Entry e = map.getEntry(this);        if (e != null) {               @SuppressWarnings("unchecked")            T result = (T)e.value;            return result;        }    }    return setInitialValue();}public void set(T value) {       Thread t = Thread.currentThread();    ThreadLocalMap map = getMap(t);    if (map != null)        map.set(this, value);    else        createMap(t, value);}

ThreadLocal类中的getset方法可以大致看出来,有一个ThreadLocalMap变量,这个变量存储着键值对形式的数据。

-keythis,也就是当前ThreadLocal变量。

  • valueT,也就是要存储的值。

然后继续看看ThreadLocalMap哪来的,也就是getMap方法:

//ThreadLocal.javaThreadLocalMap getMap(Thread t) {       return t.threadLocals;}//Thread.javaThreadLocal.ThreadLocalMap threadLocals = null;

原来这个ThreadLocalMap变量是存储在线程类Thread中的。

所以ThreadLocal的基本机制就搞清楚了。

在每个线程中都有一个threadLocals变量,这个变量存储着ThreadLocal对应的需要保存的对象

这样带来的好处就是,在不同的线程,访问同一个ThreadLocal对象,但是能获取到的值却不一样。

挺神奇的是不是,其实就是其内部获取到的Map不同,MapThread绑定,所以虽然访问的是同一个ThreadLocal对象,但是访问的Map却不是同一个,所以取得值也不一样。

ThreadLocal原理见下图(个人理解,如果有误,欢迎指正):

在这里插入图片描述

这样,由于Looper里面的ThreadLocal是final,且只有一个的,所以每个线程来访问时,只会有同一个ThreadLocal对象,并且在prepare函数中创建Looper时,如果已经存在就抛出异常,由此就保证了每个线程都只有一个Looper对象。

static final ThreadLocal
sThreadLocal = new ThreadLocal
();private static void prepare(boolean quitAllowed) { if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper(quitAllowed));}public static @Nullable Looper myLooper() { return sThreadLocal.get();}
上一篇:Linux_MySQL_CentOS6/7_双网卡配置
下一篇:Jetpack之DataBinding讲解

发表评论

最新留言

留言是一种美德,欢迎回访!
[***.207.175.100]2025年03月30日 02时02分18秒