深入理解Android消息机制Word文档格式.docx
《深入理解Android消息机制Word文档格式.docx》由会员分享,可在线阅读,更多相关《深入理解Android消息机制Word文档格式.docx(16页珍藏版)》请在冰豆网上搜索。
7.}
这里可以设置Message为异步消息
查看queue的enqueueMessage方法,我们剥离出核心代码:
1.if
(p
==
null
||
when
0
p.when)
//
New
head,
wake
up
the
event
queue
blocked.
msg.next
p;
mMessages
msg;
needWake
mBlocked;
如果是新的队列头,直接插入队列
如果队列里面已经有消息了,执行如下逻辑
1.needWake
mBlocked
&
p.target
msg.isAsynchronous();
2.Message
prev;
3.for
(;
;
)
prev
p
p.next;
7.
break;
8.
9.
(needWake
p.isAsynchronous())
10.
false;
11.
12.}
13.msg.next
invariant:
prev.next
14.prev.next
插入消息的时候,一般不会唤醒消息队列。
如果消息是异步的,并且队列头不是一个异步消息的时候,会唤醒消息队列
(needWake)
nativeWake(mPtr);
3.}
消息队列的具体唤醒过程我们暂时不细看。
把关注点移到Looper上。
looper在执行的时候具体执行了什么逻辑呢?
查看Looper.java的looper()方法
looper方法中有一个死循环,在死循环中,会获取下一个Message
1.for
msg
queue.next();
might
block
(msg
!
null)
2.//
Stalled
by
a
barrier.
Find
next
asynchronous
message
in
queue.
3.do
prevMsg
msg.next;
while
msg.isAsynchronous());
当存在一个barrier消息的时候,会寻找队列中下一个异步任务。
而不是按照顺序。
例如3个消息,1,2,3,2是异步消息。
如果不存在barrier的时候,next的顺序就是1,2,3但是如果存在barrier的时候,则是2,1,3
(now
msg.when)
Next
is
not
ready.
Set
timeout
to
it
nextPollTimeoutMillis
(int)
Math.min(msg.when
-
now,
Integer.MAX_VALUE);
else
Got
message.
(prevMsg
prevMsg.next
12.
13.
null;
14.
(DEBUG)
Log.v(TAG,
"
Returning
message:
msg);
15.
msg.markInUse();
16.
17.
18.}
19.
No
more
messages.
20.
-1;
21.}
这里如果next的Message不为空,就返回,并且将它移出队列在MessageQueue为空的时候,会顺便去处理一下add过的IdleHandler,处理一些不重要的消息
(int
i
pendingIdleHandlerCount;
i++)
IdleHandler
idler
mPendingIdleHandlers[i];
mPendingIdleHandlers[i]
release
reference
handler
keep
try
idler.queueIdle();
catch
(Throwable
t)
Log.wtf(TAG,
threw
exception"
t);
(!
keep)
synchronized
(this)
mIdleHandlers.remove(idler);
16.}
查看IdleHandler的源码。
1.*
Callback
interface
for
discovering
thread
going
*
waiting
*/
public
static
/**
Called
has
run
out
of
messages
and
will
now
wait
more.
Return
true
your
idle
active,
false
have
removed.
This
may
be
called
there
are
still
pending
but
they
all
scheduled
dispatched
after
current
time.
queueIdle();
当queueIdle()为false的时候,会将它从mIdleHandlers中remove,仔细思考下,我们其实可以利用IdleHandler实现不少功能,例如
1.Looper.myQueue().addIdleHandler(new
MessageQueue.IdleHandler()
@Override
queueIdle()
6.});
我们可以在queueIdle中,趁着没有消息要处理,统计一下页面的渲染时间(消息发送完了说明UI已经渲染完了),或者算一下屏幕是否长时间没操作等等。
拿到Message对象后,会将Message分发到对应的target去
1.msg.target.dispatchMessage(msg);
查看源码
void
dispatchMessage(Message
msg)
(msg.callback
handleCallback(msg);
(mCallback
(mCallback.handleMessage(msg))
return;
handleMessage(msg);
当msg的callback不为null的时候,即通过post(Runnable)发送信息的会执行handlerCallback(msg)方法。
如果mCallback不为null并且handleMessage的结果为false,则执行handleMessage方法。
否则会停止分发。
handleCallback(Message
message)
message.callback.run();
查看handlerCallback方法源码,callback会得到执行。
到这里基本的Android消息机制就分析完了,简而言之就是,Handler不断的将Message发送到一根据时间进行排序的优先队列里面,而线程中的Looper则不停的从MQ里面取出消息,分发到相应的目标Handler执行。
为什么主线程不卡?
分析完基本的消息机制,既然Looper的looper方法是一个for(;
)循环,那么新的问题提出来了。
为什么Android会在主线程使用死循环?
执行死循环的时候为什么主线程的阻塞没有导致CPU占用的暴增?
�
继续分析在源码中我们没有分析的部分:
∙消息队列构造的时候是否调用了jni部分
∙nativeWake、nativePollOnce这些方法的作用是什么
先查看MQ的构造方法:
1.MessageQueue(boolean
quitAllowed)
mQuitAllowed
quitAllowed;
mPtr
nativeInit();
4.}
会发现消息队列还是和native层有关系,继续查看android/platform/frameworks/base/core/jni/android_os_MessageQueue_nativeInit.cpp中nativeInit的实现:
1.static
jlong
android_os_MessageQueue_nativeInit(JNIEnv*
env,
jclass
clazz)
NativeMessageQueue*
nativeMessageQueue
new
NativeMessageQueue();
nativeMessageQueue)
jniThrowRuntimeException(env,
Unable
allocate
native
queue"
);
nativeMessageQueue->
incStrong(env);
reinterpret_cast<
jlong>
(nativeMessageQueue);
10.}
这里会发现我们初始化了一个NativeMessageQueue,查看这个消息队列的构造函数
1.NativeMessageQueue:
:
NativeMessageQueue()
mPollEnv(NULL),
mPollObj(NULL),
mExceptionObj(NULL)
mLooper
Looper:
getForThread();
(mLooper
NULL)
Looper(false);
setForThread(mLooper);
8.}
这里会发现在mq中初始化了native的Looper对象,查看android/platform/framework/native/libs/utils/Looper.cpp中Looper对象的构造函数
1.//
简化后的代码
2.Looper:
Looper(bool
allowNonCallbacks)
mAllowNonCallbacks(allowNonCallbacks),
mSendingMessage(false),
mResponseIndex(0),
mNextMessageUptime(LLONG_MAX)
int
wakeFds[2];
result
pipe(wakeFds);
mWakeReadPipeFd
wakeFds[0];
mWakeWritePipeFd
wakeFds[1];
fcntl(mWakeReadPipeFd,
F_SETFL,
O_NONBLOCK);
fcntl(mWakeWritePipeFd,
mEpollFd
epoll_create(EPOLL_SIZE_HINT);
struct
epoll_event
eventItem;
18.
memset(&
eventItem,
0,
sizeof(epoll_event));
eventItem.events
EPOLLIN;
eventItem.data.fd
mWakeReadPipeFd;
21.
epoll_ctl(mEpollFd,
EPOLL_CTL_ADD,
mWakeReadPipeFd,
eventItem);
22.}
这里我们会发现,在native层创建了一个epoll,并且对epoll的event事件进行了监听。
什么是epoll
在继续分析源码之前,我们先分析一下,什么是epoll
epoll是Linux中的一种IO多路复用方式,也叫做event-driver-IO。
Linux的select多路复用IO通过一个select()调用来监视文件描述符的数组,然后轮询这个数组。
如果有IO事件,就进行处理。
select的一个缺点在于单个进程能够监视的文件描述符的数量存在最大限制,select()所维护的存储大量文件描述符的数据结构,随着文件描述符数量的增大,其复制的开销也线性增长。
epoll在select的基础上(实际是在poll的基础上)做了改进,epoll同样只告知那些就绪的文件描述符,而且当我们调用epoll_wait()获得就绪文件描述符时,返回的不是实际的描述符,而是一个代表就绪描述符数量的值,你只需要去epoll指定的一个数组中依次取得相应数量的文件描述符即可。
另一个本质的改进在于epoll采用基于事件的就绪通知方式(设置回调)。
在select中,进程只有在调用一定的方法后,内核才对所有监视的文件描述符进行扫描,而epoll事先通过epoll_ctl()来注册一个文件描述符,一旦基于某个文件描述符就绪时,内核会采用类似callback的回调机制,迅速激活这个文件描述符,当进程调用epoll_wait()时便得到通知
关于epoll和select,可以举一个例子来表达意思。
select的情况和班长告诉全班同学交作业类似,会挨个去询问作业是否完成,如果没有完成,班长会继续询问。
而epoll的情况则是班长询问的时候只是统计了待交作业的人数,然后告诉同学作业完成的时候告诉把作业放在某处,然后喊一下他。
然后班长每次都去这个地方收作业。
大致了解了epoll之后,我们继续查看nativePollOnce方法,同理,会调用nativeLooper的pollOnce方法
1.while
(mResponseIndex
mResponses.size())
const
Response&
response
mResponses.itemAt(mResponseIndex++);
ident
response.request.ident;
(ident
>
fd
response.request.fd;
events
response.events;
void*
data
response.request.data;
(outFd
*outFd
fd;
(outEvents
*outEvents
events;
(outData
*outData
data;
ident;
在pollOnce中,会先处理没有callback的response(ALOOPER_POLL_CALLBACK=-2),处理完后会执行pollInner方法
移除了部分细节处理和日志代码
添加了分析源码的日志
3.int
pollInner(int
timeoutMillis)
(timeoutMillis
mNextMessageUptime
LLONG_MAX)
nsecs_t
systemTime(SYSTEM_TIME_MONOTONIC);
messageTimeoutMillis
toMillisecondTimeoutDelay(now,
mNextMessageUptime);
(messageTimeoutMillis
timeoutMillis))
timeoutMillis
messageTimeoutMillis;
Poll.
ALOOPER_POLL_WAKE;
mResponses.clear();
16.