我是代码搬运工,不能仅仅只是搬运,还要整理一下。
使用Handler之前的准备工作有三步:
调用Looper.prepare()(主线程不需要调这个,因为APP创建时,main方法里面已经帮我们创建了)
创建Handler对象,重写handleMessage方法(你可以不重写),用于处理message回调的
调用Looper.loop()
其中:
Looper.prepare()的作用主要有以下三点:
创建Looper对象
创建MessageQueue对象,并让Looper对象持有
让Looper对象持有当前线程
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
// 规定了一个线程只有一个Looper,也就是一个线程只能调用一次Looper.prepare()
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
// 如果当前线程没有Looper,那么就创建一个,存到sThreadLocal中
sThreadLocal.set(new Looper(quitAllowed));
}
从上面的代码可以看出,一个线程最多只有一个Looper对象。当没有Looper对象时,去创建一个Looper,并存放到sThreadLocal中
private Looper(boolean quitAllowed) {
// 创建了MessageQueue,并供Looper持有
mQueue = new MessageQueue(quitAllowed);
// 让Looper持有当前线程对象
mThread = Thread.currentThread();
}
这里主要就是创建了消息队列MessageQueue,并让它供Looper持有,因为一个线程最多只有一个Looper对象,所以一个线程最多也只有一个消息队列。然后再把当前线程赋值给mThread。
Handler使用流程:
Handler.post(或sendMessage): handler发送消息msg
MessageQueue.enqueueMessage(msg, uptimeMillis):msg加入message队列
loop() 方法中从MessageQue中取出msg,然后回调handler的dispatchMessage,然后执行callback(如果有的话) 或 handleMessage。(注意,loop方法是一直在循环的,从前面的handler准备工作开始就已经一直在运行了)
如图所示:
Message的获取方式有两种:
1. Message msg = new Message();
2. Message msg = Message.obtain();
从全局池返回一个新的消息实例。允许我们在许多情况下避免分配新对象。
/**
* Return a new Message instance from the global pool. Allows us to
* avoid allocating new objects in many cases.
*/
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 对象,这里实际上是用到了一种享元设计模式,这种设计模式最大的特点就是复用对象,避免重复创建导致的内存浪费
/**
* Recycles a Message that may be in-use.
* Used internally by the MessageQueue and Looper when disposing of queued Messages.
*/
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 = -1;
when = 0;
target = null;
callback = null;
data = null;
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
每次message使用完了之后会调用recycleUnchecked回收message,方便下次使用
Message核心的信息有这些:
public int what;
public int arg1;
public int arg2;
public Object obj;
/*package*/ int flags;
/*package*/ long when;
/*package*/ Bundle data;
/*package*/ Handler target;
/*package*/ Runnable callback;
// sometimes we store linked lists of these things
/*package*/ Message next;
handler提供的发送消息的方法有很多,大致分为两类:
虽然方法很多,但最终都会回调到这个方法:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
这个方法就是将消息添加到队列里。
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;
}
// 标记这个 Message 已经被使用
msg.markInUse();
msg.when = when;
// 这是这个消息队列里的目前第一条待处理的消息(当前消息队列的头部,有可能为空)
Message p = mMessages;
boolean needWake;
// 如果目前队列里没有消息 或 这条消息msg需要立即执行 或 这条消息msg的延迟时间比队列里的第一条待处理的消息还要早的话,走这个逻辑
if (p == null || when == 0 || when < p.when) {
// 把消息插入到消息队列的头部
// 最新的消息,如果已经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循环的作用就是找出msg应该放置的正确位置
// 经过下面这个for循环,最终会找出msg的前一个消息是prev,后一个消息是p
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
// 上面的for循环得出的结果就是:msg应该在prev后面,在p前面
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;
}
上面就是handler发送消息过来,然后添加到消息队列里。
下面就开始讲添加消息到队列之后的事情:取消息,然后执行。
前面已经说到,在使用Handler之前有三步准备工作:
调用Looper.prepare()(主线程不需要调这个,因为APP创建时,main方法里面已经帮我们创建了)
创建Handler对象,重写handleMessage方法(你可以不重写),用于处理message回调的
调用Looper.loop()
其中第三步的Looper.loop()的作用就是不断的从MessageQueue队列里取消息,也就是说,在使用handler发消息之前,就已经开始了loop的循环了。
loop()源码比较长,这里摘取核心部分:
/**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the loop.
*
* 大概的翻译就是:在这个线程中运行消息队列。确保调用{@link #quit()}来结束循环。
*
*/
public static void loop() {
····
····
for (;;) {
// 不断的从MessageQueue的next方法里取出队列的头部消息
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
·····
·····
·····
try {
// msg.target是Message在创建时传入的Handler,也就是发送这条消息的发送者handler
// 所以最终会回调到handler的dispatchMessage方法
msg.target.dispatchMessage(msg);
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
····
····
// 回收msg,重复利用
msg.recycleUnchecked();
}
}
loop()的作用就是不断的从MessageQueue里取消息,然后回调到dispatchMessage里,再看看dispatchMessage里干啥了
/**
* Handle system messages here.
*/
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
所以最终会执行callback或handleMessage。
答:App的运行、更新UI、AsyncTask、Glide、RxJava等
答:创建Handler所使用的Looper所在的线程
答: 是根据when在MessageQueue中升序排序的,when=开机到现在的毫秒数+延时毫秒数
答:不会导致App的ANR,是Linux的pipe机制保证的,阻塞时,线程挂起;需要时,唤醒线程
答:可以使用,但是Toast的显示是基于Handler实现的,所以需要先创建Looper,然后调用Looper.loop。
答:可以停止,Looper提供了quit和quitSafely方法
答: 静态内部类+弱引用 、Handler的removeCallbacksAndMessages等方法移除MessageQueue中的消息
下面逐个来看看这些问题:
App的入口其实是ActivityThread的main方法:
public static void main(String[] args) {
// 创建主线程中的Looper
Looper.prepareMainLooper();
// 创建ActivityThread对象
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
// 创建用于通信的Handler
sMainThreadHandler = thread.getHandler();
}
// 执行loop方法
Looper.loop();
}
可以看到这个main方法中,主要有如下几步:
回顾下,Handler机制的原理图:
可以知道,App启动后,因为Looper.loop是一个死循环,导致main方法一直没有执行完,也就是说,我们后续App中的所有操作,都是发生在Looper.loop中的
那Handler机制,是怎么保证App的运行的呢?我们来看看ActivityThread中用于通信的Handler的定义:
private class H extends Handler {
public static final int LAUNCH_ACTIVITY = 100;
public static final int PAUSE_ACTIVITY = 101;
public static final int PAUSE_ACTIVITY_FINISHING= 102;
public static final int STOP_ACTIVITY_SHOW = 103;
public static final int STOP_ACTIVITY_HIDE = 104;
// 省略部分代码
String codeToString(int code) {
if (DEBUG_MESSAGES) {
switch (code) {
case LAUNCH_ACTIVITY: return "LAUNCH_ACTIVITY";
case PAUSE_ACTIVITY: return "PAUSE_ACTIVITY";
case PAUSE_ACTIVITY_FINISHING: return "PAUSE_ACTIVITY_FINISHING";
case STOP_ACTIVITY_SHOW: return "STOP_ACTIVITY_SHOW";
case STOP_ACTIVITY_HIDE: return "STOP_ACTIVITY_HIDE";
// 省略部分代码
}
}
return Integer.toString(code);
}
public void handleMessage(Message msg) {
if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
switch (msg.what) {
case LAUNCH_ACTIVITY: {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
r.packageInfo = getPackageInfoNoCheck(
r.activityInfo.applicationInfo, r.compatInfo);
handleLaunchActivity(r, null);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
} break;
case RELAUNCH_ACTIVITY: {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityRestart");
ActivityClientRecord r = (ActivityClientRecord)msg.obj;
handleRelaunchActivity(r);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
} break;
case PAUSE_ACTIVITY:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause");
handlePauseActivity((IBinder)msg.obj, false, (msg.arg1&1) != 0, msg.arg2,
(msg.arg1&2) != 0);
maybeSnapshot();
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case PAUSE_ACTIVITY_FINISHING:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause");
handlePauseActivity((IBinder)msg.obj, true, (msg.arg1&1) != 0, msg.arg2,
(msg.arg1&1) != 0);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case STOP_ACTIVITY_SHOW:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStop");
handleStopActivity((IBinder)msg.obj, true, msg.arg2);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case STOP_ACTIVITY_HIDE:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStop");
handleStopActivity((IBinder)msg.obj, false, msg.arg2);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
// 省略部分代码
}
if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: " + codeToString(msg.what));
}
通过代码可以看到,H继承于Handler,定义了很多消息的类型,比如启动Activity、停止Activity、显示Window、电量过低等,它重写了handleMessage方法,用于处理这些消息。
那发消息是在哪里发的呢?我们以锁屏调用Activity的onStop生命周期为例,其实就是在ApplicationThread中,它是一个ActivityThread的内部类我们拿启动Activity这个消息举例,ActivityManagerService会先通过binder调用ApplicationThread中的scheduleStopActivity方法(牵涉到进程间通信,不懂可以略过),这个方法是在我们App的Bindler线程池中执行的,那看看它是怎么切换到主线程去启动Activity的。
public final void scheduleStopActivity(IBinder token, boolean showWindow,
int configChanges) {
sendMessage(
showWindow ? H.STOP_ACTIVITY_SHOW : H.STOP_ACTIVITY_HIDE,
token, 0, configChanges);
}
我们看到,这里就是调用sendMessage方法,第一个参数,不就是上面H中定义的消息类型么?接着看 sendMessage方法,它最后会调用到如下这个多参的构造方法:
private void sendMessage(int what, Object obj, int arg1, int arg2, boolean async) {
if (DEBUG_MESSAGES) Slog.v(
TAG, "SCHEDULE " + what + " " + mH.codeToString(what)
+ ": " + arg1 + " / " + obj);
Message msg = Message.obtain();
msg.what = what;
msg.obj = obj;
msg.arg1 = arg1;
msg.arg2 = arg2;
if (async) {
msg.setAsynchronous(true);
}
mH.sendMessage(msg);
}
可以看到,就是使用mH发送一个消息,而mH就是ActivityThread中定义的类型为H的成员变量,定义如下:
final H mH = new H();
所以,调用锁屏时,调用Activity的onStop方法,流程如下:
流程图如下:
那么,我们App运行原理就出来了,App的运行依赖于Handler机制,当主线程当MessageQueue有消息时,主线程的Looper.loop会不断从主线程中的MessageQueue中取出消息来处理(比如Activity的onCreate其实就是属于对MessageQueue中取出的一个消息的处理),这样就保证了App运行
当MessageQueue没有消息时,MessageQueue的next方法会阻赛,导致当前线程挂起,等有消息(一般为系统进程通过binder调用App的ApplicationThread中的方法,注意,方法在binder线程池中执行,然后ApplicationThread使用ActivityThread中的H对象发送消息,加入消息到主线程的MessageQueue中,当发现主线程被挂起了,则会唤醒主线程)
所以,当没有任何消息时,我们的App的主线程,是属于挂起的状态。有消息来时(锁屏、点击等),主线程会被唤醒,所以说,Handler机制保证了App的运行。
我们知道,如果在子线程直接更新UI会抛出异常,异常如下:
我们可以使用Handler在子线程中更新UI,常用的方式有如下几种:
2.1 Handler的sendMessage方式
final Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
btn.setText("handler.sendMessage方式");
}
};
new Thread(new Runnable() {
@Override
public void run() {
// 使用sendMessage方式
Message msg = Message.obtain();
msg.what = 100;
handler.sendMessage(msg);
}
}).start();
这种方式,就是在子线程中,用主线程中创建的hander调用sendMessage发送消息,把Message加入到主线程的MessageQueue中,等主线程的Looper从MessageQueue中取到这个消息时,会调用这个Message的target的handleMessage方法,这个target其实就是我们发消息用到的handler,也就是调用了我们重写的handleMessage方法。
发消息:Handler.sendMessage(Message)
处理消息:Message.target.handleMessage(其中target就是发消息的handler)
2.2 Handler的post方法
final Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
btn.setText("handler.sendMessage方式");
}
};
new Thread(new Runnable() {
@Override
public void run() {
// 使用post
handler.post(new Runnable() {
@Override
public void run() {
btn.setText("handler.post方式");
}
});
}
}).start();
在子线程中使用handler的post方法,也是可以更新UI的,post方法需要传入一个Runnalbe对象。我们来看看post方法源码
public final boolean post(Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
可以看到,先调用了getPostMessage构建了一个Message对象,然后调用了sendMessageDelayed方法,前面知道,sendMessage也是调用的这个方法,所以我们只要关注怎么构建的Message对象,看getPostMessage方法。
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
// post方法传入的Runnable对象作为Message的callback
m.callback = r;
return m;
}
其实很简单,就是把post方法传入的参数作为Message的callback来创建一个Message。
我们再来回顾一下从MessageQueue中取出消息来对消息对处理,方法是Handler对dispatchMessage方法
public void dispatchMessage(Message msg) {
// callback其实就是post方法传入对Runnable对象
if (msg.callback != null) {
handleCallback(msg);
} else {
// 不会执行到这里
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
private static void handleCallback(Message message) {
// 相当于调用了post方法传入对Runnable对象对run方法
message.callback.run();
}
可以知道,我们使用post发送消息,就是使用传入对Runnable对象封装成一个Message,然后加入到主线程中的MessageQueue,等主线程的Looper取出该消息处理时,因为Message.callback不为空,而调用其run方法,也就是我们调用post方法传入的Runnable对象的run方法,且不会调用Hander的handleMessage方法。
发送消息:Handler.post(Runnable)
处理消息:Message.callback.run(callback为调用post方法传入的Runnable)
2.3 Activity的runOnUiThread方法
new Thread(new Runnable() {
@Override
public void run() {
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
btn.setText("Activity的runOnUiThread方法");
}
});
}
}).start();
我们如果能在子线程中获取到Activity对象,是可以调用其runOnUiThread方法,来更新UI。我们来看看Activity的runOnUiThread源码。
public final void runOnUiThread(Runnable action) {
// 如果不是UI线程,则调用Handler的post
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
} else {
// 如果是ui线程,则直接回调Runnable的run方法
action.run();
}
}
如果是UI线程,就直接调用了传入的Runnable对象的run方法,我们主要是看非UI线程的逻辑。
如果不是UI线程,则直接使用mHandler的post方法,看来Activity的runOnUiThread方法也是基于Handler的post方法来实现的,后面的逻辑就是把传入的Runnable封装成Message发送出去,上面讲过了,就不再复述了。
我们再看看这个mHandler的定义,其实就是Activity的成员属性
final Handler mHandler = new Handler();
Activity是在主线程创建的,所以这个Handler也是在主线程中创建的,且持有的Looper为主线程的Looper。那么使用这个Handler调用post方法发出的消息,是加入到主线程的MessageQueue中,这样就完成了子线程跟主线程的通信。
发送消息:Activity. runOnUiThread(Runnable)
处理消息:Message.callback.run(callback为runOnUiThread方法传入的Runnable)
2.4 View的post方法
new Thread(new Runnable() {
@Override
public void run() {
// 调用View的post方法
btn.post(new Runnable() {
@Override
public void run() {
btn.setText("View.post方式");
}
});
}
}).start();
我们直接看View的post方法
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
// Assume that post will succeed later
ViewRootImpl.getRunQueue().post(action);
return true;
}
可以看到这里也是调用的Handler的post方法,跟Activity. runOnUiThread类似。
发送消息:View.post(Runnable)
处理消息:Message.callback.run(callback为post方法传入的Runnable对象)
总结一下:
所以,以上子线程更新主线程UI的所有方式,都是依赖于Handler机制。
当我们想在子线程中做耗时任务时,会考虑使用AsyncTask,我们来举个栗子,在子线程中去创建自定义的MyAsyncTask并执行它,在doInBackground中去模拟耗时操作:
public class AsyncTaskActivity extends AppCompatActivity {
private static final String TAG = "AsyncTaskActivity";
private Button btn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_asynctask);
btn = (Button) findViewById(R.id.btn);
// 开启一个子线程,去执行异步任务
new Thread(new Runnable() {
@Override
public void run() {
Log.e(TAG, "run, thread name:" + Thread.currentThread().getName());
MyAsyncTask asyncTask = new MyAsyncTask();
asyncTask.execute();
}
}).start();
}
// 自定义AsyncTask,并重写相关方法,并打印执行所在线程
class MyAsyncTask extends AsyncTask{
@Override
protected Void doInBackground(Void... voids) {
Log.e(TAG, "doInBackground, thread name:" + Thread.currentThread().getName());
// 模拟耗时任务
for (int i = 0; i < 5; i ++) {
SystemClock.sleep(1000);
publishProgress(i);
}
return null;
}
@Override
protected void onPreExecute() {
Log.e(TAG, "onPreExecute, thread name:" + Thread.currentThread().getName());
}
@Override
protected void onPostExecute(Void aVoid) {
Log.e(TAG, "onPostExecute, thread name:" + Thread.currentThread().getName());
btn.setText("执行完了!");
}
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
Log.e(TAG, "onProgressUpdate, thread name:" + Thread.currentThread().getName());
}
}
}
可以看到,我们创建的子线程名为Thread-4,而AsyncTask的方法所在线程如下:
关于onPreExecute,这很好实现,不需要切换线程,直接回调就可以;而doInBackground方法的执行,可以直接取AsyncTask维持的线程池来执行就可以。我们重点关注onProgressUpdate和onPostExecute方法,是怎么从子线程切换到主线程的。
我们从AsyncTask的源码中可以看到这样一个内部类
private static class InternalHandler extends Handler {
public InternalHandler() {
// 使用主线程的Looper去创建Handler
super(Looper.getMainLooper());
}
@SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
@Override
public void handleMessage(Message msg) {
AsyncTaskResult result = (AsyncTaskResult) msg.obj;
switch (msg.what) {
case MESSAGE_POST_RESULT:
// finish中主要调用onPostExecute
result.mTask.finish(result.mData[0]);
break;
case MESSAGE_POST_PROGRESS:
// 调用onProgressUpdate
result.mTask.onProgressUpdate(result.mData);
break;
}
}
}
从handleMessage方法看到,这里切换到主线程,也是使用的Handler机制来实现的,但是为什么我们不管在任何线程创建的AsyncTask去执行,最后都可以在主线程去回调这两个方法呢?主要是创建InternalHandler时,使用的是主线程的Looper,这样使用这个InternalHandler发送消息时,消息就会加入到主线程Looper对应的MessageQueue中,所以主线程Looper取出消息处理时,InternalHandler的handleMessage方法就是在主线程中回调的了。
所以AsyncTask其实就是基于线程池+Handler机制来实现的。
其他使用Handler的地方
RxJava: 子线程切换到主线程执行观察者的回调方法(RxJava我不熟悉)
Glide:图片准备好以后的回显
LocalBroadcastManager: 传统的广播是基于binder实现的,本地广播LocalBroadcastManager是基于Handler实现的
其实还有很多使用到handler机制的地方,就不一一举例了,反正记住,Handler机制很重要。
以前总觉得,处理消息的线程,就是创建Handler的线程,但是上一篇文章的分析,我们知道这样说其实是不准确的(因为我们创建Handler通常使用的默认Looper)。
处理消息的线程,其实是发送handler所持有的Looper所在的线程。
其实原理很好理解,我们知道Handler的原理如图
所以,消息的处理分发所在线程完全取决于消息所在MessageQueue的线程,如果想要在某个线程中处理消息,只要做到把消息加入到那个线程所对应的MessageQueue中。
就像上面讲到AsyncTask的例子,就算我们在子线程创建了AsyncTask(即在子线程创建了用于通信的Handler),但只要我们创建Handler的时候,通过Looper.getMainLooper()传入主线程的Looper ,那么消息就加入到了主线程所对应的MessageQueue中,消息就是在主线程中处理的。
如下,我们在子线程中创建一个handler,然后在主线程发送消息,因为创建handler使用的是子线程中的Looper,所以消息是在主线程中处理的。代码如下:
public class ThreadActivity extends AppCompatActivity {
private static final String TAG = "ThreadActivity";
private Button btn;
private Handler mHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_refresh);
btn = (Button) findViewById(R.id.btn);
// 开启一个子线程,去创建Handler
new Thread(new Runnable() {
@Override
public void run() {
Log.e(TAG, "run: , thread name: " + Thread.currentThread().getName());
Looper.prepare();
mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Log.e(TAG, "handleMessage, thread name: " + Thread.currentThread().getName());
}
};
Looper.loop();
}
}).start();
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 发送一个消息
mHandler.sendEmptyMessage(100);
}
});
}
}
log输出,可以看出,处理消息是在子线程中:
按照之前的说法,如果我们想在主线程中处理消息,只要把消息加入到主线程的MessageQueue中,所以我们可以创建Looper时,传入主线程的Looper,代码如下:
public class ThreadActivity extends AppCompatActivity {
private static final String TAG = "ThreadActivity";
private Button btn;
private Handler mHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_refresh);
btn = (Button) findViewById(R.id.btn);
// 开启一个子线程,去创建Handler
new Thread(new Runnable() {
@Override
public void run() {
Log.e(TAG, "run: , thread name: " + Thread.currentThread().getName());
Looper.prepare();
// 创建Handler传入主线程的Looper
mHandler = new Handler(Looper.getMainLooper()){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Log.e(TAG, "handleMessage, thread name: " + Thread.currentThread().getName());
}
};
Looper.loop();
}
}).start();
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 发送一个消息
mHandler.sendEmptyMessage(100);
}
});
}
}
可以看到,创建Handler使用了主线程的Looper后,的确消息是在主线程中处理的了:
我们之前说,所有handler.post和handler.sendMessage都会调用到Handler的sendMessageDelayed方法,方法如下:
public final boolean sendMessageDelayed(Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
这里逻辑就很简单了,直接调用了sendMessageAtTime方法,第一个参数为Message,第二个参数为SystemClock.uptimeMillis() + delayMillis,其中delayMillis为延时的时间,单位为毫秒,SystemClock.uptimeMillis() 为开机到现在的时间(不包括休眠时间),单位为毫秒。第二个参数主要是决定该Message在MessageQueue的顺序,比如现在开机时间为100s,发送一个延时20s的消息,则两者之和为120s; 过了5秒,又发了一个延时5s的消息,则两者只喝为105+5 = 110s。
sendMessageAtTime最后会调用到MessageQueue的enqueueMessage方法,我们来看看这个方法:
boolean enqueueMessage(Message msg, long when) {
synchronized (this) {
// when = 开机到目前的时间 + 延时时间
msg.when = when;
Message p = mMessages;
boolean needWake;
// 如果当前消息队列为空或者当前when小于队头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 {
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
// 死循环,根据when寻找Message插入到MessageQueue合适的位置
for (;;) {
prev = p;
p = p.next;
// MessageQueue中依次往后找到第一个Message.when大于当前Message.when的Message
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
// 把当前Message插入到MessageQueue的合适位置
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
// 如果需要唤醒,则调用nativeWake去唤醒处理线程
nativeWake(mPtr);
}
}
return true;
}
从上面的代码,可以很容易看出,Message在MessageQueue中是根据when从小到大来排队的,when是开机到现在的时间+延时时间。
比如,我们假设开机时间为100s,此时MessageQueue没有消息,这时候发一个延时20s的消息,即when为120000,则MessageQueue中的消息情况如图:
过了5s,我们又发了一个延时10s的消息,则when为115000,此时MessageQueue如图:
又过了5s,我们发了一个不延时的消息,即when为110000,此时MessageQueue如图:
所以,Message在MessageQueue中是根据when从小到大来排队的,when是开机到现在的时间+延时时间。
我们知道Activity如果5s的事件都不能相应用户的请求,则会ANR。我们在来回顾下Looper.loop方法:
public static void loop() {
final Looper me = myLooper();
// 取到当前线程的MessageQueue
final MessageQueue queue = me.mQueue;
// 死循环
for (;;) {
// 调用MessageQueue.next,从队列中取出消息
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// 对消息对分发
msg.target.dispatchMessage(msg);
// 省略无关代码
}
}
在前面讲过Looper.loop方法维持了App的运行,它是里面使用了一个死循环,我们App平常的操作(Activity的生命周期、点击事件等)都是属于调用了 msg.target.dispatchMessage(msg)对消息的处理。但是如果MessageQueue中没有消息时,MessageQueue的next方法会阻塞,那它会导致App对ANR么?
我们来看看MessageQueue的next方法
Message next() {
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
// 死循环
for (;;) {
// 阻塞MessageQueue
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// 如果是延时消息,则算出需要阻塞的时间nextPollTimeoutMillis
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// 如果不是延时显示,则直接把消息返回,以供处理
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;
}
} else {
// 没有消息时,设置nextPollTimeoutMillis为-1,阻塞MessageQueue
nextPollTimeoutMillis = -1;
}
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
// 一般都会满足if条件,然后mBlocked设置为true,继续continue
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
}
// 省略无关代码
}
pendingIdleHandlerCount = 0;
nextPollTimeoutMillis = 0;
}
}
next方法中,首先设置一个死循环,然后调用nativePollOnce(ptr, nextPollTimeoutMillis)方法,它是一个native方法,用于阻塞MessageQueue,主要是关注它的第二个参数nextPollTimeoutMillis,有如下三种可能:
我们先继续往下看,开始nextPollTimeoutMillis为0,也就是不会阻塞,则继续往下,这时候有三种情况
所以,当消息队列为空时,其实是调用本地方法nativePollOnce,且第二个参数为-1,它会导致当前线程阻塞,且不会超时,所以不会出现ANR的情况,其实这是由Linux的管道机制(pipe)来保证的,当线程阻塞时,对CPU等资源等消耗时极低的,具体的原理可以自行查阅。
那线程阻塞以后,什么时候才能再唤醒呢?记得之前我们说消息加入MessageQueue的逻辑么?我们再来回顾一下enqueueMessage的流程:
boolean enqueueMessage(Message msg, long when) {
synchronized (this) {
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;
// 如果MessageQueue为空
needWake = mBlocked;
} else {
// 如果MessageQueue中有消息
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;
}
// 如果需要唤醒当前线程,则调用nativeWake方法
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
其实就是使用Handler发送消息,把消息加入到MessageQueue时,会判断当前MessageQueue是否阻塞,如果阻塞了,则需要调用nativeWake方法去唤醒线程,而这个阻塞是在前面提到的MessageQueue的next方法中,MessageQueue没有消息或者消息为延时消息时设置的。
所以MessageQueue的next方法可能会阻塞线程,但不会造成ANR。
有时候,我们需要在子线程中直接弹出Toast来提示一些信息,代码如下:
public class ToastActivity extends AppCompatActivity {
private static final String TAG = "ToastActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_toast);
new Thread(new Runnable() {
@Override
public void run() {
// 子线程中弹出toast
Toast.makeText(ToastActivity.this, "提示一下!", Toast.LENGTH_LONG).show();
}
}).start();
}
}
运行程序,会发现程序会崩溃。是因为子线程不能更新UI么?其实不是不这样的。
既然不是这两方面的原因,我们来看看报错的log吧
这跟我们在子线程中直接使用handler好像报的错误类似,那我们也跟使用hendler的套路一样,先调用Looper.prepare然后再调用Looper.loop呢?代码如下:
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
// 子线程中弹出toast
Toast.makeText(ToastActivity.this, "提示一下!", Toast.LENGTH_LONG).show();
Looper.loop();
}
}).start();
发现就可以了,可以看出Toast也是需要使用Handler,我们来看看Toast的实现,直接看Toast中的一个内部类TN,它是一个IBinder实现类,我们来看它的定义:
private static class TN extends ITransientNotification.Stub {
final Runnable mShow = new Runnable() {
@Override
public void run() {
// 调用handleShow,处理显示逻辑
handleShow();
}
}
final Handler mHandler = new Handler();
// 省略无关代码
WindowManager mWM;
TN() {
// XXX This should be changed to use a Dialog, with a Theme.Toast
// defined that sets up the layout params appropriately.
final WindowManager.LayoutParams params = mParams;
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
params.format = PixelFormat.TRANSLUCENT;
params.windowAnimations = com.android.internal.R.style.Animation_Toast;
params.type = WindowManager.LayoutParams.TYPE_TOAST;
params.setTitle("Toast");
params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
}
/**
* schedule handleShow into the right thread
*/
@Override
public void show() {
if (localLOGV) Log.v(TAG, "SHOW: " + this);
// 这里是Binder线程池,用handler切换到有Looper的线程
mHandler.post(mShow);
}
// Toast的真实显示
public void handleShow() {
if (mView != mNextView) {
mView = mNextView;
// 省略无关代码
// 把View加入到Window上
mWM.addView(mView, mParams);
}
}
//省略无关代码
}
可以看出,Toast的显示,使用了Binder通信,其实就是WindowManagerService会拿到TN对象,调用其show方法,但是这是Binder线程池中执行的,所以使用handler切换到调用Toast的show方法所在的线程去执行,这里使用的就是handler.post,所以就需要调用Toast.show方法所在线程有Looper。最后调用的就是handleShow方法,把View加载到Window上。
总结一下Handler:
前面我们知道,loop方法中是一个死循环,又因为代码是顺序执行的,所以它之后的代码是得不到执行的,如下:
public class LooperActivity extends AppCompatActivity {
private static final String TAG = "LooperActivity";
private Button btn;
private Handler mHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_looper);
btn = (Button) findViewById(R.id.btn);
// 开启一个子线程,去执行异步任务
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
Log.e(TAG, "Looper.loop之前" );
// Looper.loop方法是一个死循环
Looper.loop();
// 得不到执行
Log.e(TAG, "Looper.loop之后" );
}
}).start();
}
}
log如图,只会打印loop方法之前的,loop之后的代码得不到执行:
这样我们就要考虑一个问题了,并不是所有线程都需要像主线程一样一直运行下去,有些线程希望做完耗时任务后能回收,但是因为Looper.loop方法,导致线程只是阻塞,随时有被唤醒的可能,不能释放。那有什么办法能停止loop方法么?
其实Looper提供了quit和quitSafely方法来停止Looper,我们先来看看quit的用法,在点击事件中调用了Looper的quit方法,修改后的代码如下
public class LooperActivity extends AppCompatActivity {
private static final String TAG = "LooperActivity";
private Button btn;
private Handler mHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_looper);
btn = (Button) findViewById(R.id.btn);
// 开启一个子线程,去执行异步任务
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
Log.e(TAG, "Looper.loop之前" );
// Looper.loop方法是一个死循环
Looper.loop();
// 得不到执行
Log.e(TAG, "Looper.loop之后" );
}
}).start();
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 调用Looper的quit方法,停止Looper
mHandler.getLooper().quit();
}
});
}
}
开始和之前一样,Looper.loop后的方法不会得到执行,我们点击按钮后,Looper会停止,Looper.loop之后的代码也可以得到执行,log如下:
我们来看看Looper的quit和quitSafely的源码:
public void quit() {
mQueue.quit(false);
}
public void quitSafely() {
mQueue.quit(true);
}
我们发现,两个方法都是调用了MessageQueue的quit方法,只是传入的参数不同,我们来看看MessageQueue的quit方法:
void quit(boolean safe) {
synchronized (this) {
if (mQuitting) {
return;
}
// MessageQueue正在停止,用于next方法退出死循环
mQuitting = true;
if (safe) {
// 删除MessageQueue中的延时消息
removeAllFutureMessagesLocked();
} else {
// 删除MessageQueue中的所有消息
removeAllMessagesLocked();
}
// We can assume mPtr != 0 because mQuitting was previously false.
nativeWake(mPtr);
}
}
首先把mQuitting设置为true,主要用于MessageQueue的next方法退出死循环,然后通过safe去判断逻辑逻辑,这里就可以看出Looper的quit和quitSafely的区别了
我们继续看mQuitting对MessageQueue的next方法的影响,回到next方法,我们只看关键性代码:
Message next() {
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
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;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
// 判断mQuitting
if (mQuitting) {
dispose();
return null;
}
}
}
}
直接看最后的部分,对mQuitting做判断,我们之前在MessageQueue的quit方法中,会把这个属性设置为true,其实就是会影响到这里。满足条件以后,调用了dispose方法,并返回了null。
我们先来看dispose方法
private void dispose() {
if (mPtr != 0) {
nativeDestroy(mPtr);
mPtr = 0;
}
}
其实就是调用了nativeDestroy方法,它是一个native方法,用于在底层停止MessageQueue。
这里只是停止了MessageQueue的next中的死循环,Looper.loop方法中的死循环还是没有退出,我们继续看Looper.loop方法。
public static void loop() {
final Looper me = myLooper();
final MessageQueue queue = me.mQueue;
for (;;) {
// 当mQuitting为true,queue.next方法返回了null
Message msg = queue.next(); // might block
if (msg == null) {
// 直接return,退出loop的死循环
return;
}
// 省略无关代码
}
}
前面我们知道,当调用了Looper的quit或者quitSafely时,会设置当前线程的MessageQueue的 mQuitting为true,然后导致了MessageQueue的next返回了null,然后直接return了,退出了loop中的死循环,这样就完成了停止Looper的逻辑。
我们通常会使用如下的方式去使用handler来通信
public class HandlerActivity extends AppCompatActivity {
private static final String TAG = "HandlerActivity";
private Handler mHandler;
private Button btn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler);
// 匿名内部类
mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
// 处理消息
}
};
btn = (Button) findViewById(R.id.btn);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 发送延时100s的消息
mHandler.sendEmptyMessageDelayed(100, 100 * 1000);
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
// 使用leakcanary做内存泄漏检测
RefWatcher refWatcher = MyApplication.getRefWatcher(this);
if (refWatcher != null) {
refWatcher.watch(this);
}
}
}
但是会有一个问题,我们进入这个页面然后点击按钮,发送一个延时100s的消息,再退出这个Activity,这时候可能导致内存泄漏。
根本原因是因为我们创建的匿名内部类Handler对象持有了外部类Activity的对象,我们知道,当使用handler发送消息时,会把handler作为Message的target保存到MessageQueue,由于延时了100s,所以这个Message暂时没有得到处理,这时候它们的引用关系为MessageQueue持有了Message,Message持有了Handler,Handler持有了Activity,如下图所示
当退出这个Activity时,因为Handler还持有Activity,所以gc时不能回收该Activity,导致了内存泄漏,使用LeakCanary检测,效果如下图所示:
当然,过了100s,延时消息得到了处理,Activity对象属于不可达的状态时,会被回收。
那怎么来解决Handler泄漏呢?主要有如下两种方式:
我们知道,静态内部类是不会引用外部类的对象的,但是既然静态内部类对象没有持有外部类的对象,那么我们怎么去调用外部类Activity的方法呢?答案是使用弱引用。代码如下:
public class HandlerActivity extends AppCompatActivity {
private static final String TAG = "HandlerActivity";
private Handler mHandler;
private Button btn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler);
// 创建Handler对象,把Activity对象传入
mHandler = new MyHandler(HandlerActivity.this);
btn = (Button) findViewById(R.id.btn);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 发送延时100s的消息
mHandler.sendEmptyMessageDelayed(100, 100 * 1000);
}
});
}
// 静态内部类
static class MyHandler extends Handler {
private WeakReference activityWeakReference;
public MyHandler(Activity activity) {
activityWeakReference = new WeakReference(activity);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
// 处理消息
if (activityWeakReference != null) {
Activity activity = activityWeakReference.get();
// 拿到activity对象以后,调用activity的方法
if (activity != null) {
}
}
}
}
}
首先,我们自定义了一个静态内部类MyHandler,然后创建MyHandler对象时传入当前Activity的对象,供Hander以弱应用的方式持有,这个时候Activity就被强引用和弱引用两种方式引用了,我们继续发起一个延时100s的消息,然后退出当前Activity,这个时候Activity的强引用就不存在了,只存在弱引用,gc运行时会回收掉只有弱引用的Activity,这样就不会造成内存泄漏了。
但这个延时消息还是存在于MessageQueue中,得到这个Message被取出时,还是会进行分发处理,只是这时候Activity被回收掉了,activity为null,不能再继续调用Activity的方法了。所以,其实这是Activity可以被回收了,而Handler、Message都不能被回收。
至于为什么使用弱引用而没有使用软引用,其实很简单,对比下两者回收前提条件就清楚了
很明显,当我们Activity退出时,我们希望不管内存是否足够,都应该回收Activity对象,所以使用弱引用合适。
我们知道,内存泄漏的源头是MessageQueue持有的Message持有了Handler持有了Activity,那我们在合适的地方把Message从MessageQueue中移除,不就可以解决内存泄漏了么?
Handler为我们提供了removeCallbacksAndMessages等方法用于移除消息,比如,在Activity的onDestroy中调用Handler的removeCallbacksAndMessages,代码如下:
@Override
protected void onDestroy() {
super.onDestroy();
// 移除MessageQueue中target为该mHandler的Message
mHandler.removeCallbacksAndMessages(null);
}
其实就是在Activity的onDestroy方法中调用mHandler.removeCallbacksAndMessages(null),这样就移除了MessageQueue中target为该mHandler的Message,因为MessageQueue没有引用该Handler发送的Message了,所以当Activity退出时,Message、Handler、Activity都是可回收的了,这样就能解决内存泄漏的问题了。
Handler的知识不多,但细节特别多,一旦久一点没看就会忘记。
所以,不管是别人写的还是自己写的,先把相关知识记下来,下次忘记了回来再看一下就行了。