这篇是以前Looper、Handler、Message以及MessageQueue之间的关系后续版本,将更加详细讲解一下以前没提到的细节内容。java
先说一下这篇文章主要要解决的几个点:bash
以前文章提到过要在线程(包括UI线程)中使用Handler,就须要Looper.prepare()以及Looper.loop()相关操做,可是为何Activity中能够直接使用Handler呢?这个问题在以前文章中提到过,是由于系统已经帮咱们作了Looper相关的准备操做,咱们来看一下具体实现。app
Android进程启动过程当中会建立一个ActivityThread对象,并调用其中的main(),用以接收系统的各类操做。这个ActivityThread对象就是咱们常说的UI线程,其实并非一个线程。在main()中咱们就能看到Looper初始化相关的操做。异步
public static void main(String[] args) {
...
Looper.prepareMainLooper();
...
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
...
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
复制代码
这里咱们只看到了MainLooper初始化操做,并无发现它跟Activity有何关联,接着往下看。ide
ActivityThread有一个成员变量mH,是一个H类型的对象,这个H是是Handler的子类,内部用于处理4大组件的启动、生命周期相关方法以及其余一些系统的回调。固然系统的启动过程太过冗长,这里不作细致的分析,如今只须要知道mH这个Handler对象。oop
final H mH = new H();
复制代码
就是这个mH接收了来自ActivityManagerService传递过来的启动Activity的消息,并调用了ActivityThread中的performLaunchActivity()来建立一个Activity并调用相关生命周期方法()post
/** Core implementation of activity launch. */
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
Activity activity = null;
try {
java.lang.ClassLoader cl = appContext.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
StrictMode.incrementExpectedActivityCount(activity.getClass());
r.intent.setExtrasClassLoader(cl);
r.intent.prepareToEnterProcess();
if (r.state != null) {
r.state.setClassLoader(cl);
}
} catch (Exception e) {
if (!mInstrumentation.onException(activity, e)) {
throw new RuntimeException(
"Unable to instantiate activity " + component
+ ": " + e.toString(), e);
}
}
....
return activity;
}
复制代码
这里咱们能够知道,Activity运行在mH内部。再看最上面ActivityThread的建立,能够看出mH中调用的方法也是在MainLooper中执行,Activity在MainLooper中执行,因此咱们并不须要先初始化Looper再使用Handler。ui
首先要明白一个问题,UI卡顿实际上是MessageQueue中某些方法,好比onCreate()、onResume()等生命周期的回调执行过长,致使UI绘制某些帧丢失甚至ANR。MessageQueue虽然是个死循环,可是当消息队列为空时,只是阻塞消息队列,当时并不占用cpu资源,因此并不会引发UI的卡顿。至于为何MessageQueue能够作到没有消息时阻塞队列,有消息时能够唤起继续工做的,读者能够查阅Handler机制在Native层中的实现,主要是Linux中的epoll。this
固然你还有疑问为何要将MessageQueue写成死循环,这是由于UI线程须要一直接收系统,以及其余组件发送过来的事件,中间可能会有空闲的状况,为了保证MessageQueue不退出,就只能写成死循环了。spa
前面的文章咱们能够知道,无论Handler经过什么形式发送消息,最后都会调用到enqueueMessage()。这个方法内部调用了MessageQueue的enqueueMessage()。
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
//除了系统的同步控制,全部的msg都必需要有target来处理msg
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) {
//循环队列正在退出,直接将msg回收
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入队操做,标记msg的使用状态,处理时间等
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
//若是当前队列为空,
//或者msg须要当即处理,
//或者当前消息队列中头消息处理的时间大于当前消息,
//都是将头消息置为当前消息,
//同时若是当前队列阻塞,则唤起消息队列进入循环
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
//进入这个分支,是将msg按执行时间前后插入消息链表中
//正常状况下咱们不须要唤醒队列,除非当前消息对列正在
//同步消息控制,同时当前消息为第一条异步消息
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;
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
复制代码
上面的方法咱们能够看到,处理了几种状况都在注释里作了说明,下面再来看看消息具体是如何取出来的
Message next() {
//ptr = 0 当前队列正在退出
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1; // -1 only during first iteration
//这个nextPollTimeoutMillis用来控制消息队列是否要阻塞,以及阻塞多久
//nextPollTimeoutMillis = 0 表示不阻塞
//nextPollTimeoutMillis = -1 表示一直阻塞,直到被唤醒
//nextPollTimeoutMillis > 0 表示阻塞这么长时间以后唤醒
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
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) {
//当前消息队列被同步控制,找到第一条异步的消息
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
//第一条异步消息还没有到执行时间,计算须要阻塞的时间
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;
}
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}
//首次循环的时候执行设置的IdleHandler
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
....
// Reset the idle handler count to 0 so we do not run them again.
pendingIdleHandlerCount = 0;
// While calling an idle handler, a new message could have been delivered
// so go back and look again for a pending message without waiting.
nextPollTimeoutMillis = 0;
}
}
复制代码
上面消息的enqueueMessage()以及next()取出操做能够很清晰的看到Handler.postDelay()的实现原理,就是消息按执行时间入队,并延迟处理。
子线程中须要咱们手动准备Looper,而后很方便的进行线程间通讯,可是在消息处理完成以后要手动将Looper退出,以避免形成内存泄漏。 从SDK 23 开始系统提供一个HandlerThread供咱们方便的在子线程中使用Handler;
当咱们在Activity内部直接建立一个非静态的Handler成员变量时,IDE就会提醒咱们,这样使用Handler可能会形成内存泄漏,咱们来分析一下缘由在哪里。
好,根据上面3点,咱们来看一下Activity的引用:
public class MainActivity extends AppCompatActivity {
private Handler mHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mHandler = new Handler();
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
//do something
}
}, 10 * 60 * 1000);
}
}
复制代码
当这个runnable消息非发送出去以后,mHandler被msg持有,并安排到10分钟以后执行,同时MainActivity也一直被mHandler和runnable持用。 当MainActivity退出时,由于Looper是UI线程的Looper,咱们没法退出,msg还在消息队列中还没有执行,会阻止MainActivity被系统回收,就形成了内存泄漏。
避免这种状况方法有不少种这里提2种经常使用的方式:
定义静态Handler对象,并传人UI线程Looper
static Handler sHandler = new Handler(Looper.getMainLooper());
复制代码
将咱们须要的Handler定义成静态内部类,同时使用弱引用保存对Activity的引用
public static class MyHandler extends Handler {
private final WeakReference<Activity> mActivityWf;
public MyHandler(WeakReference<Activity> activityWf) {
mActivityWf = activityWf;
}
}
复制代码
消息队列的同步控制是经过postSyncBarrier()这个方法来实现, 说是Barrier,其实就是在队列的头部插入一条特殊的msg
public int postSyncBarrier() {
return postSyncBarrier(SystemClock.uptimeMillis());
}
private int postSyncBarrier(long when) {
// Enqueue a new sync barrier token.
// We don't need to wake the queue because the purpose of a barrier is to stall it. synchronized (this) { final int token = mNextBarrierToken++; final Message msg = Message.obtain(); msg.markInUse(); msg.when = when; msg.arg1 = token; Message prev = null; Message p = mMessages; if (when != 0) { while (p != null && p.when <= when) { prev = p; p = p.next; } } if (prev != null) { // invariant: p == prev.next msg.next = p; prev.next = msg; } else { msg.next = p; mMessages = msg; } return token; } } 复制代码
代码其实也挺简单的,就是在消息队列适当的位置插入一条没有target的msg,next()出去消息时,若是发现个同步控制消息,则它以后的同步消息就暂时不出列。
默认状况下,咱们构建的Handler发送的都是同步消息,除非在Handler构建的时候指定发送的全部消息类型都为异步消息。
固然通常咱们也不须要同步控制,虽然当前方法是public修饰的,可是hide,系统并无打算将此方法开放给咱们使用。
咱们再来看一下,系统哪里用到了这个同步控制。
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
复制代码
这是ViewRootImpl中的一个方法,这个方法执行以后,系统就开始进行页面的绘制流程,咱们看到这里调用postSyncBarrier(),这么作的目的就是要让UI绘制尽快完成,以避免形成卡顿。