Handler工作流程
(1)通过sendMessage将Message存入MessageQueue:
Handler.sendMessage(Message msg)
->Handler.sendMessageDelayed(Message msg,long delayMillis)
->Handler.sendMessageAtTime(MEssage msg,long uptimeMillis)
->Handler.enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis)
->MessageQueue.enqueueMessage(Message msg, long when)
流程中的MessageQueue.enqueueMessage的作用是存储消息,源码如下:
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
synchronized (this) {
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle();
return false;
}
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
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;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
从源码中可以看出, MessageQueue.enqueueMessage是根据when的值将Message插入消息链表,when的值越小插入的位置越靠前,如果when是0,则插入链表表头。调用sendMessage时when的值为SystemClock.uptimeMillis(),调用sendMessageDelayed(Message msg, long delayMillis)时when的值为SystemClock.uptimeMillis() + delayMillis。可以看出when的值实际上就是当前时间+指定延迟毫秒数,MessageQueue中消息是按照时间顺序排序的。
(2)Looper.loop()从MessageQueue中取出消息,交给Handler处理。
public static void loop() {
...
...
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
...
try {
msg.target.dispatchMessage(msg);
...
}
}
...
}
由源码可以看出,loop()通过MessageQueue的next()方法从消息队列中取出消息。然后调用Handler.dispatchMessage(Message msg) -> Handler.handleMessage(Message msg)方法让Handler处理消息。如果消息队列没有消息可以取出了,MessageQueue的next()方法会被阻塞,所以loop()方法也会阻塞,直到消息队列中又存在消息了,next()不再阻塞。如果一直没有新消息处理,loop()会一直阻塞,如果想退出loop()循环,可以调用MessageQueue的quit(boolean safe)方法。
相关问题
一个线程有几个Handler?
一个线程有任意多个Handler。
一个线程有几个Looper?如何保证?1个Looper有几个MessageQueue?
一个线程有1个Looper。1个Looper有一个MessageQueue。
Looper的构造方法是私有的,Looper实例由Looper.prepare()来构造,prepare()代码如下:
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));
}
由代码可见,先判断ThreadLocal的get()方法判断ThreadLocalMap里面有没有Looper,如果已经有Looper了,则抛出异常,如果没有,则new一个Looper并调用ThreadLocal的set()方法将Looper保存进ThreadLocalMap中。
每一个线程Thread都会有一个变量ThreadLocal.ThreadLocalMap,这个Map是用来保存线程上下文的。(一个线程对应一个Map)prepare代码段中调用了set()方法,set()方法代码如下:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);//this就是ThreadLocal,value在这里对应Looper
else
createMap(t, value);
}
由代码中注释可以看出,ThreadLocal和Looper分别作为键和值被存入了Map中。 所以:一个Thread对应一个ThreadLocalMap,ThreadLocalMap中存在着一个键值对
在Looper类中,变量MessageQueue的类型为final,所以当Looper中的MessageQueue一旦初始化,就不能再被修改,所以一个Looper有一个MessageQueue。
public class MainActivity extends AppCompatActivity {
TextView textView;
static int count = 0;
Handler mHandler = new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
if(msg.what == 1){
super.handleMessage(msg);
textView.setText(String.valueOf(msg.arg1));
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = findViewById(R.id.text1);
new Thread() {
@Override
public void run() {
super.run();
for (int i = 0; i < 100; i++) {
Message msg = new Message();
msg.what = 1;
msg.arg1 = count ++;
mHandler.sendMessage(msg);
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
}
}
Handler是一个匿名内部类,它持有外部类Activity的实例引用,当GC垃圾回收机制进行回收时发现这个Activity居然还有其它引用存在,因而就不会去回收这个Activity,进而导致Activity泄露。
解决内存泄露的方法:使用static和WeakReference。因为静态内部类不持有外部类的引用,所以使用静态的handler不会导致activity的泄露。同时,我们需要使用外部类的成员,可以通过使用“activity.”类获取外部变量,如果直接使用强引用,显然会导致Activity泄露,所以使用弱引用WeakReference。解决问题后的代码如下:
public class MainActivity extends AppCompatActivity {
TextView textView;
static int count = 0;
MyHandler myHandler;
public static class MyHandler extends Handler{
private WeakReference<MainActivity> mWeakReference;
public MyHandler(MainActivity activity){
mWeakReference = new WeakReference<>(activity);
}
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
MainActivity mainActivity = mWeakReference.get();
if(msg.what == 1){
super.handleMessage(msg);
mainActivity.textView.setText(String.valueOf(msg.arg1));
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = findViewById(R.id.text1);
myHandler = new MyHandler(MainActivity.this);
new Thread() {
@Override
public void run() {
super.run();
for (int i = 0; i < 100; i++) {
Message msg = new Message();
msg.what = 1;
msg.arg1 = count ++;
myHandler.sendMessage(msg);
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
}
}
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
msg.target = this;//this是Handler
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
关注第一行代码:msg.target=this,这句话将Message和Handler绑定到了一起,即Message由同一个Handler发送,由同一个Handler处理。我们又知道,当Handler作为非静态内部类使用时,Handler持有外部Activity。所以造成了以下关系:Message->包含Handler->持有Activity。而Message是放在MessageQueue中等待让Handler处理的,试想一种情况,一个Message发送到MessageQueue后设定其1分钟之后再处理,那么在这一分钟之内,MessageQueue会一直占用一块内存,同时MessageQueue持有这个刚发送过来的Message,Message又持有Activity,所以这个Activity即使调用onDestroy()也不能将Activity的内存进行释放。
总结:MessageQueue->持有Message->包含Handler->持有Activity,只要MessageQueue中有消息未处理,该消息对应的Activity就一直不能被释放。
回到正题,为什么其他的内部类没有这个问题呢?
所有的内部类都会持有外部类对象,但其他内部类在持有外部类对象的时候没有什么耗时的操作,也没有另外一个东西去持有这个内部类。
public class Test1 extends AppCompatActivity {
Handler threadHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test1);
test();
Button button = findViewById(R.id.button2);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Message msg = new Message();
msg.what = 1;
threadHandler.sendMessage(msg);
}
});
}
public void test(){
new Thread(new Runnable() {
@Override
public void run() {
if(threadHandler == null){
Looper.prepare();
threadHandler = new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
if(msg.what == 1)
Toast.makeText(getApplicationContext(),"test",Toast.LENGTH_SHORT).show();
}
};
Looper.loop();
}
}
}).start();
}
}
可以调用Looper.quit()方法结束Looper。
6. 子线程中维护的Looper,消息队列无消息的时候的处理方式是什么?有什么用?
查看Looper.loop()源代码如下:
public static void loop(){
...
for (;;) {
Message msg = queue.next(); // might block
...
}
...
}
可见通过一个无限循环来不断去除Message,当没有Message时,阻塞,下面查看MessageQueue.next()方法:
Message next() {
...
for (;;) {
...
//nativePollOnce是一个native函数, nextPollTimeoutMillis值为-1则表示无限等待,直到
//有事件发生为止。如果值为0,则无需等待,立即返回。
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
...
if (msg != null && msg.target == null) {
...
}
if (msg != null) {
...
} else {
// No more messages.当没有消息时
nextPollTimeoutMillis = -1;
}
}
...
}
...
}
可以看出,当MessageQueue中没有消息时,会将nextPollTimeoutMillis设置为-1,等下次循环调用nativePollOnce()时,会使当前线程无限等待,线程被挂起,即MessageQueue.next()函数被阻塞->Looper.loop()函数被阻塞。
可以调用Looper.quit()方法结束Looper,唤醒当前线程。Looper.quit()具体实现下面再讲。
7. 既然可以存在多个Handler往MessageQueue中添加数据,那它内部是如何确保线程安全的?
查看源码,可以看到关于MessageQueue的操作都会加锁:
synchronized (this) {
...
}
对this加锁,this即为MessageQueue,保证了同时只有一个Handler访问MessageQueue。
8. 我们使用Message时应该如何创建它?
我们应该使用Handler.obtainMessage()方法获取Message,而不是使用new Message()。
首先,我们看一下Looper.quit(),它的作用是销毁一个Looper。Looper.quit()->调用MessageQueue.quit()->调用MessageQueue.removeAllMessagesLocked()->调用Message.recycleUnchecked()。Message.recycleUnchecked()源码如下:
...
private static Message sPool;//空消息队列
...
@UnsupportedAppUsage
void recycleUnchecked() {
// Mark the message as in use while it remains in the recycled object pool.
// Clear out all other details.
flags = FLAG_IN_USE;
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = UID_NONE;
workSourceUid = UID_NONE;
when = 0;
target = null;
callback = null;
data = null;
//将这个消息插入空消息链表头部
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;//把这个Message的next指向sPool
sPool = this;//sPool表头变为this刚销毁的Message
sPoolSize++;//记录空消息链表中空Message的数目
}
}
}
由以上源码可以看出,销毁一个Message时并没有把这个Message销毁掉,而是把这个Message的所有属性都重置为空,然后将这个Message的next属性指向sPool,sPool是空消息链表。此时刚刚被销毁的Message变成了空消息链表的表头。从内存上看,这个Message并没有被销毁,而是清除数据后存入了另一个消息列表中。
Handler.obtainMessage()会调用Message.obtain()来获取Message,以下是Message.obtain()的源码:
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
很显然的可以看出,Message.obtain()是在从sPoolSync空消息链表中取Message的,如果sPoolSync为空,才会new Message()。这样做就减少了不断的去new Message()的过程。试想如果我们每次创建Message时都用new Message()的方法,不仅浪费了时间,而且sPoolSync会变的越来越大,也浪费了内存。
这种模式称为享元设计模式,源码中使用该模式的好处在于,节省了new Message()的时间,而且不必在不用某个消息时去回收这个消息。如果不使用这种模式,需要Message时就new Message(),不使用时就销毁Message,则可能出现内存抖动(OOM)的问题。
9. Looper死循环为什么不会导致应用卡死?
首先了解一下什么是应用卡死(ANR):5秒钟之内没有响应输入的事件,比如按键、屏幕触摸等;广播接收器在10秒内没有执行完毕。
AMS管理机制:每一个应用都运行于自己的虚拟机中,也就是每一个应用都有自己的一个main函数。 应用启动流程:launcher->zygote->art application->activityThread->main()。应用中所有生命周期的函数(包括Activity、Service所有生命周期)都运行在这个Looper里面,而且,它们都是以消息的方式存在的。
看完上面两个概念不禁会想,不是说5秒钟不响应就会出现ANR吗?为什么在没有消息的时候Looper.loop()休眠好长时间都不会出现ANR呢?
分析:Looper.loop()休眠,即主线程休眠了,这时CPU时间片交给了应用中其它线程来使用,应用的主线程仍处于等待状态。仔细阅读ANR的概念会发现,ANR指的是消息(按键、屏幕触摸、广播等都是由Message形式传递的)没有被及时处理,产生ANR的根本原因不是由于线程在睡眠,而是由于没有及时处理消息。所以当MessageQueue中没有Message时主线程阻塞,此时没有消息可以处理,也就自然不会出现ANR。
比如,下面这个事件才会真正导致ANR:比如线程当前正在处理消息1,这时发生了一个点击事件,这个点击事件被排在了消息1之后处理,而消息1在点击事件之后5秒钟之内还没有被处理完,所以就造成了点击事件在5秒钟之内没有响应,所以会导致ANR。
结论:应用卡死ANR与Looper没有任何关系,Application在没有消息需要处理时,主线程睡眠;ANR导致应用卡死,而Looper在没有消息处理时会睡眠。