【移动应用开发技术】Android中Handler机制的工作原理是什么_第1页
【移动应用开发技术】Android中Handler机制的工作原理是什么_第2页
【移动应用开发技术】Android中Handler机制的工作原理是什么_第3页
【移动应用开发技术】Android中Handler机制的工作原理是什么_第4页
【移动应用开发技术】Android中Handler机制的工作原理是什么_第5页
已阅读5页,还剩7页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

【移动应用开发技术】Android中Handler机制的工作原理是什么

本篇文章为大家展示了Android中Handler机制的工作原理是什么,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。Looper在使用Handler之前,我们必须得初始化Looper,并让Looper跑起来。Looper.prepare();

...

Looper.loop();执行上面两条语句之后,Looper就可以跑起来了。先来看看对应的源码:public

static

void

prepare()

{

prepare(true);

}

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));

}

private

Looper(boolean

quitAllowed)

{

mQueue

=

new

MessageQueue(quitAllowed);

mThread

=

Thread.currentThread();

}必须保证一个线程中有且只有一个Looper对象,所以在初始化Looper的时候,会检查当前线程有没有Looper对象。Looper的初始化会创建一个MessageQueue。创建完Looper后会放到ThreadLocal中去,关于ThreadLocal,后面会说到。public

static

void

loop()

{

//

判断当前线程有没有初始化Looper

final

Looper

me

=

myLooper();

if

(me

==

null)

{

throw

new

RuntimeException("No

Looper;

Looper.prepare()

wasn't

called

on

this

thread.");

}

final

MessageQueue

queue

=

me.mQueue;

...

for

(;;)

{

Message

msg

=

queue.next();

//

might

block

if

(msg

==

null)

{

//

No

message

indicates

that

the

message

queue

is

quitting.

return;

}

...

final

long

traceTag

=

me.mTraceTag;

if

(traceTag

!=

0

&&

Trace.isTagEnabled(traceTag))

{

Trace.traceBegin(traceTag,

msg.target.getTraceName(msg));

}

try

{

//

target指的是Handler

msg.target.dispatchMessage(msg);

}

finally

{

if

(traceTag

!=

0)

{

Trace.traceEnd(traceTag);

}

}

...

msg.recycleUnchecked();

}

}方法比较长,所以只把最核心的代码放了出来。省略掉的代码中有一个比较有意思的:我们可以指定一个阈值比如说200,当Message的处理超过200ms时,就会输出Log。这可以在开发中帮助我们发现一些潜在的性能问题。可惜的是,设置阈值的方法是隐藏的,无法直接调用,所以这里就不放出代码了,感兴趣的朋友自己翻一下源码吧。简化后的代码可以看出逻辑十分简单,可以说Looper在当中扮演着搬砖工的角色,从MessageQueue中取出Message,然后交给Handler去分发,再去MessageQueue中取出Message...无穷无尽,就像愚公移山一样。看到这里,应该多多少少会觉得有点不对劲,因为这里是一个死循环,按道理来说会一直占着CPU资源的,并且消息也总有处理完的时候,难道处理完就从消息队列返回Null,然后Looper结束吗?显然不是,注意看注释mightblock。MessageQueue答案就在MessageQueue里面,直接来看一下next():Message

next()

{

...

int

pendingIdleHandlerCount

=

-1;

//

-1

only

during

first

iteration

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)

{

//

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;

}

//

Process

the

quit

message

now

that

all

pending

messages

have

been

handled.

if

(mQuitting)

{

dispose();

return

null;

}

//

If

first

time

idle,

then

get

the

number

of

idlers

to

run.

//

Idle

handles

only

run

if

the

queue

is

empty

or

if

the

first

message

//

in

the

queue

(possibly

a

barrier)

is

due

to

be

handled

in

the

future.

if

(pendingIdleHandlerCount

<

0

&&

(mMessages

==

null

||

now

<

mMessages.when))

{

pendingIdleHandlerCount

=

mIdleHandlers.size();

}

if

(pendingIdleHandlerCount

<=

0)

{

//

No

idle

handlers

to

run.

Loop

and

wait

some

more.

mBlocked

=

true;

continue;

}

if

(mPendingIdleHandlers

==

null)

{

mPendingIdleHandlers

=

new

IdleHandler[Math.max(pendingIdleHandlerCount,

4)];

}

mPendingIdleHandlers

=

mIdleHandlers.toArray(mPendingIdleHandlers);

}

//

Run

the

idle

handlers.

//

We

only

ever

reach

this

code

block

during

the

first

iteration.

for

(int

i

=

0;

i

<

pendingIdleHandlerCount;

i++)

{

final

IdleHandler

idler

=

mPendingIdleHandlers[i];

mPendingIdleHandlers[i]

=

null;

//

release

the

reference

to

the

handler

boolean

keep

=

false;

try

{

keep

=

idler.queueIdle();

}

catch

(Throwable

t)

{

Log.wtf(TAG,

"IdleHandler

threw

exception",

t);

}

if

(!keep)

{

synchronized

(this)

{

mIdleHandlers.remove(idler);

}

}

}

//

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;

}

}代码有点长,这次不打算省略掉一些了,因为这里面还有一个小彩蛋。方法中最重要的应该就是这一行了nativePollOnce(ptr,

nextPollTimeoutMillis);简单来说,当nextPollTimeoutMillis==-1时,挂起当前线程,释放CPU资源,当nextPollTimeoutMillis>=0时会延时指定的时间激活一次线程,让代码继续执行下去。这里涉及到了底层的pipe管道和epoll机制,就不再讲下去了(其实是因为讲不下去了)。这也就可以回答上面的问题了,当没有消息的时候只需要让线程挂起就行了,这样可以保证不占用CPU资源的同时保住Looper的死循环。然后我们来看消息是如何取出来的。MessageQueue中有一个Message,Message类中又有一个Message成员next,可以看出Message是一个单链表结构。消息的顺序是根据时间先后顺序排列的。一般来说,我们要取的Message就是第一个(这里先不考虑异步消息,关于异步消息以后会讲到的,又成功给自己挖了一个坑哈哈),如果当前时间大于等于Message中指定的时间,那么将消息取出来,返回给Looper。由于此时nextPollTimeoutMillis的值为0,所以当前面的消息处理完之后,Looper就又来取消息了。如果当前的时间小于Message中指定的时间,那么设置nextPollTimeoutMillis值以便下次唤醒。还有另外一种当前已经没有消息了,nextPollTimeoutMillis会被设置为-1,也就是挂起线程。别急,还没那么快呢,接着往下看。紧接着的逻辑是判断当前有没有IdleHandler,没有的话就continue,该挂起就挂起,该延时就延时,有IdleHandler的话会执行它的queueIdle()方法。这个IdleHandler是干什么的呢?从名字应该也能猜出个一二来,这里就不再展开讲了。关于它的一些妙用可以看我之前写的Android启动优化之延时加载。执行完queueIdle()方法后,会将nextPollTimeoutMillis置为0,重新看一下消息队列中有没有新的消息。Handler上面将取消息的流程都讲清楚了,万事俱备,就差往消息队列中添加消息了,该我们最熟悉的Handler出场了。Handler往队列中添加消息,主要有两种方式:Handler.sendXXX();

Handler.postXXX();第一种主要是发送Message,第二种是Runnable。无论是哪种方式,最终都会进入到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;

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;

}一般情况下,我们通过Handler发送消息的时候,会通过SystemClock.uptimeMillis()获取一个开机时间,然后MessageQueue就会根据这个时间来对Message进行排序。所以enqueueMessage()方法中就分了两种情况,一种是直接可以在队头插入的。一种是排在中间,需要遍历一下,然后寻一个合适的坑插入。when==0对应的是Handler的sendMessageAtFrontOfQueue()和postAtFrontOfQueue()方法。needWake的作用是根据情况唤醒Looper线程。上面有一点还没有讲,就是Looper从MessageQueue中取出Message后,会交由Handler进行消息的分发。public

void

dispatchMessage(Message

msg)

{

if

(msg.callback

!=

null)

{

handleCallback(msg);

}

else

{

if

(mCallback

!=

null)

{

if

(mCallback.handleMessage(msg))

{

return;

}

}

handleMessage(msg);

}

}优先级顺序是Message自带的callback,接着是Handler自带的callback,最后才是handleMessage()这个回调。ThreadLocal还记得Looper中有一个ThreadLocal吧,把它放到最后来讲是因为它可以单独拿出来讲,不想在上面干扰到整个流程。ThreadLocal是一个数据存储类,它最神奇的地方就是明明是同一个ThreadLocal对象,但是在不同线程中可以存储不同的对象,比如说在线程A中存储了"Hello",而在线程B中存储了"World"。它们之间互相不干扰。在Handler机制中,由于一个Looper对应着一个线程,所以将Looper存进ThreadLocal最合适不过了。ThreadLocal比价常用的就set()和get()方法。分别来看看怎么实现的吧。public

void

set(T

value)

{

Thread

t

=

Thread.currentThread();

ThreadLocalMap

map

=

getMap(t);

if

(map

!=

null)

map.set(this,

value);

else

c

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论