Handler详解(上)
Handler详解(中)
本章节讲述Handler的更多的知识
一.Handler内存泄露问题
上章节讲到,发送消息最终都会执行到Handler类的enqueueMessage()方法。而enqueueMessage()方法中将this对象赋值给Message的target。而Message有可能因为需要延时而存放在MessageQueue中暂时得不到释放。通俗一点将就是 每个发出去的Message对象都会持有Handler对象的引用。而如果Activity/Fragment中使用Handler时,如果使用内部类方式使用。就造成Handler持有Activity/Fragment的引用。这样如果退出页面时还有未处理的消息。就会导致Handler被持有,也就会导致Activity/Fragment被持有。显然就容易造成内存泄漏。
反面举例
匿名内部类 方式声明Handler
package com.example.myapplication.handler;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import com.example.myapplication.R;
public class HandlerActivity extends AppCompatActivity {
private TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler);
findView();
}
/**
* 初始化各种View
*/
private void findView() {
textView = findViewById(R.id.activity_handler_textview);
newThreadMethod();
}
/**
* Handler内部类
*/
private Handler handler = new Handler() {
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 1:
int num1 = msg.arg1;
int num2 = msg.arg2;
String string = (String) msg.obj;
String result = "主线程即UI线程 接收 子线程发送的Message num1: " + num1 + " num2: " + num2 + " result: " + string;
textView.setText(result);
Log.d("TAG", "主线程即UI线程 接收 子线程发送的Message num1: " + num1 + " num2: " + num2 + " result: " + result);
break;
}
}
};
/**
* 模拟子线程
*/
private void newThreadMethod() {
new Thread(new Runnable() {
@Override
public void run() {
Message msg = Message.obtain();
msg.what = 1;
msg.arg1 = 11;
msg.arg2 = 111;
msg.obj = "子线程发送的消息";
handler.sendMessage(msg);
}
}).start();
}
}
这样的话,就有可能造成内存泄露。
解决方法
静态内部类+弱引用方式+Activity销毁时销毁Handler
代码
package com.example.myapplication.handler;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import com.example.myapplication.R;
import java.lang.ref.WeakReference;
public class HandlerActivity extends AppCompatActivity {
private TextView textView;
private Handler mHandler = new MyHandler(this);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler);
findView();
}
/**
* 初始化各种View
*/
private void findView() {
textView = findViewById(R.id.activity_handler_textview);
newThreadMethod();
}
/**
* Handler静态内部类
*/
private static class MyHandler extends Handler {
private WeakReference<HandlerActivity> activityWeakReference;
public MyHandler(HandlerActivity handlerActivity) {
if (null == handlerActivity) {
return;
}
activityWeakReference = new WeakReference<>(handlerActivity);
}
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
if (null != activityWeakReference) {
HandlerActivity handlerActivity = activityWeakReference.get();
if (null != handlerActivity) {
switch (msg.what) {
case 3:
int num1 = msg.arg1;
int num2 = msg.arg2;
String string = (String) msg.obj;
String result = "内部静态类方式声明Handler获取num1----:" + num1 + " num2----:" + num2 + " Msg----:" + string;
handlerActivity.setTextView(result);
break;
}
}
}
}
}
/**
* 模拟子线程
*/
private void newThreadMethod() {
if (null == mHandler) {
return;
}
new Thread(new Runnable() {
@Override
public void run() {
Message msg = Message.obtain();
msg.what = 3;
msg.arg1 = 33;
msg.arg2 = 333;
msg.obj = "子线程发送的消息";
mHandler.sendMessage(msg);
}
}).start();
}
/**
* 模拟Handler中调用Activity方法
*/
private void setTextView(String result) {
textView.setText(result);
Log.d("TAG", result);
}
/**
* onDestroy方法
*/
@Override
protected void onDestroy() {
super.onDestroy();
if (mHandler != null) {
mHandler.removeCallbacksAndMessages(null);
mHandler = null;
}
}
}
结果
D/TAG: 内部静态类方式声明Handler获取num1----:33 num2----:333 Msg----:子线程发送的消息
总结
<1> 首先,静态内部类不会持有外部类的引用。这样就避免了Activity/Fragment页面退出时,不能被回收的问题。
<2> 其次,创建Handler时,选择弱引用。也是希望内存能够得到及时的回收。因为 创建Handler选择了静态内部类 如果想调用Handler所在的类的方法,要么方法是静态的(这样显然也会造成内存泄漏问题)。要么能够拿到Handler所在的类的对象。同样为了防止内存泄漏问题,我们选择了弱引用。
<3> 最后,销毁页面时,将Handler中的Message清空,且将Handler对象置空。及时清理未发送的消息。对内存增加一层保护。
Handler类的removeCallbacksAndMessages方法源码
public final void removeCallbacksAndMessages(Object token) {
mQueue.removeCallbacksAndMessages(this, token);
}
可是看出,Handler类的removeCallbacksAndMessages方法其实调用的是MessageQueue类的removeCallbacksAndMessages方法
MessageQueue类的removeCallbacksAndMessages方法源码
void removeCallbacksAndMessages(Handler h, Object object) {
if (h == null) {
return;
}
synchronized (this) {
Message p = mMessages;
// Remove all messages at front.
while (p != null && p.target == h
&& (object == null || p.obj == object)) {
Message n = p.next;
mMessages = n;
p.recycleUnchecked();
p = n;
}
// Remove all messages after front.
while (p != null) {
Message n = p.next;
if (n != null) {
if (n.target == h && (object == null || n.obj == object)) {
Message nn = n.next;
n.recycleUnchecked();
p.next = nn;
continue;
}
}
p = n;
}
}
}
该方法中
参数1:Handler h 就是我们传进来的Handler对象。
参数2:Object类型,我们传的null。
下面看源码
Message p = mMessages;
while (p != null && p.target == h&& (object == null || p.obj == object)) {
Message n = p.next;
mMessages = n;
p.recycleUnchecked();
p = n;
}
也就是说,先拿到消息队列中的头部消息。然后while循环中遍历它的next也就是下一个消息。然后执行Message对象的recycleUnchecked()方法。这个方法我们已经讲过。是用来回收消息的。
总结
咱们在使用Handler时,在页面销毁时,调用的Handler类的removeCallbacksAndMessages方法。其实内部调用的是MessageQueue类的removeCallbacksAndMessages方法。最终调用的是Message类的recycleUnchecked方法。这个方法我们前面已经讲过。主要用来回收消息。
二.线程切换原理
上一章节已经讲解过 消息发送和消息接收的源码 可以看出 消息发送时 是Looper创建的MessageQueue对象。消息接收时 是Looper取出的MessageQueue对象。也就是说Handler的线程切换是Looper做的处理。
也就是说,无论消息在什么线程发送。
Looper在主线程中创建。那么消息的接收就在主线程中接收。
Looper在子线程中创建。那么消息的接收就在子线程中接收。
举例子
1.主线程创建Handler接收消息 子线程发送消息
代码
package com.example.rxjava20;
import android.annotation.SuppressLint;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
public class HandlerActivity extends AppCompatActivity {
@SuppressLint("HandlerLeak")
private Handler handler = new Handler() {
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case MSG_WHAT:
Log.d("HandlerActivity", "handleMessage方法接收消息 线程:" + Thread.currentThread().getName());
break;
}
}
};
private TextView mTextView;
private final int MSG_WHAT = 1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler);
//模拟子线程发送消息
mTextView = findViewById(R.id.activity_handler_textview);
mTextView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new Thread(new Runnable() {
@Override
public void run() {
handler.sendEmptyMessage(MSG_WHAT);
Log.d("HandlerActivity", "sendEmptyMessage方法发送消息 线程:" + Thread.currentThread().getName());
}
}).start();
}
});
}
}
结果
D/HandlerActivity: sendEmptyMessage方法发送消息 线程:Thread-7
D/HandlerActivity: handleMessage方法接收消息 线程:main
说明
因为创建Handler没有传Looper对象。Looper对象是取的系统默认的主线程中的创建的Looper。源码上章节已讲。这里不再赘述。从结果可以看出 消息是在子线程发送的,接收却是在主线程。
2.子线程创建Handler。
代码
package com.example.rxjava20;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
public class HandlerActivity extends AppCompatActivity {
private Handler handler;
private TextView mTextView;
private final int MSG_WHAT = 1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler);
//模拟子线程发送消息
mTextView = findViewById(R.id.activity_handler_textview);
mTextView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
handler = new Handler() {
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
Log.d("HandlerActivity", "handleMessage方法接收消息 线程:" + Thread.currentThread().getName());
}
};
handler.sendEmptyMessage(MSG_WHAT);
Log.d("HandlerActivity", "sendEmptyMessage方法发送消息 线程:" + Thread.currentThread().getName());
Looper.loop();
}
}).start();
}
});
}
}
结果
D/HandlerActivity: sendEmptyMessage方法发送消息 线程:Thread-7
D/HandlerActivity: handleMessage方法接收消息 线程:Thread-7
说明
消息是在子线程发送的,接收也是在子线程(当前子线程)。
4.总结
Looper作为消息循环的核心,其内部包含了一个消息队列MessageQueue ,用于记录所有待处理的消息。
通过Looper.loop()不断地从MessageQueue中抽取Message,按分发机制将消息分发给目标处理者(消息的target对象,即发送Message的Handler对象)。
其实 MessageQueue是Looper拿出来的,Message是存在MessageQueue中的,消息分发是调用了Message的target对象及发送消息的Handler对象。然后在消息分发的方法中调用 handleCallback(msg);方法或者handleMessage(msg);方法。完成消息的接收。
这就是线程切换的本质。
5.注意
手动创建Looper时,调用Looper.loop()方法时 切记一定要在适当的时机调用Looper的quit()方法。目的是取消消息队列中的消息。
Looper类的quit()方法源码
public void quit() {
mQueue.quit(false);
}
可见,是调用的MessageQueue的quit方法。
MessageQueue类的quit()方法源码
void quit(boolean safe) {
if (!mQuitAllowed) {
throw new IllegalStateException("Main thread not allowed to quit.");
}
synchronized (this) {
if (mQuitting) {
return;
}
mQuitting = true;
if (safe) {
removeAllFutureMessagesLocked();
} else {
removeAllMessagesLocked();
}
// We can assume mPtr != 0 because mQuitting was previously false.
nativeWake(mPtr);
}
}
而该方法内部的 removeAllFutureMessagesLocked();方法和removeAllMessagesLocked();方法和上述讲解的removeCallbacksAndMessages();方法一样。都是清空MessageQueue中消息状态的。这里就不再赘述了。
6.最后
刚刚说到手动调用Looper类的loop()方法时,需要收到调用Looper类的quit()方法。清空消息。但是默认主线程ActivityThread类的main()方法中创建的Looper是不允许退出的。详情上一篇已经讲过。
不过ActivityThread类中 handleMessage方法中 系统也给我们处理了quit方法。
public void handleMessage(Message msg) {
...
case EXIT_APPLICATION:
if (mInitialApplication != null) {
mInitialApplication.onTerminate();
}
Looper.myLooper().quit();
break;
...
}
三.同步屏障
1.简介
同步屏障是一套为了让某些特殊的消息得以更快被执行的机制。
2.设置同步的地方 源码可知有两个地方可以设置同步消息。
设置1处
final boolean mAsynchronous;
@hide
public Handler(@Nullable Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
//赋值是否同步
mAsynchronous = async;
}
设置2处
final boolean mAsynchronous;
@hide
public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
//赋值是否同步
mAsynchronous = async;
}
也就是说,赋值是否同步的地方是Handler的两个构造方法。但是这两个构造方法都是@hide类型的。也就是我们是主动调不了的。那还有什么意义呢?接着向下看。
由于我们无法主动创建设置同步的Handler。也就是说Handler类的mAsynchronous属性我们自己使用时(系统可以设置同步消息 下面会讲)这个字段就会是默认的false。那么这个字段什么时候使用呢?
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
源码我们已经分析过了。也就是发送消息在Handler类的最后一个方法enqueueMessage()方法中,会设置Message是否同步
Message类的setAsynchronous()方法源码。
public void setAsynchronous(boolean async) {
if (async) {
flags |= FLAG_ASYNCHRONOUS;
} else {
flags &= ~FLAG_ASYNCHRONOUS;
}
}
Message类中还有一个方法isAsynchronous()方法。来取Message的同步状态。
isAsynchronous()方法源码
public boolean isAsynchronous() {
return (flags & FLAG_ASYNCHRONOUS) != 0;
}
也就是说,在发送消息时,在Handler类的enqueueMessage()方法调用Message类的setAsynchronous()方法设置消息是否同步。然后外部通过isAsynchronous()方法获取消息是否同步。那么isAsynchronous()方法什么时候调用呢?
MessageQueue类中的enqueueMessage()方法中有两处调用。
boolean enqueueMessage(Message msg, long when) {
synchronized (this) {
if (p == null || when == 0 || when < p.when) {
} else {
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() 即消息入队列时。会判断消息是否是同步消息。
MessageQueue类中的next()方法中有一处调用。
Message next() {
// 阻塞时间
int nextPollTimeoutMillis = 0;
for (;;) {
// 阻塞对应时间
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
// 同步屏障,找到下一个异步消息
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
// 如果上面有同步屏障,但却没找到异步消息,
// 那么msg会循环到链表尾,也就是msg==null
if (msg != null) {
···
} else {
// 没有消息,进入阻塞状态
nextPollTimeoutMillis = -1;
}
···
}
}
}
如何发送异步消息呢?上面我们说过了。创建Handler时两个可以设置同步消息的构造方法都是@hide的。也就是说谷歌不推荐也不允许我们使用这两个构造方法创建同步消息?那么其他地方可以发送同步消息吗?答案是可以的。既然Handler的构造方法不能使用,那么怎么设置同步消息呢?
通过调用Message类的setAsynchronous方法。
调用出举例
举例1
ViewRootImpl类
public void synthesizeInputEvent(InputEvent event) {
Message msg = mHandler.obtainMessage(MSG_SYNTHESIZE_INPUT_EVENT, event);
msg.setAsynchronous(true);
mHandler.sendMessage(msg);
}
举例2
InputMethodManager类
private void flushPendingEventsLocked() {
mH.removeMessages(MSG_FLUSH_INPUT_EVENT);
final int count = mPendingEvents.size();
for (int i = 0; i < count; i++) {
int seq = mPendingEvents.keyAt(i);
Message msg = mH.obtainMessage(MSG_FLUSH_INPUT_EVENT, seq, 0);
msg.setAsynchronous(true);
msg.sendToTarget();
}
}
举例3
PhoneWindowManager类
private void showPictureInPictureMenu(KeyEvent event) {
if (DEBUG_INPUT) Log.d(TAG, "showPictureInPictureMenu event=" + event);
mHandler.removeMessages(MSG_SHOW_PICTURE_IN_PICTURE_MENU);
Message msg = mHandler.obtainMessage(MSG_SHOW_PICTURE_IN_PICTURE_MENU);
msg.setAsynchronous(true);
msg.sendToTarget();
}
经过举例可知,同步消息一般都是系统绘制UI或者响应事件时发送的。
比如:屏幕刷新机制
我们的手机屏幕刷新频率有不同的类型,60Hz、120Hz等。60Hz表示屏幕在一秒内刷新60次,也就是每隔16.6ms刷新一次。屏幕会在每次刷新的时候发出一个 VSYNC 信号,通知CPU进行绘制计算。具体到我们的代码中,可以认为就是执行onMesure()、onLayout()、onDraw()这些方法。
四.Looper循环和ANR
由源码中可知,Looper类的loop()方法是个无限循环的方法。但是使用Handler时还必须创建Looper对象,并且使用Looper类的loop()方法。而且默认是在主线程中。那么这样不会造成ANR吗?答案是:不会造成ANR。
ActivityThread类main方法部分源码
public static void main(String[] args) {
...
Looper.prepareMainLooper();
...
Looper.loop();
}
Looper类的loop方法部分源码
public static void loop() {
...
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
...
}
...
}
也就是说,当从MessageQueue中取出的Message为空时,即消息队列暂时没有消息时,会主动退出For循环(无限循环)。那么消息队列中再有新的消息来时,怎么唤醒呢?是通过Linux中的epoll机制来实现的。
For无限循环举例
代码
package com.wjn.rxdemo.handler;
import android.os.Bundle;
import android.util.Log;
import androidx.appcompat.app.AppCompatActivity;
import com.wjn.rxdemo.R;
public class HandlerActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler);
for (; ; ) {
Log.d("TAG", "For循环执行...");
}
}
}
结果 无限打印
D/TAG: For循环执行...
...
...
...
对于Java程序来说,一个类有main方法。就是入口。main方法中的代码执行完成后程序就结束了。Android的ActivityThread类中也是有main方法的。因为APP只要你不主动退出。它会一直运行的。为了让程序不退出的话,写一个死循环,那么main方法中的代码永远不会执行完,这样程序就不会自己退出了。Android当然也是这么干的。
那么无限循环还是在主线程中。不会ANR吗?不会对CPU资源过度消耗吗?个人认为既然Android是这样设计的。那么可能不会。那么原因是什么呢?
1.ANR问题
<1> 什么是ANR
ANR是Application Not Responding 也就是Android程序无响应。为什么没响应呢?一般是因为主线程做了耗时操作。
<2> 什么是响应
响应就是UI界面的刷新,UI交互的处理 比如点击按钮等等操作。
<3> 谁来响应
就是loop方法中进行响应的。
<4> 为什么没响应
就是loop方法中被阻塞了,导致无法处理其他的Message了。
<5> 结论
主线程做耗时操作本质上不是阻塞了主线程,而是阻塞了Looper的loop方法。导致loop方法无法处理其他事件,导致出现了ANR事件。
也就是说Looper类的loop()循环的取MessageQueue中的Message。然后分发。如果其中一个Message有问题,导致loop方法不能操作其他的Message。这样就会卡在这一个有问题的Message中。导致队列中其他的消息无法得到及时的处理。造成事故也就是无响应即ANR。
2.资源问题
ANR的问题说明白了,那么无限的循环不会造成资源的浪费吗?loop循环在没有Message时,会怎样呢?
其实 在消息队列为空的时候,Looper实际上处于休眠状态,只要当有Handler发送消息的时候,Looper才会被唤醒,去进行分发消息。那么是怎么实现的呢?
Looper的唤醒与挂起是靠Linux中的epoll机制来实现的,通过对文件的可读事件的监听来实现唤醒。
这就是C++层面的知识了。我也没太动多少。但是看Java层源码时发现MessageQueue中有很多native的代码。所以猜测MessageQueue类是实现Java层与C++层的互动的纽带。
MessageQueue类中native源码
private native static long nativeInit();
private native static void nativeDestroy(long ptr);
private native void nativePollOnce(long ptr, int timeoutMillis); /*non-static for callbacks*/
private native static void nativeWake(long ptr);
private native static boolean nativeIsPolling(long ptr);
private native static void nativeSetFileDescriptorEvents(long ptr, int fd, int events);
MessageQueue类使用native方法举例
(1) MessageQueue的构造方法就使用了native方法
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}
private long mPtr; // used by native code
调用了nativeInit方法,在native层创建了native层的MessageQueue,mPtr是保存了NativeMessageQueue的指针,后续的线程挂起和线程的唤醒都要通过这个指针来完成。
(2) 大家还记不记得在发送消息时最后走到MessageQueue类的enqueueMessage方法
boolean enqueueMessage(Message msg, long when) {
...
synchronized (this) {
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
在此方法中调用了nativeWake方法。来唤醒线程。但是只有needWake=true是才会执行
两种情况会唤醒线程
1.(队列为空,消息无需延时或消息执行时间比队列头部消息早) && (线程处于挂起状态时(mBlocked = true))。
2.线程挂起(mBlocked = true)&& 消息循环处于同步屏障状态,这时如果插入的是一个异步消息,则需要唤醒。
唤醒操作具体靠Linux中的epoll机制来实现的,通过对文件的可读事件的监听来实现唤醒。
补充:什么是epoll机制
epoll可以简单的理解为一个监听事件,在Linux上通过epoll机制监听一个事件,没什么事的时候我就让出CPU,进行休眠,当这个事件触发的时候我就从沉睡中唤醒开始处理。就像按钮的点击事件一样,点击了,监听到这个点击事件就会触发按钮的onClick方法。不过在LInxu上是通过文件的读写来完成的。
五.Message优先级
由源码分析可知,如图
举例 发送消息 sendMessage(msg)
handler.sendMessage(msg)
内部调用 sendMessageDelayed方法
sendMessageDelayed(msg, 0);
sendMessageDelayed方法源码
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
也就是说, sendMessageDelayed方法内部 关于延时的处理是 获取 从开机到现在的毫秒数
SystemClock.uptimeMillis()
也就是说,虽然我们调用的是sendMessage方法,没有延时操作。但是Handler会给我们加上 从开机到现在的毫秒数的时间。然后最后执行MessageQueue类的enqueueMessage方法入栈。具体上一章节已经讲过。
也就是说,如果我们想提升我们Message的优先级,最好的办法就是把这个延时的long类型的字段变成0。这样入栈的时候消息会在链表的头部,取的时候,会优先取。那么有没有这个延时字段是0的发送消息的方法呢?
我们知道,Handler类发送消息,最终都会走到Handler类的enqueueMessage方法,然后再到MessageQueue类的enqueueMessage方法。
Handler类的enqueueMessage方法有两处地方调用,
调用处1 sendMessageAtTime方法
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
这个方法,已经讲过。这里不赘述了。这个方法我们是没有办法把延时字段uptimeMills置成0的。
调用处2 sendMessageAtFrontOfQueue方法
public final boolean sendMessageAtFrontOfQueue(@NonNull Message msg) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, 0);
}
可以看到,sendMessageAtFrontOfQueue方法最后调用enqueueMessage方法时,延时字段传的就是0。
sendMessageAtFrontOfQueue方法 注释
优点
因为没有延时,所以可以提升Message的优先处理。
缺点
it can easily starve the message queue, cause ordering problems, or have other unexpected side-effects
它很容易使消息队列挨饿,导致排序问题,或产生其他意想不到的副作用
大致的意思是,这个方法发送消息可能造成意想不到的BUG。