【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】源码分析
发现很多send
和post
开头函数,都是发送消息的,最终都会调用到sendMessageAtTime
这个函数,而这个函数又会调用到handler里面的enqueueMessage
函数,这个函数又会调用到MessageQueue里面的enqueueMessage
函数。然后我们来分析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的头部取出就可以了。
首先我们要知道每个线程都只有一个Looper实例,这个是怎么实现的呢?
要想清楚这个问题,我们就必须知道ThreadLocal
下面就具体说说ThreadLocal运行机制。
//ThreadLocal.java
public 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
类中的get
和set
方法可以大致看出来,有一个ThreadLocalMap
变量,这个变量存储着键值对形式的数据。
-key
为this
,也就是当前ThreadLocal
变量。
value
为T
,也就是要存储的值。
然后继续看看ThreadLocalMap哪来的,也就是getMap方法:
//ThreadLocal.java
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
//Thread.java
ThreadLocal.ThreadLocalMap threadLocals = null;
原来这个ThreadLocalMap
变量是存储在线程类Thread
中的。
所以ThreadLocal
的基本机制就搞清楚了。
在每个线程中都有一个threadLocals
变量,这个变量存储着ThreadLocal
和对应的需要保存的对象
。
这样带来的好处就是,在不同的线程,访问同一个ThreadLocal
对象,但是能获取到的值却不一样。
挺神奇的是不是,其实就是其内部获取到的Map
不同,Map
和Thread
绑定,所以虽然访问的是同一个ThreadLocal
对象,但是访问的Map
却不是同一个,所以取得值也不一样。
ThreadLocal原理见下图(个人理解,如果有误,欢迎指正):
这样,由于Looper里面的ThreadLocal是final,且只有一个的,所以每个线程来访问时,只会有同一个ThreadLocal对象,并且在prepare函数中创建Looper时,如果已经存在就抛出异常,由此就保证了每个线程都只有一个Looper对象。
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
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();
}
转载:https://blog.csdn.net/qq_45866344/article/details/116453332