暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

android源码解 析

yBmZlQzJ 2024-03-17
212

原文出处:android源码解

作者:qq_23547831

本系列文章经作者授权在

看云整理发布,未经作者

允许,请勿转载!

android源码解析

主要用于解析android

framework层源码,干货满

满;

PS一句:最终还是选择

CSDN来整理发表这几年的

知识点,该文章平行迁移到

CSDN。因为CSDN也支持

MarkDown语法了,牛逼

啊!

【工匠若

水 http://blog.csdn.net/yanbob

er】

Notice:阅读完该篇之后如

果想继续深入阅读Android触

摸屏事件派发机制详解与源

码分析下一篇请点击

《Android触摸屏事件派发机

制详解与源码分析二

(ViewGroup篇)》查看。

1

背景

最近在简书和微博还有Q群

看见很多人说Android自定义

控件(Vie w/Vie wGroup)如

何学习?为啥那么难?其实

答案很简单:“基础不牢,

地动山摇。 ”

不扯蛋了,进入正题。就算

你不自定义控件,你也必须

要了解Android控件的触摸屏

事件传递机制(之所以说触

摸屏是因为该系列以触摸屏

的事件机制分析为主,对于

类似TV设备等的物理事件机

制的分析雷同但有区别。哈

哈,谁让我之前是做Android

TV BOX的,悲催!),只

有这样才能将你的控件事件

运用的如鱼得水。接下来的

控件触摸屏事件传递机制分

析依据Android 5.1.1源码

API 22)。

2

基础实例现象

2

-1 例子

从一个例子分析说起吧。如

下是一个很简单不过的

Android实例:

<

"

<

?xml version="1.0" encoding=

utf-8"?>

LinearLayout xmlns:android="

http://schemas.android.com/ap

k/res/android"

android:orientation="vert

ical"

android:gravity="center"

android:layout_width="fil

l_parent"

android:layout_height="fi

ll_parent"

android:id="@+id/mylayout

"

>

<

Button

android:id="@+id/my_b

tn"

android:layout_width=

match_parent"

android:layout_height

"wrap_content"

android:text="click t

"

=

est"/>

</LinearLayout>

public class ListenerActivity

extends Activity implements

View.OnTouchListener, View.On

ClickListener {

private LinearLayout mLay

private Button mButton;

out;

@

Override

public void onCreate(Bund

le savedInstanceState) {

super.onCreate(savedI

nstanceState);

setContentView(R.layo

ut.main);

mLayout = (LinearLayo

ut) this.findViewById(R.id.my

layout);

mButton = (Button) th

is.findViewById(R.id.my_btn);

mLayout.setOnTouchLis

tener(this);

mButton.setOnTouchLis

tener(this);

mLayout.setOnClickLis

tener(this);

mButton.setOnClickLis

tener(this);

}

@

Override

public boolean onTouch(Vi

ew v, MotionEvent event) {

Log.i(null, "OnTouchL

istener--onTouch-- action="+e

vent.getAction()+" --"+v);

return false;

}

@

Override

public void onClick(View

v) {

Log.i(null, "OnClickL

istener--onClick--"+v);

}

}

2

-2 现象

如上代码很简单,但凡学过

几天Android的人都能看懂

吧。Ac tivity中有一个

LinearLayout(Vie wGroup的

子类,Vie wGroup是Vie w的

子类)布局,布局中包含一

个按钮(Vie w的子类);然

后分别对这两个控件设置了

Touch与Clic k的监听事件,

具体运行结果如下:

1

2

. 当稳稳的点击Button时打

印如下:

. 当稳稳的点击除过Button

以外的其他地方时打印如

下:

3

. 当收指点击Button时按在

Button上晃动了一下松开

后的打印如下:

机智的你看完这个结果指定

知道为啥吧?

我们看下onTouch和

onClick,从参数都能看出来

onTouch比onClick强大灵

活,毕竟多了一个event参

数。这样onTouch里就可以

处理ACTION_DOWN 、

ACTION_UP、

ACTION_MOVE等等的各种

触摸。现在来分析下上面的

打印结果;在1中,当我们

点击Button时会先触发

onTouch事件(之所以打印

action为0,1各一次是因为按

下抬起两个触摸动作被触

发)然后才触发onClick事

件;在2中也同理类似1;在

中会发现onTouch被多次调

3

运后才调运onClick,是因为

手指晃动了,所以触发了

ACTION_DOWN-

>

>

ACTION_MOVE…-

ACTION_UP。

如果你眼睛比较尖你会看见

onTouch会有一个返回值,

而且在上面返回了false。你

可能会疑惑这个返回值有啥

效果?那就验证一下吧,我

们将上面的onTouch返回值

改为ture。如下:

@

Override

public boolean onTouch(Vi

ew v, MotionEvent event) {

Log.i(null, "OnTouchL

istener--onTouch-- action="+e

vent.getAction()+" --"+v);

return true;

}

再次点击Button结果如下:

看见了吧,如果onTouch返

回true则onClick不会被调运

了。

-3 总结结论

2

好了,经过这个简单的实例

验证你可以总结发现:

1

. Android控件的Listener事

件触发顺序是先触发

onTouch,其次onClick。

. 如果控件的onTouch返回

true将会阻止事件继续传

递,返回false事件会继续

传递。

2

对于伸手党码农来说其实到

这足矣应付常规的App事件

监听处理使用开发了,但是

对于复杂的事件监听处理或

者想自定义控件的码农来说

这才是刚刚开始,只是个热

身。既然这样那就继续

喽。。。

3

Android 5.1.1(API 22)

View触摸屏事件传递

源码分析

3-1 写在前面的话

其实Android源码无论哪个版

本对于触摸屏事件的传递机

制都类似,这里只是选用了

目前最新版本的源码来分析

而已。分析Android Vie w事

件传递机制之前有必要先看

下源码的一些关系,如下是

几个继承关系图:

怎么样?看了官方这个继承

图是不是明白了上面例子中

说的LinearLayout 是

Vie wGroup的子类,

Vie wGroup是Vie w的子类,

Button是Vie w的子类关系

呢?其实,在Android中所有

的控件无非都是Vie wGroup

或者Vie w的子类,说高尚点

就是所有控件都是Vie w的子

类。

这里通过继承关系是说明一

切控件都是Vie w,同时Vie w

与Vie wGroup又存在一些区

别,所以该模块才只单单先

分析Vie w触摸屏事件传递机

制。

-2 从Vie w的

3

dispatchTouchEvent方法说起

在Android中你只要触摸控件

首先都会触发控件的

dispatchTouchEvent方法(其

实这个方法一般都没在具体

的控件类中,而在他的父类

Vie w中),所以我们先来看

下Vie w的dispatchTouchEvent

方法,如下:

public boolean dispatchTouchE

vent(MotionEvent event) {

/ If the event shoul

/

d be handled by accessibility

focus first.

if (event.isTargetAcc

essibilityFocus()) {

/

/ We don't have

focus or no virtual descendan

t has it, do not handle the e

vent.

if (!isAccessibil

ityFocusedViewOrHost()) {

return false;

}

/

/ We have focus

and got the event, then use n

ormal event dispatch.

event.setTargetAc

cessibilityFocus(false);

}

boolean result = fals

e;

if (mInputEventConsis

tencyVerifier != null) {

mInputEventConsis

tencyVerifier.onTouchEvent(ev

ent, 0);

}

final int actionMaske

d = event.getActionMasked();

if (actionMasked == M

otionEvent.ACTION_DOWN) {

/

/ Defensive clea

nup for new gesture

stopNestedScroll(

)

;

}

if (onFilterTouchEven

tForSecurity(event)) {

/noinspection Si

/

mplifiableIfStatement

ListenerInfo li =

mListenerInfo;

if (li != null &&

li.mOnTouchListener != null

& (mView

&

Flags & ENABLED_MASK) == ENAB

LED

&

& li.mOn

TouchListener.onTouch(this, e

vent)) {

result = true

;

}

if (!result && on

TouchEvent(event)) {

result = true

;

}

}

if (!result && mInput

EventConsistencyVerifier != n

ull) {

mInputEventConsis

tencyVerifier.onUnhandledEven

t(event, 0);

}

/

/ Clean up after nes

ted scrolls if this is the en

d of a gesture;

/

/ also cancel it if

we tried an ACTION_DOWN but w

e didn't want the rest

/

/ of the gesture.

if (actionMasked == M

otionEvent.ACTION_UP ||

actionMasked

= MotionEvent.ACTION_CANCEL

|

=

|

(actionMasked

=

= MotionEvent.ACTION_DOWN &

&

)

!result)) {

stopNestedScroll(

;

}

return result;

}

dispatchTouchEvent的代码有

点长,咱们看重点就可以。

前面都是设置一些标记和处

理input与手势等传递,到24

行的

if (onFilterTouchEventFor

语句判断当前Vie w是否没被

遮住等,接着26行定义

ListenerInfo局部变量,

ListenerInfo是Vie w的静态

内部类,用来定义一堆关于

Vie w的XXXListener等方

法;接着

if (li != null && li.mOnT

ENABLED && li.mOnTouchLis

语句就是重点,首先li对象

自然不会为null,

li. mOnTouc hListe ne r呢?你

会发现ListenerInfo

的mOnTouchListener成员是

在哪儿赋值的呢?怎么确认

他是不是null呢?通过在

Vie w类里搜索可以看到:

/

**

*

Register a callback to

be invoked when a touch even

t is sent to this view.

*

@param l the touch lis

tener to attach to this view

*

/

public void setOnTouchLis

tener(OnTouchListener l) {

getListenerInfo().mOn

TouchListener = l;

}

li. mOnTouc hListe ne r是不是

null取决于控件(Vie w)是

否设置setOnTouchListener 监

听,在上面的实例中我们是

设置过Button 的

setOnTouchListener方法的,

所以也不为null;接着通过

位与运算确定控件(Vie w)

是不是ENABLED 的,默认

控件都是ENABLED 的;

接着判断onTouch的返回值

是不是true。通过如上判断

之后如果都为true则设置默

认为false的result为true,那

么接下来的

if (!result && onTouchEve

就不会执行,最终

dispatchTouchEvent也会返回

true。而如果

if (li != null && li.mOnT

语句有一个为false则

if (!result && onTouchEve

就会执行,如果

onTouchEvent(event)返回false

则dispatchTouchEvent返回

false,

否则返回true。

这下再看前面的实例部分明

白了吧?控件触摸就会调运

dispatchTouchEvent方法,而

在dispatchTouchEvent中先执

行的是onTouch方法,所以

验证了实例结论总结中的

onTouch优先于onClick执行

道理。如果控件是ENABLE

且在onTouch方法里返回了

true则dispatchTouchEvent 方

法也返回true,不会再继续

往下执行;反之,onTouch

返回false则会继续向下执行

onTouchEvent方法,且

dispatchTouchEvent的返回值

与onTouchEvent返回值相

同。

所以依据这个结论和上面实

例打印结果你指定已经大胆

猜测认为onClick一定与

onTouchEvent有关系?是不

是呢?先告诉你,是的。下

面我们会分析。

3-2-1 总结结论

在Vie w的触摸屏传递机制中

通过分析dispatchTouchEvent

方法源码我们会得出如下基

本结论:

1

. 触摸控件(Vie w)首先执

行dispatchTouchEvent 方

法。

2

. 在dispatchTouchEvent方法

中先执行onTouch方法,

后执行onClick方法

(onClick方法在

onTouchEvent中执行,下

面会分析)。

3

. 如果控件(Vie w)的

onTouch返回false或者

mOnTouchListener为

null(控件没有设置

setOnTouchListener方法)

或者控件不是enable的情

况下会调运

onTouchEvent,

dispatchTouchEvent返回值

与onTouchEvent返回一

样。

4

. 如果控件不是enable的设

置了onTouch方法也不会

执行,只能通过重写控件

的onTouchEvent方法处理

(上面已经处理分析

了),dispatchTouchEvent

返回值与onTouchEvent返

回一样。

5

. 如果控件(Vie w)是

enable且onTouch返回true

情况下,

dispatchTouchEvent直接返

回true,不会调用

onTouchEvent方法。

上面说了onClick一定与

onTouchEvent有关系,那么

接下来就分析分析

dispatchTouchEvent方法中的

onTouchEvent方法。

3

-3 继续说说Vie w的

dispatchTouchEvent方法中调

运的onTouchEvent方法

上面说了dispatchTouchEvent

方法中如果onTouch返回false

或者mOnTouchListener 为

null(控件没有设置

setOnTouchListener方法)或

者控件不是enable的情况下

会调运onTouchEvent,所以

接着看就知道了,如下:

public boolean onTouchEvent(M

otionEvent event) {

final float x = event

.

.

getX();

getY();

final float y = event

final int viewFlags =

mViewFlags;

if ((viewFlags & ENAB

LED_MASK) == DISABLED) {

if (event.getActi

on() == MotionEvent.ACTION_UP

& (mPrivateFlags & PFLAG_PR

&

ESSED) != 0) {

setPressed(fa

lse);

}

/

/ A disabled vie

w that is clickable still con

sumes the touch

/

/ events, it jus

t doesn't respond to them.

return (((viewFla

gs & CLICKABLE) == CLICKABLE

|

|

(viewFlag

s & LONG_CLICKABLE) == LONG_C

LICKABLE));

}

if (mTouchDelegate !=

null) {

if (mTouchDelegat

e.onTouchEvent(event)) {

return true;

}

}

if (((viewFlags & CLI

CKABLE) == CLICKABLE ||

(viewFlags &

LONG_CLICKABLE) == LONG_CLICK

ABLE)) {

switch (event.get

Action()) {

case MotionEv

ent.ACTION_UP:

boolean p

repressed = (mPrivateFlags &

PFLAG_PREPRESSED) != 0;

if ((mPri

vateFlags & PFLAG_PRESSED) !=

0

|| prepressed) {

/

/ ta

ke focus if we don't have it

already and we should in

uch mode.

/

/ to

boole

if (i

an focusTaken = false;

sFocusable() && isFocusableIn

TouchMode() && !isFocused())

{

f

ocusTaken = requestFocus();

}

if (p

repressed) {

/

The button is being release

d before we actually

/

/

/

showed it as pressed. Make

it show the pressed

/

/

state now (before schedulin

g the click) to ensure

/

s

/

the user sees it.

etPressed(true, x, y);

}

if (!

mHasPerformedLongPress) {

/

r

/

/

This is a tap, so remove th

e longpress check

emoveLongPressCallback();

/

Only perform take click act

ions if we were in the presse

d state

i

f (!focusTaken) {

/

/ Use a Runnable and post

this rather than calling

/

/ performClick directly.

This lets other visual state

/

/ of the view update befo

re click actions start.

if (mPerformClick == null)

{

mPerformClick = new Pe

rformClick();

}

if (!post(mPerformClick))

{

performClick();

}

}

}

if (m

UnsetPressedState == null) {

m

UnsetPressedState = new Unset

PressedState();

}

if (p

repressed) {

p

ostDelayed(mUnsetPressedState

,

ViewConfiguration.getP

ressedStateDuration());

}

els

e if (!post(mUnsetPressedStat

e)) {

/

/

If the post failed, unpress

right now

m

UnsetPressedState.run();

}

remov

eTapCallback();

}

break;

case MotionEv

ent.ACTION_DOWN:

mHasPerfo

rmedLongPress = false;

if (perfo

rmButtonActionOnTouchDown(eve

nt)) {

break

;

}

/

/ Walk u

p the hierarchy to determine

if we're inside a scrolling c

ontainer.

boolean i

sInScrollingContainer = isInS

crollingContainer();

/

/ For vi

ews inside a scrolling contai

ner, delay the pressed feedba

ck for

/

/ a shor

t period in case this is a sc

roll.

if (isInS

crollingContainer) {

mPriv

ateFlags |= PFLAG_PREPRESSED;

if (m

PendingCheckForTap == null) {

m

PendingCheckForTap = new Chec

kForTap();

}

mPend

ingCheckForTap.x = event.getX

();

mPend

ingCheckForTap.y = event.getY

();

postD

elayed(mPendingCheckForTap, V

iewConfiguration.getTapTimeou

t());

}

else {

/ No

/

t inside a scrolling containe

r, so show the feedback right

away

setPr

essed(true, x, y);

ForLongClick(0);

check

}

break;

case MotionEv

ent.ACTION_CANCEL:

setPresse

removeTap

removeLon

break;

d(false);

Callback();

gPressCallback();

case MotionEv

ent.ACTION_MOVE:

drawableH

otspotChanged(x, y);

/

/ Be len

ient about moving outside of

buttons

if (!poin

tInView(x, y, mTouchSlop)) {

/

/ Ou

tside button

remov

eTapCallback();

if ((

mPrivateFlags & PFLAG_PRESSED

)

!= 0) {

/

/

Remove any future long pres

s/tap checks

r

s

emoveLongPressCallback();

etPressed(false);

}

}

break;

}

return true;

}

return false;

}

我勒个去!一个方法比一个

方法代码多。好吧,那咱们

继续只挑重点来说明呗。

首先地6到14行可以看出,

如果控件(Vie w)是

disenable状态,同时是可以

clickable的则onTouchEvent直

接消费事件返回true,反之

如果控件(Vie w)是

disenable状态,同时是

disc lic ka ble的则onTouchEvent

直接false。多说一句,关于

控件的enable或者clickable属

性可以通过java或者xml直接

设置,每个vie w都有这些属

性。

接着22行可以看见,如果一

个控件是enable且disc lic ka ble

则onTouchEvent直接返回

false了;反之,如果一个控

件是enable且clickable则继续

进入过于一个event的switch

判断中,然后最终

onTouchEvent都返回了true。

switch的ACTION_DOWN与

ACTION_MOVE都进行了一

些必要的设置与置位,接着

到手抬起来ACTION_UP时

你会发现,首先判断了是否

按下过,同时是不是可以得

到焦点,然后尝试获取焦

点,然后判断如果不是

longPressed则通过post在UI

Thread中执行一个

PerformClick的Runnable,也

就是performClick方法。具体

如下:

public boolean performClick()

{

final boolean result;

final ListenerInfo li

=

mListenerInfo;

if (li != null && li.

mOnClickListener != null) {

playSoundEffect(S

oundEffectConstants.CLICK);

li.mOnClickListen

er.onClick(this);

result = true;

}

}

else {

result = false;

sendAccessibilityEven

t(AccessibilityEvent.TYPE_VIE

W_CLICKED);

return result;

}

这个方法也是先定义一个

ListenerInfo的变量然后赋

值,接着判断

li.mOnClickListener是不是为

null,决定执行不执行

onClick。你指定现在已经很

机智了,和onTouch一样,

搜一下mOnClickListener在哪

赋值的呗,结果发现:

public void setOnClickListene

r(OnClickListener l) {

if (!isClickable()) {

setClickable(true

)

;

}

getListenerInfo().mOn

ClickListener = l;

}

看见了吧!控件只要监听了

onClick方法则

mOnClickListener就不为

null,而且有意思的是如果

调运setOnClickListener方法

设置监听且控件是

disc lic ka ble的情况下默认会

帮设置为clickable。

我勒个去!!!惊讶

吧!!!猜的没错onClick就

在onTouchEvent中执行的,

而且是在onTouchEvent的

ACTION_UP事件中执行

的。

3-3-1 总结结论

1

. onTouchEvent方法中会在

ACTION_UP分支中触发

onClick的监听。

2

. 当dispatchTouchEvent在进

行事件分发的时候,只有

前一个action返回true,才

会触发下一个action。

到此上面例子中关于Button

点击的各种打印的真实原因

都找到了可靠的证据,也就

是说Vie w的触摸屏事件传递

机制其实也就这么回事。

4

透过源码继续进阶实

例验证

其实上面分析完Vie w的触摸

传递机制之后已经足够用

了。如下的实例验证可以说

是加深阅读源码的理解,还

有一个主要作用就是为将来

自定义控件打下坚实基础。

因为自定义控件中时常会与

这几个方法打交道。

4-1 例子

我们自定义一个

Button(Button实质继承自

Vie w),如下:

public class TestButton exten

ds Button {

public TestButton(Context

context, AttributeSet attrs)

{

super(context, attrs)

;

}

@

Override

public boolean dispatchTo

uchEvent(MotionEvent event) {

Log.i(null, "dispatch

TouchEvent-- action=" + event

.

getAction());

return super.dispatch

TouchEvent(event);

}

@

Override

public boolean onTouchEve

nt(MotionEvent event) {

Log.i(null, "onTouchE

vent-- action="+event.getActi

on());

return super.onTouchE

vent(event);

}

}

其他代码如下:

<

"

<

?xml version="1.0" encoding=

utf-8"?>

LinearLayout xmlns:android="

http://schemas.android.com/ap

k/res/android"

android:orientation="vert

ical"

android:gravity="center"

android:layout_width="fil

l_parent"

android:layout_height="fi

ll_parent"

android:id="@+id/mylayout

"

>

<

com.zzci.light.TestButto

android:id="@+id/my_b

android:layout_width=

n

tn"

"

=

match_parent"

android:layout_height

"wrap_content"

android:text="click t

est"/>

<

/LinearLayout>

public class ListenerActivity

extends Activity implements

View.OnTouchListener, View.On

ClickListener {

private LinearLayout mLay

out;

n;

private TestButton mButto

@

Override

public void onCreate(Bund

le savedInstanceState) {

super.onCreate(savedI

nstanceState);

setContentView(R.layo

ut.main);

mLayout = (LinearLayo

ut) this.findViewById(R.id.my

layout);

mButton = (TestButton

)

this.findViewById(R.id.my_b

tn);

mLayout.setOnTouchLis

tener(this);

mButton.setOnTouchLis

tener(this);

mLayout.setOnClickLis

tener(this);

mButton.setOnClickLis

tener(this);

}

@

Override

public boolean onTouch(Vi

ew v, MotionEvent event) {

Log.i(null, "OnTouchL

istener--onTouch-- action="+e

vent.getAction()+" --"+v);

return false;

}

@

Override

public void onClick(View

v) {

Log.i(null, "OnClickL

istener--onClick--"+v);

}

}

其实这段代码只是对上面例

子中的Button换为了自定义

Button而已。

4-2 现象分析

4-2-1 点击Button(手抽筋了

一下)

可以发现,如上打印完全符

合源码分析结果,

dispatchTouchEvent方法先派

发down事件,完事调运

onTouch,完事调运

onTouchEvent返回true,同时

dispatchTouchEvent返回

true,然后

dispatchTouchEvent继续派发

move或者up事件,循环,直

到onTouchEvent处理up事件

时调运onClick事件,完事返

回true,同时

dispatchTouchEvent返回

true;一次完整的Vie w事件

派发流程结束。

4-2-2 简单修改onTouchEvent

返回值为true

将TestButton类的

onTouchEvent方法修改如

下,其他和基础代码保持不

变:

@

Override

public boolean onTouchEve

nt(MotionEvent event) {

Log.i(null, "onTouchE

vent-- action="+event.getActi

on());

return true;

}

点击Button打印如下:

可以发现,当自定义了控件

(Vie w)的onTouchEvent直

接返回true而不调运super方

法时,事件派发机制如同

4.2.1类似,只是最后up事件

没有触发onClick而已(因为

没有调用super)。

所以可想而知,如果

TestButton类的onTouchEvent

修改为如下:

@

Override

public boolean onTouchEve

nt(MotionEvent event) {

Log.i(null, "onTouchE

vent-- action="+event.getActi

on());

super.onTouchEvent(ev

ent);

return true;

}

点击Button如下:

整个派发机制和4.2.1完全类

似。

4

-2-3 简单修改onTouchEvent

返回值为false

将TestButton类的

onTouchEvent方法修改如

下,其他和基础代码保持不

变:

@

Override

public boolean onTouchEve

nt(MotionEvent event) {

Log.i(null, "onTouchE

vent-- action="+event.getActi

on());

return false;

}

点击Button如下:

你会发现如果onTouchEvent

返回false(也即

dispatchTouchEvent一旦返回

false将不再继续派发其他

action,立即停止派发),这

里只派发了down事件。至于

后面触发了LinearLayout的

touch与click事件我们这里不

做关注,下一篇博客会详细

解释为啥(其实你可以想下

的,LinearLayout 是

Vie wGroup的子类,你懂

的),这里你只用知道Vie w

的onTouchEvent返回false会

阻止继续派发事件。

同理修改如下:

@

Override

public boolean onTouchEve

nt(MotionEvent event) {

Log.i(null, "onTouchE

vent-- action="+event.getActi

on());

super.onTouchEvent(ev

ent);

return false;

}

点击Button如下:

4-2-4 简单修改

dispatchTouchEvent返回值为

true

将TestButton类的

dispatchTouchEvent方法修改

如下,其他和基础代码保持

不变:

@

Override

public boolean dispatchTo

uchEvent(MotionEvent event) {

Log.i(null, "dispatch

TouchEvent-- action=" + event

.

getAction());

return true;

}

点击Button如下:

你会发现如果

dispatchTouchEvent直接返回

true且不调运super任何事件

都得不到触发。

继续修改如下呢?

将TestButton类的

dispatchTouchEvent方法修改

如下,其他和基础代码保持

不变:

@

Override

public boolean dispatchTo

uchEvent(MotionEvent event) {

Log.i(null, "dispatch

TouchEvent-- action=" + event

.

getAction());

super.dispatchTouchEv

ent(event);

return true;

}

点击Button如下:

可以发现所有事件都可以得

到正常派发,和4.2.1类似。

4-2-5 简单修改

dispatchTouchEvent返回值为

false

将TestButton类的

dispatchTouchEvent方法修改

如下,其他和基础代码保持

不变:

@

Override

public boolean dispatchTo

uchEvent(MotionEvent event) {

Log.i(null, "dispatch

TouchEvent-- action=" + event

.

getAction());

return false;

}

点击Button如下:

你会发现事件不进行任何继

续触发,关于点击Button触

发了LinearLayout的事件暂时

不用关注,下篇详解。

继续修改如下呢?

将TestButton类的

dispatchTouchEvent方法修改

如下,其他和基础代码保持

不变:

@

Override

public boolean dispatchTo

uchEvent(MotionEvent event) {

Log.i(null, "dispatch

TouchEvent-- action=" + event

.

getAction());

super.dispatchTouchEv

ent(event);

return false;

}

点击Button如下:

你会发现结果和4.2.3的第二

部分结果一样,也就是说如

果dispatchTouchEvent返回

false事件将不再继续派发下

一次。

4-2-6 简单修改

dispatchTouchEvent与

onTouchEvent返回值

修改dispatchTouchEvent返回

值为true,onTouchEvent为

false:

将TestButton类的

dispatchTouchEvent方法和

onTouchEvent方法修改如

下,其他和基础代码保持不

变:

@

Override

public boolean dispatchTo

uchEvent(MotionEvent event) {

Log.i(null, "dispatch

TouchEvent-- action=" + event

getAction());

super.dispatchTouchEv

ent(event);

return true;

.

}

@

Override

public boolean onTouchEve

nt(MotionEvent event) {

Log.i(null, "onTouchE

vent-- action=" + event.getAc

tion());

super.onTouchEvent(ev

ent);

return false;

}

点击Button如下:

修改dispatchTouchEvent返回

值为false,onTouchEvent为

true:

将TestButton类的

dispatchTouchEvent方法和

onTouchEvent方法修改如

下,其他和基础代码保持不

变:

@

Override

public boolean dispatchTo

uchEvent(MotionEvent event) {

Log.i(null, "dispatch

TouchEvent-- action=" + event

.

getAction());

super.dispatchTouchEv

ent(event);

return false;

}

@

Override

public boolean onTouchEve

nt(MotionEvent event) {

Log.i(null, "onTouchE

vent-- action=" + event.getAc

tion());

super.onTouchEvent(ev

ent);

return true;

}

点击Button如下:

由此对比得出结论,

dispatchTouchEvent事件派发

是传递的,如果返回值为

false将停止下次事件派发,

如果返回true将继续下次派

发。譬如,当前派发down事

件,如果返回true则继续派

发up,如果返回false派发完

down就停止了。

4

-1 总结

这个例子组合了很多种情况

的值去验证上面源码的分

析,同时也为自定义控件打

下了基础。仔细理解这个例

子对于Vie w的事件传递就差

不多了。

5

总结View触摸屏事件

传递机制

上面例子也测试了,源码也

分析了,总得有个最终结论

方便平时写代码作为参考依

据呀,不能每次都再去分析

一遍源码,那得多蛋疼呢!

综合得出Android Vie w的触

摸屏事件传递机制有如下特

征:

1

2

. 触摸控件(Vie w)首先执

行dispatchTouchEvent 方

法。

. 在dispatchTouchEvent方法

中先执行onTouch方法,

后执行onClick方法

(onClick方法在

onTouchEvent中执行,下

面会分析)。

3

. 如果控件(Vie w)的

onTouch返回false或者

mOnTouchListener为

null(控件没有设置

setOnTouchListener方法)

或者控件不是enable的情

况下会调运

onTouchEvent,

dispatchTouchEvent返回值

与onTouchEvent返回一

样。

4

. 如果控件不是enable的设

置了onTouch方法也不会

执行,只能通过重写控件

的onTouchEvent方法处理

(上面已经处理分析

了),dispatchTouchEvent

返回值与onTouchEvent返

回一样。

5

. 如果控件(Vie w)是

enable且onTouch返回true

情况下,

dispatchTouchEvent直接返

回true,不会调用

onTouchEvent方法。

. 当dispatchTouchEvent在进

行事件分发的时候,只有

前一个action返回true,才

会触发下一个action(也

就是说dispatchTouchEvent

6

返回true才会进行下一次

action派发)。

【工匠若

水 http://blog.csdn.net/yanbob

er】

关于上面的疑惑还有

Vie wGroup事件派发机制你

可以继续阅读下一篇博客

Android触摸屏事件派发机

制详解与源码分析二

(ViewGroup篇)》,以便继续

分析Vie w之外的Vie wGroup

事件传递机制。

PS一句:最终还是选择

CSDN来整理发表这几年的

知识点,该文章平行迁移到

CSDN。因为CSDN也支持

MarkDown语法了,牛逼

啊!

【工匠若

水 http://blog.csdn.net/yanbob

er】

该篇承接上一篇《Android触

摸屏事件派发机制详解与源

码分析一(View篇)》,阅读

本篇之前建议先阅读。当

然,阅读完这一篇之后可以

阅读继续进阶的下一篇

Android触摸屏事件派发机

制详解与源码分析三( Ac tivity

篇)》。

1

背景

还记得前一篇《Android触摸

屏事件派发机制详解与源码

分析一(View篇)》中关于透

过源码继续进阶实例验证模

块中存在的点击Button却触

发了LinearLayout的事件疑惑

吗?当时说了,在那一篇咱

们只讨论Vie w的触摸事件派

发机制,这个疑惑留在了这

一篇解释,也就是

Vie wGroup的事件派发机

制。

PS:阅读本篇前建议先查看

前一篇《Android触摸屏事件

派发机制详解与源码分析一

(View篇)》,这一篇承接上

一篇。

关于Vie w与Vie wGroup的区

别在前一篇的Android

5

.1.1(API 22) Vie w触摸屏事

件传递源码分析部分的写在

前面的话里面有详细介绍。

其实你只要记住类似Button

这种控件都是Vie w的子类,

类似布局这种控件都是

Vie wGroup的子类,而

Vie wGroup又是Vie w的子类

而已。具体查阅《Android触

摸屏事件派发机制详解与源

码分析一(View篇)》。

2

基础实例现象

2

-1 例子

这个例子布局等还和上一篇

的例子相似,只是重写了

Button和LinearLayout而已,

所以效果图不在提供,具体

参见上一篇。

首先我们简单的自定义一个

Button(Vie w的子类),再

自定义一个

LinearLayout(Vie wGroup的

子类),其实没有自定义任

何属性,只是重写部分方法

(添加了打印,方便查看)

而已,如下:

public class TestButton exten

ds Button {

public TestButton(Context

context, AttributeSet attrs)

{

super(context, attrs)

;

}

@

Override

public boolean dispatchTo

uchEvent(MotionEvent event) {

Log.i(null, "TestButt

on dispatchTouchEvent-- actio

n=" + event.getAction());

return super.dispatch

TouchEvent(event);

}

@

Override

public boolean onTouchEve

nt(MotionEvent event) {

Log.i(null, "TestButt

on onTouchEvent-- action=" +

event.getAction());

return super.onTouchE

vent(event);

}

}

public class TestLinearLayout

extends LinearLayout {

public TestLinearLayout(C

ontext context, AttributeSet

attrs) {

super(context, attrs)

;

}

@

Override

public boolean onIntercep

tTouchEvent(MotionEvent ev) {

Log.i(null, "TestLine

arLayout onInterceptTouchEven

t-- action=" + ev.getAction()

)

;

return super.onInterc

eptTouchEvent(ev);

}

@

Override

public boolean dispatchTo

uchEvent(MotionEvent event) {

Log.i(null, "TestLine

arLayout dispatchTouchEvent--

action=" + event.getAction()

)

;

return super.dispatch

TouchEvent(event);

}

@

Override

public boolean onTouchEve

nt(MotionEvent event) {

Log.i(null, "TestLine

arLayout onTouchEvent-- actio

n=" + event.getAction());

return super.onTouchE

vent(event);

}

}

如上两个控件很简单吧,不

解释,继续看其他代码:

<

"

<

?xml version="1.0" encoding=

utf-8"?>

com.zzci.light.TestLinearLay

out xmlns:android="http://sch

emas.android.com/apk/res/andr

oid"

android:orientation="vert

ical"

android:gravity="center"

android:layout_width="fil

l_parent"

android:layout_height="fi

ll_parent"

android:id="@+id/mylayout

"

n

>

<

com.zzci.light.TestButto

android:id="@+id/my_b

android:layout_width=

tn"

"

=

match_parent"

android:layout_height

"wrap_content"

android:text="click t

est"/>

<

/com.zzci.light.TestLinearLa

yout>

public class ListenerActivity

extends Activity implements

View.OnTouchListener, View.On

ClickListener {

private TestLinearLayout

mLayout;

private TestButton mButto

n;

@

Override

public void onCreate(Bund

le savedInstanceState) {

super.onCreate(savedI

nstanceState);

setContentView(R.layo

ut.main);

mLayout = (TestLinear

Layout) this.findViewById(R.i

d.mylayout);

mButton = (TestButton

)

this.findViewById(R.id.my_b

tn);

mLayout.setOnTouchLis

tener(this);

mButton.setOnTouchLis

tener(this);

mLayout.setOnClickLis

tener(this);

mButton.setOnClickLis

tener(this);

}

@

Override

public boolean onTouch(Vi

ew v, MotionEvent event) {

Log.i(null, "OnTouchL

istener--onTouch-- action="+e

vent.getAction()+" --"+v);

return false;

}

@

Override

public void onClick(View

v) {

Log.i(null, "OnClickL

istener--onClick--"+v);

}

}

到此基础示例的代码编写完

成。没有啥难度,很简单易

懂,不多解释了。

2-2 运行现象

当直接点击Button时打印现

象如下:

TestLinearLayout dispatchTouc

hEvent-- action=0

TestLinearLayout onInterceptT

ouchEvent-- action=0

TestButton dispatchTouchEvent

-

- action=0

OnTouchListener--onTouch-- ac

tion=0 --com.zzci.light.TestB

utton

TestButton onTouchEvent-- act

ion=0

TestLinearLayout dispatchTouc

hEvent-- action=1

TestLinearLayout onInterceptT

ouchEvent-- action=1

TestButton dispatchTouchEvent

-

- action=1

OnTouchListener--onTouch-- ac

tion=1 --com.zzci.light.TestB

utton

TestButton onTouchEvent-- act

ion=1

OnClickListener--onClick--com

.

zzci.light.TestButton

分析:你会发现这个结果好

惊讶吧,点击了Button却先

执行了

TestLinearLayout(Vie wGrou

p)的dispatchTouchEvent ,

接着执行

TestLinearLayout(Vie wGrou

p)的

onInterceptTouchEvent,接着

执行

TestButton(TestLinearLayout

包含的成员Vie w)的

dispatchTouchEvent

接着就是Vie w触摸事件的

分发流程,上一篇已经讲过

了。也就是说当点击Vie w时

事件派发每一个down,up的

action顺序是先触发最父级控

这里为LinearLayout)的

dispatchTouchEvent-

onInterceptTouchEvent->然

>

后向前一级传递(这里就是

传递到Button Vie w)。

那么继续看,当直接点击除

Button以外的其他部分时打

印如下:

TestLinearLayout dispatchTouc

hEvent-- action=0

TestLinearLayout onInterceptT

ouchEvent-- action=0

OnTouchListener--onTouch-- ac

tion=0 --com.zzci.light.TestL

inearLayout

TestLinearLayout onTouchEvent

-

- action=0

TestLinearLayout dispatchTouc

hEvent-- action=1

OnTouchListener--onTouch-- ac

tion=1 --com.zzci.light.TestL

inearLayout

TestLinearLayout onTouchEvent

-

- action=1

OnClickListener--onClick--com

zzci.light.TestLinearLayout

.

分析:你会发现一个奇怪的

现象,派发

ACTION_DOWN(action=0

事件时顺序为

dispatchTouchEvent-

onInterceptTouchEvent-

>

>

onTouch

-

>onTouchEvent,而接着派

发ACTION_UP(action=1)

事件时与上面顺序不同的时

竟然没触发

onInterceptTouchEvent方法。

这是为啥呢?

我也纳闷,那就留着下面分

析源码再找答案吧,先记住

这个问题。

有了上面这个例子你是不是

发现包含Vie wGroup与Vie w

的事件触发有些相似又有很

大差异吧(PS:在Android中

继承Vie w实现的控件已经是

最小单位了,也即在XML布

局等操作中不能再包含子项

了,而继承Vie wGroup实现

的控件通常不是最小单位,

可以包含不确定数目的子

项)。具体差异是啥呢?咱

们类似上篇一样,带着这个

实例疑惑去看源码找答案

吧。

3

Android 5.1.1(API 22)

ViewGrou p触摸屏事件

传递源码分析

通过上面例子的打印我们可

以确定分析源码的顺序,那

就开始分析呗。

3-1 从Vie wGroup的

dispatchTouchEvent方法说起

前一篇的3-2小节说在

Android中你只要触摸控件首

先都会触发控件的

dispatchTouchEvent方法(其

实这个方法一般都没在具体

的控件类中,而在他的父类

Vie w中)。这其实是思维单

单局限在Vie w的角度去看待

的,这里通过上面的例子你

是否发现触摸控件会先从他

的父级dispatchTouchEvent 方

法开始派发呢?是的,所以

咱们先从Vie wGroup的

dispatchTouchEvent方法说

起,如下:

public boolean dispatchTouchE

vent(MotionEvent ev) {

if (mInputEventConsis

tencyVerifier != null) {

mInputEventConsis

tencyVerifier.onTouchEvent(ev

,

1);

}

/

/ If the event targe

ts the accessibility focused

view and this is it, start

/

/ normal event dispa

tch. Maybe a descendant is wh

at will handle the click.

if (ev.isTargetAccess

ibilityFocus() && isAccessibi

lityFocusedViewOrHost()) {

ev.setTargetAcces

sibilityFocus(false);

}

boolean handled = fal

se;

if (onFilterTouchEven

tForSecurity(ev)) {

final int action

ev.getAction();

final int actionM

=

asked = action & MotionEvent.

ACTION_MASK;

/

/ Handle an init

ial down.

if (actionMasked

= MotionEvent.ACTION_DOWN) {

/ Throw away

all previous state when star

ting a new touch gesture.

=

/

/

/ The framew

ork may have dropped the up o

r cancel event for the previo

us gesture

/

/ due to an

app switch, ANR, or some othe

r state change.

cancelAndClea

rTouchTargets(ev);

resetTouchSta

te();

}

/

/ Check for inte

rception.

ercepted;

final boolean int

if (actionMasked

=

= MotionEvent.ACTION_DOWN

| mFirst

TouchTarget != null) {

final boolean

|

disallowIntercept = (mGroupF

lags & FLAG_DISALLOW_INTERCEP

T) != 0;

if (!disallow

Intercept) {

intercept

ed = onInterceptTouchEvent(ev

)

;

ev.setAct

ion(action); // restore actio

n in case it was changed

}

else {

intercept

ed = false;

}

}

else {

/ There are

/

no touch targets and this act

ion is not an initial down

/

/ so this vi

ew group continues to interce

pt touches.

intercepted =

true;

}

/

/ If intercepted

,

.

start normal event dispatch

Also if there is already

/

/ a view that is

handling the gesture, do nor

mal event dispatch.

if (intercepted |

mFirstTouchTarget != null)

|

{

ev.setTargetA

ccessibilityFocus(false);

}

/

/ Check for canc

elation.

final boolean can

celed = resetCancelNextUpFlag

(this)

|

| action

Masked == MotionEvent.ACTION_

CANCEL;

/

/ Update list of

touch targets for pointer do

wn, if needed.

final boolean spl

it = (mGroupFlags & FLAG_SPLI

T_MOTION_EVENTS) != 0;

TouchTarget newTo

uchTarget = null;

boolean alreadyDi

spatchedToNewTouchTarget = fa

lse;

if (!canceled &&

!

intercepted) {

/

/ If the eve

nt is targeting accessiiblity

focus we give it to the

/

/ view that

has accessibility focus and i

f it does not handle it

/

/ we clear t

he flag and dispatch the even

t to all children as usual.

/

/ We are loo

king up the accessibility foc

used host to avoid keeping

/

/ state sinc

e these events are very rare.

View childWit

hAccessibilityFocus = ev.isTa

rgetAccessibilityFocus()

?

fin

dChildWithAccessibilityFocus(

: null;

)

if (actionMas

ked == MotionEvent.ACTION_DOW

N

|

| (s

plit && actionMasked == Motio

nEvent.ACTION_POINTER_DOWN)

|

| ac

tionMasked == MotionEvent.ACT

ION_HOVER_MOVE) {

final int

actionIndex = ev.getActionIn

dex(); // always 0 for down

final int

idBitsToAssign = split ? 1 <

<

)

ev.getPointerId(actionIndex

:

TouchTarget.ALL_POINTER_IDS;

/

/ Clean

up earlier touch targets for

this pointer id in case they

/

/ have b

ecome out of sync.

removePoi

ntersFromTouchTargets(idBitsT

oAssign);

final int

childrenCount = mChildrenCou

nt;

if (newTo

uchTarget == null && children

Count != 0) {

final

float x = ev.getX(actionInde

x);

final

float y = ev.getY(actionInde

x);

/

/ Fi

nd a child that can receive t

he event.

/

/ Sc

an children from front to bac

k.

final

ArrayList<View> preorderedLi

st = buildOrderedChildList();

final

boolean customOrder = preord

eredList == null

&

& isChildrenDrawingOrderE

nabled();

final

View[] children = mChildren;

for (

int i = childrenCount - 1; i

>= 0; i--) {

f

inal int childIndex = customO

rder

?

getChildDrawingOrder

(childrenCount, i) : i;

f

inal View child = (preordered

List == null)

?

children[childIndex]

preorderedList.get(childIn

dex);

:

/

/

If there is a view that has

accessibility focus we want

it

/

/

to get the event first and

if not handled we will perfor

m a

/

/

normal dispatch. We may do

a double iteration but this i

s

/

/

safer given the timeframe.

i

f (childWithAccessibilityFocu

s != null) {

if (childWithAccessibility

Focus != child) {

continue;

}

childWithAccessibilityFocu

s = null;

i = childrenCount - 1;

}

i

f (!canViewReceivePointerEven

ts(child)

|

| !isTransformedTouch

PointInView(x, y, child, null

) {

)

ev.setTargetAccessibilityF

ocus(false);

continue;

}

n

ewTouchTarget = getTouchTarge

t(child);

i

f (newTouchTarget != null) {

/

/ Child is already receiv

ing touch within its bounds.

/

/ Give it the new pointer

in addition to the ones it i

s handling.

newTouchTarget.pointerIdBi

ts |= idBitsToAssign;

break;

}

r

esetCancelNextUpFlag(child);

i

f (dispatchTransformedTouchEv

ent(ev, false, child, idBitsT

oAssign)) {

/

/ Child wants to receive

touch within its bounds.

mLastTouchDownTime = ev.ge

tDownTime();

if (preorderedList != null

)

{

/

/ childIndex points i

nto presorted list, find orig

inal index

for (int j = 0; j < ch

ildrenCount; j++) {

if (children[child

Index] == mChildren[j]) {

mLastTouchDown

Index = j;

break;

}

}

}

else {

mLastTouchDownIndex =

childIndex;

}

mLastTouchDownX = ev.getX(

mLastTouchDownY = ev.getY(

newTouchTarget = addTouchT

)

)

;

;

arget(child, idBitsToAssign);

alreadyDispatchedToNewTouc

hTarget = true;

break;

}

/

/

The accessibility focus did

n't handle the event, so clea

r

/

the flag and do a normal di

spatch to all children.

/

e

v.setTargetAccessibilityFocus

(false);

}

if (p

reorderedList != null) preord

eredList.clear();

}

if (newTo

uchTarget == null && mFirstTo

uchTarget != null) {

/

/ Di

d not find a child to receive

the event.

/

/ As

sign the pointer to the least

recently added target.

newTo

uchTarget = mFirstTouchTarget

;

while

(newTouchTarget.next != null

)

{

n

ewTouchTarget = newTouchTarge

t.next;

}

newTo

uchTarget.pointerIdBits |= id

BitsToAssign;

}

}

}

/

/ Dispatch to to

uch targets.

if (mFirstTouchTa

rget == null) {

/

/ No touch t

argets so treat this as an or

dinary view.

handled = dis

patchTransformedTouchEvent(ev

,

canceled, null,

Touch

Target.ALL_POINTER_IDS);

else {

/ Dispatch t

}

/

o touch targets, excluding th

e new touch target if we alre

ady

/

/ dispatched

to it. Cancel touch targets

if necessary.

TouchTarget p

redecessor = null;

TouchTarget t

arget = mFirstTouchTarget;

while (target

!

= null) {

final Tou

chTarget next = target.next;

if (alrea

dyDispatchedToNewTouchTarget

&

{

& target == newTouchTarget)

handl

ed = true;

}

else {

final

boolean cancelChild = resetC

ancelNextUpFlag(target.child)

|

| intercepted;

if (d

ispatchTransformedTouchEvent(

ev, cancelChild,

target.child, target.point

erIdBits)) {

h

andled = true;

}

if (c

ancelChild) {

i

f (predecessor == null) {

mFirstTouchTarget = next;

}

else {

predecessor.next = next;

}

t

arget.recycle();

t

arget = next;

ontinue;

c

}

}

predecess

target =

or = target;

next;

}

}

/

/ Update list of

touch targets for pointer up

or cancel, if needed.

if (canceled

|

| action

Masked == MotionEvent.ACTION_

UP

|

| action

Masked == MotionEvent.ACTION_

HOVER_MOVE) {

resetTouchSta

else if (split

te();

}

&

& actionMasked == MotionEven

t.ACTION_POINTER_UP) {

final int act

ionIndex = ev.getActionIndex(

)

;

final int idB

itsToRemove = 1 << ev.getPoin

terId(actionIndex);

removePointer

sFromTouchTargets(idBitsToRem

ove);

}

}

if (!handled && mInpu

tEventConsistencyVerifier !=

null) {

mInputEventConsis

tencyVerifier.onUnhandledEven

t(ev, 1);

}

return handled;

}

我勒个去!!!这比Vie w的

dispatchTouchEvent方法长很

多啊,那就只关注重点分析

吧。

第一步,17-24行,对

ACTION_DOWN进行处理。

因为ACTION_DOWN是一系

列事件的开端,当是

ACTION_DOWN时进行一些

初始化操作,从上面源码中

注释也可以看出来,清除以

往的Touch状态然后开始新

的手势。在这里你会发现

cancelAndClearTouchTargets(

ev)方法中有一个非常重要的

操作就是将

mFirstTouc hTa rge t设置为了

null(刚开始分析大眼瞄一

眼没留意,结果越往下看越

迷糊,所以这个是分析

Vie wGroup 的

dispatchTouchEvent方法第一

步中重点要记住的一个地

方),接着在

resetTouchState()方法中重置

Touch状态标识。

第二步,26-47行,检查是否

要拦截。

dispatchTouchEvent(MotionEv

ent ev)这段代码中使用变量

intercepted来标记Vie wGroup

是否拦截Touch事件的传

递,该变量类似第一步的

mFirstTouc hTa rge t变量,在

后续代码中起着很重要的作

用。

if (actionMasked == Motio

这一条判断语句说明当事件

为ACTION_DOWN或者

mFirstTouc hTa rge t不为null(即

已经找到能够接收touch事件

的目标组件)时if成立,否则if

不成立,然后将intercepted

设置为true,也即拦截事

件。当当事件为

ACTION_DOWN或者

mFirstTouc hTa rge t不为null时

判断disallowIntercept(禁止拦

截)标志位,而这个标记在

Vie wGroup中提供了public的

设置方法,如下:

public void requestDisallowIn

terceptTouchEvent(boolean dis

allowIntercept) {

if (disallowIntercept

=

= ((mGroupFlags & FLAG_DISA

LLOW_INTERCEPT) != 0)) {

/

/ We're already

in this state, assume our anc

estors are too

return;

}

if (disallowIntercept

)

{

mGroupFlags |= FL

AG_DISALLOW_INTERCEPT;

else {

mGroupFlags &= ~F

}

LAG_DISALLOW_INTERCEPT;

}

/

/ Pass it up to our

parent

{

if (mParent != null)

mParent.requestDi

sallowInterceptTouchEvent(dis

allowIntercept);

}

}

所以你可以在其他地方调用

requestDisallowInterceptTouc

hEvent(boolean

disallowIntercept)方法,从而

禁止执行是否需要拦截的判

断。

当disallowIntercept为true(禁

止拦截判断)时则

intercepted直接设置为false,

否则调用

onInterceptTouchEvent(ev)方

法,然后将结果赋值给

intercepted。那就来看下

Vie wGroup与众不同与Vie w

特有的onInterceptTouchEvent

方法,如下:

public boolean onInterceptTou

chEvent(MotionEvent ev) {

return false;

}

看见了吧,默认的

onInterceptTouchEvent方法只

是返回了一个false,也即

intercepted=false。所以可以

说明上面例子的部分打印

>

dispatchTouchEvent-

onInterceptTouchEvent-

>

onTouchEvent),这里很明

显表明在Vie wGroup的

dispatchTouchEvent()中默认

(不在其他地方调运

requestDisallowInterceptTouc

hEvent方法设置禁止拦截标

记)首先调用了

onInterceptTouchEvent()方

法。

第三步,49-51行,检查

cancel。

通过标记和action检查

cancel,然后将结果赋值给

局部boolean变量canceled。

第四步,53-函数结束,事件

分发。

5

4行首先可以看见获取一个

boolean变量标记split来标

记,默认是true,作用是是

否把事件分发给多个子

Vie w,这个同样在

Vie wGroup中提供了public的

方法设置,如下:

public void setMotionEventSpl

ittingEnabled(boolean split)

{

/

/ TODO Applications

really shouldn't change this

setting mid-touch event,

/

/ but perhaps this s

hould handle that case and se

nd ACTION_CANCELs to any chil

d views

/

/ with gestures in p

rogress when this is changed.

if (split) {

mGroupFlags |= FL

AG_SPLIT_MOTION_EVENTS;

}

else {

mGroupFlags &= ~F

LAG_SPLIT_MOTION_EVENTS;

}

}

接着57 行

if (!canceled && !interce

判断表明,事件不是

ACTION_CANCEL并且

Vie wGroup的拦截标志位

intercepted为false(不拦截)则

会进入

其中。事件分发步骤中关于

ACTION_DOWN的特殊处理

接着67行这个很大的if语句

if (actionMasked == Motio

actionMasked == MotionEve

处理ACTION_DOWN事件,

这个环节比较繁琐,也比较

重要,如下具体分析。

在79行判断了childrenCount

个数是否不为0,然后接着

在84行拿到了子Vie w的list集

合preorderedList;接着在88

行通过一个for循环i从

childrenCount - 1开始遍历到

0,倒序遍历所有的子vie w,

这是因为preorderedList中的

顺序是按照addView或者

XML布局文件中的顺序来

的,后addView添加的子

Vie w,会因为Android的UI后

刷新机制显示在上层;假如

点击的地方有两个子Vie w都

包含的点击的坐标,那么后

被添加到布局中的那个子

vie w会先响应事件;这样其

实也是符合人的思维方式

的,因为后被添加的子vie w

会浮在上层,所以我们去点

击的时候一般都会希望点击

最上层的那个组件先去响应

事件。

接着在106到112行通过

ge tTouc hTa rge t去查找当前子

Vie w是否在

mFirstTouc hTa rge t. ne xt这条

target链中的某一个targe中,

如果在则返回这个target,否

则返回null。在这段代码的if

判断通过说明找到了接收

Touch事件的子Vie w,即

newTouchTarget,那么,既

然已经找到了,所以执行

break跳出for循环。如果没有

break则继续向下执行走到

115行开始到134行,这里你

可以看见一段if判断的代码

if (dispatchTransformedTo

,这个被if的大括弧括起来

的一段代码很重要,具体解

释如下:

调用方法

dispatchTransformedTouchEv

ent()将Touch事件传递给特定

的子Vie w。该方法十分重

要,在该方法中为一个递归

调用,会递归调用

dispatchTouchEvent()方法。

在dispatchTouchEvent()中如

果子Vie w为Vie wGroup并且

Touch没有被拦截那么递归

调用dispatchTouchEvent() ,

如果子Vie w为Vie w那么就会

调用其onTouchEvent() 。

dispatchTransformedTouchEv

ent方法如果返回true则表示

子Vie w消费掉该事件,同时

进入

该if判断。满足if语句后重要

的操作有:

给newTouchTarget赋值;

alreadyDispatchedToNewT

ouchTarget赋值为true;

执行break,因为该for循

环遍历子Vie w判断哪个子

Vie w接受Touch事件,既

然已经找到了就跳出该外

层for循环;

如果115行if判断中的

dispatchTransformedTouchEv

ent()方法返回false,即子

Vie w的onTouchEvent返回

false(即Touch事件未被消

费),那么就不满足该if条

件,也就无法执行

addTouchTarget(),从而导致

mFirstTouc hTa rge t为null(没

法对mFirstTouc hTa rge t赋

值,因为上面分析了

mFirstTouc hTa rge t一进来是

ACTION_DOWN就置位为

null了),那么该子Vie w就

无法继续处理

ACTION_MOVE事件和

ACTION_UP事件(28行的

判断为false,也即

intercepted=true了,所以之

后一系列判断无法通过)。

如果115行if判断中的

dispatchTransformedTouchEv

ent()方法返回true,即子

Vie w的onTouchEvent返回

true(即Touch事件被消费),

那么就满足该if条件,从而

mFirstTouc hTa rge t不为null。

继续看143行的判断

if (newTouchTarget == nul

。该if表示经过前面的for循

环没有找到子Vie w接收

Touch事件并且之前的

mFirstTouc hTa rge t不为空则

为真,然后newTouchTarget

指向了最初的Touc hTa rge t。

通过上面67到157行关于事

件分发步骤中

ACTION_DOWN的特殊处理

可以发现,对于此处

ACTION_DOWN的处理具体

体现在

dispatchTransformedTouchEv

ent()方法,该方法返回值具

备如下特征:

re turn de scription set

事件被消

true

mFi r stTouchTa

mFi r stTouchTa

事件未被

消费

false

因为在

dispatchTransformedTouchEv

ent()会调用递归调用

dispatchTouchEvent()和

onTouchEvent(),所以

dispatchTransformedTouchEv

ent()的返回值实际上是由

onTouchEvent()决定的。简

单地说onTouchEvent()是否

消费了Touch事件的返回值

决定了

dispatchTransformedTouchEv

ent()的返回值,从而决定

mFirstTouc hTa rge t是否为

null,进一步决定了

Vie wGroup是否处理Touch事

件,这一点在160行开始的

代码中有体现。如下分析事

件分发步骤中关于

ACTION_DOWN处理之后的

其他处理逻辑,也即160行

开始剩余的逻辑。

事件分发步骤中关于

ACTION_DOWN处理之后的

其他处理逻辑

可以看到,如果派发的事件

不是ACTION_DOWN就不会

经过上面的流程,而是直接

从此处开始执行。上面说

了,经过上面对于

ACTION_DOWN的处理后

mFirstTouc hTa rge t可能为null

或者不为null。所以可以看

见161行代码

if (mFirstTouchTarget ==

判断了mFirstTouc hTa rge t值

是否为null的情况,完全符

合如上分析。那我们分情况

继续分析一下:

当161行if判断的

mFirstTouc hTa rge t为null时,

也就是说Touch事件未被消

费,即没有找到能够消费

touch事件的子组件或Touch

事件被拦截了,则调用

Vie wGroup 的

dispatchTransformedTouchEv

ent()方法处理Touch事件(和

普通Vie w一样),即子Vie w

没有消费Touch事件,那么

子Vie w的上层Vie wGroup才

会调用其onTouchEvent()处

理Touch事件。具体就是在

调用

dispatchTransformedTouchEv

ent()时第三个参数为null,关

dispatchTransformedTouchEv

ent方法下面会分析,暂时先

记住就行。

这下再回想上面例子,点击

Button时为啥触发了Button的

一系列touch方法而没有触发

父级LinearLayout的touch方

法的疑惑?明白了吧?

子vie w对于Touch事件处理返

回true那么其上层的

Vie wGroup就无法处理Touch

事件了,子vie w对于Touch事

件处理返回false那么其上层

的Vie wGroup才可以处理

Touch事件。

当161行if判断的

mFirstTouc hTa rge t不为null

时,也就是说找到了可以消

费Touch事件的子Vie w且后

续Touch事件可以传递到该

子Vie w。可以看见在源码的

else中对于非

ACTION_DOWN事件继续传

递给目标子组件进行处理,

依然是递归调用

dispatchTransformedTouchEv

ent()方法来实现的处理。

到此Vie wGroup 的

dispatchTouchEvent方法分析

完毕。

上面说了Vie wGroup的

dispatchTouchEvent方法详细

情况,也知道在其中可能会

执行onInterceptTouchEvent 方

法,所以接下来咱们先简单

分析一下这个方法。

3

-2 说说Vie wGroup的

dispatchTouchEvent中可能执

行的onInterceptTouchEvent 方

如下系统源码:

public boolean onInterceptTou

chEvent(MotionEvent ev) {

return false;

}

看到了吧,这个方法算是

Vie wGroup不同于Vie w特有

的一个事件派发调运方法。

在源码中可以看到这个方法

实现很简单,但是有一堆注

释。其实上面

分析了,如果Vie wGroup的

onInterceptTouchEvent返回

false就不阻止事件继续传递

派发,否则阻止传递派发。

对了,还记得在

dispatchTouchEvent方法中除

过可能执行的

onInterceptTouchEvent以外在

后面派发事件时执行的

dispatchTransformedTouchEv

ent方法吗?上面分析

dispatchTouchEvent时说了下

面会仔细分析,那么现在就

来继续看看这个方法吧。

-3 继续说说Vie wGroup的

3

dispatchTouchEvent中执行的

dispatchTransformedTouchEv

ent方法

Vie wGroup 的

dispatchTransformedTouchEv

ent方法系统源码如下:

private boolean dispatchTrans

formedTouchEvent(MotionEvent

event, boolean cancel,

View child, int d

esiredPointerIdBits) {

final boolean handled

;

/

/ Canceling motions

is a special case. We don't

need to perform any transform

ations

/

/ or filtering. The

important part is the action

not the contents.

,

final int oldAction =

event.getAction();

if (cancel || oldActi

on == MotionEvent.ACTION_CANC

EL) {

event.setAction(M

otionEvent.ACTION_CANCEL);

if (child == null

)

{

handled = sup

er.dispatchTouchEvent(event);

else {

handled = chi

}

ld.dispatchTouchEvent(event);

}

event.setAction(o

ldAction);

return handled;

}

/

/ Calculate the numb

er of pointers to deliver.

final int oldPointerI

dBits = event.getPointerIdBit

s();

final int newPointerI

dBits = oldPointerIdBits & de

siredPointerIdBits;

/

/ If for some reason

we ended up in an inconsiste

nt state where it looks like

we

/

/ might produce a mo

tion event with no pointers i

n it, then drop the event.

if (newPointerIdBits

=

= 0) {

return false;

}

/

/ If the number of p

ointers is the same and we do

n't need to perform any fancy

/

/ irreversible trans

formations, then we can reuse

the motion event for this

/

/ dispatch as long a

s we are careful to revert an

y changes we make.

/

/ Otherwise we need

to make a copy.

final MotionEvent tra

nsformedEvent;

if (newPointerIdBits

= oldPointerIdBits) {

if (child == null

=

|

| child.hasIdentityMatrix()

)

{

if (child ==

null) {

handled =

super.dispatchTouchEvent(eve

nt);

}

else {

final flo

at offsetX = mScrollX - child

mLeft;

.

final flo

at offsetY = mScrollY - child

mTop;

.

event.off

setLocation(offsetX, offsetY)

;

handled =

child.dispatchTouchEvent(eve

nt);

event.off

setLocation(-offsetX, -offset

Y);

}

return handle

d;

}

transformedEvent

=

MotionEvent.obtain(event);

else {

transformedEvent

event.split(newPointerIdBit

}

=

s);

}

/

/ Perform any necess

ary transformations and dispa

tch.

if (child == null) {

handled = super.d

ispatchTouchEvent(transformed

Event);

}

else {

final float offse

tX = mScrollX - child.mLeft;

final float offse

tY = mScrollY - child.mTop;

transformedEvent.

offsetLocation(offsetX, offse

tY);

if (! child.hasId

entityMatrix()) {

transformedEv

ent.transform(child.getInvers

eMatrix());

}

handled = child.d

ispatchTouchEvent(transformed

Event);

}

/

/ Done.

transformedEvent.recy

cle();

}

return handled;

看到了吧,这个方法也算是

Vie wGroup不同于Vie w特有

的一个事件派发调运方法,

而且奇葩的就是这个方法也

很长。那也继续分析

吧。。。

上面分析了,在

dispatchTouchEvent()中调用

dispatchTransformedTouchEv

ent()将事件分发给子Vie w处

理。在此我们需要重点分析

该方法的第三个参数(Vie w

c hild)。在

dispatchTouchEvent()中多次

调用了

dispatchTransformedTouchEv

ent()方法,而且有时候第三

个参数为null,有时又不

是,他们到底有啥区别呢?

这段源码中很明显展示了结

果。在

dispatchTransformedTouchEv

ent()源码中可以发现多次对

于c hild是否为null的判断,并

且均做出如下类似的操作。

其中,当c hild == null时会将

Touch事件传递给该

Vie wGroup自身的

dispatchTouchEvent()处理,

super.dispatchTouchEvent(eve

nt)(也就是Vie w的这个方

法,因为Vie wGroup的父类

是Vie w);当c hild != null时

会调用该子view(当然该vie w

可能是一个Vie w也可能是一

个ViewGroup)的

dispatchTouchEvent(event)处

理,即

child.dispatchTouchEvent(eve

nt)。别的代码几乎没啥需要

具体注意分析的。

所以,到此你也会发现

Vie wGroup没有重写Vie w的

onTouchEvent(MotionEvent

event) 方法,也就是说接下

来的调运关系就是上一篇分

析的流程了,这里不在多

说。

好了,到此你是不是即明白

了上面实例演示的代码结

果,也明白了上一篇最后升

级实例验证模块留下的点击

Button触发了LinearLayout的

一些疑惑呢?答案自然是必

须的!

4

Android 5.1.1(API 22)

ViewGrou p触摸屏事件

传递总结

如上就是所有Vie wGroup关

于触摸屏事件的传递机制源

码分析与实例演示。具体总

结如下:

1

. Android事件派发是先传递

到最顶级的Vie wGroup,

再由Vie wGroup递归传递

到Vie w的。

2

. 在Vie wGroup中可以通过

onInterceptTouchEvent方法

对事件传递进行拦截,

onInterceptTouchEvent方法

返回true代表不允许事件

继续向子Vie w传递,返回

false代表不对事件进行拦

截,默认返回false。

3

. 子Vie w中如果将传递的事

件消费掉,Vie wGroup中

将无法接收到任何事件。

【工匠若

水 http://blog.csdn.net/yanbob

er】

好了,至此整个Vie w与

Vie wGroup的触摸屏事件派

发机制分析完毕。关于他们

的事件是哪派发来的可以继

续进阶的阅读下一篇

《Android触摸屏事件派发机

制详解与源码分析三( Ac tivity

篇)》

PS一句:最终还是选择

CSDN来整理发表这几年的

知识点,该文章平行迁移到

CSDN。因为CSDN也支持

MarkDown语法了,牛逼

啊!

【工匠若

水 http://blog.csdn.net/yanbob

er】

该篇承接上一篇《Android触

摸屏事件派发机制详解与源

码分析二(ViewGroup篇)》,

阅读本篇之前建议先阅读。

1

背景

还记得前面两篇从Android的

基础最小元素控件(Vie w)

到Vie wGroup控件的触摸屏

事件分发机制分析吗?你可

能看完会有疑惑,Vie w的事

件是Vie wGroup派发的,那

Vie wGroup的事件呢?他包

含在Ac tivity上,是不是

Ac tivity也有类似的事件派发

方法呢?带着这些疑惑咱们

继续实例验证加源码分析

吧。

PS:阅读本篇前建议先查看

前一篇《Android触摸屏事件

派发机制详解与源码分析二

(ViewGroup篇)》与

Android触摸屏事件派发机

制详解与源码分析一(Vie w

篇)》,这一篇承接上一篇。

2

实例验证

2

-1 代码

如下实例与前面实例相同,

一个Button在LinearLayout

里,只不过我们这次重写了

Ac tivity的一些方法而已。具

体如下:

自定义的Button与

LinearLayout:

public class TestButton exten

ds Button {

public TestButton(Context

context, AttributeSet attrs)

{

super(context, attrs)

;

}

@

Override

public boolean dispatchTo

uchEvent(MotionEvent event) {

Log.i(null, "TestButt

on--dispatchTouchEvent--actio

n="+event.getAction());

return super.dispatch

TouchEvent(event);

}

@

Override

public boolean onTouchEve

nt(MotionEvent event) {

Log.i(null, "TestButt

on--onTouchEvent--action="+ev

ent.getAction());

return super.onTouchE

vent(event);

}

}

public class TestLinearLayout

extends LinearLayout {

public TestLinearLayout(C

ontext context, AttributeSet

attrs) {

super(context, attrs)

;

}

@

Override

public boolean onIntercep

tTouchEvent(MotionEvent ev) {

Log.i(null, "TestLine

arLayout--onInterceptTouchEve

nt--action="+ev.getAction());

return super.onInterc

eptTouchEvent(ev);

}

@

Override

public boolean dispatchTo

uchEvent(MotionEvent event) {

Log.i(null, "TestLine

arLayout--dispatchTouchEvent-

-

)

action=" + event.getAction()

;

return super.dispatch

TouchEvent(event);

}

@

Override

public boolean onTouchEve

nt(MotionEvent event) {

Log.i(null, "TestLine

arLayout--onTouchEvent--actio

n="+event.getAction());

return super.onTouchE

vent(event);

}

}

整个界面的布局文件:

<

com.example.yanbo.myapplicat

ion.TestLinearLayout

xmlns:android="http://sch

emas.android.com/apk/res/andr

oid"

android:layout_width="mat

ch_parent"

android:layout_height="ma

tch_parent"

android:id="@+id/layout">

<

com.example.yanbo.myappl

ication.TestButton

android:text="click t

est"

android:layout_width=

android:layout_height

"

match_parent"

=

"wrap_content"

android:id="@+id/butt

on"/>

<

/com.example.yanbo.myapplica

tion.TestLinearLayout>

整个界面Ac tivity,重写了

Ac tivity的一些关于触摸派发

的方法(三个):

public class MainActivity ext

ends Activity implements View

.

OnClickListener, View.OnTouc

hListener {

private TestButton mButto

n;

private TestLinearLayout

mLayout;

@

Override

protected void onCreate(B

undle savedInstanceState) {

super.onCreate(savedI

nstanceState);

setContentView(R.layo

ut.activity_main);

mButton = (TestButton

)

this.findViewById(R.id.butt

on);

mLayout = (TestLinear

Layout) this.findViewById(R.i

d.layout);

mButton.setOnClickLis

tener(this);

mLayout.setOnClickLis

tener(this);

mButton.setOnTouchLis

tener(this);

mLayout.setOnTouchLis

tener(this);

}

@

Override

public void onClick(View

v) {

Log.i(null, "onClick-

}

-

--v=" + v);

@

Override

public boolean onTouch(Vi

ew v, MotionEvent event) {

Log.i(null, "onTouch-

-

-

action="+event.getAction()+"

-v="+v);

return false;

}

@

Override

public boolean dispatchTo

uchEvent(MotionEvent ev) {

Log.i(null, "MainActi

vity--dispatchTouchEvent--act

ion=" + ev.getAction());

return super.dispatch

TouchEvent(ev);

}

@

Override

public void onUserInterac

tion() {

Log.i(null, "MainActi

vity--onUserInteraction");

super.onUserInteracti

on();

}

@

Override

public boolean onTouchEve

nt(MotionEvent event) {

Log.i(null, "MainActi

vity--onTouchEvent--action="+

event.getAction());

return super.onTouchE

vent(event);

}

}

如上就是实例测试代码,非

常简单,没必要分析,直接

看结果吧。

2-2 结果分析

直接点击Button按钮打印如

下:

MainActivity--dispatchTouchEv

ent--action=0

MainActivity--onUserInteracti

on

TestLinearLayout--dispatchTou

chEvent--action=0

TestLinearLayout--onIntercept

TouchEvent--action=0

TestButton--dispatchTouchEven

t--action=0

onTouch--action=0--v=com.exam

ple.yanbo.myapplication.TestB

utton

TestButton--onTouchEvent--act

ion=0

MainActivity--dispatchTouchEv

ent--action=1

TestLinearLayout--dispatchTou

chEvent--action=1

TestLinearLayout--onIntercept

TouchEvent--action=1

TestButton--dispatchTouchEven

t--action=1

onTouch--action=1--v=com.exam

ple.yanbo.myapplication.TestB

utton

TestButton--onTouchEvent--act

ion=1

onClick----v=com.example.yanb

o.myapplication.TestButton

分析可以发现,当点击

Button时除过派发Ac tivity的

几个新方法之外其他完全符

合前面两篇分析的Vie w与

Vie wGroup的触摸事件派发

机制。对于Ac tivity

来说,ACTION_DOWN事件

首先触发

dispatchTouchEvent,然后触

发onUserInteraction,再次

onTouchEvent,接着的

ACTION_UP事件触发

dispatchTouchEvent后触发了

onTouchEvent,也就是说

ACTION_UP事件时不会触

发onUserInteraction(待会可

查看源代码分析原因)。

直接点击Button以外的其他

区域:

MainActivity--dispatchTouchEv

ent--action=0

MainActivity--onUserInteracti

on

TestLinearLayout--dispatchTou

chEvent--action=0

TestLinearLayout--onIntercept

TouchEvent--action=0

onTouch--action=0--v=com.exam

ple.yanbo.myapplication.TestL

inearLayout

TestLinearLayout--onTouchEven

t--action=0

MainActivity--dispatchTouchEv

ent--action=1

TestLinearLayout--dispatchTou

chEvent--action=1

onTouch--action=1--v=com.exam

ple.yanbo.myapplication.TestL

inearLayout

TestLinearLayout--onTouchEven

t--action=1

onClick----v=com.example.yanb

o.myapplication.TestLinearLay

out

怎么样?完全符合上面点击

Button结果分析的猜想。

那接下来还是要看看Ac tivity

里关于这几个方法的源码

了。

3

Android 5.1.1(API 22)

Activity触摸屏事件传

递源码分析

通过上面例子的打印我们可

以确定分析源码的顺序,那

就开始分析呗。

3-1 从Ac tivity的

dispatchTouchEvent方法说起

-1-1 开始分析

先上源码,如下:

3

/

**

*

Called to process touc

h screen events. You can ove

rride this to

*

intercept all touch sc

reen events before they are d

ispatched to the

*

window. Be sure to ca

ll this implementation for to

uch screen events

*

that should be handled

normally.

*

*

@param ev The touch sc

reen event.

*

*

@return boolean Return

true if this event was consu

med.

*

/

public boolean dispatchTo

uchEvent(MotionEvent ev) {

if (ev.getAction() ==

MotionEvent.ACTION_DOWN) {

onUserInteraction

();

}

if (getWindow().super

DispatchTouchEvent(ev)) {

return true;

}

return onTouchEvent(e

v);

}

哎呦!这次看着代码好少的

样子,不过别高兴,浓缩才

是精华,这里代码虽少,涉

及的问题点还是很多的,那

么咱们就来一点一点分析

吧。

12到14行看见了吧?上面例

子咱们看见只有

ACTION_DOWN事件派发时

调运了onUserInteraction 方

法,当时还在疑惑呢,这下

明白了吧,不多解释,咱们

直接跳进去可以看见是一个

空方法,具体下面会分析。

好了,自己分析15到17行,

看着简单吧,我勒个去,我

怎么有点懵,这是哪的方

法?咱们分析分析吧。

首先分析Ac tivity的attach方

法可以发现getWindow()返回

的就是PhoneWindow对象

PhoneWindow为抽象

Window的实现子类),那就

简单了,也就相当于

PhoneWindow类的方法,而

PhoneWindow类实现于

Window抽象类,所以先看下

Window类中抽象方法的定

义,如下:

/

,

**

*

Used by custom windows

such as Dialog, to pass the

touch screen event

*

further down the view

hierarchy. Application develo

pers should

*

not need to implement

or call this.

*

*

/

public abstract boolean s

uperDispatchTouchEvent(Motion

Event event);

看见注释没有?用户不需要

重写实现的方法,实质也不

能,在Ac tivity中没有提供重

写的机会,因为Window是以

组合模式与Ac tivity建立关系

的。好了

,看完了抽象的Window方

法,那就去PhoneWindow里

看下Window抽象方法的实现

吧,如下:

@

Override

public boolean superDispa

tchTouchEvent(MotionEvent eve

nt) {

return mDecor.superDi

spatchTouchEvent(event);

}

又是看着好简单的样子哦,

实际又是一堆问题,继续分

析。你会发现在

PhoneWindow的

superDispatchTouchEvent方

法里又直接返回了另一个

mDecor

对象的

superDispatchTouchEvent方

法,mDecor是啥?继续分析

吧。

在PhoneWindow类里发现,

mDecor是DecorView类的实

例,同时DecorView是

PhoneWindow的内部类。最

惊人的发现是DecorView

extends FrameLayout

implements

RootViewSurfaceTaker,看见

没有?它是一个真正Ac tivity

的root vie w,它继承了

FrameLayout。怎么验证他一

定是root vie w呢?很简单,

不知道大家是不是熟悉

Android App开发技巧中关于

UI布局优化使用的SDK工具

Hierarchy Viewer。咱们通过

他来看下上面刚刚展示的那

个例子的Hierarchy Viewer你

就明白了,如下我在Ubuntu

上截图的Hierarchy Viewer分

析结果:

看见没有,我们上面例子中

Ac tivity中setContentView时放

入的xml layout是一个

LinearLayout,其中包含一个

Button,上图展示了我们放

置的LinearLayout被放置在一

个id为content的FrameLayout

的布局中,这也就是为啥

Ac tivity的setContentView方法

叫set content vie w了,就是

把我们的xml放入了这个id为

content的FrameLayout中。

赶快回过头,你是不是发现

上面PhoneWindow 的

superDispatchTouchEvent直

接返回了DecorView的

superDispatchTouchEvent,

而DecorView又是

FrameLayout的子类,

FrameLayout又是Vie wGroup

的子类。机智的你想到了啥

木有?

没想到就继续看下

DecorView类的

superDispatchTouchEvent方

法吧,如下:

public boolean superDispatchT

ouchEvent(MotionEvent event)

{

return super.disp

atchTouchEvent(event);

}

这回你一定恍然大悟了吧,

不然就得脑补前面两篇博客

的内容了。。。

搞半天Ac tivity的

dispatchTouchEvent方法的15

if (getWindow().superDisp

本质执行的是一个

Vie wGroup 的

dispatchTouchEvent方法(这

个Vie wGroup是Ac tivity特有

的root vie w,也就是id为

content的FrameLayout布

局),接下来就不用多说了

吧,完全是前面两篇分析的

执行过程。

接下来依据派发事件返回值

决定是否触发Ac tivity的

onTouchEvent方法。

3

-1-2 小总结一下

在Ac tivity的触摸屏事件派发

中:

1. 首先会触发Ac tivity的

dispatchTouchEvent方法。

. dispatchTouchEvent方法中

如果是ACTION_DOWN的

情况下会接着触发

2

onUserInteraction方法。

. 接着在dispatchTouchEvent

方法中会通过Ac tivity的

root Vie w(id为content的

FrameLayout),实质是

Vie wGroup,通过

3

super.dispatchTouchEvent

把touchevent派发给各个

a c tivity的子vie w,也就是

我们再Ac tivity. onCre a t方

法中setContentView时设置

的vie w 。

4

. 若Ac tivity下面的子vie w拦

截了touchevent事件(返回

true)则

Ac tivity. onTouc hEve nt方法

就不会执行。

3

-2 继续Activity的

dispatchTouchEvent方

法中调运的

onUserInteraction方法

如下源码:

/

**

*

Called whenever a key,

touch, or trackball event is

dispatched to the

*

activity. Implement t

his method if you wish to kno

w that the user has

*

interacted with the de

vice in some way while your a

ctivity is running.

*

This callback and {@li

nk #onUserLeaveHint} are inte

nded to help

*

activities manage stat

us bar notifications intellig

ently; specifically,

*

for helping activities

determine the proper time to

cancel a notfication.

*

*

<p>All calls to your a

ctivity's {@link #onUserLeave

Hint} callback will

*

be accompanied by call

s to {@link #onUserInteractio

n}. This

*

ensures that your acti

vity will be told of relevant

user activity such

*

as pulling down the no

tification pane and touching

an item there.

*

*

<p>Note that this call

back will be invoked for the

touch down action

*

that begins a touch ge

sture, but may not be invoked

for the touch-moved

*

and touch-up actions t

hat follow.

*

*

@see #onUserLeaveHint(

)

*

/

public void onUserInterac

tion() {

}

搞了半天就像上面说的,这

是一个空方法,那它的作用

是啥呢?

此方法是a c tivity的方法,当

此a c tivity在栈顶时,触屏点

击按home,back,menu键等

都会触发此方法。下拉

statubar、旋转屏幕、锁屏不

会触发此方法。所以它会用

在屏保应用上,因为当你触

屏机器 就会立马触发一个事

件,而这个事件又不太明确

是什么,正好屏保满足此需

求;或者对于一个Ac tivity,

控制多长时间没有用户点响

应的时候,自己消失等。

这个方法也分析完了,那就

剩下onTouchEvent方法了,

如下继续分析。

3-3 继续Ac tivity的

dispatchTouchEvent方法中调

运的onTouchEvent方法

如下源码:

/

**

*

Called when a touch sc

reen event was not handled by

any of the views

*

under it. This is mos

t useful to process touch eve

nts that happen

*

outside of your window

bounds, where there is no vi

ew to receive it.

*

*

@param event The touch

screen event being processed

.

*

*

@return Return true if

you have consumed the event,

false if you haven't.

*

The default implementa

tion always returns false.

*

/

public boolean onTouchEve

nt(MotionEvent event) {

if (mWindow.shouldClo

seOnTouch(this, event)) {

finish();

return true;

}

return false;

}

看见没有,这个方法看起来

好简单的样子。

如果一个屏幕触摸事件没有

被这个Ac tivity下的任何Vie w

所处理,Ac tivity的

onTouchEvent将会调用。这

对于处理window边界之外的

Touch事件非常有用,因为

通常是没有Vie w会接收到它

们的。返回值为true表明你

已经消费了这个事件,false

则表示没有消费,默认实现

中返回false 。

继续分析吧,重点就一句,

mWindow. shouldClose OnTouc

h(this, event)中的mWindow实

际就是上面分析

dispatchTouchEvent方法里的

getWindow()对象,所以直接

到Window抽象类和

PhoneWindow子类查看吧,

发现PhoneWindow没有重写

Window的

shouldCloseOnTouch方法,

所以看下Window类的

shouldCloseOnTouch实现

吧,如下:

/

** @hide */

public boolean shouldClos

eOnTouch(Context context, Mot

ionEvent event) {

if (mCloseOnTouchOuts

ide && event.getAction() == M

otionEvent.ACTION_DOWN

&

& isOutOfBou

nds(context, event) && peekDe

corView() != null) {

return true;

}

return false;

}

这其实就是一个判断,判断

mCloseOnTouchOutside标记

及是否为ACTION_DOWN事

件,同时判断event的x、y坐

标是不是超出Bounds,然后

检查

FrameLayout的content的id的

DecorView是否为空。其实

没啥太重要的,这只是对于

处理window边界之外的

Touch事件有判断价值而

已。

所以,到此Ac tivity的

onTouchEvent分析完毕。

4

Android触摸事件综

合总结

到此整个Android的Ac tivity-

ViewGroup->View的触摸屏

>

事件分发机制完全分析完

毕。这时候你可以回过头看

这三篇文章的例子,你会完

全明白那些打印的含义与原

理。

当然,了解这些源码机制不

仅对你写普通代码时有帮

助,最重要的是对你想自定

义装逼控件时有不可磨灭的

基础性指导作用与技巧提示

作用。

工匠若

水 http://blog.csdn.net/yanbob

er 转载烦请注明出处,尊重

分享成果】

1

背景

其实之所以要说这个话题有

几个原因:

1

. 理解xml等控件是咋被显

示的原理,通常大家写代

码都是直接在onCreate里

setContentView就完事,没

怎么关注其实现原理。

2

. 前面分析《Android触摸屏

事件派发机制详解与源码

分析三( Ac tivity篇)》时提

到了一些关于布局嵌套的

问题,当时没有深入解

释。

所以接下来主要分析的就是

Vie w或者Vie wGroup对象是

如何添加至应用程序界面

(窗口)显示的。我们准备

从Ac tivity的setContentView方

法开始来说(因为默认

Ac tivity中放入我们的xml或

者Java控件是通过

setContentView方法来操作

的,当调运了setContentView

所有的控件就得到了显

示)。

工匠若

水 http://blog.csdn.net/yanbob

er 转载烦请注明出处,尊重

分享成果】

2

2

Android5.1.1(API

2)从Activity 的

setContentView方法说

2-1 Ac tivity的setContentView

方法解析

Ac tivity的源码中提供了三个

重载的setContentView方法,

如下:

public void setContentView(in

t layoutResID) {

getWindow().setConten

tView(layoutResID);

initWindowDecorAction

Bar();

}

public void setContentVie

w(View view) {

getWindow().setConten

tView(view);

initWindowDecorAction

Bar();

}

public void setContentVie

w(View view, ViewGroup.Layout

Params params) {

getWindow().setConten

tView(view, params);

initWindowDecorAction

Bar();

}

可以看见他们都先调运了

getWindow()的

setContentView方法,然后调

运Ac tivity 的

initWindowDecorActionBar方

法,关于

initWindowDecorActionBar方

法后面准备写一篇关于

Android ActionBar原理解析

的文章,所以暂时跳过不解

释。

2-2 关于窗口Window类的一

些关系

在开始分析Ac tivity组合对象

Window的setContentView方

法之前请先明确如下关系

前面分析《Android触摸屏

事件派发机制详解与源码分

析三( Ac tivity篇)》时也有说

过)。

看见上面图没?Ac tivity中有

一个成员为Window,其实例

化对象为PhoneWindow,

PhoneWindow为抽象Window

类的实现类。

这里先简要说明下这些类的

职责:

1

2

. Window是一个抽象类,提

供了绘制窗口的一组通用

API。

. PhoneWindow是Window的

具体继承实现类。而且该

类内部包含了一个

DecorView对象,该

DectorView对象是所有应

用窗口( Ac tivity界面)的根

Vie w 。

3

. DecorView是

PhoneWindow的内部类,

是FrameLayout的子类,

是对FrameLayout进行功

能的修饰(所以叫

DecorXXX),是所有应

用窗口的根Vie w 。

依据面向对象从抽象到具体

我们可以类比上面关系就像

如下:

Window是一块电子屏,

PhoneWindow是一块手机电

子屏,DecorView就是电子

屏要显示的内容,Ac tivity就

是手机电子屏安装位置。

-3 窗口PhoneWindow类的

2

setContentView方法

我们可以看见Window类的

setContentView方法都是抽象

的。所以我们直接先看

PhoneWindow类的

setContentView(int

layoutResID)方法源码,如

下:

public void setContentView(in

t layoutResID) {

/

/ Note: FEATURE_CONT

ENT_TRANSITIONS may be set in

the process of installing th

e window

/

/ decor, when theme

attributes and the like are c

rystalized. Do not check the

feature

/

/ before this happen

s.

null) {

if (mContentParent ==

installDecor();

}

else if (!hasFeatur

e(FEATURE_CONTENT_TRANSITIONS

) {

)

mContentParent.re

moveAllViews();

}

if (hasFeature(FEATUR

E_CONTENT_TRANSITIONS)) {

final Scene newSc

ene = Scene.getSceneForLayout

(mContentParent, layoutResID,

getContex

t());

transitionTo(newS

cene);

}

else {

mLayoutInflater.i

nflate(layoutResID, mContentP

arent);

}

final Callback cb = g

etCallback();

if (cb != null && !is

Destroyed()) {

cb.onContentChang

ed();

}

}

可以看见,第五行首先判断

mContentParent是否为null,

也就是第一次调运);如果

是第一次调用,则调用

installDecor()方法,否则判

断是否设置

FEATURE_CONTENT_TRA

NSITIONS Window属性(默

认false),如果没有就移除

该mContentParent内所有的

所有子Vie w;接着16行

mLayoutInflater.inflate(l

将我们的资源文件通过

LayoutInflater对象转换为

Vie w树,并且添加至

mContentParent视图中

其中mLayoutInflater是在

PhoneWindow的构造函数中

得到实例对象的

LayoutInflater.from(conte

)。

再来看下PhoneWindow类的

se tConte ntVie w(Vie w view)方

法和se tConte ntVie w(Vie w

vie w,

ViewGroup.LayoutParams

params)方法源码,如下:

@

Override

public void setContentVie

w(View view) {

setContentView(view,

new ViewGroup.LayoutParams(MA

TCH_PARENT, MATCH_PARENT));

}

@

Override

public void setContentVie

w(View view, ViewGroup.Layout

Params params) {

/

/ Note: FEATURE_CONT

ENT_TRANSITIONS may be set in

the process of installing th

e window

/

/ decor, when theme

attributes and the like are c

rystalized. Do not check the

feature

/

/ before this happen

s.

null) {

if (mContentParent ==

installDecor();

}

else if (!hasFeatur

e(FEATURE_CONTENT_TRANSITIONS

) {

)

mContentParent.re

moveAllViews();

}

if (hasFeature(FEATUR

E_CONTENT_TRANSITIONS)) {

view.setLayoutPar

ams(params);

final Scene newSc

ene = new Scene(mContentParen

t, view);

transitionTo(newS

cene);

}

else {

mContentParent.ad

dView(view, params);

}

final Callback cb = g

etCallback();

if (cb != null && !is

Destroyed()) {

cb.onContentChang

ed();

}

}

看见没有,我们其实只用分

析se tConte ntVie w(Vie w vie w,

ViewGroup.LayoutParams

params)方法即可,如果你在

Ac tivity中调运

se tConte ntVie w(Vie w view)方

法,实质也是调运

se tConte ntVie w(Vie w vie w,

ViewGroup.LayoutParams

params),只是LayoutParams

设置为了

MATCH_PARENT而已。

所以直接分析

se tConte ntVie w(Vie w vie w,

ViewGroup.LayoutParams

params)方法就行,可以看见

该方法与setContentView(int

layoutResID)类似,只是少了

LayoutInflater将xml文件解析

装换为Vie w而已,这里直接

使用Vie w的addView方法追

加道了当前mContentParent

而已。

所以说在我们的应用程序里

可以多次调用

setContentView()来显示界

面,因为会

re move AllVie ws。

2-4 窗口PhoneWindow类的

installDecor方法

回过头,我们继续看上面

PhoneWindow类

setContentView方法的第6行

installDecor();代码,在

PhoneWindow中查看

installDecor源码如下:

private void installDecor() {

if (mDecor == null) {

mDecor = generate

Decor();

mDecor.setDescend

antFocusability(ViewGroup.FOC

US_AFTER_DESCENDANTS);

mDecor.setIsRootN

amespace(true);

if (!mInvalidateP

anelMenuPosted && mInvalidate

PanelMenuFeatures != 0) {

mDecor.postOn

Animation(mInvalidatePanelMen

uRunnable);

}

}

if (mContentParent ==

null) {

/

/根据窗口的风格修饰

选择对应的修饰布局文件,并且将id

为content的FrameLayout赋值给mCo

ntentParent

mContentParent =

generateLayout(mDecor);

/

/

/......

/初始化一堆属性值

}

}

我勒个去!又是一个死长的

方法,抓重点分析吧。第 2

到9行可以看出,首先判断

mDecor对象是否为空,如果

为空则调用generateDecor()

创建一个

DecorView(该类

是 FrameLayout子类,即一

个Vie wGroup视图),然后设

置一些属性,我们看下

PhoneWindow的

generateDecor方法,如下:

protected DecorView generateD

ecor() {

return new DecorView(

getContext(), -1);

}

可以看见generateDecor方法

仅仅是new一个DecorView的

实例。

回到installDecor方法继续往

下看,第10行开始到方法结

束都需要一个

if (mContentParent == nul

判断为真才会执行,当

mContentParent对象不为空

则调用generateLayout()方法

去创建mContentParent 对

象。所以我们看下

generateLayout方法源码,如

下:

protected ViewGroup generateL

ayout(DecorView decor) {

/

/ Apply data from cu

rrent theme.

TypedArray a = getWin

dowStyle();

/

/

/......

/依据主题style设置一堆

值进行设置

/

/ Inflate the window

decor.

int layoutResource;

int features = getLoc

alFeatures();

/......

/根据设定好的features

/

/

值选择不同的窗口修饰布局文件,得到la

youtResource 值

/

/把选中的窗口修饰布局文

件添加到DecorView对象里,并且指定

contentParent 值

View in = mLayoutInfl

ater.inflate(layoutResource,

null);

decor.addView(in, new

ViewGroup.LayoutParams(MATCH

_

PARENT, MATCH_PARENT));

mContentRoot = (ViewG

roup) in;

ViewGroup contentPare

nt = (ViewGroup)findViewById(

ID_ANDROID_CONTENT);

if (contentParent ==

null) {

throw new Runtime

Exception("Window couldn't fi

nd content container view");

}

/

/

/......

/继续一堆属性设置,完事

返回contentParent

return contentParent;

}

可以看见上面方法主要作用

就是根据窗口的风格修饰类

型为该窗口选择不同的窗口

根布局文件。mDecor做为根

视图将该窗口根布局添加进

去,然后获

取id为content的FrameLayout

返回给mContentParent 对

象。所以installDecor方法实

质就是产生mDecor和

mContentParent对象。

在这里顺带提一下:还记得

我们平时写应用Ac tivity时设

置的theme或者feature吗(全

屏啥的,NoTitle等)?我们

一般是不是通过XML的

android:theme属性或者java的

requestFeature()方法来设置

的呢?譬如:

通过java文件设置:

requestWindowFeature(Window.F

EATURE_NO_TITLE);

通过xml文件设置:

android:theme="@android:style

/

Theme.NoTitleBar"

对的,其实我们平时

requestWindowFeature()设置

的值就是在这里通过

getLocalFeature()获取的;而

android:theme属性也是通过

这里的

getWindowStyle()获取的。所

以这下你应该就明白在ja va

文件设置Ac tivity的属性时必

须在setContentView方法之前

调用requestFeature()方法的

原因了吧。

我们继续关注一下

generateLayout方法的

layoutResource变量赋值情

况。因为它最终通过

View in = mLayoutInflater

decor.addView(in, new Vie

将in添加到PhoneWindow的

mDecor对象。为例验证这一

段代码分析我们用一个实例

来进行说明,如下是一个简

单的App主要代码:

AndroidManifest.xml文件

<

"

<

:

?xml version="1.0" encoding=

utf-8"?>

manifest xmlns:android="http

//schemas.android.com/apk/re

s/android"

package="com.yanbober.mya

pplication" >

<

application

.

/

.....

/看重点,我们将主题设置

为NoTitleBar

android:theme="@andro

id:style/Theme.Black.NoTitleB

ar" >

.

.....

<

/application>

<

/manifest>

主界面布局文件:

<

=

RelativeLayout xmlns:android

"http://schemas.android.com/

apk/res/android"

android:layout_width="mat

ch_parent"

android:layout_height="ma

tch_parent">

<

TextView android:text="@

string/hello_world"

android:layout_width=

wrap_content"

android:layout_height

"wrap_content" />

"

=

<

/RelativeLayout>

APP运行界面:

看见没有,上面我们将主题

设置为NoTitleBar,所以在

generateLayout方法中的

layoutResource变量值为

R.layout.screen_simple

,所以我们看下系统这个

screen_simple.xml布局文

件,如下:

<

LinearLayout xmlns:android="

http://schemas.android.com/ap

k/res/android"

android:layout_width="mat

ch_parent"

android:layout_height="ma

tch_parent"

android:fitsSystemWindows

=

"true"

android:orientation="vert

ical">

<

ViewStub android:id="@+i

d/action_mode_bar_stub"

android:inflate

dId="@+id/action_mode_bar"

android:layout=

"

@layout/action_mode_bar"

android:layout_

width="match_parent"

android:layout_

height="wrap_content"

android:theme="

attr/actionBarTheme" />

FrameLayout

android:id="@android

id/content"

android:layout_width

"match_parent"

android:layout_heigh

t="match_parent"

android:foregroundIn

sidePadding="false"

android:foregroundGr

?

<

:

=

avity="fill_horizontal|top"

android:foreground="

?

android:attr/windowContentOv

erlay" />

/LinearLayout>

<

布局中,一般会包含

ActionBar,Title,和一个id

为content的FrameLayout,这

个布局是NoTitle的。

再来看下上面这个App的

hierarchyviewer图谱,如

下:

看见了吧,通过这个App的

hierarchyviewer和系统

screen_simple.xml文件比较

就验证了上面我们分析的结

论,不再做过多解释。

然后回过头可以看见上面

PhoneWindow类的

setContentView方法最后通过

调运

mLayoutInflater.inflate(l

或者

mContentParent.addView(vi

语句将我们的xml或者ja va

Vie w插入到了

mContentParent(id为content

的FrameLayout对象)

Vie wGroup中。最后

setContentView还会调用一个

Callback接口的成员函数

onContentChanged来通知对

应的Ac tivity组件视图内容发

生了变化。

2-5 Window类内部接口

Callback的onContentChanged

方法

上面刚刚说了PhoneWindow

类的setContentView方法中最

后调运了onContentChanged

方法。我们这里看下

setContentView这段代码,如

下:

public void setContentView(in

t layoutResID) {

.

.....

final Callback cb = g

etCallback();

if (cb != null && !is

Destroyed()) {

cb.onContentChang

ed();

}

}

看着没有,首先通过

getCallback获取对象cb(回

调接口),PhoneWindow没

有重写Window的这个方法,

所以到抽象类Window中可以

看到:

/

**

*

Return the current Cal

lback interface for this wind

ow.

*

/

public final Callback get

Callback() {

return mCallback;

}

这个mCallback在哪赋值的

呢,继续看Window类发现有

一个方法,如下:

public void setCallback(Callb

ack callback) {

mCallback = callback;

}

Window中的mCallback是通

过这个方法赋值的,那就回

想一下,Window又是Ac tivity

的组合成员,那就是Ac tivity

一定调运这个方法了,回到

Ac tivity

发现在Ac tivity的attach方法

中进行了设置,如下:

final void attach(Context con

text, ActivityThread aThread,

.

.....

mWindow.setCallback(t

his);

}

.

.....

也就是说Ac tivity类实现了

Window的Callback接口。那

就是看下Ac tivity实现的

onContentChanged方法。如

下:

public void onContentChanged(

)

{

}

咦?onContentChanged是个

空方法。那就说明当Ac tivity

的布局改动时,即

setContentView()或者

addContentView()方法执行完

毕时就会调用该方法。

所以当我们写App时,

Ac tivity的各种Vie w的

findViewById()方法等都可以

放到该方法中,系统会帮忙

回调。

-6 setContentView源码分析

2

总结

可以看出来setContentView整

个过程主要是如何把Ac tivity

的布局文件或者java的Vie w

添加至窗口里,上面的过程

可以重点概括为:

1

. 创建一个DecorView的对

象mDecor,该mDecor对

象将作为整个应用窗口的

根视图。

2

. 依据Feature等style theme

创建不同的窗口修饰布局

文件,并且通过

findViewById获取Ac tivity

布局文件该存放的地方

(窗口修饰布局文件中id

为content 的

FrameLayout)。

3. 将Ac tivity的布局文件添加

至id为content的

FrameLayout内。

至此整个setContentView的主

要流程就分析完毕。你可能

这时会疑惑,这么设置完一

堆Vie w关系后系统是怎么知

道该显示了呢?下面我们就

初探一下关于Ac tivity的

setContentView在onCreate中

如何显示的(声明一下,这

里有些会暂时直接给出结

论,该系列文章后面会详细

分析的)。

2

-7 setContentView完以后

Ac tivity显示界面初探

这一小部分已经不属于

sentContentView的分析范畴

了,只是简单说明

setContentView之后怎么被显

示出来的(注意:Ac tivity调

运setContentView方法自身不

会显示布局的)。

记得前面有一篇文章

《Android异步消息处理机制

详解及源码分析》的3-1-2小

节说过,一个Ac tivity的开始

实际是Ac tivityThre a d的ma in

方法(至于为什么后面会写

文章分析,这里站在应用层

角度先有这个概念就行)。

那在这一篇我们再直接说一

个知识点(至于为什么后面

会写文章分析,这里站在应

用层角度先有这个概念就

行)。

当启动Ac tivity调运完

Ac tivityThre a d的main方法之

后,接着调用Ac tivityThre a d

类performLaunchActivity来创

建要启动的Ac tivity组件,在

创建Ac tivity组件的过程中,

还会为该Ac tivity组件创建窗

口对象和视图对象;接着

Ac tivity组件创建完成之后,

通过调用Ac tivityThre a d类的

ha ndle Re sume Ac tivity将它激

活。

所以我们先看下

ha ndle Re sume Ac tivity方法一

个重点,如下:

final void handleResumeActivi

ty(IBinder token,

boolean clearHide

,

boolean isForward, boolean

reallyResume) {

/

/ If we are getting

ready to gc after going to th

e background, well

/

/ we are back active

so skip it.

.

/

.....

/ TODO Push resumeAr

gs into the activity for cons

ideration

ActivityClientRecord

r = performResumeActivity(tok

en, clearHide);

if (r != null) {

.

/

.....

/ If the window

hasn't yet been added to the

window manager,

/

/ and this guy d

idn't finish itself or start

another activity,

/

/ then go ahead

and add the window.

.

/

.....

/ If the window

has already been added, but d

uring resume

/

/ we started ano

ther activity, then don't yet

make the

/

/ window visible

.

.

/

.....

/ The window is

now visible if it has been ad

ded, we are not

/

/ simply finishi

ng, and we are not starting a

nother activity.

if (!r.activity.m

Finished && willBeVisible

&

& r.acti

vity.mDecor != null && !r.hid

eForNow) {

.

.....

if (r.activit

y.mVisibleFromClient) {

r.activit

y.makeVisible();

}

}

.

.....

else {

/ If an exceptio

}

/

n was thrown when trying to r

esume, then

/

/ just end this

activity.

.

.....

}

}

看见

r.activity.makeVisible();

语句没?调用Ac tivity的

ma ke Visible方法显示我们上

面通过setContentView创建的

mDecor视图族。所以我们看

Ac tivity的ma ke Visible方法,

如下:

void makeVisible() {

if (!mWindowAdded) {

ViewManager wm =

getWindowManager();

wm.addView(mDecor

,

)

getWindow().getAttributes()

;

mWindowAdded = tr

ue;

}

mDecor.setVisibility(

View.VISIBLE);

}

看见没有,通过

DecorView(FrameLayout,

也即Vie w)的se tVisibility方

法将Vie w设置为VISIBLE,

至此显示出来。

到此setContentView的完整流

程分析完毕。

【工匠若

水 http://blog.csdn.net/yanbob

er 转载烦请注明出处,尊重

分享成果】

3

2

Android5.1.1(API

2)看看LayoutInflater

机制原理

上面在分析setContentView过

程中可以看见,在

PhoneWindow的

setContentView中调运了

mLayoutInflater.inflate(l

在PhoneWindow 的

generateLayout中调运了

View in = mLayoutInflater

,当时我们没有详细分析,

只是告诉通过xml得到Vie w

对象。现在我们就来分析分

析这一问题。

3-1 通过实例引出问题

在开始之前我们先来做一个

测试,我们平时最常见的就

是ListVie w的Adapter中使用

LayoutInflater加载xml的ite m

布局文件,所以咱们就以

ListVie w为例,如下:

省略掉Ac tivity代码等,首先

给出Ac tivity的布局文件,如

下:

<

LinearLayout xmlns:android="

http://schemas.android.com/ap

k/res/android"

android:layout_width="mat

ch_parent"

android:layout_height="ma

tch_parent"

android:orientation="vert

ical">

<

ListView

android:id="@+id/list

view"

android:dividerHeight

android:layout_width=

=

"5dp"

"

=

<

match_parent"

android:layout_height

"match_parent"></ListView>

/LinearLayout>

给出两种不同的ListVie w的

item布局文件。

te xtvie w_la yout. xml文件:

<

"

<

:

?xml version="1.0" encoding=

utf-8"?>

TextView xmlns:android="http

//schemas.android.com/apk/re

s/android"

android:layout_width="100

dp"

android:layout_height="40

dp"

android:text="Text Test"

android:background="#ffa0

a00c"/>

textview_layout_parent.xml文

件:

<

"

<

?xml version="1.0" encoding=

utf-8"?>

LinearLayout

android:layout_height="wr

ap_content"

android:layout_width="wra

p_content"

xmlns:android="http://sch

emas.android.com/apk/res/andr

oid">

<

TextView xmlns:android="

http://schemas.android.com/ap

k/res/android"

android:layout_width=

"

=

100dp"

"40dp"

android:layout_height

android:text="Text Te

android:background="#

st"

ffa0a00c"/>

<

/LinearLayout>

ListVie w的自定义Adapter文

件:

public class InflateAdapter e

xtends BaseAdapter {

private LayoutInflater mI

nflater = null;

public InflateAdapter(Con

text context) {

mInflater = LayoutInf

later.from(context);

}

@

Override

public int getCount() {

return 8;

}

@

Override

public Object getItem(int

position) {

return null;

}

@

Override

public long getItemId(int

position) {

return 0;

}

@

Override

public View getView(int p

osition, View convertView, Vi

ewGroup parent) {

/

/说明:这里是测试inflat

e方法参数代码,不再考虑性能优化等TA

G处理

return getXmlToView(c

onvertView, position, parent)

;

}

private View getXmlToView

(View convertView, int positi

on, ViewGroup parent) {

View[] viewList = {

mInflater.inf

late(R.layout.textview_layout

,

/

null),

/

mInflater.i

nflate(R.layout.textview_layo

ut, parent),

mInflater.inf

late(R.layout.textview_layout

,

/

parent, false),

/

mInflater.i

nflate(R.layout.textview_layo

ut, parent, true),

mInflater.inf

late(R.layout.textview_layout

,

null, true),

mInflater.inf

late(R.layout.textview_layout

null, false),

,

mInflater.inf

late(R.layout.textview_layout

_

/

parent, null),

/

mInflater.i

nflate(R.layout.textview_layo

ut_parent, parent),

mInflater.inf

late(R.layout.textview_layout

_

/

parent, parent, false),

/

mInflater.i

nflate(R.layout.textview_layo

ut_parent, parent, true),

mInflater.inf

late(R.layout.textview_layout

_

parent, null, true),

mInflater.inf

late(R.layout.textview_layout

parent, null, false),

_

}

;

convertView = viewLis

t[position];

return convertView;

}

}

当前代码运行结果:

PS:当打开上面vie w List数组

中任意一行注释都会抛出异

(java.lang.UnsupportedOpera

tionException: a ddVie w(Vie w,

LayoutParams) is not

supported in

AdapterView)。

你指定有些蒙圈了,而且比

较郁闷,同时想弄明白

inflate的这些参数都是啥意

思。运行结果为何有这么大

差异呢?

那我告诉你,你现在先别多

想,记住这回事,咱们先看

源码,下面会告诉你为啥。

3-2 从LayoutInflater源码实例

化说起

我们先看一下源码中

LayoutInflater实例化获取的

方法:

public static LayoutInflater

from(Context context) {

LayoutInflater Layout

Inflater =

(LayoutInflat

er) context.getSystemService(

Context.LAYOUT_INFLATER_SERVI

CE);

if (LayoutInflater ==

null) {

throw new Asserti

onError("LayoutInflater not f

ound.");

}

return LayoutInflater

;

}

看见没有?是否很熟悉?我

们平时写应用获取

LayoutInflater实例时不也就

两种写法吗,如下:

LayoutInflater lif = LayoutIn

flater.from(Context context);

LayoutInflater lif = (Lay

outInflater) context.getSyste

mService(Context.LAYOUT_INFLA

TER_SERVICE);

可以看见from方法仅仅是对

getSystemService的一个安全

封装而已。

3-3 LayoutInflater源码的Vie w

inflate(…)方法族剖析

得到LayoutInflater对象之后

我们就是传递xml然后解析

得到Vie w,如下方法:

public View inflate(int resou

rce, ViewGroup root) {

return inflate(resour

ce, root, root != null);

}

继续看inflate(int resource,

Vie wGroup root, boolean

attachToRoot)方法,如下:

public View inflate(int resou

rce, ViewGroup root, boolean

attachToRoot) {

final Resources res =

getContext().getResources();

if (DEBUG) {

Log.d(TAG, "INFLA

TING from resource: \"" + res

.

"

getResourceName(resource) +

\" ("

+

Integer

.

;

toHexString(resource) + ")")

}

final XmlResourcePars

er parser = res.getLayout(res

ource);

try {

return inflate(pa

rser, root, attachToRoot);

}

finally {

parser.close();

}

}

这个方法的第8行获取到

XmlResourceParser接口的实

例(Android默认实现类为

P ull解析XmlPullParser)。接

着看第10 行

inflate(parser, root, att

,你会发现无论哪个inflate

重载方法最后都调运了

inflate(XmlPullParser par

方法,如下:

public View inflate(XmlPullPa

rser parser, ViewGroup root,

boolean attachToRoot) {

synchronized (mConstr

uctorArgs) {

Trace.traceBegin(

Trace.TRACE_TAG_VIEW, "inflat

e");

final AttributeSe

t attrs = Xml.asAttributeSet(

parser);

Context lastConte

xt = (Context)mConstructorArg

s[0];

mConstructorArgs[

0

] = mContext;

/定义返回值,初始化

为传入的形参root

View result = roo

/

t;

try {

/

/ Look for t

he root node.

int type;

while ((type

=

parser.next()) != XmlPullPa

rser.START_TAG &&

type

!

)

= XmlPullParser.END_DOCUMENT

{

/

/ Empty

}

/

/如果一开始就

是END_DOCUMENT,那说明xml文件有

问题

if (type != X

mlPullParser.START_TAG) {

throw new

InflateException(parser.getP

ositionDescription()

+

"

: No start tag found!");

}

/

/有了上面判断

说明这里type一定是START_TAG,也就

是xml文件里的root node

final String

name = parser.getName();

if (DEBUG) {

System.ou

t.println("******************

*

*******");

System.ou

t.println("Creating root view

:

"

+

name);

System.ou

t.println("******************

*

*******");

}

if (TAG_MERGE

.

equals(name)) {

/

/处理merge t

ag的情况(merge,你懂的,APP的xml

性能优化)

/

/root必

须非空且attachToRoot为true,否则

抛异常结束(APP使用merge时要注意的

地方,

/

/因为mer

ge的xml并不代表某个具体的view,只

是将它包起来的其他xml的内容加到某个

上层

/

/ViewGro

up中。)

if (root

= null || !attachToRoot) {

=

throw

new InflateException("<merge

/

> can be used only with a v

alid "

+

"ViewGroup root and atta

chToRoot=true");

}

/

/递归inf

late方法调运

rInflate(

parser, root, attrs, false, f

alse);

}

else {

/ Temp i

/

s the root view that was foun

d in the xml

/

/xml文件

中的root view,根据tag节点创建vi

ew对象

final Vie

w temp = createViewFromTag(ro

ot, name, attrs, false);

ViewGroup

.

!

LayoutParams params = null;

if (root

= null) {

if (D

EBUG) {

S

ystem.out.println("Creating p

arams from root: " +

root);

}

/

/ Cr

eate layout params that match

root, if supplied

/

/根

据root生成合适的LayoutParams实例

param

s = root.generateLayoutParams

(attrs);

if (!

attachToRoot) {

/

/

Set the layout params for t

emp if we are not

/

/

/

attaching. (If we are, we u

se addView, below)

/如果attachToRoot=false就调用vi

ew的setLayoutParams方法

t

emp.setLayoutParams(params);

}

}

if (DEBUG

)

{

Syste

m.out.println("-----> start i

nflating children");

}

/

/ Inflat

e all children under temp

/

/递归inf

late剩下的children

rInflate(

parser, temp, attrs, true, tr

ue);

if (DEBUG

)

{

Syste

m.out.println("-----> done in

flating children");

}

/

/ We are

supposed to attach all the v

iews we found (int temp)

/

/ to roo

t. Do that now.

if (root

= null && attachToRoot) {

/roo

!

/

t非空且attachToRoot=true则将xml

文件的root view加到形参提供的root

root.

addView(temp, params);

}

/

/ Decide

whether to return the root t

hat was passed in or the

/

/ top vi

ew found in xml.

if (root

=

= null || !attachToRoot) {

/

/返

回xml里解析的root view

resul

t = temp;

}

}

}

catch (XmlPullP

arserException e) {

InflateExcept

ion ex = new InflateException

(e.getMessage());

ex.initCause(

e);

throw ex;

}

catch (IOExcept

ion e) {

InflateExcept

ion ex = new InflateException

(

parse

r.getPositionDescription()

+

":

"

+ e.getMessage());

ex.initCause(

e);

throw ex;

finally {

}

/

/ Don't reta

in static reference on contex

t.

mConstructorA

rgs[0] = lastContext;

mConstructorA

rgs[1] = null;

}

Trace.traceEnd(Tr

ace.TRACE_TAG_VIEW);

/

/返回参数root或xml

文件里的root view

return result;

}

}

从上面的源码分析我们可以

看出inflate方法的参数含

义:

inflate(xmlId, null); 只创建

temp的Vie w,然后直接返

回temp 。

inflate(xmlId, parent); 创建

temp的Vie w,然后执行

root.addView(temp,

params);最后返回root。

inflate(xmlId, parent, false);

创建temp的Vie w,然后执

temp.setLayoutParams(para

ms);然后再返回temp。

inflate(xmlId, parent, true);

创建temp的Vie w,然后执

行root.addView(temp,

params);最后返回root。

inflate(xmlId, null, false);

只创建temp的Vie w,然后

直接返回temp。

inflate(xmlId, null, true); 只

创建temp的Vie w,然后直

接返回temp。

到此其实已经可以说明我们

上面示例部分执行效果差异

的原因了(在此先强调一个

Android的概念,下一篇文章

我们会对这段话作一解释:

我们经常使用Vie w的

layout_width和layout_height

来设置Vie w的大小,而且一

般都可以正常工作,所以有

人时常认为这两个属性就是

设置Vie w的真实大小一样;

然而实际上这些属性是用于

设置Vie w在Vie wGroup布局

中的大小的;这就是为什么

Google的工程师在变量命名

上将这种属性叫作

layout_width和

layout_height,而不是width

和height的原因了。),如

下:

mInflater.inflate(R.lay

不能正确处理我们设置的

宽和高是因为

layout_width,

layout_height是相对了父

级设置的,而此temp的

getLayoutParams为null。

mInflater.inflate(R.lay

能正确显示我们设置的宽

高是因为我们的Vie w在设

置setLayoutParams 时

params = root.generateL

不为空。

Inflate(resId , parent,false )

可以正确处理,因为

temp.setLayoutParams(para

ms);这个params正是

root.generateLayoutParams

(attrs);得到的。

mInflater.inflate(R.lay

不能正确处理我们设置的

宽和高是因为

layout_width,

layout_height是相对了父

级设置的,而此temp的

getLayoutParams为null。

textview_layout_parent.xml

作为item可以正确显示的

原因是因为Te xtVie w具备

上级Vie wGroup,上级

Vie wGroup 的

layout_width,

layout_height会失效,当

前的Te xtVie w会有效而

已。

上面例子中说放开那些注

释运行会报错

java.lang.UnsupportedOper

ationException:

a ddVie w(Vie w,

LayoutParams) is not

supported是因为

AdapterView源码中调用了

root.addView(temp,

params);而此时的root是我

们的ListVie w,ListVie w为

AdapterView的子类,所以

我们看下AdapterView抽象

类中addView源码即可明

白为啥了,如下:

/

**

*

This method is not sup

ported and throws an Unsuppor

tedOperationException when ca

lled.

*

*

*

@param child Ignored.

*

@throws UnsupportedOpe

rationException Every time th

is method is invoked.

*

/

@

Override

public void addView(View

child) {

throw new Unsupported

OperationException("addView(V

iew) is not supported in Adap

terView");

}

这里不再做过多解释。

咦?别急,到这里指定机智

的人会问,我们在写App时

Ac tivity中指定布局文件的时

候,xml布局文件或者我们

用java编写的Vie w最外层的

那个布局是可以指定大小的

啊?他们最外层的

layout_width和layout_height

都是有作用的啊?

是这样的,还记得我们上面

的分析吗?我们自己的xml

布局通过setContentView()方

法放置到哪去了呢?记不记

得id为content的FrameLayout

呢?所以我们xml或者java的

Vie w的最外层布局的

layout_width和layout_height

属性才会有效果,就是这么

回事而已。

3

-4 LayoutInflater源码

inflate(…)方法中调运的一些

非public方法剖析

看下inflate方法中被调运的

rInflate方法,源码如下:

void rInflate(XmlPullParser p

arser, View parent, final Att

ributeSet attrs,

boolean finishInf

late, boolean inheritContext)

throws XmlPullParserExceptio

n,

IOException {

final int depth = par

ser.getDepth();

int type;

/XmlPullParser解析器

的标准解析模式

while (((type = parse

/

r.next()) != XmlPullParser.EN

D_TAG ||

parser.getDep

th() > depth) && type != XmlP

ullParser.END_DOCUMENT) {

/

/找到START_TAG节

点程序才继续执行这个判断语句之后的逻

if (type != XmlPu

llParser.START_TAG) {

continue;

}

/

/获取Name标记

final String name

parser.getName();

=

/

/处理REQUEST_FOC

US的标记

if (TAG_REQUEST_F

OCUS.equals(name)) {

parseRequestF

ocus(parser, parent);

}

else if (TAG_TA

G.equals(name)) {

/

/处理tag标记

parseViewTag(

parser, parent, attrs);

else if (TAG_IN

CLUDE.equals(name)) {

}

/

/处理include

标记

if (parser.ge

tDepth() == 0) {

/

/include

节点如果是根节点就抛异常

throw new

InflateException("<include /

cannot be the root element"

>

)

;

}

parseInclude(

parser, parent, attrs, inheri

tContext);

}

else if (TAG_ME

RGE.equals(name)) {

/

/merge节点必

须是xml文件里的根节点(这里不该再出

现merge节点)

throw new Inf

lateException("<merge /> must

be the root element");

}

else {

/

/其他自定义节

final View vi

ew = createViewFromTag(parent

,

)

name, attrs, inheritContext

;

final ViewGro

up viewGroup = (ViewGroup) pa

rent;

final ViewGro

up.LayoutParams params = view

Group.generateLayoutParams(at

trs);

rInflate(pars

er, view, attrs, true, true);

viewGroup.add

View(view, params);

}

}

/

/parent的所有子节点都i

nflate完毕的时候回onFinishInfla

te方法

if (finishInflate) pa

rent.onFinishInflate();

}

可以看见,上面方法主要就

是循环递归解析xml文件,

解析结束回调Vie w类的

onFinishInflate方法,所以

Vie w类的onFinishInflate方法

是一个空方法,如下:

/

**

*

Finalize inflating a v

iew from XML. This is called

as the last phase

*

of inflation, after al

l child views have been added

.

*

*

<p>Even if the subclas

s overrides onFinishInflate,

they should always be

*

sure to call the super

method, so that we get calle

d.

*

/

protected void onFinishIn

flate() {

}

可以看见,当我们自定义

Vie w时在构造函数inflate一

个xml后可以实现

onFinishInflate这个方法一些

自定义的逻辑。

至此LayoutInflater的源码核

心部分已经分析完毕。

4

从LayoutInflater 与

setContentView来说说

应用布局文件的优化

技巧

通过上面的源码分析可以发

现,xml文件解析实质是递

归控件,解析属性的过程。

所以说嵌套过深不仅效率低

下还可能引起调运栈溢出。

同时在解析那些tag时也有一

些特殊处理,从源码看编写

xml还是有很多要注意的地

方的。所以说对于Android的

xml来说是有一些优化技巧

的(PS:布局优化可以通过

hierarchyviewer来查看,通

过lint也可以自动检查出来一

些),如下:

尽量使用相对布局,减少不

必要层级结构。不用解释

吧?递归解析的原因。

使用merge属性。使用它可

以有效的将某些符合条件的

多余的层级优化掉。使用

merge的场合主要有两处:

自定义Vie w中使用,父元素

尽量是FrameLayout,当然如

果父元素是其他布局,而且

不是太复杂的情况下也是可

以使用的;Ac tivity中的整体

布局,根元素需要是

FrameLayout。但是使用

merge标签还是有一些限制

的,具体是:merge只能用

在布局XML文件的根元素;

使用merge来inflate一个布局

时,必须指定一个

Vie wGroup作为其父元素,

并且要设置inflate的

attachToRoot参数为true。

参照inflate(int, Vie wGroup,

boolean)方法);不能在

Vie wStub中使用merge标签;

最直观的一个原因就是

Vie wStub的inflate方法中根本

没有attachToRoot的设置。

使用Vie wStub。一个轻量级

的页面,我们通常使用它来

做预加载处理,来改善页面

加载速度和提高流畅性,

Vie wStub本身不会占用层

级,它最终会被它指定的层

级取代。Vie wStub也是有一

些缺点,譬如:Vie wStub只

能Inflate一次,之后Vie wStub

对象会被置为空。按句话

说,某个被Vie wStub指定的

布局被Inflate后,就不能够

再通过Vie wStub来控制它

了。所以它不适用 于需要按

需显示隐藏的情况;

Vie wStub只能用来Inflate一个

布局文件,而不是某个具体

的Vie w,当然也可以把Vie w

写在某个布局文件中。如果

想操作一个具体的vie w,还

是使用visibility属性吧;

VIewStub中不能嵌套merge

标签。

使用include。这个标签是为

了布局重用。

控件设置widget以后对于

layout_hORw-xxx设置0dp。

减少系统运算次数。

如上就是一些APP布局文件

基础的优化技巧。

5

总结

至此整个Ac tivity的

setContentView与Android的

LayoutInflater相关原理都已

经分析完毕。关于本篇中有

些地方直接给出结论的知识

点后面的文章中会做一说

明。

setContentView整个过程主要

是如何把Ac tivity的布局文件

或者java的Vie w添加至窗口

里,重点概括为:

1

. 创建一个DecorView的对

象mDecor,该mDecor对

象将作为整个应用窗口的

根视图。

2

. 依据Feature等style theme

创建不同的窗口修饰布局

文件,并且通过

findViewById获取Ac tivity

布局文件该存放的地方

(窗口修饰布局文件中id

为content 的

FrameLayout)。

3. 将Ac tivity的布局文件添加

至id为content的

FrameLayout内。

4

. 当setContentView设置显示

OK以后会回调Ac tivity的

onContentChanged方法。

Ac tivity的各种Vie w的

findViewById()方法等都可

以放到该方法中,系统会

帮忙回调。

如下就是整个Ac tivity的分析

简单关系图:

LayoutInflater的使用中重点

关注inflate方法的参数含

义:

inflate(xmlId, null); 只创建

temp的Vie w,然后直接返

回temp 。

inflate(xmlId, parent); 创建

temp的Vie w,然后执行

root.addView(temp,

params);最后返回root。

inflate(xmlId, parent, false);

创建temp的Vie w,然后执

temp.setLayoutParams(para

ms);然后再返回temp。

inflate(xmlId, parent, true);

创建temp的Vie w,然后执

行root.addView(temp,

params);最后返回root。

inflate(xmlId, null, false);

只创建temp的Vie w,然后

直接返回temp。

inflate(xmlId, null, true); 只

创建temp的Vie w,然后直

接返回temp。

当我们自定义Vie w时在构造

函数inflate一个xml后可以实

现onFinishInflate这个方法一

些自定义的逻辑。

【工匠若

水 http://blog.csdn.net/yanbob

er 转载烦请注明出处,尊重

分享成果】

工匠若

水 http://blog.csdn.net/yanbob

er 转载烦请注明出处,尊重

分享成果】

1

背景

今天突然想起之前在上家公

司(做TV与BOX盒子)时

有好几个人问过我关于

Android的Context到底是啥的

问题,所以就马上要诞生这

篇文章。我们平时在开发

App应用程序时一直都在使

用Context(别说你没用过,

访问当前应用的资源、启动

一个a c tivity等都用到了

Context),但是很少有人关

注过这玩意到底是啥,也很

少有人知道getApplication与

getApplicationContext方法有

啥区别,以及一个App到底

有多少个Context等等的细

节。

更为致命的是Context使用不

当还会造成内存泄漏。所以

说完全有必要拿出来单独分

析分析(基于Android 5.1.1

API 22)源码分析)。

工匠若

水 http://blog.csdn.net/yanbob

er 转载烦请注明出处,尊重

分享成果】

2

Context基本信息

2

-1 Context概念

先看下源码Context类基本情

况,如下:

/

**

*

Interface to global inform

ation about an application en

vironment. This is

*

an abstract class whose im

plementation is provided by

*

*

the Android system. It

allows access to applicati

on-specific resources and cla

sses, as well as

*

up-calls for application-l

evel operations such as launc

hing activities,

*

broadcasting and receiving

intents, etc.

*

/

public abstract class Context

{

.

.....

}

从源码注释可以看见,

Context提供了关于应用环境

全局信息的接口。它是一个

抽象类,它的执行被Android

系统所提供。它允许获取以

应用为特征的

资源和类型,是一个统领一

些资源(应用程序环境变量

等)的上下文。

看见上面的Class OverView了

吗?翻译就是说,它描述一

个应用程序环境的信息(即

上下文);是一个抽象类,

Android提供了该抽象类的具

体实现类;通过它我们可以

获取应用程序的资源和类

(包括应用级别操作,如启

动Ac tivity,发广播,接受

Intent等)。

既然上面Context是一个抽象

类,那么肯定有他的实现类

咯,我们在Context的源码中

通过IDE可以查看到他的子

类如下:

吓尿了,737个子类,经过

粗略浏览这些子类名字和查

阅资料发现,这些子类无非

就下面一些主要的继承关

系。这737个类都是如下关

系图的直接或者间接子类而

已。如下是主要的继承关

系:

从这里可以发现,Service和

Application的类继承类似,

Ac tivity继承

ContextThemeWrapper。这

是因为Ac tivity有主题

Ac tivity提供UI显示,所以

需要主题),而Service是没

有界面的服务。

所以说,我们从这张主要关

系图入手来分析Context相关

源码。

2-2 Context之间关系源码概

有了上述通过IDE查看的大

致关系和图谱之后我们在源

码中来仔细看下这些继承关

系。

先来看下Context类源码注

释:

/

**

*

Interface to global inform

ation about an application en

vironment. This is

*

an abstract class whose im

plementation is provided by

*

*

the Android system. It

allows access to applicati

on-specific resources and cla

sses, as well as

*

up-calls for application-l

evel operations such as launc

hing activities,

*

broadcasting and receiving

intents, etc.

*

/

public abstract class Context

{

.

.....

}

看见没有,抽象类Context ,

提供了一组通用的API。

再来看看Context的实现类

ContextImpl源码注释:

/

**

*

Common implementation of C

ontext API, which provides th

e base

*

context object for Activit

y and other application compo

nents.

*

/

class ContextImpl extends Con

text {

private Context mOuterCon

text;

.

.....

}

该类实现了Context类的所有

功能。

再来看看Context的包装类

ContextWrapper源码注释:

/

**

*

Proxying implementation of

Context that simply delegate

s all of its calls to

another Context. Can be s

*

ubclassed to modify behavior

without changing

*

*

the original Context.

/

public class ContextWrapper e

xtends Context {

Context mBase;

public ContextWrapper(Con

text base) {

mBase = base;

}

/

**

*

Set the base context f

or this ContextWrapper. All

calls will then be

*

delegated to the base

context. Throws

*

IllegalStateException

if a base context has already

been set.

*

*

@param base The new ba

se context for this wrapper.

*

/

protected void attachBase

Context(Context base) {

if (mBase != null) {

throw new Illegal

StateException("Base context

already set");

}

mBase = base;

}

.

.....

}

该类的构造函数包含了一个

真正的Context引用

ContextImpl对象),然后

就变成了ContextImpl的装饰

着模式。

再来看看ContextWrapper的

子类ContextThemeWrapper

源码注释:

/

**

*

A ContextWrapper that allo

ws you to modify the theme fr

om what is in the

*

*

wrapped context.

/

public class ContextThemeWrap

per extends ContextWrapper {

.

.....

}

该类内部包含了主题Theme

相关的接口,即

android:theme属性指定的。

再来看看Ac tivity、Service、

Application类的继承关系源

码:

public class Activity extends

ContextThemeWrapper

implements LayoutInfl

ater.Factory2,

Window.Callback, KeyE

vent.Callback,

OnCreateContextMenuLi

stener, ComponentCallbacks2,

Window.OnWindowDismis

sedCallback {

.

.....

}

public abstract class Service

extends ContextWrapper imple

ments ComponentCallbacks2 {

.

.....

}

public class Application exte

nds ContextWrapper implements

ComponentCallbacks2 {

.

.....

}

看见没有?他们完全符合上

面我们绘制的结构图与概

述。

2

-3 解决应用Context个数疑

有了上面的Context继承关系

验证与分析之后我们来看下

一个应用程序到底有多个

Context?

Android应用程序只有四大组

件,而其中两大组件都继承

自Context,另外每个应用程

序还有一个全局的

Application对象。所以在我

们了解了上面继承关系之后

我们就可以计算出来Context

总数,如下:

APP Context总数 = Application

数(1) + Activity数(Customer)

+

Service数(Customer);

到此,我们也明确了Context

啥,继承关系是啥样,应

用中Context个数是多少的问

题。接下来就有必要继续深

入分析这些Context都是怎么

来的。

【工匠若

水 http://blog.csdn.net/yanbob

er 转载烦请注明出处,尊重

分享成果】

3

各种Context 在

ActivityThread中实例

化过程源码分析

在开始分析之前还是和

《Android异步消息处理机制

详解及源码分析》的3-1-2小

节及《Android应用

setContentView与

LayoutInflater加载解析机制

源码分析》的2-6小节一样直

接先给出关于Ac tivity启动的

一些概念,后面会写文章分

析这一过程。

Context的实现是

ContextImpl,Ac tivity与

Application和Service的创建

都是在Ac tivityThre a d中完成

的,至于在Ac tivityThre a d何

时、怎样调运的关系后面会

写文章分析,这里先直接给

出结论,因为我们分析的重

点是Context过程。

3-1 Ac tivity中ContextImpl实

例化源码分析

通过sta r tAc tivity启动一个新

的Ac tivity时系统会回调

Ac tivityThre a d的

handleLaunchActivity()方法,

该方法内部会调用

performLaunchActivity()方法

去创建一个Ac tivity实例,然

后回调Ac tivity的onCreate()等

方法。所以Ac tivity的

ContextImpl实例化是在

Ac tivityThre a d类的

performLaunchActivity方法

中,如下:

private Activity performLaunc

hActivity(ActivityClientRecor

d r, Intent customIntent) {

.

.....

/

/已经创建好新的act

ivity实例

if (activity != n

ull) {

/

/创建一个Cont

ext对象

Context appCo

ntext = createBaseContextForA

ctivity(r, activity);

.

/

.....

/将上面创建的a

ppContext传入到activity的attac

h方法

activity.atta

ch(appContext, this, getInstr

umentation(), r.token,

r.ide

nt, app, r.intent, r.activity

Info, title, r.parent,

r.emb

eddedID, r.lastNonConfigurati

onInstances, config,

r.ref

errer, r.voiceInteractor);

.

.....

}

.

.....

return activity;

}

看见上面

performLaunchActivity的核心

代码了吗?通过

createBaseContextForActiv

创建appContext,然后通过

activity.attach设置值。

具体我们先看下

createBaseContextForActivity

方法源码,如下:

private Context createBaseCon

textForActivity(ActivityClien

tRecord r,

final Activity ac

tivity) {

/

/实质就是new一个Contex

tImpl对象,调运ContextImpl的有参

构造初始化一些参数

ContextImpl appContex

t = ContextImpl.createActivit

yContext(this, r.packageInfo,

r.token);

/

/

/特别特别留意这里!!!

/ContextImpl中有一个C

ontext的成员叫mOuterContext,通

过这条语句就可将当前新Activity对象

赋值到创建的ContextImpl的成员mOu

terContext(也就是让ContextImpl

内部持有Activity)。

appContext.setOuterCo

ntext(activity);

/

/创建返回值并且赋值

Context baseContext =

appContext;

.....

/返回ContextImpl对象

return baseContext;

.

/

}

再来看看activity.attach,也

就是Ac tivity中的attach方

法,如下:

final void attach(Context con

text, ActivityThread aThread,

Instrumentation i

nstr, IBinder token, int iden

t,

Application appli

cation, Intent intent, Activi

tyInfo info,

CharSequence titl

e, Activity parent, String id

,

NonConfigurationI

nstances lastNonConfiguration

Instances,

Configuration con

fig, String referrer, IVoiceI

nteractor voiceInteractor) {

/

/

/特别特别留意这里!!!

/与上面createBaseCont

extForActivity方法中setOuterCo

ntext语句类似,不同的在于:

/

/通过ContextThemeWra

pper类的attachBaseContext方法,

将createBaseContextForActivit

y中实例化的ContextImpl对象传入到C

ontextWrapper类的mBase变量,这

样ContextWrapper(Context子类)

类的成员mBase就被实例化为Context

的实现类ContextImpl

attachBaseContext(con

text);

.

.....

}

通过上面Ac tivity的Context实

例化分析再结合上面Context

继承关系可以看出:

Ac tivity通过ContextWrapper

的成员mBase来引用了一个

ContextImpl对象,这样,

Ac tivity组件以后就可以通过

这个ContextImpl对象来执行

一些具体的操作(启动

Service等);同时

ContextImpl类又通过自己的

成员mOuterContext引用了与

它关联的Ac tivity,这样

ContextImpl类也可以操作

Ac tivity。

SO,由此说明一个Ac tivity就

有一个Context,而且生命周

期和Ac tivity类相同(记住这

句话,写应用就可以避免一

些低级的内存泄漏问题)。

3-2 Service中ContextImpl实

例化源码分析

写APP时我们通过

startService或者bindService方

法创建一个新Service时就会

回调Ac tivityThre a d类的

handleCreateService()方法完

成相关数据操作(具体关于

Ac tivityThre a d调运

handleCreateService时机等细

节分析与上面Ac tivity雷同,

后边文章会做分析)。具体

handleCreateService方法代码

如下:

private void handleCreateServ

ice(CreateServiceData data) {

.

/

.....

/类似上面Activity的创

建,这里创建service对象实例

Service service = nul

l;

try {

java.lang.ClassLo

ader cl = packageInfo.getClas

sLoader();

service = (Servic

e) cl.loadClass(data.info.nam

e).newInstance();

}

catch (Exception e)

{

.

.....

}

try {

.

/

.....

/不做过多解释,创建

一个Context对象

ContextImpl conte

xt = ContextImpl.createAppCon

text(this, packageInfo);

/

/特别特别留意这里!

/

/ContextImpl中有

一个Context的成员叫mOuterContex

t,通过这条语句就可将当前新Service

对象赋值到创建的ContextImpl的成员

mOuterContext(也就是让ContextI

mpl内部持有Service)。

context.setOuterC

ontext(service);

Application app =

packageInfo.makeApplication(

false, mInstrumentation);

/

/将上面创建的conte

xt传入到service的attach方法

service.attach(co

ntext, this, data.info.name,

data.token, app,

ActivityM

anagerNative.getDefault());

service.onCreate(

)

;

.

.....

}

}

catch (Exception e)

{

.

.....

}

再来看看service.attach,也

就是Service中的attach方法,

如下:

public final void attach(

Context context,

ActivityThread th

read, String className, IBind

er token,

Application appli

cation, Object activityManage

r) {

/

/特别特别留意这里!

/

/与上面handleCrea

teService方法中setOuterContext

语句类似,不同的在于:

/

/通过ContextWrap

per类的attachBaseContext方法,

将handleCreateService中实例化的

ContextImpl对象传入到ContextWra

pper类的mBase变量,这样ContextW

rapper(Context子类)类的成员mBa

se就被实例化为Context的实现类Cont

extImpl

attachBaseContext(con

text);

.

.....

}

可以看出步骤流程和Ac tivity

的类似,只是实现细节略有

不同而已。

SO,由此说明一个Service就

有一个Context,而且生命周

期和Service类相同(记住这

句话,写应用就可以避免一

些低级的内存泄漏问题)。

3-3 Application中ContextImpl

实例化源码分析

当我们写好一个APP以后每

次重新启动时都会首先创建

Application对象(每个APP都

有一个唯一的全局

Application对象,与整个APP

的生命周期相同)。创建

Application的过程也在

Ac tivityThre a d类的

handleBindApplication()方法

完成相关数据操作(具体关

于Ac tivityThre a d调运

handleBindApplication时机等

细节分析与上面Ac tivity雷

同,后边文章会做分析)。

而ContextImpl的创建是在该

方法中调运LoadedApk类的

makeApplication方法中实

现,LoadedApk类的

makeApplication()方法中源

代码如下:

public Application makeApplic

ation(boolean forceDefaultApp

Class,

Instrumentation i

nstrumentation) {

/

/只有新创建的APP才会走i

f代码块之后的剩余逻辑

if (mApplication != n

ull) {

return mApplicati

on;

}

/

/即将创建的Application

对象

Application app = nul

l;

String appClass = mAp

plicationInfo.className;

if (forceDefaultAppCl

ass || (appClass == null)) {

appClass = "andro

id.app.Application";

}

try {

java.lang.ClassLo

ader cl = getClassLoader();

if (!mPackageName

.

equals("android")) {

initializeJav

aContextClassLoader();

}

/

/不做过多解释,创建

一个Context对象

ContextImpl appCo

ntext = ContextImpl.createApp

Context(mActivityThread, this

)

;

/

/将Context传入Ins

trumentation类的newApplicatio

n方法

app = mActivityTh

read.mInstrumentation.newAppl

ication(

cl, appCl

ass, appContext);

/

/特别特别留意这里!

!!

/

/ContextImpl中有

一个Context的成员叫mOuterContex

t,通过这条语句就可将当前新Applica

tion对象赋值到创建的ContextImpl

的成员mOuterContext(也就是让Con

textImpl内部持有Application)。

appContext.setOut

erContext(app);

}

catch (Exception e)

.....

.....

{

.

}

.

return app;

}

接着看看

Instrumentation.newApplicatio

n方法。如下源码:

public Application newApplica

tion(ClassLoader cl, String c

lassName, Context context)

throws Instantiat

ionException, IllegalAccessEx

ception,

ClassNotFoundExce

ption {

return newApplication

(cl.loadClass(className), con

text);

}

继续看重载两个参数的

newApplication方法,如下:

static public Application new

Application(Class<?> clazz, C

ontext context)

throws Instantiat

ionException, IllegalAccessEx

ception,

ClassNotFoundExce

ption {

.

/

.....

/继续传递context

app.attach(context);

return app;

}

继续看下Application类的

attach方法,如下:

final void attach(Context con

text) {

/

/

/特别特别留意这里!!!

/与上面makeApplicatio

n方法中setOuterContext语句类似,

不同的在于:

/

/通过ContextWrapper类

的attachBaseContext方法,将make

Application中实例化的ContextImp

l对象传入到ContextWrapper类的mB

ase变量,这样ContextWrapper(Co

ntext子类)类的成员mBase就被实例

化为Application的实现类ContextI

mpl

attachBaseContext(con

text);

.

.....

}

可以看出步骤流程和Ac tivity

的类似,只是实现细节略有

不同而已。

SO,由此说明一个

Application就有一个

Context,而且生命周期和

Application类相同(然而一

个App只有一个Application,

而且与应用生命周期相

同)。

4

应用程序APP各种

Context访问资源的唯

一性分析

你可能会有疑问,这么多

Context都是不同实例,那么

我们平时写App时通过

context.getResources得到资

源是不是就不是同一份呢?

下面我们从源码来分析下,

如下:

class ContextImpl extends Con

text {

.

.....

private final ResourcesMa

nager mResourcesManager;

private final Resources m

Resources;

.

@

.....

Override

public Resources getResou

rces() {

return mResources;

}

.

.....

}

看见没,有了上面分析我们

可以很确定平时写的App中

context.getResources方法获

得的Resources对象就是上面

ContextImpl的成员变量

mResources。

那我们追踪可以发现

mResources的赋值操作如

下:

private ContextImpl(ContextIm

pl container, ActivityThread

mainThread,

LoadedApk package

Info, IBinder activityToken,

UserHandle user, boolean rest

ricted,

Display display,

Configuration overrideConfigu

ration) {

.

/

.....

/单例模式获取Resources

Manager对象

mResourcesManager = R

esourcesManager.getInstance()

;

.

/

.....

/packageInfo对于一个A

PP来说只有一个,所以resources 是

同一份

Resources resources =

packageInfo.getResources(mai

nThread);

if (resources != null

if (activityToken

)

{

!

= null

|

| displa

yId != Display.DEFAULT_DISPLA

Y

|

| overri

deConfiguration != null

|

tInfo != null && compatInfo.a

pplicationScale

| (compa

!

=

resources.getCompatibilityI

nfo().applicationScale)) {

/

anager是单例,所以resources是同

一份

/mResourcesM

resources = m

ResourcesManager.getTopLevelR

esources(packageInfo.getResDi

r(),

packa

geInfo.getSplitResDirs(), pac

kageInfo.getOverlayDirs(),

packa

geInfo.getApplicationInfo().s

haredLibraryFiles, displayId,

overr

ideConfiguration, compatInfo,

activityToken);

}

/把resources赋值给mRe

mResources = resource

.....

}

/

sources

s;

.

}

由此可以看出在设备其他因

素不变的情况下我们通过不

同的Context实例得到的

Resources是同一套资源。

PS一句,同样的分析方法也

可以发现Context类的

packageInfo对于一个应用来

说也只有一份。感兴趣可以

自行分析。

5

应用程序APP各种

Context使用区分源码

分析

5-1 先来解决getApplication和

getApplicationContext的区别

很多人一直区分不开这两个

方法的区别,这里从源码来

分析一下,如下:

首先来看getApplication方

法,你会发现Application与

Context都没有提供该方法,

这个方法是哪提供的呢?我

们看下Ac tivity与Service中的

代码,可以发下如下:

public class Activity extends

ContextThemeWrapper

implements LayoutInfl

ater.Factory2,

Window.Callback, KeyE

vent.Callback,

OnCreateContextMenuLi

stener, ComponentCallbacks2,

Window.OnWindowDismis

sedCallback {

.

.....

public final Application

getApplication() {

return mApplication;

}

.

.....

}

public abstract class Service

extends ContextWrapper imple

ments ComponentCallbacks2 {

.

.....

public final Application

getApplication() {

return mApplication;

.....

}

.

}

Ac tivity和Service提供了

getApplication,而且返回类

型都是Application。这个

mApplication都是在各自类的

attach方法参数出入的,也就

是说这个

mApplication都是在

Ac tivityThre a d中各自实例化

时获取的makeApplication方

法返回值。

所以不同的Ac tivity和Service

返回的Application均为同一

个全局对象。

再来看看

getApplicationContext方法,

如下:

class ContextImpl extends Con

text {

.

@

.....

Override

public Context getApplica

tionContext() {

return (mPackageInfo

= null) ?

!

mPackageInfo.

getApplication() : mMainThrea

d.getApplication();

}

.

.....

}

可以看到

getApplicationContext方法是

Context的方法,而且返回值

是Context类型,返回对象和

上面通过Service或者Ac tivity

的getApplication返回

的是一个对象。所以说对于

客户化的第三方应用来说两

个方法返回值一样,只是返

回值类型不同,还有就是依

附的对象不同而已。

5-2 各种获取Context方法的

差异及开发要点提示

可以看出来,Application的

Context生命周期与应用程序

完全相同。

Ac tivity或者Service的Context

与他们各自类生命周期相

同。

所以说对于Context使用不当

会引起内存泄漏。

譬如一个单例模式的自定义

数据库管理工具类需要传入

一个Context,而这个数据库

管理对象又需要在Ac tivity中

使用,如果我们传递Ac tivity

的Context就可能造成内存泄

漏,所以需要传递

Application的Context。

6

Context分析总结

到此整个Android应用的

Context疑惑就完全解开了,

同时也依据源码分析结果给

出了平时开发APP中该注意

的内存泄漏问题提示与解决

方案。相信通过这一篇你在

开发APP时对于Context的使

用将不再迷惑。

【工匠若

水 http://blog.csdn.net/yanbob

er 转载烦请注明出处,尊重

分享成果】

PS一句:最终还是选择

CSDN来整理发表这几年的

知识点,该文章平行迁移到

CSDN。因为CSDN也支持

MarkDown语法了,牛逼

啊!

工匠若

水 http://blog.csdn.net/yanbob

er 转载烦请注明出处,尊重

分享成果】

最近相对来说比较闲,加上

养病,所以没事干就撸些自

己之前的知识点为博客,方

便自己也方便别人。

1

背景

之所以选择这个知识点来分

析有以下几个原因:

1

. 逛GitHub时发现关注的

isuss中有人不停的在讨论

Android中的Looper ,

Handler , Message有什么

关系。

2

. 其实这个知识点对于

Android初学者来说很常

用,但是初学者可能前期

一直处于会用不知其原理

的阶段。

3

. 这个知识点也是Android面

试中一个高频问题。

基于以上几点也得拿出来分

析分析,该篇博客从实例到

源码完全进行了剖析(包含

Handler、Message、

MessageQueue、Looper、

HandlerThread等源码),不

同于网上很多只是分析局部

的博客。

你可能在刚开始接触Android

开发时就会知道如下问题:

Android的UI时线程不安全

的,如果在线程中更新UI会

出现异常,导致程序崩溃;

同时如果UI中做耗时操作又

会导致臭名昭著的ANR异

常。

为了解决如上这些问题,我

们怎办呢?很简单,通常最

经典常用的做法就是使用

Android的异步消息机制实现

即可(创建一个Message对

象,使用Handler发送出去,

然后在Handler 的

handleMessage()方法中获得

刚才发送的Message对象,

然后在这里进行UI操作)。

所以说还是很有必要了解异

步消息机制的Looper ,

Handler , Message等原理

的。

如下开始一个示例使用,然

后通过源码分析吧。

2

示例展示

如下示例展示了UI Thread与

Child Thread之间互相发送消

息,同时在UI Thread与Child

Thread中分别定义Handler。

这样如果没有mCount的判

断,这段程序会一直死循环

打印下去。

public class MainActivity ext

ends ActionBarActivity {

private int mCount = 0;

private Handler mHandlerT

hr = null;

private Handler mHandler

=

new Handler() {

Override

public void handleMes

sage(Message msg) {

super.handleMessa

@

ge(msg);

Log.d(null, ">>>>

>>>>>>>>UI# mHandler--handle

>

Message--msg.what="+msg.what)

;

/

/接收发送到UI线程的

消息,然后向线程中的Handler发送ms

g 1。

mHandlerThr.sendE

mptyMessage(1);

mCount++;

if (mCount >= 3)

{

/

/由于mHandle

rThr是在Child Thread创建,Loope

r手动死循环阻塞,所以需要quit。

mHandlerThr.g

etLooper().quit();

}

}

}

@

;

Override

protected void onCreate(B

undle savedInstanceState) {

super.onCreate(savedI

nstanceState);

setContentView(R.layo

ut.activity_main);

initData();

}

@

Override

protected void onStop() {

super.onStop();

/

/删除所有call与msg

mHandler.removeCallba

cksAndMessages(null);

}

private void initData() {

Log.d(null, ">>>>>>>>

>>>>UI# begin start thread!!

");

>

!

new Thread() {

@

Override

public void run()

{

super.run();

Looper.prepar

e();

new Handler() {

mHandlerThr =

@

Override

public vo

id handleMessage(Message msg)

{

super

.

handleMessage(msg);

Log.d

(null, ">>>>>>>>>>>>>Child# m

HandlerThr--handleMessage--ms

g.what=" + msg.what);

/

/接

收发送到子线程的消息,然后向UI线程

中的Handler发送msg 0。

mHand

ler.sendEmptyMessage(0);

}

}

;

Log.d(null, "

>

>>>>>>>>>>>>Child# begin sta

rt send msg!!!");

/

/Activity中

启动Thread,在Thread结束前发送ms

g 0到UI Thread。

mHandler.send

EmptyMessage(0);

Looper.loop()

;

//不能在这个后面添加代码,程序是

无法运行到这行之后的。

}

}

.start();

}

}

运行结果展示如下:

>

>>>>>>>>>>>>UI# begin start

thread!!!

>>>>>>>>>>>>Child# begin sta

rt send msg!!!

>>>>>>>>>>>>UI# mHandler--ha

ndleMessage--msg.what=0

>>>>>>>>>>>>Child# mHandlerT

hr--handleMessage--msg.what=1

>>>>>>>>>>>>UI# mHandler--ha

ndleMessage--msg.what=0

>>>>>>>>>>>>Child# mHandlerT

hr--handleMessage--msg.what=1

>

>

>

>

>

>

>>>>>>>>>>>>UI# mHandler--ha

ndleMessage--msg.what=0

怎么样,这和你平时用的

Handler一样吧,对于

Handler异步处理的简单基础

示例先说到这,接下来依据

上面示例的写法分析原因与

源代码原理。

3

5

分析Android

.1.1(API 22)异步

消息机制源码

3

-1 看看Handler的实例化过

程源码

-1-1 Handler实例化源码

3

从哪着手分析呢?当然是实

例化构造函数呀,所以我们

先从Handler的默认构造函数

开始分析,如下:

/

**

*

Default constructor as

sociates this handler with th

e {@link Looper} for the

*

*

*

current thread.

If this thread does no

t have a looper, this handler

won't be able to receive mes

sages

own.

*

*

so an exception is thr

/

public Handler() {

this(null, false);

}

通过注释也能看到,默认构

造函数没有参数,而且调运

了带有两个参数的其他构造

函数,第一个参数传递为

null,第二个传递为false。

这个构造函数得到的Handler

默认属于当前线程,而且如

果当前线程如果没有Looper

则通过这个默认构造实例化

Handler时会抛出异常,至于

是啥异常还有为啥咱们继续

往下分析,this(null, false)的

实现如下:

public Handler(Callback callb

ack, boolean async) {

if (FIND_POTENTIAL_LE

AKS) {

final Class<? ext

ends Handler> klass = getClas

s();

if ((klass.isAnon

ymousClass() || klass.isMembe

rClass() || klass.isLocalClas

s()) &&

(klass.ge

tModifiers() & Modifier.STATI

C) == 0) {

Log.w(TAG, "T

he following Handler class sh

ould be static or leaks might

occur: " +

klass.get

CanonicalName());

}

}

mLooper = Looper.myLo

oper();

if (mLooper == null)

{

throw new Runtime

Exception(

"

Can't create

handler inside thread that h

as not called Looper.prepare(

)

");

}

mQueue = mLooper.mQue

ue;

;

mCallback = callback;

mAsynchronous = async

}

可以看到,在第11行调用了

mLooper = Looper.myLooper

语句,然后获取了一个

Looper对象mLooper ,如果

mLooper实例为空,则会抛

出一个运行

时异常(Can’t create handler

inside thread that has not

called

Looper.prepare()!)。

3-1-2 Looper实例化源码

好奇的你指定在想什么时候

mLooper 对象才可能为空

呢?很简单,跳进去看下

吧,Looper类的静态方法

myLooper如下:

/

**

*

Return the Looper obje

ct associated with the curren

t thread. Returns

*

null if the calling th

read is not associated with a

Looper.

*

/

public static Looper myLo

oper() {

return sThreadLocal.g

et();

}

咦?这里好简单。单单就是

从sThreadLocal对象中get了

一个Looper对象返回。跟踪

了一下sThreadLocal对象,

发现他定义在Looper中,是

一个static

fina l类型的

ThreadLocal<Looper> 对象

在Java中,一般情况下,

通过ThreadLocal.set() 到线

程中的对象是该线程自己使

用的对象,其他线程是不需

访问的,也访问不到的,各

个线程中访问的是不同的对

象。)。所以可以看出,如

果sThreadLocal中有Looper存

在就返回Looper,没有

Looper存在

自然就返回null了。

这时候你一定有疑惑,既然

这里是通过sThreadLocal的

get获得Looper,那指定有地

方对sThreadLocal进行set操

作吧?是的,我们在Looper

类中跟踪发现如下:

private static void prepare(b

oolean quitAllowed) {

if (sThreadLocal.get(

)

!= null) {

throw new Runtime

Exception("Only one Looper ma

y be created per thread");

}

sThreadLocal.set(new

Looper(quitAllowed));

}

看着这个Looper的static方法

prepare没有?这段代码首先

判断sThreadLocal中是否已

经存在Looper了,如果还没

有则创建一个新的Looper设

置进去。

那就看下Looper的实例化,

如下:

private Looper(boolean quitAl

lowed) {

mQueue = new MessageQ

ueue(quitAllowed);

mThread = Thread.curr

entThread();

}

可以看见Looper构造函数无

非就是创建了一个

MessageQueue(它是一个消

息队列,用于将所有收到的

消息以队列的形式进行排

列,并提供入队和出

队的方法。)和货到当前

Thread实例引用而已。通过

这里可以发现,一个Looper

只能对应了一个

MessageQueue。

你可能会说上面的例子在子

线程中明明先调运的是

Looper.prepare();方法,这里

怎么有参数了?那就继续看

吧,如下:

public static void prepare()

{

prepare(true);

}

可以看见,prepare()仅仅是

对prepare(boolean

quitAllowed) 的封装而已,

默认传入了true,也就是将

MessageQueue对象中的

quitAllowed标记标

记为true而已,至于

MessageQueue后面会分析。

稀奇古怪的事情来了!如果

你足够留意上面的例子,你

会发现我们在UI Thread中创

建Handler时没有调用

Looper.prepare();,而在

initData方法中创建的Child

Thread中首先就调运了

Looper.prepare();。你指定很

奇怪吧?UI Thread为啥不需

要呢?上面源码分析明明需

要先保证mLooper对象不为

null呀?

这是由于在UI线程(Ac tivity

等)启动的时候系统已经帮

我们自动调用了

Looper.prepare()方法。

那么在哪启动的呢?这个涉

及Android系统架构问题比较

多,后面文章会分析Ac tivity

的启动流程。这里你只要知

道,以前一直都说Ac tivity的

人口是onCreate方法,其实

android上一个应用的入口应

该是Ac tivityThre a d类的ma in

方法就行了。

所以为了解开UI Thread为何

不需要创建Looper对象的原

因,我们看下Ac tivityThre a d

的main方法,如下:

public static void main(Strin

g[] args) {

SamplingProfilerInteg

ration.start();

/

/ CloseGuard default

s to true and can be quite sp

ammy. We

/

/ disable it here, b

ut selectively enable it late

r (via

/

/ StrictMode) on deb

ug builds, but using DropBox,

not logs.

CloseGuard.setEnabled

(false);

Environment.initForCu

rrentUser();

/

/ Set the reporter f

or event logging in libcore

EventLogger.setReport

er(new EventLoggingReporter()

)

;

Security.addProvider(

new AndroidKeyStoreProvider()

)

;

/

/ Make sure TrustedC

ertificateStore looks in the

right place for CA certificat

es

final File configDir

=

Environment.getUserConfigDi

rectory(UserHandle.myUserId()

)

;

TrustedCertificateSto

re.setDefaultUserDirectory(co

nfigDir);

Process.setArgV0("<pr

e-initialized>");

Looper.prepareMainLoo

per();

ActivityThread thread

=

new ActivityThread();

thread.attach(false);

if (sMainThreadHandle

r == null) {

sMainThreadHandle

r = thread.getHandler();

}

if (false) {

Looper.myLooper()

setMessageLogging(new

LogPrinte

r(Log.DEBUG, "ActivityThread"

);

.

)

}

Looper.loop();

throw new RuntimeExce

ption("Main thread loop unexp

ectedly exited");

}

看见22行没?没说错吧?

Looper.prepareMainLooper(

我们跳到Looper看下

prepareMainLooper方法,如

下:

public static void prepareMai

nLooper() {

prepare(false);

synchronized (Looper.

class) {

if (sMainLooper !

=

null) {

throw new Ill

egalStateException("The main

Looper has already been prepa

red.");

}

sMainLooper = myL

ooper();

}

}

可以看到,UI线程中会始终

存在一个Looper对象

sMainLooper 保存在

Looper类中,UI线程通过

getMainLooper方法获取UI线

程的Looper对象),

从而不需要再手动去调用

Looper.prepare()方法了。如

下Looper类提供的get方法:

public static Looper getMainL

ooper() {

synchronized (Looper.

class) {

return sMainLoope

r;

}

}

看见没有,到这里整个

Handler实例化与为何子线程

在实例化Handler之前需要先

调运Looper.prepare();语句的

原理分析完毕。

3-1-3 Handler与Looper实例

化总结

到此先初步总结下上面关于

Handler实例化的一些关键信

息,具体如下:

1

. 在主线程中可以直接创建

Handler对象,而在子线程

中需要先调用

Looper.prepare()才能创建

Handler对象,否则运行抛

出”Can’t create handler

inside thread that has not

called Looper.prepare()”异

常信息。

2

. 每个线程中最多只能有一

个Looper对象,否则抛出

异常。

3. 可以通过

Looper.myLooper()获取当

前线程的Looper实例,通

过Looper.getMainLooper()

获取主(UI)线程的

Looper实例。

4. 一个Looper只能对应了一

个MessageQueue 。

5. 一个线程中只有一个

Looper实例,一个

MessageQueue实例,可以

有多个Handler实例。

Handler对象也创建好了,接

下来就该用了吧,所以下面

咱们从Handler的收发消息角

度来分析分析源码。

3-2 继续看看Handler消息收

发机制源码

3

-2-1 通过Handler发消息到

消息队列

还记得上面的例子吗?我们

在Child Thread的最后通过主

线程的Handler对象调运

sendEmptyMessage方法发送

出去了一条消息。

当然,其实Handler类提供了

许发送消息的方法,我们这

个例子只是用了最简单的发

送一个empty消息而已,有

时候我们会先定义一个

Message,然后通过Handler

提供的其他方法进行发送。

通过分析Handler源码发现

Handler中提供的很多个发送

消息方法中除了

sendMessageAtFrontOfQueue

()方法之外,其它的发送消

息方法最终都调用了

sendMessageAtTime()方法。

所以,咱们先来看下这个

sendMessageAtTime方法,如

下:

public boolean sendMessageAtT

ime(Message msg, long uptimeM

illis) {

MessageQueue queue =

mQueue;

if (queue == null) {

RuntimeException

e = new RuntimeException(

this + "

sendMessageAtTime() called wi

th no mQueue");

Log.w("Looper", e

.

getMessage(), e);

return false;

}

return enqueueMessage

(queue, msg, uptimeMillis);

}

再看下Handler中和其他发送

方法不同的

sendMessageAtFrontOfQueue

方法,如下:

public final boolean sendMess

ageAtFrontOfQueue(Message msg

)

{

MessageQueue queue =

mQueue;

if (queue == null) {

RuntimeException

e = new RuntimeException(

this + " send

MessageAtTime() called with n

o mQueue");

Log.w("Looper", e

.

getMessage(), e);

return false;

}

return enqueueMessage

(queue, msg, 0);

}

对比上面两个方法可以发

现,表面上说Handler的

sendMessageAtFrontOfQueue

方法和其他发送方法不同,

其实实质是相同的,仅仅是

sendMessageAtFrontOfQueue

方法是sendMessageAtTime 方

法的一个特例而已

(sendMessageAtTime最后一

个参数传递0就变为了

sendMessageAtFrontOfQueue

方法)。所以咱们现在继续

分析sendMessageAtTime 方

法,如下分析:

sendMessageAtTime(Message

msg, long uptimeMillis)方法有

两个参数;msg是我们发送

的Message对象,uptimeMillis

表示发送消息的时间,

uptimeMillis的值等于从系统

开机到当前时间的毫秒数再

加上延迟时间。

在该方法的第二行可以看到

queue = mQueue,而mQueue

是在Handler实例化时构造函

数中实例化的。在Handler的

构造函数中可以看见mQueue

=

mLooper.mQueue;,而

Looper的mQueue对象上面分

析过了,是在Looper的构造

函数中创建的一个

MessageQueue。

接着第9行可以看到,上面

说的两个参数和刚刚得到的

queue对象都传递到了

enqueueMessage(queue, msg,

uptimeMillis)方法中,那就看

下这个方法吧,如下:

private boolean enqueueMessag

e(MessageQueue queue, Message

msg, long uptimeMillis) {

msg.target = this;

if (mAsynchronous) {

msg.setAsynchrono

us(true);

}

return queue.enqueueM

essage(msg, uptimeMillis);

}

这个方法首先将我们要发送

的消息Message的target属性

设置为当前Handler对象(进

行关联);接着将msg与

uptimeMillis这两个参数都传

递到

MessageQueue(消息队列)

的enqueueMessage()方法

中,所以接下来我们就继续

分析MessageQueue类的

enqueueMessage方法,如

下:

boolean enqueueMessage(Messag

e msg, long when) {

if (msg.target == nul

l) {

throw new Illegal

ArgumentException("Message mu

st have a target.");

}

if (msg.isInUse()) {

throw new Illegal

StateException(msg + " This m

essage is already in use.");

}

synchronized (this) {

if (mQuitting) {

IllegalStateE

xception e = new IllegalState

Exception(

msg.t

arget + " sending message to

a Handler on a dead thread");

Log.w("Messag

eQueue", e.getMessage(), e);

msg.recycle()

;

return false;

}

msg.markInUse();

msg.when = when;

Message p = mMess

ages;

boolean needWake;

if (p == null ||

when == 0 || when < p.when) {

/

/ New head,

wake up the event queue if bl

ocked.

msg.next = p;

mMessages = m

sg;

needWake = mB

locked;

}

else {

/ Inserted w

ithin the middle of the queue

/

.

Usually we don't have to w

ake

/

/ up the eve

nt queue unless there is a ba

rrier at the head of the queu

e

/

/ and the me

ssage is the earliest asynchr

onous message in the queue.

needWake = mB

locked && p.target == null &&

msg.isAsynchronous();

Message prev;

for (;;) {

prev = p;

p = p.nex

t;

if (p ==

null || when < p.when) {

break

;

}

if (needW

ake && p.isAsynchronous()) {

needW

ake = false;

}

}

msg.next = p;

/ invariant: p == prev.next

prev.next = m

/

sg;

}

/

/ We can assume

mPtr != 0 because mQuitting i

s false.

if (needWake) {

nativeWake(mP

tr);

}

}

return true;

}

通过这个方法名可以看出来

通过Handler发送消息实质就

是把消息Message添加到

MessageQueue消息队列中的

过程而已。

通过上面遍历等next操作可

以看出来,MessageQueue消

息队列对于消息排队是通过

类似c语言的链表来存储这

些有序的消息的。其中的

mMessages对象表示当前待

处理的消息;然后18到49行

可以看出,消息插入队列的

实质就是将所有的消息按时

间(uptimeMillis参数,上面

有介绍)进行排序。所以还

记得上面

sendMessageAtFrontOfQueue

方法吗?它的实质就是把消

息添加到MessageQueue消息

队列的头部(uptimeMillis为

0,上面有分析)。

到此Handler的发送消息及发

送的消息如何存入到

MessageQueue消息队列的逻

辑分析完成。

那么问题来了!既然消息都

存入到了MessageQueue消息

队列,当然要取出来消息

吧,不然存半天有啥意义

呢?我们知道MessageQueue

的对象在Looper构造函数中

实例化的;一个Looper对应

一个MessageQueue,所以说

Handler发送消息是通过

Handler构造函数里拿到的

Looper对象的成员

MessageQueue的

enqueueMessage方法将消息

插入队列,也就是说出队列

一定也与Handler和Looper和

MessageQueue有关系。

还记不记得上面实例部分中

Child Thread最后调运的

Looper.loop();方法呢?这个

方法其实就是取出

MessageQueue消息队列里的

消息方法。具体在下面分

析。

3-2-2 通过Handler接收发送

的消息

先来看下上面例子中

Looper.loop();这行代码调运

的方法,如下:

/

**

*

Run the message queue

in this thread. Be sure to ca

ll

*

{@link #quit()} to end

the loop.

*

/

public static void loop()

{

final Looper me = myL

ooper();

if (me == null) {

throw new Runtime

Exception("No Looper; Looper.

prepare() wasn't called on th

is thread.");

}

final MessageQueue qu

eue = me.mQueue;

/

/ Make sure the iden

tity of this thread is that o

f the local process,

/

/ and keep track of

what that identity token actu

ally is.

Binder.clearCallingId

entity();

final long ident = Bi

nder.clearCallingIdentity();

for (;;) {

Message msg = que

ue.next(); // might block

if (msg == null)

{

/

/ No message

indicates that the message q

ueue is quitting.

return;

}

/

/ This must be i

n a local variable, in case a

UI event sets the logger

Printer logging =

me.mLogging;

if (logging != nu

ll) {

logging.print

ln(">>>>> Dispatching to " +

msg.target + " " +

msg.c

allback + ": " + msg.what);

}

msg.target.dispat

chMessage(msg);

if (logging != nu

ll) {

logging.print

ln("<<<<< Finished to " + msg

.

;

target + " " + msg.callback)

}

/

/ Make sure that

during the course of dispatc

hing the

/

/ identity of th

e thread wasn't corrupted.

final long newIde

nt = Binder.clearCallingIdent

ity();

if (ident != newI

dent) {

Log.wtf(TAG,

"

Thread identity changed from

0x"

+

Lon

g.toHexString(ident) + " to 0

x"

+

Lon

g.toHexString(newIdent) + " w

hile dispatching to "

+

msg

.

+

target.getClass().getName()

" "

+

msg

.

callback + " what=" + msg.wh

at);

}

msg.recycleUnchec

ked();

}

}

可以看到,第6行首先得到

了当前线程的Looper对象

me,接着第10行通过当前

Looper对象得到与Looper对

象一一对应的MessageQueue

消息队列

也就类似上面发送消息部

分,Handler通过myLoop方

法得到Looper对象,然后获

取Looper的MessageQueue消

息队列对象)。17行进入一

个死循环

,18行不断地调用

MessageQueue的next()方

法,进入MessageQueue这个

类查看next方法,如下:

Message next() {

/

/ Return here if the

message loop has already qui

t and been disposed.

/

/ This can happen if

the application tries to res

tart a looper after quit

/

/ which is not suppo

rted.

;

final long ptr = mPtr

if (ptr == 0) {

return null;

}

int pendingIdleHandle

rCount = -1; // -1 only durin

g first iteration

int nextPollTimeoutMi

llis = 0;

for (;;) {

if (nextPollTimeo

utMillis != 0) {

Binder.flushP

endingCommands();

}

nativePollOnce(pt

r, nextPollTimeoutMillis);

synchronized (thi

s) {

/

/ Try to ret

rieve the next message. Retu

rn if found.

final long no

w = SystemClock.uptimeMillis(

)

;

Message prevM

Message msg =

sg = null;

mMessages;

if (msg != nu

ll && msg.target == null) {

/

/ Stalle

d by a barrier. Find the nex

t asynchronous message in the

queue.

do {

prevM

sg = msg;

msg =

while (

msg.next;

}

msg != null && !msg.isAsynchr

onous());

}

if (msg != nu

ll) {

if (now <

msg.when) {

/

/ Ne

xt message is not ready. Set

a timeout to wake up when it

is ready.

nextP

ollTimeoutMillis = (int) Math

.

min(msg.when - now, Integer.

MAX_VALUE);

}

else {

/

/ Go

t a message.

mBloc

if (p

p

ked = false;

revMsg != null) {

revMsg.next = msg.next;

e {

}

els

m

Messages = msg.next;

}

msg.n

ext = null;

if (f

alse) Log.v("MessageQueue", "

Returning message: " + msg);

retur

n msg;

}

else {

}

/

/ No mor

e messages.

nextPollT

imeoutMillis = -1;

}

/

/ Process th

e quit message now that all p

ending messages have been han

dled.

if (mQuitting

)

;

{

dispose()

return nu

ll;

}

/

/ If first t

ime idle, then get the number

of idlers to run.

/

/ Idle handl

es only run if the queue is e

mpty or if the first message

/

/ in the que

ue (possibly a barrier) is du

e to be handled in the future

.

if (pendingId

leHandlerCount < 0

&

& (m

Messages == null || now < mMe

ssages.when)) {

pendingId

leHandlerCount = mIdleHandler

s.size();

}

if (pendingId

leHandlerCount <= 0) {

/ No idl

/

e handlers to run. Loop and

wait some more.

mBlocked

=

true;

continue;

}

if (mPendingI

dleHandlers == null) {

mPendingI

dleHandlers = new IdleHandler

Math.max(pendingIdleHandlerC

ount, 4)];

[

}

mPendingIdleH

andlers = mIdleHandlers.toArr

ay(mPendingIdleHandlers);

}

/

/

/ Run the idle h

/ We only ever r

andlers.

each this code block during t

he first iteration.

for (int i = 0; i

<

pendingIdleHandlerCount; i

+

+) {

final IdleHan

dler idler = mPendingIdleHand

lers[i];

mPendingIdleH

andlers[i] = null; // release

the reference to the handler

boolean keep

=

false;

try {

keep = id

ler.queueIdle();

wable t) {

}

catch (Thro

Log.wtf("

MessageQueue", "IdleHandler t

hrew exception", t);

}

if (!keep) {

synchroni

zed (this) {

mIdle

Handlers.remove(idler);

}

}

}

/

/ Reset the idle

handler count to 0 so we do

not run them again.

pendingIdleHandle

rCount = 0;

/

/ While calling

an idle handler, a new messag

e could have been delivered

/ so go back and

/

look again for a pending mes

sage without waiting.

nextPollTimeoutMi

llis = 0;

}

}

可以看出来,这个next方法

就是消息队列的出队方法

(与上面分析的

MessageQueue消息队列的

enqueueMessage方法对

比)。可以看见上面代码就

是如果当前MessageQueue中

存在待处理的消息

mMessages就将这个消息出

队,然后让下一条消息成为

mMessages,否则就进入一

个阻塞状态(在

上面Looper类的loop方法上

面也有英文注释,明确说到

了阻塞特性),一直等到有

新的消息入队。

继续看loop()方法的第30行

(msg.target.dispatchMessage

(msg);),每当有一个消息

出队就将它传递到msg.target

的dispatchMessage()方法

中。其中这个msg.target其实

就是上面分析Handler发送消

息代码部分Handler的

enqueueMessage方法中的

msg.target = this;语句,也就

是当前Handler对象。所以接

下来的重点自然就是回到

Handler类看看我们熟悉的

dispatchMessage()方法,如

下:

/

**

*

here.

*

Handle system messages

/

public void dispatchMessa

ge(Message msg) {

if (msg.callback != n

ull) {

handleCallback(ms

g);

}

else {

if (mCallback !=

null) {

if (mCallback

handleMessage(msg)) {

return;

.

)

}

}

handleMessage(msg

;

}

}

可以看见dispatchMessage方

法中的逻辑比较简单,具体

就是如果mCallback不为空,

则调用mCallback 的

handleMessage()方法,否则

直接调用

Handler的handleMessage()方

法,并将消息对象作为参数

传递过去。

这样我相信大家就都明白了

为什么handleMessage()方法

中可以获取到之前发送的消

息了吧!

对了,既然上面说了获取消

息在MessageQueue消息队列

中是一个死循环的阻塞等

待,所以Looper的quit方法也

很重要,这样在不需要时可

以退出这个死循环,如上面

实例部分使用所示。

3-2-3 结束MessageQueue消

息队列阻塞死循环源码分析

如下展示了Looper类的quit方

法源码:

public void quit() {

mQueue.quit(false);

}

看见没有,quit方法实质就

是调运了MessageQueue消息

队列的quit,如下:

void quit(boolean safe) {

if (!mQuitAllowed) {

throw new Illegal

StateException("Main thread n

ot allowed to quit.");

}

synchronized (this) {

if (mQuitting) {

return;

}

mQuitting = true;

if (safe) {

removeAllFutu

reMessagesLocked();

else {

removeAllMess

}

agesLocked();

}

/

/ We can assume

mPtr != 0 because mQuitting w

as previously false.

nativeWake(mPtr);

}

}

看见上面2到4行代码没有,

通过判断标记mQuitAllowed

来决定该消息队列是否可以

退出,然而当mQuitAllowed

为fasle时抛出的异常竟然是”

Ma in thread not allowed to

quit.”,Ma in Thread,所以

可以说明Ma in Thread关联的

Looper一一对应的

MessageQueue消息队列是不

能通过该方法退出的。

你可能会疑惑这个

mQuitAllowed在哪设置的?

其实他是MessageQueue构造

函数传递参数传入的,而

MessageQueue对象的实例化

是在Looper的构造函数实现

的,所以不难发现

mQuitAllowed参数实质是从

Looper的构函数传入的。上

面实例化Handler模块源码分

析时说过,Looper实例化是

在Looper的静态方法

prepare(boolean quitAllowed)

中处理的,也就是说

mQuitAllowed是由

Looper.prpeare(boolean

quitAllowed)参数传入的。追

根到底说明mQuitAllowed就

是Looper.prpeare的参数,我

们默认调运的

Looper.prpeare();其中对

mQuitAllowed设置为了true,

所以可以通过quit方法退

出,而主线程Ac tivityThre a d

的main中使用的是

Looper.prepareMainLooper();

,这个方法里对

mQuitAllowed设置为false,

所以才会有上面说的”Main

thread not allowed to quit.”。

回到quit方法继续看,可以

发现实质就是对mQuitting标

记置位,这个mQuitting标记

在MessageQueue的阻塞等待

next方法中用做了判断条

件,所以可以通过quit方法

退出整个当前线程的loop循

环。

到此整个Android的一次完整

异步消息机制分析使用流程

结束。接下来进行一些总结

提升与拓展。

-3 简单小结下Handler整个

3

使用过程与原理

通过上面的源码分析原理我

们可以总结出整个异步消息

处理流程的关系图如下:

这幅图很明显的表达出了

Handler异步机制的来龙去

脉,不做过多解释。

上面实例部分我们只是演示

了Handler的局部方法,具体

Handler还有很多方法,下面

详细介绍。

-4 再来看看Handler源码的

3

其他常用方法

在上面例子中我们只是演示

了发送消息的

sendEmptyMessage(int what)

方法,其实Handler有如下一

些发送方式:

sendMessage(Message msg);

sendEmptyMessage(int what

sendEmptyMessageDelayed(i

sendMessageDelayed(Messag

sendMessageAtFrontOfQueue

方法。

这些方法不再做过多解释,

用法雷同,顶一个Message

决定啥时发送到target去。

post(Runnable r);

postDelayed(Runnable r, l

等post系列方法。

该方法实质源码其实就是如

下:

public final boolean postDela

yed(Runnable r, long delayMil

lis)

{

return sendMessageDel

ayed(getPostMessage(r), delay

Millis);

}

额,原来post方法的实质也

是调运sendMessageDelayed()

方法去处理的额,看见

getPostMessage(r)方法没?

如下源码:

private static Message getPos

tMessage(Runnable r) {

Message m = Message.o

btain();

m.callback = r;

return m;

}

如上方法仅仅是将消息的

callback字段指定为传入的

Runnable对象r。其实这个

Message对象的m.callback就

是上面分析Handler接收消息

回调处理

dispatchMessage()方法中调

运的。在Handler的

dispatchMessage方法中首先

判断如果Message的callback

等于null才会去调用

handleMessage()方法

,否则就调用

handleCallback()方法。那就

再看下Handler 的

handleCallback()方法源码,

如下:

private static void handleCal

lback(Message message) {

message.callback.run(

)

;

}

额,这里竟然直接执行了

Runnable对象的run()方法。

所以说我们在Runnable对象

的run()方法里更新UI的效果

完全和在handleMessage()方

法中更新UI

相同,特别强调这个

Runnable的run方法还在当前

线程中阻塞执行,没有创建

新的线程(很多人以为是

Runnable就创建了新线

程)。

Activity.runOnUiThread(Ru

方法。

首先看下Ac tivity的

runOnUiThread方法源码:

public final void runOnUiThre

ad(Runnable action) {

if (Thread.currentThr

ead() != mUiThread) {

mHandler.post(act

ion);

}

}

else {

action.run();

}

看见没有,实质还是在UI线

程中执行了Runnable的run方

法。不做过多解释。

View.post(Runnable);和Vie

方法。

首先看下Vie w的postDelayed

方法源码:

public boolean postDelayed(R

unnable action, long delayMil

lis) {

final AttachInfo atta

chInfo = mAttachInfo;

if (attachInfo != nul

l) {

return attachInfo

.

mHandler.postDelayed(action,

delayMillis);

}

/

/ Assume that post w

ill succeed later

ViewRootImpl.getRunQu

eue().postDelayed(action, del

ayMillis);

return true;

}

看见没有,实质还是在UI线

程中执行了Runnable的run方

法。不做过多解释。

到此基本上关于Handler的所

有发送消息方式都被解释明

白了。既然会用了基本的那

就得提高下,接下来看看关

于Message的一点优化技

巧。

3-5 关于Handler发送消息的

一点优化分析

还记得我们说过,当发送一

个消息时我们首先会new一

个Message对象,然后再发

送吗?你是不是觉得每次

new Message很浪费呢?那

么我们就来分析一下这个问

题。

如下是我们正常发送消息的

代码局部片段:

Message message = new Message

();

message.arg1 = 110;

message.arg2 = 119;

message.what = 0x120;

message.obj = "Test Messa

ge Content!";

mHandler.sendMessage(mess

age);

相信很多初学者都是这么发

送消息的吧?当有大量多次

发送时如上写法会不太高

效。不卖关子,先直接看达

到同样效果的优化写法,如

下:

mHandler.sendMessage(mHandler

.

9

"

obtainMessage(0x120, 110, 11

, "\"Test Message Content!\"

));

咦?怎么send时没实例化

Message?这是咋回事?我

们看下

mHandler.obtainMessage(0x

这一段代码,

obtainMessage是Handler提供

的一个方法,看下源码:

public final Message obtainMe

ssage(int what, int arg1, int

arg2, Object obj)

{

return Message.obtain

(this, what, arg1, arg2, obj)

;

}

这方法竟然直接调运了

Message类的静态方法

obtain,我们再去看看obtain

的源码,如下:

public static Message obtain(

Handler h, int what,

int arg1, int arg

, Object obj) {

2

Message m = obtain();

m.target = h;

m.what = what;

m.arg1 = arg1;

m.arg2 = arg2;

m.obj = obj;

return m;

}

看见没有?首先又调运一个

无参的obtain方法,然后设

置Message各种参数后返

回。我们继续看下这个无参

方法,如下:

/

**

*

Return a new Message i

nstance from the global pool.

Allows us to

*

avoid allocating new o

bjects in many cases.

*

/

public static Message obt

ain() {

synchronized (sPoolSy

if (sPool != null

Message m = s

nc) {

)

{

Pool;

t;

sPool = m.nex

m.next = null

;

m.flags = 0;

/

/ clear in-use flag

sPoolSize--;

return m;

}

}

return new Message();

}

真相大白了!看见注释没

有?从整个Messge池中返回

一个新的Message实例,在

许多情况下使用它,因为它

能避免分配新的对象。

所以看见这两种获取

Message写法的优缺点没有

呢?明显可以看见通过调用

Handler的obtainMessage方法

获取Message对象就能避免

创建对象,从而减少内存的

开销了。所以推荐这种写

法!!!

3-6 关于Handler导致内存泄

露的分析与解决方法

正如上面我们实例部分的代

码,使用Android Lint会提示

我们这样一个Wa rning,如

下:

In Android, Handler classes s

hould be static or leaks migh

t occur.

意思是说在Android中

Handler类应该是静态的否则

可能发生泄漏。

啥是内存泄漏呢?

Java通过GC自动检查内存中

的对象,如果GC发现一个或

一组对象为不可到达状态,

则将该对象从内存中回收。

也就是说,一个对象不被任

何引用所指向,则该对象会

在被GC发现的时候被回收;

另外,如果一组对象中只包

含互相的引用,而没有来自

它们外部的引用(例如有两

个对象A和B互相持有引用,

但没有任何外部对象持有指

向A或B的引用),这仍然属

于不可到达,同样会被GC回

收。本该被回收的对象没被

回收就是内存泄漏。

Handler中怎么泄漏的呢?

当使用内部类(包括匿名

类)来创建Handler的时候,

Handler对象会隐式地持有一

个外部类对象(通常是一个

Ac tivity)的引用。而Handler

通常会伴随着一个耗时的后

台线程一起出现,这个后台

线程在任务执行完毕之后,

通过消息机制通知Handler,

然后Handler把消息发送到UI

线程。然而,如果用户在耗

时线程执行过程中关闭了

Ac tivity(正常情况下Ac tivity

不再被使用,它就有可能在

GC检查时被回收掉),由于

这时线程尚未执行完,而该

线程持有Handler的引用,这

个Handler又持有Ac tivity的引

用,就导致该Ac tivity暂时无

法被回收(即内存泄露)。

Handler内存泄漏解决方案

呢?

方案一:通过程序逻辑来进

行保护

1. 在关闭Ac tivity的时候停掉

你的后台线程。线程停掉

了,就相当于切断了

Handler和外部连接的线,

Ac tivity自然会在合适的时

候被回收。

2

. 如果你的Handler是被

delay的Message持有了引

用,那么使用相应的

Handler的

removeCallbacks()方法,

把消息对象从消息队列移

除就行了(如上面的例子

部分的onStop中代码)。

方案二:将Handler声明为静

态类

静态类不持有外部类的对

象,所以你的Ac tivity可以随

意被回收。代码如下:

static class TestHandler exte

nds Handler {

@

Override

public void handleMessage

(Message msg) {

mImageView.setImageBi

tmap(mBitmap);

}

}

这时你会发现,由于Handler

不再持有外部类对象的引

用,导致程序不允许你在

Handler中操作Ac tivity中的对

象了。所以你需要在Handler

中增加一个

对Ac tivity的弱引用

(WeakReference),如下:

static class TestHandler exte

nds Handler {

WeakReference<Activity >

mActivityReference;

TestHandler(Activity acti

vity) {

mActivityReference= n

ew WeakReference<Activity>(ac

tivity);

}

@

Override

public void handleMessage

(Message msg) {

final Activity activi

ty = mActivityReference.get()

;

if (activity != null)

{

mImageView.setIma

geBitmap(mBitmap);

}

}

}

如上就是关于Handler内存泄

漏的分析及解决方案。

可能在你会用了Handler之后

见过HandlerThread这个关键

字,那我们接下来就看看

HandlerThread吧。

4

关于Android异步消

息处理机制进阶的

HandlerThread源码分

4

-1 Android 5.1.1(API 22)

HandlerThread源码

很多人在会使用Handler以后

会发现有些代码里出现了

HandlerThread,然后就分不

清HandlerThread与Handler啥

关系,咋回事之类的。这里

就来分析分析HandlerThread

的源码。如下:

public class HandlerThread ex

tends Thread {

/

/线程的优先级

int mPriority;

/线程的id

int mTid = -1;

/一个与Handler关联的Looper

/

/

对象

Looper mLooper;

public HandlerThread(Stri

ng name) {

super(name);

/设置优先级为默认线程

/

mPriority = android.o

s.Process.THREAD_PRIORITY_DEF

AULT;

}

public HandlerThread(Stri

ng name, int priority) {

super(name);

mPriority = priority;

}

/

/可重写方法,Looper.loop之

前在线程中需要处理的其他逻辑在这里实

protected void onLooperPr

epared() {

}

/

/HandlerThread线程的run方

@

Override

public void run() {

/获取当前线程的id

mTid = Process.myTid(

/

)

;

/

/

/创建Looper对象

/这就是为什么我们要在调

用线程的start()方法后才能得到Loop

er(Looper.myLooper不为Null)

Looper.prepare();

/

/同步代码块,当获得mLoo

per对象后,唤醒所有线程

synchronized (this) {

mLooper = Looper.

myLooper();

notifyAll();

}

/

/设置线程优先级

Process.setThreadPrio

rity(mPriority);

/Looper.loop之前在线程

/

中需要处理的其他逻辑

onLooperPrepared();

/建立了消息循环

Looper.loop();

/

/

/一般执行不到这句,除非q

uit消息队列

mTid = -1;

}

public Looper getLooper()

{

if (!isAlive()) {

/

/线程死了

return null;

}

/

/同步代码块,正好和上面r

un方法中同步块对应

/

/只要线程活着并且mLoope

r为null,则一直等待

/

/ If the thread has

been started, wait until the

looper has been created.

synchronized (this) {

while (isAlive()

&

& mLooper == null) {

try {

wait();

catch (Inte

}

rruptedException e) {

}

}

}

return mLooper;

}

public boolean quit() {

Looper looper = getLo

oper();

if (looper != null) {

/

/退出消息循环

looper.quit();

return true;

}

return false;

}

public boolean quitSafely

Looper looper = getLo

() {

oper();

if (looper != null) {

/退出消息循环

/

looper.quitSafely

();

return true;

}

return false;

}

public int getThreadId()

{

}

/

/返回线程id

return mTid;

}

看见没有,这就是

HandlerThread的系统源码,

整个HandlerThread类很简

单。如上对重点都进行了注

释。

现在可以很负责的告诉你

Handler到底与HandlerThread

啥关系,其实HandlerThread

就是Thread、Looper和

Handler的组合实现,

Android系统这么封装体现了

Android系统组件的思想,同

时也方便了开发者开发。

上面源码可以看到,

HandlerThread主要是对

Looper进行初始化,并提供

一个Looper对象给新创建的

Handler对象,使得Handler

处理消息事件在子线程中处

理。这样就发挥了Handler的

优势,同时又可以很好的和

线程结合到一起。

到此HandlerThread源码原理

也分析完了,那么就差实战

了,如下继续。

-2 Android HandlerThread实

4

上面分析了关于

HandlerThread源码,下面就

来演示一个实例,如下:

public class ListenerActivity

extends Activity {

private HandlerThread mHa

ndlerThread = null;

private Handler mThreadHa

ndler = null;

private Handler mUiHandle

r = null;

@

Override

public void onCreate(Bund

le savedInstanceState) {

super.onCreate(savedI

nstanceState);

setContentView(R.layo

ut.main);

initData();

}

private void initData() {

Log.i(null, "Main Thr

ead id="+Thread.currentThread

().getId());

mHandlerThread = new

HandlerThread("HandlerWorkThr

ead");

/

/必须在实例化mThreadHa

ndler之前调运start方法,原因上面

源码已经分析了

mHandlerThread.start(

)

;

/

/将当前mHandlerThread

子线程的Looper传入mThreadHandle

r,使得

/

/mThreadHandler的消息

队列依赖于子线程(在子线程中执行)

mThreadHandler = new

Handler(mHandlerThread.getLoo

per()) {

@

Override

public void handl

eMessage(Message msg) {

super.handleM

essage(msg);

Log.i(null, "

在子线程中处理!id="+Thread.curr

entThread().getId());

/

/从子线程往主

线程发送消息

mUiHandler.se

ndEmptyMessage(0);

}

}

;

mUiHandler = new Hand

ler() {

@

Override

public void handl

eMessage(Message msg) {

super.handleM

essage(msg);

Log.i(null, "

在UI主线程中处理!id="+Thread.cu

rrentThread().getId());

}

}

/

;

/从主线程往子线程发送消

mThreadHandler.sendEm

ptyMessage(1);

}

}

运行结果如下:

Main Thread id=1

在子线程中处理!id=113

在UI主线程中处理!id=1

好了,不做过多解释了,很

简单的。

5

关于Android异步消

息处理机制总结

到此整个Android的异步处理

机制Handler与HandlerThread

等分析完成(关于Android的

另一种异步处理机制

AsyncTask后面有时间再分

析)。相信通过这一篇文章

你对Android的Handler使用

还是原理都会有一个质的飞

跃。

【工匠若

水 http://blog.csdn.net/yanbob

er 转载烦请注明出处,尊重

分享成果】

工匠若

水 http://blog.csdn.net/yanbob

er 转载烦请注明出处,尊重

劳动成果】

1

背景

之所以写这一篇博客的原因

是因为之前有写过一篇

Android应用

setContentView与

LayoutInflater加载解析机制

源码分析》,然后有人在文

章下面评论和微博私信中问

我关于Android应用Ac tivity、

Dia log、PopWindow加载显

示机制是咋回事,所以我就

写一篇文章来分析分析吧

2

本文以Android5.1.1 (API

2)源码为基础分析),以

便大家在应用层开发时不再

迷糊。

PS一句:不仅有人微博私信

我这个问题,还有人问博客

插图这些是用啥画的,这里

告诉大家。就是我,快来猛

戳我

还记得之前《Android应用

setContentView与

LayoutInflater加载解析机制

源码分析》这篇文章的最后

分析结果吗?就是如下这幅

图:

在那篇文章里我们当时重点

是Ac tivity的Vie w加载解析

xml机制分析,当时说到了

Window的东西,但只是皮毛

的分析了Ac tivity相关的一些

逻辑。(PS:看到这不清楚

上面啥意思的建议先移步到

Android应用

setContentView与

LayoutInflater加载解析机制

源码分析》,完事再回头继

续看这篇文章。)当时给大

家承诺过我们要从应用控件

点一点往下慢慢深入分

析,所以现在开始深入,但

是本篇的深入也只是仅限

Window相关的东东,之后文

章还会继续慢慢深入。

【工匠若

水 http://blog.csdn.net/yanbob

er 转载烦请注明出处,尊重

劳动成果】

2

浅析Window 与

WindowManager相关

关系及源码

通过上面那幅图可以很直观

的看见,Android屏幕显示的

就是Window和各种Vie w,

Ac tivity在其中的作用主要是

管理生命周期、建立窗口

等。也就是说Window相关的

东西对于Android屏幕来说是

至关重要的(虽然前面分析

Ac tivity的setContentView等原

理时说过一点Window,但那

只是皮毛。),所以有必要

在分析Android应用Ac tivity、

Dia log、PopWindow加载显

示机制前再看看Window相关

的一些东西。

2

-1 Window 与

WindowManager基础关系

在分析Window 与

WindowManager之前我们先

看一张图:

接下来看一点代码,如下:

/

** Interface to let you add

and remove child views to an

Activity. To get an instance

*

of this class, call {@lin

k android.content.Context#get

SystemService(java.lang.Strin

g) Context.getSystemService()

}

.

*

/

public interface ViewManager

{

public void addView(View

view, ViewGroup.LayoutParams

params);

public void updateViewLay

out(View view, ViewGroup.Layo

utParams params);

public void removeView(Vi

ew view);

}

可以看见,ViewManager接

口定义了一组规则,也就是

add、update、remove的操作

Vie w接口。也就是说

ViewManager是用来添加和

移除a c tivity

中Vie w的接口。继续往下

看:

public interface WindowManage

r extends ViewManager {

.

.....

public Display getDefault

Display();

public void removeViewImm

ediate(View view);

.

.....

public static class Layou

tParams extends ViewGroup.Lay

outParams

implements Parcel

able {

.

.....

}

}

看见没有,WindowManager

继承自ViewManager,然后

自己还是一个接口,同时又

定义了一个静态内部类

LayoutParams(这个类比较

重要,后面

会分析。提前透漏下,如果

你在APP做过类似360助手屏

幕的那个悬浮窗或者做过那

种类似IOS的小白圆点,点

击展开菜单功能,你或多或

少就能猜到

这个类的重要性。)。

WindowManager用来在应用

与Window之间的接口、窗口

顺序、消息等的管理。继续

看下ViewManager的另一个

实现子类

Vie wGroup,如下:

public abstract class ViewGro

up extends View implements Vi

ewParent, ViewManager {

/

/protected ViewParent mP

arent;

/

/这个成员是View定义的,View

Group继承自View,所以也可以拥有。

/

/这个变量就是前面我们一系列文

章分析View向上传递的父节点,类似于

一个链表Node的next一样

/

.

/最终指向了ViewRoot

.....

public void addView(View

child, LayoutParams params) {

addView(child, -1, pa

rams);

}

.

.....

public void addView(View

child, int index, LayoutParam

s params) {

.

/

.....

/ addViewInner() wil

l call child.requestLayout()

when setting the new LayoutPa

rams

/

/ therefore, we call

requestLayout() on ourselves

before, so that the child's

request

/

/ will be blocked at

our level

requestLayout();

invalidate(true);

addViewInner(child, i

ndex, params, false);

}

.

.....

}

这下理解上面那幅图了吧,

所以说Vie w通过Vie wGroup

的addView方法添加到

Vie wGroup中,而Vie wGroup

层层嵌套到最顶级都会显示

在在一个窗

口Window中(正如上面背景

介绍中《Android应用

setContentView与

LayoutInflater加载解析机制

源码分析》的示意图一

样),其中每个Vie w都有

一个ViewParent类型的父节

点mParent,最顶上的节点也

是一个vie wGroup,也即前面

文章分析的Window的内部类

DecorView(从《Android应

用setContentView与

LayoutInflater加载解析机制

源码分析》的总结部分或者

Android应用层Vie w绘制流

程与源码分析》的5-1小节都

可以验证

这个结论)对象。同时通过

上面背景中那幅图可以看出

来,对于一个Ac tivity只有一

DecorView(Vie wRoot),

也只有一个Window。

2-2 Ac tivity窗口添加流程拓

前面文章说过,

Ac tivityThre a d类的

performLaunchActivity方法中

调运了activity.attach(…)方法

进行初始化。如下是Ac tivity

的attach方法源码:

final void attach(Context con

text, ActivityThread aThread,

Instrumentation i

nstr, IBinder token, int iden

t,

Application appli

cation, Intent intent, Activi

tyInfo info,

CharSequence titl

e, Activity parent, String id

,

NonConfigurationI

nstances lastNonConfiguration

Instances,

Configuration con

fig, String referrer, IVoiceI

nteractor voiceInteractor) {

.

/

.....

/创建Window类型的mWind

ow对象,实际为PhoneWindow类实现了

抽象Window 类

mWindow = PolicyManag

er.makeNewWindow(this);

.

/

.....

/通过抽象Window类的set

WindowManager方法给Window类的成

员变量WindowManager赋值实例化

mWindow.setWindowMana

ger(

(WindowManage

r)context.getSystemService(Co

ntext.WINDOW_SERVICE),

mToken, mComp

onent.flattenToString(),

(info.flags &

ActivityInfo.FLAG_HARDWARE_A

CCELERATED) != 0);

.

.....

/

/把抽象Window类相关的W

indowManager对象拿出来关联到Acti

vity的WindowManager类型成员变量

mWindowManager

mWindowManager = mWin

dow.getWindowManager();

.

.....

}

看见没有,Ac tivity类中的

attach方法又创建了Window

类型的新成员变量

mWindow(PhoneWindow实

现类)与Ac tivity相关联,接

着在Ac tivity 类

的attach方法最后又通过

mWindow. se tWindowMa na ge r

(…)方法创建了与Window相

关联的WindowManager 对

象,最后又通过

mWindow. ge tWindowMa na ge r

()将Window 的

WindowManager成员变量赋

值给Ac tivity 的

WindowManager成员变量

mWindowManager。

接下来我们看下上面代码中

mWindow. se tWindowMa na ge r

(…)方法源码

PhoneWindow没有重写抽

象Window的

setWindowManager方法,所

以直接看Window类的该方法

源码),如下:

public void setWindowManager(

WindowManager wm, IBinder app

Token, String appName,

boolean hardwareA

ccelerated) {

.

.....

if (wm == null) {

wm = (WindowManag

er)mContext.getSystemService(

Context.WINDOW_SERVICE);

}

/

/实例化Window类的Windo

wManager类型成员mWindowManager

mWindowManager = ((Wi

ndowManagerImpl)wm).createLoc

alWindowManager(this);

}

可以看见,Window的

setWindowManager方法中通

过WindowManagerImpl实例

createLocalWindowManager

方法获取了WindowManager

例,如下:

public final class WindowMana

gerImpl implements WindowMana

ger {

.

.....

private WindowManagerImpl

(Display display, Window pare

ntWindow) {

mDisplay = display;

mParentWindow = paren

tWindow;

}

.

.....

public WindowManagerImpl

createLocalWindowManager(Wind

ow parentWindow) {

return new WindowMana

gerImpl(mDisplay, parentWindo

w);

}

.

.....

}

看见没有?这样就把Ac tivity

的Window与WindowManager

关联起来了。Ac tivity类的

Window类型成员变量

mWindow及WindowManager

类型成

员变量mWindowManager 就

是这么来的。

回过头继续看上面刚刚贴的

Ac tivity的attach方法代码,

看见

mWindow. se tWindowMa na ge r

方法传递的第一个参数没?

有人会想

(WindowManager)context.get

SystemService(Context.WIND

OW_SERVICE)这行代码是

什么意思,现在告诉你。

《Android应用Context详解及

源码解析》一文中第三部分

曾经说过Ac tivityThre a d中创

建了Ac itivty(执行attach等

方法)等东东,在创建这个

Ac tivity之前得到了Context的

实例。记不记得当时说

Context的实现类就是

ContextImpl吗?下面我们看

下ContextImpl类的静态方法

块,如下:

class ContextImpl extends Con

text {

.

/

.....

/静态代码块,类加载时执行一次

static {

.

/

.....

/这里有一堆类似的XXX_SE

RVICE的注册

.

.....

registerService(WINDO

W_SERVICE, new ServiceFetcher

() {

Display mDefa

public Object

ultDisplay;

getService(ContextImpl ctx)

{

/

/搞一个Di

splay实例

Display d

isplay = ctx.mDisplay;

if (displ

ay == null) {

if (m

DefaultDisplay == null) {

D

isplayManager dm = (DisplayMa

nager)ctx.getOuterContext().

getSystemService(Conte

xt.DISPLAY_SERVICE);

m

DefaultDisplay = dm.getDispla

y(Display.DEFAULT_DISPLAY);

}

displ

ay = mDefaultDisplay;

}

/

/返回一个

WindowManagerImpl实例

return ne

w WindowManagerImpl(display);

}

});

.

.....

}

/

/这就是你在外面调运Context的

getSystemService获取到的Window

ManagerImpl实例

@

Override

public Object getSystemSe

rvice(String name) {

ServiceFetcher fetche

r = SYSTEM_SERVICE_MAP.get(na

me);

return fetcher == nul

l ? null : fetcher.getService

(this);

}

/

/上面static代码块创建Windo

wManagerImpl实例用到的方法

private static void regis

terService(String serviceName

,

ServiceFetcher fetcher) {

if (!(fetcher instanc

eof StaticServiceFetcher)) {

fetcher.mContextC

acheIndex = sNextPerContextSe

rviceCacheIndex++;

}

SYSTEM_SERVICE_MAP.pu

t(serviceName, fetcher);

}

}

看见没有,我们都知道Java

的静态代码块是类加载是执

行一次的,也就相当于一个

全局的,这样就相当于每个

Application只有一个

WindowManagerImpl(display)

实例。

还记不记得《Android应用

setContentView与

LayoutInflater加载解析机制

源码分析》一文2-6小节中说

的,setContentView的实质显

示是触发了Ac tivity的resume

状态,也就是触发了

ma ke Visible方法,那我们再

来看下这个方法,如下:

void makeVisible() {

if (!mWindowAdded) {

/

/也就是获取Activit

y的mWindowManager

/

/这个mWindowMana

ger是在Activity的attach中通过mW

indow.getWindowManager()获得

ViewManager wm =

getWindowManager();

/

/调运的实质就是Vie

wManager接口的addView方法,传入

的是mDecorView

wm.addView(mDecor

,

)

getWindow().getAttributes()

;

mWindowAdded = tr

ue;

}

mDecor.setVisibility(

View.VISIBLE);

}

特别注意,看见ma ke Visible

方法的wm变量没,这个变

量就是Window类中通过调运

WindowManagerImpl的

createLocalWindowManager

创建的

实例,也就是说每一个

Ac tivity都会新创建这么一个

WindowManager实例来显示

Ac tivity的界面的,有点和上

面分析的ContextImpl中static

块创建

的WindowManager不太一样

的地方就在于Context的

WindowManager对每个APP

来说是一个全局单例的,而

Ac tivity的WindowManager是

每个Ac tivity都会新创建一个

的(其实你从上面分析的两

个实例化

WindowManagerImpl的构造

函数参数传递就可以看出

来,Ac tivity中Window

的WindowManager成员在构

造实例化时传入给

WindowManagerImpl中

mParentWindow成员的是当

前Window对象,而

ContextImpl的static块

中单例实例化

WindowManagerImpl时传入

给WindowManagerImpl中

mParentWindow成员的是null

值)。

继续看ma ke Visible中调运的

WindowManagerImpl的

addView方法如下:

public final class WindowMana

gerImpl implements WindowMana

ger {

/

/继承自Object的单例类

private final WindowManag

erGlobal mGlobal = WindowMana

gerGlobal.getInstance();

.

.....

public void addView(@NonN

ull View view, @NonNull ViewG

roup.LayoutParams params) {

applyDefaultToken(par

ams);

/

/mParentWindow是上面

分析的在Activity中获取WindowMan

agerImpl实例化时传入的当前Window

/

/view是Activity中最顶

层的mDecor

mGlobal.addView(view,

params, mDisplay, mParentWin

dow);

}

.

.....

}

这里当前传入的vie w是

mDecor,LayoutParams呢?

可以看见是

getWindow().getAttributes(),

那我们进去看看Window类的

这个属性,如下:

/

/ The current window attribu

tes.

private final WindowManag

er.LayoutParams mWindowAttrib

utes = new WindowManager.Layo

utParams();

原来是WindowManager的静

态内部类LayoutParams的默

认构造函数:

public LayoutParams() {

super(LayoutParams.MATCH_

PARENT, LayoutParams.MATCH_PA

RENT);

type = TYPE_APPLICATION;

format = PixelFormat.OPAQ

UE;

}

看见没有,Ac tivity窗体的

WindowManager.LayoutPara

ms类型是

TYPE_APPLICATION的。

继续回到

WindowManagerImpl的

addView方法,分析可以看

见WindowManagerImpl中有

一个单例模式的

WindowMa na ge rGloba l成员

mGloba l,addView最终调运

了WindowMa na ge rGloba l的

addView,源码如下:

public final class WindowMana

gerGlobal {

.

.....

private final ArrayList<V

iew> mViews = new ArrayList<V

iew>();

private final ArrayList<V

iewRootImpl> mRoots = new Arr

ayList<ViewRootImpl>();

private final ArrayList<W

indowManager.LayoutParams> mP

arams =

new ArrayList<Win

dowManager.LayoutParams>();

private final ArraySet<Vi

ew> mDyingViews = new ArraySe

t<View>();

.

.....

public void addView(View

view, ViewGroup.LayoutParams

params,

Display display,

Window parentWindow) {

.

/

.....

/获取Activity的Window

的getWindow().getAttributes()

的LayoutParams

final WindowManager.L

ayoutParams wparams = (Window

Manager.LayoutParams)params;

/

/如果是Activity中调运

的,parentWindow=Window,如果不

是Activity的,譬如是Context的静

态代码块的实例化则parentWindow为n

ull

if (parentWindow != n

ull) {

/

/依据当前Activity

的Window调节sub Window的Layout

Params

parentWindow.adju

stLayoutParamsForSubWindow(wp

arams);

}

else {

.

.....

}

ViewRootImpl root;

.....

synchronized (mLock)

.

{

.

/

.....

/为当前Window创建V

iewRoot

root = new ViewRo

otImpl(view.getContext(), dis

play);

view.setLayoutPar

ams(wparams);

/

/把当前Window相关

的东西存入各自的List中,在remove

中会删掉

mViews.add(view);

mRoots.add(root);

mParams.add(wpara

ms);

}

/

/ do this last becau

se it fires off messages to s

tart doing things

try {

/

/把View和ViewRoo

t关联起来,很重要!!!

root.setView(view

wparams, panelParentView);

catch (RuntimeExcep

,

}

tion e) {

.

.....

}

.....

}

.

}

可以看见,在addView方法

中会利用LayoutParams获得

Window的属性,然后为每个

Window创建ViewRootImpl,

最后通过ViewRootImpl的

setView方法通过mSession向

WindowManagerService发送

添加窗口请求把窗口添加到

WindowManager中,并且由

WindowManager来管理窗口

的vie w、事件、消息收集处

理等(ViewRootImpl的这一

添加过程后面会写文章分

析,这里先记住这个概念即

可)。

至此我们对上面背景中那幅

图,也就是《Android应用

setContentView与

LayoutInflater加载解析机制

源码分析》这篇文章总结部

分的那幅图又进行了更深入

的一点分析,其目的也就是

为了下面分析Android应用

Dia log、PopWindow、Toast

加载显示机制做铺垫准备。

-3 继续顺藤摸瓜

2

WindowManager.LayoutPara

ms类的源码

上面2-1分析Window与

WindowManager基础关系时

提到了WindowManager有一

个静态内部类

LayoutParams,它继承于

ViewGroup.LayoutParams,

用于向WindowManager描述

Window的管理策略。现在我

们来看下这个类(PS:在

AD上也可以看见,自备梯子

点我看AD的),如下:

public static class LayoutPar

ams extends ViewGroup.LayoutP

arams

implements Parcel

able {

/

/窗口的绝对XY位置,需要

考虑gravity属性

public int x;

public int y;

/在横纵方向上为相关的Vie

/

w预留多少扩展像素,如果是0则此view

不能被拉伸,其他情况下扩展像素被wid

get均分

public float horizont

alWeight;

public float vertical

Weight;

/

/

/

/窗口类型

/有3种主要类型如下:

/ApplicationWindows

取值在FIRST_APPLICATION_WINDOW

与LAST_APPLICATION_WINDOW之间

是常用的顶层应用程序窗口,须将tok

en设置成Activity的token;

/

/SubWindows取值在FIRS

T_SUB_WINDOW和LAST_SUB_WINDOW

之间,与顶层窗口相关联,需将token设

置成它所附着宿主窗口的token;

/

/SystemWindows取值在F

IRST_SYSTEM_WINDOW和LAST_SYST

EM_WINDOW之间,不能用于应用程序,

使用时需要有特殊权限,它是特定的系统

功能才能使用;

public int type;

/

/WindowType:开始应用

程序窗口

public static final i

nt FIRST_APPLICATION_WINDOW =

1

;

/

/WindowType:所有程序

窗口的base窗口,其他应用程序窗口都

显示在它上面

public static final i

nt TYPE_BASE_APPLICATION =

1

;

/

/WindowType:普通应用

程序窗口,token必须设置为Activity

的token来指定窗口属于谁

public static final i

nt TYPE_APPLICATION

=

2

;

/

/WindowType:应用程序

启动时所显示的窗口,应用自己不要使用

这种类型,它被系统用来显示一些信息,

直到应用程序可以开启自己的窗口为止

public static final i

nt TYPE_APPLICATION_STARTING

=

3;

/

/WindowType:结束应用

程序窗口

public static final i

nt LAST_APPLICATION_WINDOW =

9;

9

/

/WindowType:SubWind

ows子窗口,子窗口的Z序和坐标空间都

依赖于他们的宿主窗口

public static final i

nt FIRST_SUB_WINDOW

1000;

=

//WindowType: 面板窗口

,显示于宿主窗口的上层

public static final i

nt TYPE_APPLICATION_PANEL =

FIRST_SUB_WINDOW;

/

/WindowType:媒体窗口

(例如视频),显示于宿主窗口下层

public static final i

nt TYPE_APPLICATION_MEDIA =

FIRST_SUB_WINDOW+1;

/

/WindowType:应用程序

窗口的子面板,显示于所有面板窗口的上

public static final i

nt TYPE_APPLICATION_SUB_PANEL

=

FIRST_SUB_WINDOW+2;

/WindowType:对话框,

/

类似于面板窗口,绘制类似于顶层窗口,

而不是宿主的子窗口

public static final i

nt TYPE_APPLICATION_ATTACHED_

DIALOG = FIRST_SUB_WINDOW+3;

/

/WindowType:媒体信息

显示在媒体层和程序窗口之间,需要实

现半透明效果

public static final i

nt TYPE_APPLICATION_MEDIA_OVE

RLAY = FIRST_SUB_WINDOW+4;

/

/WindowType:子窗口结

public static final i

nt LAST_SUB_WINDOW

999;

=

1

/

/WindowType:系统窗口

非应用程序创建

public static final i

nt FIRST_SYSTEM_WINDOW

2000;

=

/

/WindowType:状态栏,

只能有一个状态栏,位于屏幕顶端,其他

窗口都位于它下方

public static final i

nt TYPE_STATUS_BAR

=

FIRST_SYSTEM_WINDOW;

/

/WindowType:搜索栏,

只能有一个搜索栏,位于屏幕上方

public static final i

nt TYPE_SEARCH_BAR

=

FIRST_SYSTEM_WINDOW+1;

/

/WindowType:电话窗口

它用于电话交互(特别是呼入),置于

所有应用程序之上,状态栏之下

public static final i

nt TYPE_PHONE

=

FIRST_SYSTEM_WINDOW+2;

//WindowType:系统提示

出现在应用程序窗口之上

public static final i

nt TYPE_SYSTEM_ALERT

=

FIRST_SYSTEM_WINDOW+3;

/

/WindowType:锁屏窗口

public static final i

nt TYPE_KEYGUARD

FIRST_SYSTEM_WINDOW+4;

/WindowType:信息窗口

用于显示Toast

public static final i

=

/

nt TYPE_TOAST

=

FIRST_SYSTEM_WINDOW+5;

/

/WindowType:系统顶层

窗口,显示在其他一切内容之上,此窗口

不能获得输入焦点,否则影响锁屏

public static final i

nt TYPE_SYSTEM_OVERLAY

FIRST_SYSTEM_WINDOW+6;

=

/

/WindowType:电话优先

当锁屏时显示,此窗口不能获得输入焦

点,否则影响锁屏

public static final i

nt TYPE_PRIORITY_PHONE

FIRST_SYSTEM_WINDOW+7;

=

/

/WindowType:系统对话

public static final i

nt TYPE_SYSTEM_DIALOG

FIRST_SYSTEM_WINDOW+8;

=

/

/WindowType:锁屏时显

示的对话框

public static final i

nt TYPE_KEYGUARD_DIALOG

FIRST_SYSTEM_WINDOW+9;

=

/

/WindowType:系统内部

错误提示,显示于所有内容之上

public static final i

nt TYPE_SYSTEM_ERROR

=

FIRST_SYSTEM_WINDOW+10;

/

/WindowType:内部输入

法窗口,显示于普通UI之上,应用程序

可重新布局以免被此窗口覆盖

public static final i

nt TYPE_INPUT_METHOD

=

FIRST_SYSTEM_WINDOW+11;

/

/WindowType:内部输入

法对话框,显示于当前输入法窗口之上

public static final i

nt TYPE_INPUT_METHOD_DIALOG=

FIRST_SYSTEM_WINDOW+12;

/

/WindowType:墙纸窗口

public static final i

nt TYPE_WALLPAPER

FIRST_SYSTEM_WINDOW+13;

/WindowType:状态栏的

=

/

滑动面板

public static final i

nt TYPE_STATUS_BAR_PANEL =

FIRST_SYSTEM_WINDOW+14;

/

/WindowType:安全系统

覆盖窗口,这些窗户必须不带输入焦点,

否则会干扰键盘

public static final i

nt TYPE_SECURE_SYSTEM_OVERLAY

FIRST_SYSTEM_WINDOW+15;

/WindowType:拖放伪窗

=

/

口,只有一个阻力层(最多),它被放置

在所有其他窗口上面

public static final i

nt TYPE_DRAG

=

FIRST_SYSTEM_WINDOW+16;

/

/WindowType:状态栏下

拉面板

public static final i

nt TYPE_STATUS_BAR_SUB_PANEL

FIRST_SYSTEM_WINDOW+17;

/WindowType:鼠标指针

public static final i

=

/

nt TYPE_POINTER = FIRST_SYSTE

M_WINDOW+18;

/

/WindowType:导航栏(有

别于状态栏时)

public static final i

nt TYPE_NAVIGATION_BAR = FIRS

T_SYSTEM_WINDOW+19;

/

/WindowType:音量级别

的覆盖对话框,显示当用户更改系统音量

大小

public static final i

nt TYPE_VOLUME_OVERLAY = FIRS

T_SYSTEM_WINDOW+20;

/

/WindowType:起机进度

框,在一切之上

public static final i

nt TYPE_BOOT_PROGRESS = FIRST

SYSTEM_WINDOW+21;

/WindowType:假窗,消

费导航栏隐藏时触摸事件

public static final i

_

/

nt TYPE_HIDDEN_NAV_CONSUMER =

FIRST_SYSTEM_WINDOW+22;

/

/WindowType:梦想(屏保

窗口,略高于键盘

public static final i

)

nt TYPE_DREAM = FIRST_SYSTEM_

WINDOW+23;

/

/WindowType:导航栏面

板(不同于状态栏的导航栏)

public static final i

nt TYPE_NAVIGATION_BAR_PANEL

FIRST_SYSTEM_WINDOW+24;

/WindowType:univers

e背后真正的窗户

public static final i

=

/

nt TYPE_UNIVERSE_BACKGROUND =

FIRST_SYSTEM_WINDOW+25;

/

/WindowType:显示窗口

覆盖,用于模拟辅助显示设备

public static final i

nt TYPE_DISPLAY_OVERLAY = FIR

ST_SYSTEM_WINDOW+26;

/

/WindowType:放大窗口

覆盖,用于突出显示的放大部分可访问性

放大时启用

public static final i

nt TYPE_MAGNIFICATION_OVERLAY

=

FIRST_SYSTEM_WINDOW+27;

/WindowType:......

public static final i

/

nt TYPE_KEYGUARD_SCRIM

FIRST_SYSTEM_WINDOW+29;

public static final i

nt TYPE_PRIVATE_PRESENTATION

=

=

FIRST_SYSTEM_WINDOW+30;

public static final i

nt TYPE_VOICE_INTERACTION = F

IRST_SYSTEM_WINDOW+31;

public static final i

nt TYPE_ACCESSIBILITY_OVERLAY

=

FIRST_SYSTEM_WINDOW+32;

/WindowType:系统窗口

/

结束

public static final i

nt LAST_SYSTEM_WINDOW

2999;

=

/

/MemoryType:窗口缓冲

位于主内存

public static final i

nt MEMORY_TYPE_NORMAL = 0;

/MemoryType:窗口缓冲

/

位于可以被DMA访问,或者硬件加速的内

存区域

public static final i

nt MEMORY_TYPE_HARDWARE = 1;

/

/MemoryType:窗口缓冲

位于可被图形加速器访问的区域

public static final i

nt MEMORY_TYPE_GPU = 2;

/

/MemoryType:窗口缓冲

不拥有自己的缓冲区,不能被锁定,缓冲

区由本地方法提供

public static final i

nt MEMORY_TYPE_PUSH_BUFFERS =

3

;

/

/指出窗口所使用的内存缓

冲类型,默认为NORMAL

public int memoryType

;

/

/Flag:当该window对用

户可见的时候,允许锁屏

public static final i

nt FLAG_ALLOW_LOCK_WHILE_SCRE

EN_ON

= 0x00000001;

//Flag:让该window后所

有的东西都成暗淡

public static final i

nt FLAG_DIM_BEHIND

x00000002;

= 0

/

/Flag:让该window后所

有东西都模糊(4.0以上已经放弃这种毛

玻璃效果)

public static final i

nt FLAG_BLUR_BEHIND

x00000004;

/Flag:让window不能获

=

0

/

得焦点,这样用户快就不能向该window

发送按键事

public static final i

nt FLAG_NOT_FOCUSABLE

x00000008;

/Flag:让该window不接

受触摸屏事件

=

0

/

public static final i

nt FLAG_NOT_TOUCHABLE

x00000010;

/Flag:即使在该window

=

0

/

在可获得焦点情况下,依旧把该window

之外的任何event发送到该window之后

的其他window

public static final i

nt FLAG_NOT_TOUCH_MODAL

x00000020;

/Flag:当手机处于睡眠状

=

0

/

态时,如果屏幕被按下,那么该window

将第一个收到

public static final i

nt FLAG_TOUCHABLE_WHEN_WAKING

=

0x00000040;

/Flag:当该window对用

/

户可见时,让设备屏幕处于高亮(brigh

t)状态

public static final i

nt FLAG_KEEP_SCREEN_ON

0x00000080;

=

/

/Flag:让window占满整

个手机屏幕,不留任何边界

public static final i

nt FLAG_LAYOUT_IN_SCREEN =

x00000100;

/Flag:window大小不再

0

/

不受手机屏幕大小限制,即window可能

超出屏幕之外

public static final i

nt FLAG_LAYOUT_NO_LIMITS =

0

x00000200;

/

/Flag:window全屏显示

public static final i

nt FLAG_FULLSCREEN

0000400;

= 0x0

/

/Flag:恢复window非全

屏显示

public static final i

nt FLAG_FORCE_NOT_FULLSCREEN

=

0x00000800;

/Flag:开启抖动(dithe

/

ring )

public static final i

nt FLAG_DITHER

x00001000;

/Flag:当该window在进

行显示的时候,不允许截屏

public static final i

nt FLAG_SECURE

x00002000;

/Flag:一个特殊模式的布

=

0

/

=

0

/

局参数用于执行扩展表面合成时到屏幕上

public static final i

nt FLAG_SCALED

x00004000;

/Flag:用于windows时,

=

0

/

经常会使用屏幕用户持有反对他们的脸,

它将积极过滤事件流,以防止意外按在这

种情况下,可能不需要为特定的窗口,在

检测到这样一个事件流时,应用程序将接

收取消运动事件表明,这样应用程序可以

处理这相应地采取任何行动的事件,直到

手指释放

public static final i

nt FLAG_IGNORE_CHEEK_PRESSES

0x00008000;

/Flag:一个特殊的选项只

用于结合FLAG_LAYOUT_IN_SC

public static final i

nt FLAG_LAYOUT_INSET_DECOR =

x00010000;

/Flag:转化的状态FLAG_

=

/

0

/

NOT_FOCUSABLE对这个窗口当前如何进

行交互的方法

public static final i

nt FLAG_ALT_FOCUSABLE_IM = 0x

0

0020000;

/

/Flag:如果你设置了该fl

ag,那么在你FLAG_NOT_TOUNCH_MOD

AL的情况下,即使触摸屏事件发送在该w

indow之外,其事件被发送到了后面的w

indow,那么该window仍然将以Motio

nEvent.ACTION_OUTSIDE形式收到该

触摸屏事件

public static final i

nt FLAG_WATCH_OUTSIDE_TOUCH =

0

x00040000;

/Flag:当锁屏的时候,显

示该window

public static final i

nt FLAG_SHOW_WHEN_LOCKED = 0x

/

0

0080000;

/

/Flag:在该window后显

示系统的墙纸

public static final i

nt FLAG_SHOW_WALLPAPER = 0x00

00000;

1

/

/Flag:当window被显示

的时候,系统将把它当做一个用户活动事

件,以点亮手机屏幕

public static final i

nt FLAG_TURN_SCREEN_ON = 0x00

2

00000;

/

/Flag:消失键盘

public static final i

nt FLAG_DISMISS_KEYGUARD = 0x

0

0400000;

/

/Flag:当该window在可

以接受触摸屏情况下,让因在该window

之外,而发送到后面的window的触摸屏

可以支持split touch

public static final i

nt FLAG_SPLIT_TOUCH = 0x00800

0

00;

/

/Flag:对该window进行

硬件加速,该flag必须在Activity或D

ialog的Content View之前进行设置

public static final i

nt FLAG_HARDWARE_ACCELERATED

=

0x01000000;

/Flag:让window占满整

个手机屏幕,不留任何边界

public static final i

nt FLAG_LAYOUT_IN_OVERSCAN =

x02000000;

/Flag:请求一个半透明的

/

0

/

状态栏背景以最小的系统提供保护

public static final i

nt FLAG_TRANSLUCENT_STATUS =

0

x04000000;

/

/Flag:请求一个半透明的

导航栏背景以最小的系统提供保护

public static final i

nt FLAG_TRANSLUCENT_NAVIGATIO

N = 0x08000000;

/

/Flag:......

public static final i

nt FLAG_LOCAL_FOCUS_MODE = 0x

1

0000000;

public static final i

nt FLAG_SLIPPERY = 0x20000000

;

public static final i

nt FLAG_LAYOUT_ATTACHED_IN_DE

COR = 0x40000000;

public static final i

nt FLAG_DRAWS_SYSTEM_BAR_BACK

GROUNDS = 0x80000000;

/

/行为选项标记

public int flags;

/

/PrivateFlags:.....

.

public static final i

nt PRIVATE_FLAG_FAKE_HARDWARE

_

ACCELERATED = 0x00000001;

public static final i

nt PRIVATE_FLAG_FORCE_HARDWAR

E_ACCELERATED = 0x00000002;

public static final i

nt PRIVATE_FLAG_WANTS_OFFSET_

NOTIFICATIONS = 0x00000004;

public static final i

nt PRIVATE_FLAG_SHOW_FOR_ALL_

USERS = 0x00000010;

public static final i

nt PRIVATE_FLAG_NO_MOVE_ANIMA

TION = 0x00000040;

public static final i

nt PRIVATE_FLAG_COMPATIBLE_WI

NDOW = 0x00000080;

public static final i

nt PRIVATE_FLAG_SYSTEM_ERROR

=

0x00000100;

public static final i

nt PRIVATE_FLAG_INHERIT_TRANS

LUCENT_DECOR = 0x00000200;

public static final i

nt PRIVATE_FLAG_KEYGUARD = 0x

0

0000400;

public static final i

nt PRIVATE_FLAG_DISABLE_WALLP

APER_TOUCH_EVENTS = 0x0000080

0

;

/

/私有的行为选项标记

public int privateFla

gs;

public static final i

nt NEEDS_MENU_UNSET = 0;

public static final i

nt NEEDS_MENU_SET_TRUE = 1;

public static final i

nt NEEDS_MENU_SET_FALSE = 2;

public int needsMenuK

ey = NEEDS_MENU_UNSET;

public static boolean

mayUseInputMethod(int flags)

{

.

.....

}

/

/SOFT_INPUT:用于描述

软键盘显示规则的bite的mask

public static final i

nt SOFT_INPUT_MASK_STATE = 0x

f;

0

/

/SOFT_INPUT:没有软键

盘显示的约定规则

public static final i

nt SOFT_INPUT_STATE_UNSPECIFI

ED = 0;

/

/SOFT_INPUT:可见性状

态softInputMode,请不要改变软输入

区域的状态

public static final i

nt SOFT_INPUT_STATE_UNCHANGED

=

1;

/

/SOFT_INPUT:用户导航

navigate)到你的窗口时隐藏软键盘

public static final i

nt SOFT_INPUT_STATE_HIDDEN =

2

;

/

/SOFT_INPUT:总是隐藏

软键盘

public static final i

nt SOFT_INPUT_STATE_ALWAYS_HI

DDEN = 3;

/

/SOFT_INPUT:用户导航

navigate)到你的窗口时显示软键盘

public static final i

nt SOFT_INPUT_STATE_VISIBLE =

4

;

/

/SOFT_INPUT:总是显示

软键盘

public static final i

nt SOFT_INPUT_STATE_ALWAYS_VI

SIBLE = 5;

/

/SOFT_INPUT:显示软键

盘时用于表示window调整方式的bite

的mask

public static final i

nt SOFT_INPUT_MASK_ADJUST = 0

xf0;

/

/SOFT_INPUT:不指定显

示软件盘时,window的调整方式

public static final i

nt SOFT_INPUT_ADJUST_UNSPECIF

IED = 0x00;

/

/SOFT_INPUT:当显示软

键盘时,调整window内的控件大小以便

显示软键盘

public static final i

nt SOFT_INPUT_ADJUST_RESIZE =

0

x10;

/

/SOFT_INPUT:当显示软

键盘时,调整window的空白区域来显示

软键盘,即使调整空白区域,软键盘还是

有可能遮挡一些有内容区域,这时用户就

只有退出软键盘才能看到这些被遮挡区域

并进行

public static final i

nt SOFT_INPUT_ADJUST_PAN = 0x

2

0;

/

/SOFT_INPUT:当显示软

键盘时,不调整window的布局

public static final i

nt SOFT_INPUT_ADJUST_NOTHING

=

0x30;

/

/SOFT_INPUT:用户导航

navigate)到了你的window

public static final i

nt SOFT_INPUT_IS_FORWARD_NAVI

GATION = 0x100;

/

/软输入法模式选项

public int softInputM

ode;

/

/窗口如何停靠

public int gravity;

/

/水平边距,容器与widget

之间的距离,占容器宽度的百分率

public float horizont

alMargin;

/

/纵向边距

public float vertical

Margin;

/

/积极的insets绘图表面和

窗口之间的内容

public final Rect sur

faceInsets = new Rect();

/期望的位图格式,默认为

/

不透明,参考android.graphics.Pi

xelFormat

public int format;

/

/窗口所使用的动画设置,

它必须是一个系统资源而不是应用程序资

源,因为窗口管理器不能访问应用程序

public int windowAnim

ations;

/

/整个窗口的半透明值,1.0

表示不透明,0.0表示全透明

public float alpha =

/当FLAG_DIM_BEHIND设

1

.0f;

/

置后生效,该变量指示后面的窗口变暗的

程度,1.0表示完全不透明,0.0表示没

有变暗

public float dimAmoun

t = 1.0f;

public static final f

loat BRIGHTNESS_OVERRIDE_NONE

=

-1.0f;

public static final f

loat BRIGHTNESS_OVERRIDE_OFF

0.0f;

=

public static final f

loat BRIGHTNESS_OVERRIDE_FULL

1.0f;

=

public float screenBr

ightness = BRIGHTNESS_OVERRID

E_NONE;

/

/用来覆盖用户设置的屏幕

亮度,表示应用用户设置的屏幕亮度,从

到1调整亮度从暗到最亮发生变化

0

public float buttonBr

ightness = BRIGHTNESS_OVERRID

E_NONE;

public static final i

nt ROTATION_ANIMATION_ROTATE

=

0;

public static final i

nt ROTATION_ANIMATION_CROSSFA

DE = 1;

public static final i

nt ROTATION_ANIMATION_JUMPCUT

=

2;

/

/定义出入境动画在这个窗

口旋转设备时使用

public int rotationAn

imation = ROTATION_ANIMATION_

ROTATE;

/

/窗口的标示符

public IBinder token

=

null;

/

/此窗口所在的包名

public String package

Name = null;

/

/屏幕方向

public int screenOrie

ntation = ActivityInfo.SCREEN

_

ORIENTATION_UNSPECIFIED;

/首选的刷新率的窗口

public float preferre

dRefreshRate;

/

/

/控制status bar是否显

public int systemUiVi

sibility;

/

/ui能见度所请求的视图层

次结构

public int subtreeSys

temUiVisibility;

/得到关于系统ui能见度变

/

化的回调

public boolean hasSys

temUiListeners;

public static final i

nt INPUT_FEATURE_DISABLE_POIN

TER_GESTURES = 0x00000001;

public static final i

nt INPUT_FEATURE_NO_INPUT_CHA

NNEL = 0x00000002;

public static final i

nt INPUT_FEATURE_DISABLE_USER

_

ACTIVITY = 0x00000004;

public int inputFeatu

res;

public long userActiv

ityTimeout = -1;

.

.....

public final int copy

From(LayoutParams o) {

.....

.

}

.

.....

public void scale(flo

at scale) {

}

.

.....

.

.....

}

看见没有,从上面类可以看

出,Android窗口类型主要分

成了三大类:

1

. 应用程序窗口。一般应用

程序的窗口,比如我们应

用程序的Ac tivity的窗口。

2

3

. 子窗口。一般在Ac tivity里

面的窗口,比如对话框

等。

. 系统窗口。系统的窗口,

比如输入法,Toa st,墙纸

等。

同时还可以看见,

WindowManager.LayoutPara

ms里面窗口的type类型值定

义是一个递增保留的连续增

大数值,从注释可以看出来

其实就是窗口的Z-ORDER序

列(值越大显示的位置越在

上面,你需要将屏幕想成三

维坐标模式)。创建不同类

型的窗口需要设置不同的

type值,譬如上面拓展

Ac tivity窗口加载时分析的

ma ke Visible方法中的Window

默认属性的

type=TYPE_APPLICATION

既然说这个类很重要,那总

得感性的体验一下重要性

吧,所以我们先来看几个实

例。

2

-4 通过上面

WindowManager.LayoutPara

ms分析引出的应用层开发常

用经典实例

有了上面分析相信你一定觉

WindowManager.LayoutPara

ms还是蛮熟悉的,不信我们

来看下。

Part1:开发APP时设置

Ac tivity全屏常亮的一种办法

(设置Ac tivity也就是Ac tivity

的Window):

public class MainActivity ext

ends ActionBarActivity {

@

Override

protected void onCreate(B

undle savedInstanceState) {

super.onCreate(savedI

nstanceState);

/

/设置Activity的Window

为全屏,当然也可以在xml中设置

Window window = getWi

ndow();

WindowManager.LayoutP

arams windowAttributes = wind

ow.getAttributes();

windowAttributes.flag

s = WindowManager.LayoutParam

s.FLAG_FULLSCREEN | windowAtt

ributes.flags;

window.setAttributes(

windowAttributes);

//设置Activity的Window

为保持屏幕亮

window.addFlags(Windo

wManager.LayoutParams.FLAG_KE

EP_SCREEN_ON);

setContentView(R.layo

ut.activity_main);

}

}

这是运行结果:

Part2:App开发中弹出软键

盘时下面的输入框被软件盘

挡住问题的解决办法:

在Ac tivity中的onCreate中

setContentView之前写如下代

码:

//你也可以在xml文件中设置,一样的效

getWindow().setSoftInputMode(

WindowManager.LayoutParams.SO

FT_INPUT_ADJUST_RESIZE|Window

Manager.LayoutParams.SOFT_INP

UT_STATE_HIDDEN);

Part3:创建悬浮窗口(仿

IPhone的小圆点或者魅族的

小白点或者360手机卫士的

小浮标),退出当前Ac tivity

依旧可见的一种实现方法:

省略了Ac tivity的start与stop

Service的按钮代码,直接给

出了核心代码如下:

/

**

*

Author

Time

: yanbo

: 14:47

*

*

Description : 手机屏幕悬浮

窗,仿IPhone小圆点

* (未完全实现,

只提供思路,如需请自行实现)

*

Notice : <uses-permi

ssion android:name="android.p

ermission.SYSTEM_ALERT_WINDOW

"

/>

/

*

public class WindowService ex

tends Service {

private WindowManager mWi

ndowManager;

private ImageView mImageV

iew;

@

Override

public IBinder onBind(Int

ent intent) {

return null;

}

@

Override

public void onCreate() {

super.onCreate();

/

/创建悬浮窗

createFloatWindow();

}

private void createFloatW

indow() {

/

/这里的参数设置上面刚刚

讲过,不再说明

WindowManager.LayoutP

arams layoutParams = new Wind

owManager.LayoutParams();

mWindowManager = (Win

dowManager) getApplication().

getSystemService(Context.WIND

OW_SERVICE);

/

/设置window的type

layoutParams.type = W

indowManager.LayoutParams.TYP

E_PHONE;

/

/设置效果为背景透明

layoutParams.format =

PixelFormat.RGBA_8888;

/设置浮动窗口不可聚焦

/

layoutParams.flags =

WindowManager.LayoutParams.FL

AG_NOT_FOCUSABLE;

layoutParams.gravity

=

Gravity.BOTTOM | Gravity.RI

GHT;

layoutParams.width =

WindowManager.LayoutParams.WR

AP_CONTENT;

layoutParams.height =

WindowManager.LayoutParams.W

RAP_CONTENT;

layoutParams.x = -50;

layoutParams.y = -50;

mImageView = new Imag

eView(this);

mImageView.setImageRe

source(android.R.drawable.ic_

menu_add);

/

/添加到Window

mWindowManager.addVie

w(mImageView, layoutParams);

/

/设置监听

mImageView.setOnTouch

Listener(touchListener);

}

@

Override

public void onDestroy() {

super.onDestroy();

if (mImageView != nul

/讲WindowManager

l) {

/

时说过,add,remove成对出现,所以

需要remove

mWindowManager.re

moveView(mImageView);

}

}

private View.OnTouchListe

ner touchListener = new View.

OnTouchListener() {

@

Override

public boolean onTouc

h(View v, MotionEvent event)

{

/

/模拟触摸触发的事件

Intent intent = n

ew Intent(Intent.ACTION_VIEW)

;

intent.addFlags(I

ntent.FLAG_ACTIVITY_NEW_TASK)

;

startActivity(int

ent);

return false;

}

}

;

}

如下是运行过程模拟,特别

留意屏幕右下角的变化:

怎么样,通过最后这个例子

你是不是就能体会到

WindowManager.LayoutPara

ms的Z-ORDER序列类型,

值越大显示的位置越在上

面。

2-5 总结Ac tivity的窗口添加

机制

有了上面这么多分析和前几

篇的分析,我们对Ac tivity的

窗口加载再次深入分析总结

如下:

可以看见Context的

WindowManager对每个APP

来说是一个全局单例的,而

Ac tivity的WindowManager是

每个Ac tivity都会新创建一个

的(其实你从上面分析的两

个实例化

WindowManagerImpl的构造

函数参数传递就可以看出

来,Ac tivity中Window的

WindowManager成员在构造

实例化时传入给

WindowManagerImpl中

mParentWindow成员的是当

前Window对象,而

ContextImpl的static块中单例

实例化WindowManagerImpl

时传入给

WindowManagerImpl中

mParentWindow成员的是null

值),所以上面模拟苹果浮

动小图标使用了Application

的WindowManager而不是

Ac tivity的,原因就在于这

里;使用Ac tivity的

WindowManager时当Ac tivity

结束时WindowManager就无

效了,所以使用Ac tivity的

getSysytemService(WINDOW

_SERVICE)获取的是Loc a l的

WindowManager。同时可以

看出来Ac tivity中的

WindowManager.LayoutPara

ms的type 为

TYPE_APPLICATION。

好了,上面也说了不少了,

有了上面这些知识点以后我

们就来开始分析Android应用

Ac tivity、Dia log、

PopWindow窗口显示机制。

【工匠若

水 http://blog.csdn.net/yanbob

er 转载烦请注明出处,尊重

劳动成果】

3

Android应用Dialog窗

口添加显示机制源码

3

-1 Dia log窗口源码分析

写过APP都知道,Dia log是

一系列XXXDialog的基类,

我们可以new任意Dia log或者

通过Ac tivity提供的

onCreateDialog(……)、

onPrepareDialog(……)和

showDialog(……)等方法来管

理我们的Dia log,但是究其

实质都是来源于Dia log基

类,所以我们对于各种

XXXDialog来说只用分析

Dia log的窗口加载就可以

了。

如下从Dia log的构造函数开

始分析:

public class Dialog implement

s DialogInterface, Window.Cal

lback,

KeyEvent.Callback, On

CreateContextMenuListener, Wi

ndow.OnWindowDismissedCallbac

k {

.

.....

public Dialog(Context con

text) {

this(context, 0, true

)

;

}

/

/构造函数最终都调运了这个默认

的构造函数

Dialog(Context context, i

nt theme, boolean createConte

xtThemeWrapper) {

/

/默认构造函数的createC

ontextThemeWrapper为true

if (createContextThem

eWrapper) {

/

/默认构造函数的the

me为0

if (theme == 0) {

TypedValue ou

tValue = new TypedValue();

context.getTh

eme().resolveAttribute(com.an

droid.internal.R.attr.dialogT

heme,

outVa

lue, true);

theme = outVa

lue.resourceId;

}

mContext = new Co

ntextThemeWrapper(context, th

eme);

}

else {

mContext = contex

t;

}

/

/mContext已经从外部传

入的context对象获得值(一般是个Ac

tivity)!!!非常重要,先记住!!

/

/获取WindowManager对

mWindowManager = (Win

dowManager)context.getSystemS

ervice(Context.WINDOW_SERVICE

)

;

/

/为Dialog创建新的Windo

w

Window w = PolicyMana

ger.makeNewWindow(mContext);

mWindow = w;

/

/Dialog能够接受到按键事

件的原因

w.setCallback(this);

w.setOnWindowDismisse

dCallback(this);

/

/关联WindowManager与

新Window,特别注意第二个参数token

为null,也就是说Dialog没有自己的t

oken

/

/一个Window属于Dialog

的话,那么该Window的mAppToken对

象是null

w.setWindowManager(mW

indowManager, null, null);

w.setGravity(Gravity.

CENTER);

mListenersHandler = n

ew ListenersHandler(this);

}

.

.....

}

可以看到,Dia log构造函数

首先把外部传入的参数

context对象赋值给了当前类

的成员(我们的Dia log一般

都是在Ac tivity中启动的,所

以这个

context一般是个Ac tivity),

然后调用

context.getSystemService(Con

text.WINDOW_SERVICE)获

取WindowManager,这个

WindowManager

是哪来的呢?先按照上面说

的context一般是个Ac tivity来

看待,可以发现这句实质就

是Ac tivity的ge tSyste mSe rvic e

方法,我们看下源码,如

下:

@

Override

public Object getSystemSe

rvice(@ServiceName @NonNull S

tring name) {

if (getBaseContext()

= null) {

=

throw new Illegal

StateException(

"

System s

ervices not available to Acti

vities before onCreate()");

}

/

/我们Dialog中获得的Win

dowManager对象就是这个分支

if (WINDOW_SERVICE.eq

uals(name)) {

/

/Activity的Windo

wManager

return mWindowMan

ager;

}

else if (SEARCH_SER

VICE.equals(name)) {

ensureSearchManag

er();

return mSearchMan

ager;

}

return super.getSyste

mService(name);

}

看见没有,Dia log中的

WindowManager成员实质和

Ac tivity里面是一样的,也就

是共用了一个

WindowManager。

回到Dia log的构造函数继续

分析,在得到了

WindowManager之后,程序

又新建了一个Window对象

(类型是PhoneWindow类

型,和Ac tivity的Window新建

过程类似);接着通过

w. setCallback(this)设置Dia log

为当前window的回调接口,

这样Dia log就能够接收事件

处理了;接着把从Ac tivity拿

到的WindowManager对象关

联到新创建的Window中。

至此Dia log的创建过程

Window处理已经完毕,很简

单,所以接下来我们继续看

看Dia log的show与cancel方

法,如下:

public void show() {

.....

if (!mCreated) {

.

/

/回调Dialog的onCr

eate方法

dispatchOnCreate(

null);

}

/

/回调Dialog的onStart

方法

onStart();

/类似于Activity,获取

/

当前新Window的DecorView对象,所

以有一种自定义Dialog布局的方式就是

重写Dialog的onCreate方法,使用se

tContentView传入布局,就像前面文

章分析Activity类似

mDecor = mWindow.getD

ecorView();

.

/

.....

/获取新Window的Window

Manager.LayoutParams参数,和上

面分析的Activity一样type为TYPE_

APPLICATION

WindowManager.LayoutP

arams l = mWindow.getAttribut

es();

.

.....

try {

/

/把一个View添加到A

ctivity共用的windowManager里面

mWindowManager.ad

dView(mDecor, l);

.

.....

}

}

finally {

}

可以看见Dia log的新Window

与Ac tivity的Window的type同

样都为

TYPE_APPLICATION,上面

介绍

WindowManager.LayoutPara

ms时

TYPE_APPLICATION的注释

明确说过,普通应用程序窗

口TYPE_APPLICATION的

token必须设置为Ac tivity的

token来指定窗口属于谁。

所以可以看见,既然Dia log

和Ac tivity共享同一个

WindowManager(也就是上

面分析的

WindowManagerImpl),而

WindowManagerImpl里

面有个Window类型的

mParentWindow变量,这个

变量在Ac tivity的attach中创

建WindowManagerImpl时传

入的为当前Ac tivity的

Window,

而当前Ac tivity的Window里面

的mAppToken值又为当前

Ac tivity的token,所以Ac tivity

与Dia log共享了同一个

mAppToken值,只是Dia log

Ac tivity的Window对象不同。

3

-2 Dia log窗口加载总结

通过上面分析Dia log的窗口

加载原理,我们总结如下

图:

从图中可以看出,Ac tivity和

Dia log共用了一个Token对

象,Dia log必须依赖于

Ac tivity而显示(通过别的

context搞完之后token都为

null,最终会在ViewRootImpl

的setView方法中加载时因为

token为null抛出异常),所

以Dia log的Context传入参数

一般是一个存在的Ac tivity,

如果Dia log弹出来之前

Ac tivity已经被销毁了,则这

个Dia log在弹出的时候就会

抛出异常,因为token不可用

了。在Dia log的构造函数中

我们关联了新Window的

callback事件监听处理,所以

当Dia log显示时Ac tivity无法

消费当前的事件。

到此Dia log的窗口加载机制

就分析完毕了,接下来我们

说说应用开发中常见的一个

诡异问题。

3-3 从Dia log窗口加载分析引

出的应用开发问题

有了上面的分析我们接下来

看下平时开发App初学者容

易犯的几个错误。

实现在一个Ac tivity中显示一

个Dia log,如下代码:

public class MainActivity ext

ends AppCompatActivity {

@

Override

protected void onCreate(B

undle savedInstanceState) {

setContentView(R.layo

ut.activity_main);

/

/重点关注构造函数的参数

创建一个Dialog然后显示出来

Dialog dialog = new P

rogressDialog(this);

dialog.setTitle("Test

DialogContext");

dialog.show();

}

}

分析:使用了Ac tivity为

context,也即和Ac tivity共用

token,符合上面的分析,所

以不会报错,正常执行。

实现在一个Ac tivity中显示一

个Dia log,如下代码:

public class MainActivity ext

ends AppCompatActivity {

@

Override

protected void onCreate(B

undle savedInstanceState) {

setContentView(R.layo

ut.activity_main);

/

/重点关注构造函数的参数

创建一个Dialog然后显示出来

Dialog dialog = new P

rogressDialog(getApplicationC

ontext());

dialog.setTitle("Test

DialogContext");

dialog.show();

}

}

分析:传入的是Application

的Context,导致

TYPE_APPLICATION类型

Dia log的token为null,所以抛

出如下异常,无法显示对话

框。

Caused by: android.view.Windo

wManager$BadTokenException: U

nable to add window -- token

null is not for an applicatio

n

at android.view.V

iewRootImpl.setView(ViewRootI

mpl.java:566)

at android.view.W

indowManagerGlobal.addView(Wi

ndowManagerGlobal.java:272)

at android.view.W

indowManagerImpl.addView(Wind

owManagerImpl.java:69)

at android.app.Di

alog.show(Dialog.java:298)

实现在一个Service中显示一

个Dia log,如下代码:

public class WindowService ex

tends Service {

@

Override

public IBinder onBind(Int

ent intent) {

return null;

}

@

Override

public void onCreate() {

super.onCreate();

/

/重点关注构造函数的参数

Dialog dialog = new P

rogressDialog(this);

dialog.setTitle("Test

DialogContext");

dialog.show();

}

}

分析:传入的Context是一个

Service,类似上面传入

ApplicationContext一样的后

果,一样的原因,抛出如下

异常:

Caused by: android.view.Windo

wManager$BadTokenException: U

nable to add window -- token

null is not for an applicatio

n

at android.view.V

iewRootImpl.setView(ViewRootI

mpl.java:566)

at android.view.W

indowManagerGlobal.addView(Wi

ndowManagerGlobal.java:272)

at android.view.W

indowManagerImpl.addView(Wind

owManagerImpl.java:69)

at android.app.Di

alog.show(Dialog.java:298)

至此通过我们平时使用最多

的Dia log也验证了Dia log成功

显示的必要条件,同时也让

大家避免了再次使用Dia log

不当出现异常的情况,或者

出现类似

异常后知道真实的背后原因

是什么的问题。

可以看见,Dia log的实质无

非也是使用WindowManager

的addView、

updateViewLayout、

removeView进行一些操作展

示。

【工匠若

水 http://blog.csdn.net/yanbob

er 转载烦请注明出处,尊重

劳动成果】

4

Android应用

PopWindow窗口添加

显示机制源码

PopWindow实质就是弹出式

菜单,它与Dia la g不同的地

方是不会使依赖的Ac tivity组

件失去焦点(PopupWindow

弹出后可以继续与依赖的

Ac tivity进行交互),Dia log

却不能这样。同时

PopupWindow与Dia log另一

个不同点是PopupWindow是

一个阻塞的对话框,如果你

直接在Ac tivity的onCreate等

方法中显示它则会报错,所

以PopupWindow必须在某个

事件中显示地或者是开启一

个新线程去调用。

说这么多还是直接看代码

吧。

4-1 PopWindow窗口源码分

依据PopWindow的使用,我

们选择最常用的方式来分

析,如下先看其中常用的一

种构造函数:

public class PopupWindow {

.

.....

/

/我们只分析最常用的一种构造函

public PopupWindow(View c

ontentView, int width, int he

ight, boolean focusable) {

if (contentView != nu

ll) {

/

/获取mContext,co

ntentView实质是View,View的mCon

text都是构造函数传入的,View又层级

传递,所以最终这个mContext实质是A

ctivity!!!很重要

mContext = conten

tView.getContext();

/

/获取Activity的ge

tSystemService的WindowManager

mWindowManager =

(WindowManager) mContext.getS

ystemService(Context.WINDOW_S

ERVICE);

}

/

/进行一些Window类的成员

变量初始化赋值操作

setContentView(conten

tView);

setWidth(width);

setHeight(height);

setFocusable(focusabl

e);

}

.

.....

}

可以看见,构造函数只是初

始化了一些变量,看完构造

函数继续看下PopWindow的

展示函数,如下:

public void showAsDropDown(Vi

ew anchor, int xoff, int yoff

,

int gravity) {

.

/

.....

/anchor是Activity中P

opWindow准备依附的View,这个View

的token实质也是Activity的Window

中的token,也即Activity的token

/

/第一步

Manager.LayoutParams

WindowManager.LayoutP

初始化Window

arams p = createPopupLayout(a

nchor.getWindowToken());

/

/第二步

preparePopup(p);

.

/

.....

/第三步

invokePopup(p);

}

可以看见,当我们想将

PopWindow展示在anchor的

下方向(Z轴是在anchor的上

面)旁边时经理了上面三

步,我们一步一步来分析,

先看第一步,

源码如下:

private WindowManager.Layout

Params createPopupLayout(IBin

der token) {

/

/实例化一个默认的Window

Manager.LayoutParams,其中type

TYPE_APPLICATION

WindowManager.LayoutP

=

arams p = new WindowManager.L

ayoutParams();

/

/设置Gravity

p.gravity = Gravity.S

TART | Gravity.TOP;

/

/设置宽高

p.width = mLastWidth

mWidth;

p.height = mLastHeigh

t = mHeight;

=

/

/依据背景设置format

if (mBackground != nu

ll) {

p.format = mBackg

round.getOpacity();

else {

p.format = PixelF

}

ormat.TRANSLUCENT;

}

/

/设置flags

p.flags = computeFlag

s(p.flags);

/修改type=WindowMana

/

ger.LayoutParams.TYPE_APPLICA

TION_PANEL,mWindowLayoutType

有初始值,type类型为子窗口

p.type = mWindowLayou

tType;

/

/设置token为Activity

的token

p.token = token;

.....

return p;

.

}

接着回到showAsDropDown

方法看看第二步,如下源

码:

private void preparePopup(Win

dowManager.LayoutParams p) {

.

/

.....

/有无设置PopWindow的ba

ckground区别

if (mBackground != nu

ll) {

.

/

.....

/如果有背景则创建一

个PopupViewContainer对象的View

Group

PopupViewContaine

r popupViewContainer = new Po

pupViewContainer(mContext);

PopupViewContaine

r.LayoutParams listParams = n

ew PopupViewContainer.LayoutP

arams(

ViewGroup

.

LayoutParams.MATCH_PARENT, h

eight

)

/

;

/把背景设置给Popup

ViewContainer的ViewGroup

popupViewContaine

r.setBackground(mBackground);

/把我们构造函数传入

的View添加到这个ViewGroup

popupViewContaine

/

r.addView(mContentView, listP

arams);

/

/返回这个ViewGrou

p

mPopupView = popu

pViewContainer;

else {

/如果没有通过PopWi

}

/

ndow的setBackgroundDrawable设

置背景则直接赋值当前传入的View为Po

pWindow的View

mPopupView = mCon

tentView;

}

.

.....

}

可以看见preparePopup方法

的作用就是判断设置Vie w,

如果有背景则会在传入的

contentView外面包一层

PopupViewContainer(实质

是一个重写

了事件处理的FrameLayout)

之后作为mP opupView,如果

没有背景则直接用

contentView作为

mP opupView。我们再来看下

这里的

PopupViewContainer类,如

下源码:

private class PopupViewConta

iner extends FrameLayout {

.

@

.....

Override

protected int[] onCre

ateDrawableState(int extraSpa

ce) {

.

.....

}

@

Override

public boolean dispat

chKeyEvent(KeyEvent event) {

.

.....

}

@

Override

public boolean dispat

chTouchEvent(MotionEvent ev)

{

if (mTouchInterce

ptor != null && mTouchInterce

ptor.onTouch(this, ev)) {

return true;

}

return super.disp

atchTouchEvent(ev);

}

@

Override

public boolean onTouc

hEvent(MotionEvent event) {

.

.....

if(xxx) {

dismiss();

}

.

.....

}

@

Override

public void sendAcces

sibilityEvent(int eventType)

{

.

.....

}

}

可以看见,这个

PopupViewContainer是一个

PopWindow的内部私有类,

它继承了FrameLayout,在其

中重写了Key和Touch事件的

分发处理逻辑。

同时查阅P opupView可以发

现,P opupView类自身没有

重写Key和Touch事件的处

理,所以如果没有将传入的

Vie w对象放入封装的

Vie wGroup中,

则点击Back键或者

PopWindow以外的区域

PopWindow是不会消失的

(其实PopWindow中没有向

Ac tivity及Dia log一样new新的

Window,

所以不会有新的callback设

置,也就没法处理事件消费

了)。

接着继续回到

showAsDropDown方法看看

第三步,如下源码:

private void invokePopup(Wind

owManager.LayoutParams p) {

if (mContext != null)

{

p.packageName = m

Context.getPackageName();

}

mPopupView.setFitsSys

temWindows(mLayoutInsetDecor)

;

setLayoutDirectionFro

mAnchor();

mWindowManager.addVie

w(mPopupView, p);

}

可以看见,这里使用了

Ac tivity的WindowManager将

我们的PopWindow进行了显

示。

到此可以发现,PopWindow

的实质无非也是使用

WindowManager的

addView、

updateViewLayout、

removeView进行一些操作展

示。与Dia log不同的地方是

没有新new Window而已(也

就没法设置callback,无法消

费事件,也就是前面说的

PopupWindow弹出后可以继

续与依赖的Ac tivity进行交互

的原因)。

到此P opWindw的窗口加载显

示机制就分析完毕了,接下

来进行总结与应用开发技巧

提示。

4-2 PopWindow窗口源码分

析总结及应用开发技巧提示

通过上面分析可以发现总结

如下图:

可以看见,PopWindow完全

使用了Ac tivity的Window与

WindowManager,相对来说

比较简单容易记理解。

再来看一个开发技巧:

如果设置了PopupWindow的

background,则点击Back键

或者点击PopupWindow以外

的区域时PopupWindow就会

dismiss;如果不设置

PopupWindow的

background,则点击Back键

或者点击PopupWindow以外

的区域PopupWindow不会消

失。

工匠若

水 http://blog.csdn.net/yanbob

er 转载烦请注明出处,尊重

劳动成果】

5

Android应用Toa st窗

口添加显示机制源码

5-1 基础知识准备

在开始分析这几个窗口之前

需要脑补一点东东,我们从

应用层开发来直观脑补,这

样下面分析源码时就不蛋疼

了。如下是一个我们写的两

个应用实现Service跨进程调

用服务ADIL的例子,客户端

调运远程Service的start与stop

方法控制远程Service的操

作。

Android系统中的应用程序都

运行在各自的进程中,进程

之间是无法直接交换数据

的,但是Android为开发者提

供了AIDL跨进程调用Service

的功能。其实AIDL就相当于

双方约定的一个规则而已。

先看下在Android Studio中

AIDL开发的工程目录结构,

如下:

由于AIDL文件中不能出现访

问修饰符(如public),同时

AIDL文件在两个项目中要完

全一致而且只支持基本类

型,所以我们定义的AIDL文

件如下:

ITestService.aidl

package io.github.yanbober.my

application;

interface ITestService {

void start(int id);

void stop(int id);

}

再来看下依据a idl文件自动生

成的ITestService.java文件

吧,如下:

/

*

*

This file is auto-generate

d. DO NOT MODIFY.

*

/

package io.github.yanbober.my

application;

public interface ITestService

extends android.os.IInterfac

e

{

/

/Stub类是ITestService接口

的内部静态抽象类,该类继承了Binder

public static abstract cl

ass Stub extends android.os.B

inder implements io.github.ya

nbober.myapplication.ITestSer

vice

{

.

/

.....

/这是抽象静态Stub类中的

asInterface方法,该方法负责将ser

vice返回至client的对象转换为ITes

tService.Stub

/

/把远程Service的Binde

r对象传递进去,得到的是远程服务的本

地代理

public static io.gith

ub.yanbober.myapplication.ITe

stService asInterface(android

.

os.IBinder obj)

{

.

.....

}

.

/

.....

/远程服务的本地代理,也

会继承自ITestService

private static class

Proxy implements io.github.ya

nbober.myapplication.ITestSer

vice

{

.

@

.....

Override

public void start

(int id) throws android.os.Re

moteException

{

.

.....

}

@

Override

public void stop(

int id) throws android.os.Rem

oteException

{

.

.....

}

}

.

.....

}

/

/两个方法是aidl文件中定义的

方法

public void start(int id)

throws android.os.RemoteExce

ption;

public void stop(int id)

throws android.os.RemoteExcep

tion;

}

这就是自动生成的java文

件,接下来我们看看服务端

的Service源码,如下:

/

/记得在AndroidManifet.xml中注

册Service的<action android:na

me="io.github.yanbober.myappl

ication.aidl" />

public class TestService exte

nds Service {

private TestBinder mTestB

inder;

/

/该类继承ITestService.Stu

b类而不是Binder类,因为ITestServ

ice.Stub是Binder的子类

/

/进程内的Service定义TestBi

nder内部类是继承Binder类

public class TestBinder e

xtends ITestService.Stub {

Override

@

public void start(int

id) throws RemoteException {

Log.i(null, "Serv

er Service is start!");

}

@

Override

public void stop(int

id) throws RemoteException {

Log.i(null, "Serv

er Service is stop!");

}

}

@

Override

public IBinder onBind(Int

ent intent) {

/返回Binder

return mTestBinder;

/

}

@

Override

public void onCreate() {

super.onCreate();

/

/实例化Binder

mTestBinder = new Tes

tBinder();

}

}

现在服务端App的代码已经

OK,我们来看下客户端的

代码。客户端首先也要像上

面的工程结构一样,把AIDL

文件放好,接着在客户端使

用远程服务端

的Service代码如下:

public class MainActivity ext

ends Activity {

private static final Stri

ng REMOT_SERVICE_ACTION = "io

.

github.yanbober.myapplicatio

n.aidl";

private Button mStart, mS

top;

der;

private ITestService mBin

private ServiceConnection

connection = new ServiceConn

ection() {

@

Override

public void onService

Connected(ComponentName name,

IBinder service) {

/

/获得另一个进程中的

Service传递过来的IBinder对象

/

/用IMyService.St

ub.asInterface方法转换该对象

mBinder = ITestSe

rvice.Stub.asInterface(servic

e);

}

@

Override

public void onService

Disconnected(ComponentName na

me) {

}

}

;

@

Override

protected void onCreate(B

undle savedInstanceState) {

super.onCreate(savedI

nstanceState);

setContentView(R.layo

ut.activity_main);

mStart = (Button) thi

s.findViewById(R.id.start);

mStop = (Button) this

.

findViewById(R.id.stop);

mStart.setOnClickList

ener(clickListener);

mStop.setOnClickListe

ner(clickListener);

/

/绑定远程跨进程Service

bindService(new Inten

t(REMOT_SERVICE_ACTION), conn

ection, BIND_AUTO_CREATE);

}

@

Override

protected void onDestroy(

)

{

super.onDestroy();

/

/取消绑定远程跨进程Serv

ice

unbindService(connect

ion);

}

private View.OnClickListe

ner clickListener = new View.

OnClickListener() {

@

Override

public void onClick(V

iew v) {

/

///调用远程Servic

e中的start与stop方法

switch (v.getId()

)

{

case R.id.sta

rt:

try {

mBind

er.start(0x110);

}

catch (

RemoteException e) {

ntStackTrace();

e.pri

}

break;

case R.id.sto

p:

try {

mBind

er.stop(0x120);

}

catch (

e.pri

RemoteException e) {

ntStackTrace();

}

break;

}

}

}

;

}

到此你对应用层通过AIDL使

用远程Service的形式已经很

熟悉了,至于实质的通信使

用Binder的机制我们后面会

写文章一步一步往下分析。

到此的准备

知识已经足够用来理解下面

我们的源码分析了。

5

-2 Toa st窗口源码分析

我们常用的Toa st窗口其实和

前面分析的Ac tivity、

Dia log、PopWindow都是不

同的,因为它和输入法、墙

纸类似,都是系统窗口。

我们还是按照最常用的方式

来分析源码吧。

我们先看下Toa st的静态

makeText方法吧,如下:

public static Toast makeText(

Context context, CharSequence

text, @Duration int duration

)

{

/

/new一个Toast对象

Toast result = new To

ast(context);

/

/获取前面有篇文章分析的L

ayoutInflater

LayoutInflater inflat

e = (LayoutInflater)

context.getSy

stemService(Context.LAYOUT_IN

FLATER_SERVICE);

/

/加载解析Toast的布局,

实质transient_notification.xm

l是一个LinearLayout中套了一个@an

droid:id/message的TextView而已

View v = inflate.infl

ate(com.android.internal.R.la

yout.transient_notification,

null);

/

/取出布局中的TextView

TextView tv = (TextVi

ew)v.findViewById(com.android

.

internal.R.id.message);

/把我们的文字设置到Text

/

View 上

tv.setText(text);

/设置一些属性

result.mNextView = v;

/

result.mDuration = du

ration;

}

/

/返回新建的Toast

return result;

可以看见,这个方法构造了

一个Toa st,然后把要显示的

文本放到这个Vie w的

Te xtVie w中,然后初始化相

关属性后返回这个新的Toast

对象。

当我们有了这个Toa st对象之

后,可以通过show方法来显

示出来,如下看下show方法

源码:

public void show() {

.

/

.....

/通过AIDL(Binder)通

信拿到NotificationManagerServi

ce的服务访问接口,当前Toast类相当

于上面例子的客户端!!!相当重要!!

INotificationManager

service = getService();

String pkg = mContext

.

getOpPackageName();

TN tn = mTN;

tn.mNextView = mNextV

iew;

try {

/

/把TN对象和一些参数

传递到远程NotificationManagerSe

rvice中去

service.enqueueTo

ast(pkg, tn, mDuration);

}

catch (RemoteExcept

ion e) {

}

/

/ Empty

}

我们看看show方法中调运的

getService方法,如下:

/

/远程NotificationManagerServ

ice的服务访问接口

private static INotificat

ionManager sService;

static private INotificat

ionManager getService() {

/

/单例模式

if (sService != null)

{

return sService;

}

/

/通过AIDL(Binder)通

信拿到NotificationManagerServi

ce的服务访问接口

sService = INotificat

ionManager.Stub.asInterface(S

erviceManager.getService("not

ification"));

return sService;

}

通过上面我们的基础脑补实

例你也能看懂这个getService

方法了吧。那接着我们来看

mTN吧,好像mTN在Toa st的

构造函数里见过一眼,我们

来看

看,如下:

public Toast(Context context)

{

mContext = context;

mTN = new TN();

mTN.mY = context.getR

esources().getDimensionPixelS

ize(

com.android.i

nternal.R.dimen.toast_y_offse

t);

mTN.mGravity = contex

t.getResources().getInteger(

com.android.i

nternal.R.integer.config_toas

tDefaultGravity);

}

可以看见mTN确实是在构造

函数中实例化的,那我们就

来看看这个TN类,如下:

/

/类似于上面例子的服务端实例化的Ser

vice内部类Binder

private static class TN e

xtends ITransientNotification

Stub {

.

.

/

.....

/实现了AIDL的show与hid

e方法

@

Override

public void show() {

if (localLOGV) Lo

g.v(TAG, "SHOW: " + this);

mHandler.post(mSh

ow);

}

@

Override

public void hide() {

if (localLOGV) Lo

g.v(TAG, "HIDE: " + this);

mHandler.post(mHi

de);

}

.

.....

}

看见没有,TN是Toa st内部

的一个私有静态类,继承自

ITransientNotification.Stub。

你这时指定好奇

ITransientNotification.Stub是

个啥玩意,

对吧?其实你在上面的脑补

实例中见过它的,他出现在

服务端实现的Service中,就

是一个Binder对象,也就是

对一个a idl文件的实现而已,

我们看下这个

ITransientNotification.aidl文

件,如下:

package android.app;

/

** @hide */

oneway interface ITransientNo

tification {

void show();

void hide();

}

看见没有,和我们上面的例

子很类似吧。

再回到上面分析的show()方

法中可以看到,我们的Toast

是传给远程的

NotificationManagerService管

理的,为了

NotificationManagerService回

到我们的应用程序(回

调),我们需要告诉

NotificationManagerService我

们当前程序的Binder引用是

什么(也就是TN)。是不是

觉得和上面例子有些不同,

这里感觉Toa st又充当客户

端,又充当服务端的样子,

实质就是一个回调过程而

已。

继续来看Toa st中的show方法

的service.enqueueToast(pkg,

tn, mDuration);语句,service

实质是远程的

NotificationManagerService,

所以enqueueToast方法就是

NotificationManagerService类

的,如下:

private final IBinder mServic

e = new INotificationManager.

Stub() {

/

/

/ Toasts

/ ==================

=

=

============================

============================

@

Override

public void enqueueTo

ast(String pkg, ITransientNot

ification callback, int durat

ion)

{

.

.....

synchronized (mTo

astQueue) {

int callingPi

d = Binder.getCallingPid();

long callingI

d = Binder.clearCallingIdenti

ty();

try {

ToastReco

rd record;

/

/查看该To

ast是否已经在队列当中

int index

indexOfToastLocked(pkg, ca

llback);

=

/

/ If it'

s already in the queue, we up

date it in place, we don't

/

/ move i

t to the end of the queue.

/

/注释说了

已经存在则直接取出update

if (index

>

= 0) {

recor

d = mToastQueue.get(index);

recor

d.update(duration);

}

else {

/

/ Li

mit the number of toasts that

any given package except the

android

/

/ pa

ckage can enqueue. Prevents

DOS attacks and deals with le

aks.

.

....

.

/

/将T

oast封装成ToastRecord对象,放入m

ToastQueue 中

recor

d = new ToastRecord(callingPi

d, pkg, callback, duration);

/

/把

他添加到ToastQueue队列中

mToas

index

tQueue.add(record);

=

mToastQueue.size() - 1;

/将

当前Toast所在的进程设置为前台进程

keepP

/

rocessAliveLocked(callingPid)

;

}

/

/如果ind

ex为0,说明当前入队的Toast在队头,

需要调用showNextToastLocked方法

直接显示

if (index

=

= 0) {

showN

extToastLocked();

}

}

finally {

Binder.re

storeCallingIdentity(callingI

d);

}

}

}

}

继续看下该方法中调运的

showNextToastLocked方法,

如下:

void showNextToastLocked() {

/

/取出ToastQueue中队列

最前面的ToastRecord

ToastRecord record =

mToastQueue.get(0);

while (record != null

)

{

try {

/

/Toast类中实

现的ITransientNotification.St

ub的Binder接口TN,调运了那个类的s

how方法

record.callba

ck.show();

scheduleTimeo

utLocked(record);

return;

}

catch (RemoteEx

ception e) {

.

.....

}

}

}

继续先看下该方法中调运的

scheduleTimeoutLocked方

法,如下:

private void scheduleTimeoutL

ocked(ToastRecord r)

{

/

/移除上一条消息

mHandler.removeCallba

cksAndMessages(r);

/依据Toast传入的durati

/

on参数LENGTH_LONG=1来判断决定多

久发送消息

Message m = Message.o

btain(mHandler, MESSAGE_TIMEO

UT, r);

long delay = r.durati

on == Toast.LENGTH_LONG ? LON

G_DELAY : SHORT_DELAY;

/

/依据设置的MESSAGE_TIM

EOUT后发送消息

mHandler.sendMessageD

elayed(m, delay);

}

可以看见这里先回调了Toast

的TN的show,下面timeout

可能就是hide了。接着还在

该类的mHandler处理了这条

消息,然后调运了如下处理

方法:

private void handleTimeout(To

astRecord record)

{

.

.....

synchronized (mToastQ

ueue) {

int index = index

OfToastLocked(record.pkg, rec

ord.callback);

if (index >= 0) {

cancelToastLo

cked(index);

}

}

}

我们继续看

cancelToastLocked方法,如

下:

void cancelToastLocked(int in

dex) {

ToastRecord record =

mToastQueue.get(index);

try {

/

/回调Toast的TN中实

现的hide方法

record.callback.h

ide();

}

catch (RemoteExcept

ion e) {

.

.....

}

/

/从队列移除当前显示的Toa

st

mToastQueue.remove(in

dex);

keepProcessAliveLocke

d(record.pid);

if (mToastQueue.size(

)

> 0) {

/

/如果当前的Toast显

示完毕队列里还有其他的Toast则显示其

他的Toast

showNextToastLock

ed();

}

}

到此可以发现,Toa st的远程

管理

NotificationManagerService类

的处理实质是通过Handler发

送延时消息显示取消Toast

的,而且在远程

NotificationManagerService类

中又远程回调了Toa st的TN

类实现的show与hide方法。

现在我们就回到Toa st的TN

类再看看这个show与hide方

法,如下:

private static class TN exten

ds ITransientNotification.Stu

b {

.

/

.....

/仅仅是实例化了一个Hand

ler,非常重要!!!!!!!!

final Handler mHandle

r = new Handler();

.

.....

final Runnable mShow

new Runnable() {

Override

=

@

public void run()

{

handleShow();

}

}

;

final Runnable mHide

new Runnable() {

Override

public void run()

=

@

{

handleHide();

/ Don't do t

/

his in handleHide() because i

t is also invoked by handleSh

ow()

mNextView = n

ull;

}

}

.

/

;

.....

/实现了AIDL的show与hid

e方法

@

Override

public void show() {

if (localLOGV) Lo

g.v(TAG, "SHOW: " + this);

mHandler.post(mSh

ow);

}

@

Override

public void hide() {

if (localLOGV) Lo

g.v(TAG, "HIDE: " + this);

mHandler.post(mHi

de);

}

.

.....

}

可以看见,这里实现a idl接口

的方法实质是通过handler的

post来执行的一个方法,而

这个Handler仅仅只是new了

一下,也就是说,如果我们

APP时使用Toa st在子线程中

则需要自行准备Looper对

象,只有主线程Ac tivity创建

时帮忙准备了Looper(关于

Handler与Looper如果整不明

白请

阅读《Android异步消息处理

机制详解及源码分析》)。

那我们重点关注一下

handleShow与handleHide方

法,如下:

public void handleShow() {

if (localLOGV) Lo

g.v(TAG, "HANDLE SHOW: " + th

is + " mView=" + mView

+

" mNext

View=" + mNextView);

if (mView != mNex

tView) {

/

/ remove the

old view if necessary

/

通过WindowManager的remove删掉旧

/如果有必要就

handleHide();

mView = mNext

View;

Context conte

xt = mView.getContext().getAp

plicationContext();

String packag

eName = mView.getContext().ge

tOpPackageName();

if (context =

=

null) {

context =

mView.getContext();

}

/

/通过得到的con

text(一般是ContextImpl的contex

t)获取WindowManager对象(上一篇

文章分析的单例的WindowManager)

mWM = (Window

Manager)context.getSystemServ

ice(Context.WINDOW_SERVICE);

.

/

.....

/在把Toast的V

iew添加之前发现Toast的View已经被

添加过(有partent)则删掉

if (mView.get

Parent() != null) {

.

.....

mWM.remov

eView(mView);

}

.

/

.....

/把Toast的Vi

ew添加到窗口,其中mParams.type在

构造函数中赋值为TYPE_TOAST!!!

!!!特别重要

mWM.addView(m

View, mParams);

.

.....

}

}

public void handleHide() {

if (mView != null

)

{

/

/ note: chec

king parent() just to make su

re the view has

/

/ been added

.. i have seen cases where

we get here when

.

/

/ the view i

sn't yet added, so let's try

not to crash.

/

/注释说得很清

楚了,不解释,就是remove

if (mView.get

Parent() != null) {

mWM.remov

eView(mView);

}

mView = null;

}

}

到此Toa st的窗口添加原理就

分析完毕了,接下来我们进

行总结。

5-3 Toa st窗口源码分析总结

及应用开发技巧

经过上面的分析我们总结如

下:

通过上面分析及上图直观描

述可以发现,之所以Toa st的

显示交由远程的

NotificationManagerService管

理是因为Toa st是每个应用程

序都会弹出的,而且位置和

UI风格都差不多,所以如果

我们不统一管理就会出现覆

盖叠加现象,同时导致不好

控制,所以Google把Toa st设

计成为了系统级的窗口类

型,由

NotificationManagerService统

一队列管理。

在我们开发应用程序时使用

Toa st注意事项:

1

. 通过分析TN类的handler可

以发现,如果想在非UI线

程使用Toa st需要自行声明

Looper,否则运行会抛出

Looper相关的异常;UI线

程不需要,因为系统已经

帮忙声明。

2. 在使用Toa st时context参数

尽量使用

getApplicationContext(),

可以有效的防止静态引用

导致的内存泄漏。

3

. 有时候我们会发现Toa st弹

出过多就会延迟显示,因

为上面源码分析可以看见

Toast.makeText是一个静

态工厂方法,每次调用这

个方法都会产生一个新的

Toa st对象,当我们在这个

新new的对象上调用show

方法就会使这个对象加入

NotificationManagerService

管理的mToastQueue消息

显示队列里排队等候显

示;所以如果我们不每次

都产生一个新的Toa st对象

(使用单例来处理)就不

需要排队,也就能及时更

新了。

6

Android应用

Activity、Dialog、

PopWindow、Toa st窗

口显示机制总结

可以看见上面无论Ac itivty、

Dia log、PopWindow、Toast

的实质其实都是如下接口提

供的方法操作:

public interface ViewManager

{

public void addView(View

view, ViewGroup.LayoutParams

params);

public void updateViewLay

out(View view, ViewGroup.Layo

utParams params);

public void removeView(Vi

ew view);

}

整个应用各种窗口的显示都

离不开这三个方法而已,只

是token及type与Window是否

共用的问题。

工匠若

水 http://blog.csdn.net/yanbob

er 转载烦请注明出处,尊重

劳动成果】

转载请注明出

处:http://blog.csdn.net/guolin

_

blog/article/details/44996879

在Android所有常用的原生控

件当中,用法最复杂的应该

就是ListVie w了,它专门用

于处理那种内容元素很多,

手机屏幕无法展示出所有内

容的情况。ListVie w可以使

用列表的形式来展示内容,

超出屏幕部分的内容只需要

通过手指滑动就可以移动到

屏幕内了。

另外ListVie w还有一个非常

神奇的功能,我相信大家应

该都体验过,即使在

ListVie w中加载非常非常多

的数据,比如达到成百上千

条甚至更多,ListVie w都不

会发生OOM或者崩溃,而且

随着我们手指滑动来浏览更

多数据时,程序所占用的内

存竟然都不会跟着增长。那

么ListVie w是怎么实现这么

神奇的功能的呢?当初我就

抱着学习的心态花了很长时

间把ListVie w的源码通读了

一遍,基本了解了它的工作

原理,在感叹Google大神能

够写出如此精妙代码的同时

我也有所敬畏,因为

ListVie w的代码量比较大,

复杂度也很高,很难用文字

表达清楚,于是我就放弃了

把它写成一篇博客的想法。

那么现在回想起来这件事我

已经肠子都悔青了,因为没

过几个月时间我就把当初梳

理清晰的源码又忘的一干二

净。于是现在我又重新定下

心来再次把ListVie w的源码

重读了一遍,那么这次我一

定要把它写成一篇博客,分

享给大家的同时也当成我自

己的笔记吧。

首先我们先来看一下

ListVie w的继承结构,如下

图所示:

可以看到,ListVie w的继承

结构还是相当复杂的,它是

直接继承自的AbsListVie w,

而AbsListVie w有两个子实现

类,一个是ListVie w,另一

个就是Gr idVie w,因此我们

从这一点就可以猜出来,

ListVie w和Gr idVie w在工作原

理和实现上都是有很多共同

点的。然后AbsListVie w又继

承自AdapterView ,

AdapterView继承自

Vie wGroup,后面就是我们

所熟知的了。先把ListVie w

的继承结构了解一下,待会

儿有助于我们更加清晰地分

析代码。

Adapter的作用

Adapter相信大家都不会陌

生,我们平时使用ListVie w

的时候一定都会用到它。那

么话说回来大家有没有仔细

想过,为什么需要Adapter这

个东西呢?总感觉正因为有

了Adapter,ListVie w的使用

变得要比其它控件复杂得

多。那么这里我们就先来学

习一下Adapter到底起到了什

么样的一个作用。

其实说到底,控件就是为了

交互和展示数据用的,只不

过ListVie w更加特殊,它是

为了展示很多很多数据用

的,但是ListVie w只承担交

互和展示工作而已,至于这

些数据来自哪里,ListVie w

是不关心的。因此,我们能

设想到的最基本的ListVie w

工作模式就是要有一个

ListVie w控件和一个数据

源。

不过如果真的让ListVie w和

数据源直接打交道的话,那

ListVie w所要做的适配工作

就非常繁杂了。因为数据源

这个概念太模糊了,我们只

知道它包含了很多数据而

已,至于这个数据源到底是

什么样类型,并没有严格的

定义,有可能是数组,也有

可能是集合,甚至有可能是

数据库表中查询出来的游

标。所以说如果ListVie w真

的去为每一种数据源都进行

适配操作的话,一是扩展性

会比较差,内置了几种适配

就只有几种适配,不能动态

进行添加。二是超出了它本

身应该负责的工作范围,不

再是仅仅承担交互和展示工

作就可以了,这样ListVie w

就会变得比较臃肿。

那么显然Android开发团队是

不会允许这种事情发生的,

于是就有了Adapter这样一个

机制的出现。顾名思义,

Adapter是适配器的意思,它

在ListVie w和数据源之间起

到了一个桥梁的作用,

ListVie w并不会直接和数据

源打交道,而是会借助

Adapter这个桥梁来去访问真

正的数据源,与之前不同的

是,Adapter的接口都是统一

的,因此ListVie w不用再去

担心任何适配方面的问题。

而Adapter又是一个接口

(interface),它可以去实现各

种各样的子类,每个子类都

能通过自己的逻辑来去完成

特定的功能,以及与特定数

据源的适配操作,比如说

ArrayAdapter可以用于数组

和List类型的数据源适配,

SimpleCursorAdapter可以用

于游标类型的数据源适配,

这样就非常巧妙地把数据源

适配困难的问题解决掉了,

并且还拥有相当不错的扩展

性。简单的原理示意图如下

所示:

当然Adapter的作用不仅仅只

有数据源适配这一点,还有

一个非常非常重要的方法也

需要我们在Adapter当中去重

写,就是getView()方法,这

个在下面的文章中还会详细

讲到。

RecycleBin机制

那么在开始分析ListVie w的

源码之前,还有一个东西是

我们提前需要了解的,就是

RecycleBin机制,这个机制

也是ListVie w能够实现成百

上千条数据都不会OOM最重

要的一个原因。其实

RecycleBin的代码并不多,

只有300行左右,它是写在

AbsListVie w中的一个内部

类,所以所有继承自

AbsListVie w的子类,也就是

ListVie w和Gr idVie w,都可以

使用这个机制。那我们来看

一下RecycleBin中的主要代

码,如下所示:

/

**

*

The RecycleBin facilitates

reuse of views across layout

s. The RecycleBin

has two levels of storage:

*

ActiveViews and ScrapViews.

ActiveViews are

*

those views which were ons

creen at the start of a layou

t. By

*

construction, they are dis

playing current information.

At the end of

*

layout, all views in Activ

eViews are demoted to ScrapVi

ews. ScrapViews

*

are old views that could p

otentially be used by the ada

pter to avoid

*

allocating views unnecessa

rily.

*

*

@see android.widget.AbsLis

tView#setRecyclerListener(and

roid.widget.AbsListView.Recyc

lerListener)

*

@see android.widget.AbsLis

tView.RecyclerListener

*

/

class RecycleBin {

private RecyclerListener

mRecyclerListener;

/

**

*

The position of the fi

rst view stored in mActiveVie

ws.

*

/

private int mFirstActiveP

osition;

/

**

*

Views that were on scr

een at the start of layout. T

his array is

*

populated at the start

of layout, and at the end of

layout all view

*

in mActiveViews are mo

ved to mScrapViews. Views in

mActiveViews

*

represent a contiguous

range of Views, with positio

n of the first

*

view store in mFirstAc

tivePosition.

*

/

private View[] mActiveVie

ws = new View[0];

/

**

*

Unsorted views that ca

n be used by the adapter as a

convert view.

*

/

private ArrayList<View>[]

mScrapViews;

private int mViewTypeCoun

private ArrayList<View> m

t;

CurrentScrap;

/

**

*

Fill ActiveViews with

all of the children of the Ab

sListView.

*

*

*

@param childCount

The minimum

number of views mActiveViews

should hold

*

@param firstActivePosi

tion

*

The positio

n of the first view that will

be stored in

*

mActiveView

s

*

/

void fillActiveViews(int

childCount, int firstActivePo

sition) {

if (mActiveViews.leng

th < childCount) {

mActiveViews = ne

w View[childCount];

}

mFirstActivePosition

firstActivePosition;

final View[] activeVi

ews = mActiveViews;

for (int i = 0; i < c

hildCount; i++) {

View child = getC

=

hildAt(i);

AbsListView.Layou

tParams lp = (AbsListView.Lay

outParams) child.getLayoutPar

ams();

/

/ Don't put head

er or footer views into the s

crap heap

if (lp != null &&

lp.viewType != ITEM_VIEW_TYP

E_HEADER_OR_FOOTER) {

/

/ Note: We d

o place AdapterView.ITEM_VIEW

TYPE_IGNORE in

_

/

/ active vie

ws.

/

/ However, w

e will NOT place them into sc

rap views.

activeViews[i

]

= child;

}

}

}

/

**

*

Get the view correspon

ding to the specified positio

n. The view will

*

be removed from mActiv

eViews if it is found.

*

*

*

@param position

The positio

n to look up in mActiveViews

@return The view if it

is found, null otherwise

*

*

/

View getActiveView(int po

sition) {

int index = position

mFirstActivePosition;

final View[] activeVi

ews = mActiveViews;

if (index >= 0 && ind

-

ex < activeViews.length) {

final View match

=

activeViews[index];

activeViews[index

]

= null;

return match;

}

return null;

}

/

**

*

Put a view into the Sc

apViews list. These views are

unordered.

*

*

*

@param scrap

The view to

add

*

/

void addScrapView(View sc

rap) {

AbsListView.LayoutPar

ams lp = (AbsListView.LayoutP

arams) scrap.getLayoutParams(

)

;

if (lp == null) {

return;

}

/

/ Don't put header o

r footer views or views that

should be ignored

/

/ into the scrap hea

p

int viewType = lp.vie

if (!shouldRecycleVie

wType;

wType(viewType)) {

if (viewType != I

TEM_VIEW_TYPE_HEADER_OR_FOOTE

R) {

removeDetache

dView(scrap, false);

}

return;

}

if (mViewTypeCount ==

1

) {

dispatchFinishTem

poraryDetach(scrap);

mCurrentScrap.add

(scrap);

}

else {

dispatchFinishTem

poraryDetach(scrap);

mScrapViews[viewT

ype].add(scrap);

}

if (mRecyclerListener

= null) {

!

mRecyclerListener

.

onMovedToScrapHeap(scrap);

}

}

/

**

*

@return A view from th

e ScrapViews collection. Thes

e are unordered.

*

/

View getScrapView(int pos

ition) {

ArrayList<View> scrap

if (mViewTypeCount ==

scrapViews = mCur

Views;

1

) {

rentScrap;

int size = scrapV

iews.size();

if (size > 0) {

return scrapV

iews.remove(size - 1);

else {

return null;

}

}

}

else {

int whichScrap =

mAdapter.getItemViewType(posi

tion);

if (whichScrap >=

0

&& whichScrap < mScrapView

s.length) {

scrapViews =

mScrapViews[whichScrap];

int size = sc

rapViews.size();

if (size > 0)

return sc

{

rapViews.remove(size - 1);

}

}

}

return null;

}

public void setViewTypeCo

unt(int viewTypeCount) {

if (viewTypeCount < 1

)

{

throw new Illegal

ArgumentException("Can't have

a viewTypeCount < 1");

}

/

/ noinspection unche

cked

ArrayList<View>[] scr

apViews = new ArrayList[viewT

ypeCount];

for (int i = 0; i < v

iewTypeCount; i++) {

scrapViews[i] = n

ew ArrayList<View>();

}

mViewTypeCount = view

TypeCount;

mCurrentScrap = scrap

Views[0];

mScrapViews = scrapVi

ews;

}

}

这里的RecycleBin代码并不

全,我只是把最主要的几个

方法提了出来。那么我们先

来对这几个方法进行简单解

读,这对后面分析ListVie w

的工作原理将会有很大的帮

助。

fillActiveViews() 这个方

法接收两个参数,第一个

参数表示要存储的vie w的

数量,第二个参数表示

ListVie w中第一个可见元

素的position值。

RecycleBin当中使用

mAc tive Vie ws这个数组来

存储Vie w,调用这个方法

后就会根据传入的参数来

将ListVie w中的指定元素

存储到mAc tive Vie ws数组

当中。

getActiveView() 这个方法

和fillAc tive Vie ws( )是对应

的,用于从mAc tive Vie ws

数组当中获取数据。该方

法接收一个position参数,

表示元素在ListVie w当中

的位置,方法内部会自动

将position值转换成

mAc tive Vie ws数组对应的

下标值。需要注意的是,

mAc tive Vie ws当中所存储

的Vie w,一旦被获取了之

后就会从mAc tive Vie ws当

中移除,下次获取同样位

置的Vie w将会返回null,

也就是说mAc tive Vie ws不

能被重复利用。

addSc rapVi e w() 用于将一

个废弃的Vie w进行缓存,

该方法接收一个Vie w参

数,当有某个Vie w确定要

废弃掉的时候(比如滚动出

了屏幕),就应该调用这个

方法来对Vie w进行缓存,

RecycleBin当中使用

mScrapViews和

mCurrentScrap这两个List

来存储废弃Vie w。

getScrapVi ew 用于从废弃

缓存中取出一个Vie w,这

些废弃缓存中的Vie w是没

有顺序可言的,因此

getScrapView()方法中的算

法也非常简单,就是直接

从mCurrentScrap当中获取

尾部的一个scrap vie w进行

返回。

setViewTypeCount() 我们

都知道Adapter当中可以重

写一个

getViewTypeCount()来表

示ListVie w中有几种类型

的数据项,而

setViewTypeCount()方法的

作用就是为每种类型的数

据项都单独启用一个

RecycleBin缓存机制。实

际上,

getViewTypeCount()方法

通常情况下使用的并不是

很多,所以我们只要知道

RecycleBin当中有这样一

个功能就行了。

了解了RecycleBin中的主要

方法以及它们的用处之后,

下面就可以开始来分析

ListVie w的工作原理了,这

里我将还是按照以前分析源

码的方式来进行,即跟着主

线执行流程来逐步阅读并点

到即止,不然的话要是把

ListVie w所有的代码都贴出

来,那么本篇文章将会很长

很长了。

第一次Layout

不管怎么说,ListVie w即使

再特殊最终还是继承自Vie w

的,因此它的执行流程还将

会按照Vie w的规则来执行,

对于这方面不太熟悉的朋友

可以参考我之前写

的 An droi d视图绘制流程完

全解析,带你一步步深入

了解Vi ew(二) 。

Vie w的执行流程无非就分为

三步,onMeasure()用于测量

Vie w的大小,onLayout()用

于确定Vie w的布局,

onDraw()用于将Vie w绘制到

界面上。而在ListVie w当

中,onMeasure()并没有什么

特殊的地方,因为它终归是

一个Vie w,占用的空间最多

并且通常也就是整个屏幕。

onDraw()在ListVie w当中也

没有什么意义,因为

ListVie w本身并不负责绘

制,而是由ListVie w当中的

子元素来进行绘制的。那么

ListVie w大部分的神奇功能

其实都是在onLayout()方法

中进行的了,因此我们本篇

文章也是主要分析的这个方

法里的内容。

如果你到ListVie w源码中去

找一找,你会发现ListVie w

中是没有onLayout()这个方

法的,这是因为这个方法是

在ListVie w的父类

AbsListVie w中实现的,代码

如下所示:

/

**

*

Subclasses should NOT over

ride this method but {@link #

layoutChildren()}

*

*

instead.

/

@

Override

protected void onLayout(boole

an changed, int l, int t, int

r, int b) {

super.onLayout(changed, l

,

t, r, b);

mInLayout = true;

if (changed) {

int childCount = getC

hildCount();

for (int i = 0; i < c

hildCount; i++) {

getChildAt(i).for

ceLayout();

}

mRecycler.markChildre

nDirty();

}

layoutChildren();

mInLayout = false;

}

可以看到,onLayout()方法

中并没有做什么复杂的逻辑

操作,主要就是一个判断,

如果ListVie w的大小或者位

置发生了变化,那么changed

变量就会变成true,此时会

要求所有的子布局都强制进

行重绘。除此之外倒没有什

么难理解的地方了,不过我

们注意到,在第16行调用了

layoutChildren()这个方法,

从方法名上我们就可以猜出

这个方法是用来进行子元素

布局的,不过进入到这个方

法当中你会发现这是个空方

法,没有一行代码。这当然

是可以理解的了,因为子元

素的布局应该是由具体的实

现类来负责完成的,而不是

由父类完成。那么进入

ListVie w的layoutChildren()方

法,代码如下所示:

@

Override

protected void layoutChildren

() {

final boolean blockLayout

Requests = mBlockLayoutReques

ts;

if (!blockLayoutRequests)

{

mBlockLayoutRequests

=

)

true;

}

else {

return;

}

try {

super.layoutChildren(

;

{

invalidate();

if (mAdapter == null)

resetList();

invokeOnItemScrol

lListener();

return;

}

int childrenTop = mLi

stPadding.top;

int childrenBottom =

getBottom() - getTop() - mLis

tPadding.bottom;

int childCount = getC

hildCount();

int index = 0;

int delta = 0;

View sel;

View oldSel = null;

View oldFirst = null;

View newSel = null;

View focusLayoutResto

reView = null;

/

/ Remember stuff we

will need down below

switch (mLayoutMode)

{

case LAYOUT_SET_SELEC

index = mNextSele

TION:

ctedPosition - mFirstPosition

;

if (index >= 0 &&

index < childCount) {

newSel = getC

hildAt(index);

}

break;

case LAYOUT_FORCE_TOP

:

case LAYOUT_FORCE_BOT

TOM:

case LAYOUT_SPECIFIC:

case LAYOUT_SYNC:

break;

case LAYOUT_MOVE_SELE

CTION:

default:

/

/ Remember the p

reviously selected view

index = mSelected

Position - mFirstPosition;

if (index >= 0 &&

index < childCount) {

oldSel = getC

hildAt(index);

}

/

/ Remember the p

revious first child

oldFirst = getChi

ldAt(0);

if (mNextSelected

Position >= 0) {

delta = mNext

SelectedPosition - mSelectedP

osition;

}

/

/ Caution: newSe

l might be null

newSel = getChild

At(index + delta);

}

boolean dataChanged =

mDataChanged;

if (dataChanged) {

handleDataChanged

();

}

/

/ Handle the empty s

et by removing all views that

are visible

/

/ and calling it a d

ay

{

if (mItemCount == 0)

resetList();

invokeOnItemScrol

lListener();

return;

else if (mItemCount

}

!

= mAdapter.getCount()) {

throw new Illegal

StateException("The content o

f the adapter has changed but

"

+

"ListVi

ew did not receive a notifica

tion. Make sure the content o

f "

+

"your a

dapter is not modified from a

background thread, but only

"

+

"from t

he UI thread. [in ListView("

getId() + ", " + getClass()

+

+

") with

Adapter(" + mAdapter.getClas

s() + ")]");

}

setSelectedPositionIn

t(mNextSelectedPosition);

/ Pull all children

/

into the RecycleBin.

/

/ These views will b

e reused if possible

final int firstPositi

on = mFirstPosition;

final RecycleBin recy

cleBin = mRecycler;

/

/ reset the focus re

storation

View focusLayoutResto

reDirectChild = null;

/

/ Don't put header o

r footer views into the Recyc

ler. Those are

/

/ already cached in

mHeaderViews;

if (dataChanged) {

for (int i = 0; i

childCount; i++) {

recycleBin.ad

<

dScrapView(getChildAt(i));

if (ViewDebug

.

.

TRACE_RECYCLER) {

ViewDebug

trace(getChildAt(i),

V

iewDebug.RecyclerTraceType.MO

VE_TO_SCRAP_HEAP, index, i);

}

}

else {

recycleBin.fillAc

}

tiveViews(childCount, firstPo

sition);

}

/

/ take focus back to

us temporarily to avoid the

eventual

/

/ call to clear focu

s when removing the focused c

hild below

/

/ from messing thing

s up when ViewRoot assigns fo

cus back

/

/ to someone else

final View focusedChi

ld = getFocusedChild();

if (focusedChild != n

ull) {

/

/ TODO: in some

cases focusedChild.getParent(

== null

)

/

/ we can remembe

r the focused view to restore

after relayout if the

/

/ data hasn't ch

anged, or if the focused posi

tion is a header or footer

if (!dataChanged

|

| isDirectChildHeaderOrFoote

r(focusedChild)) {

focusLayoutRe

storeDirectChild = focusedChi

ld;

/

/ remember t

he specific view that had foc

us

focusLayoutRe

storeView = findFocus();

if (focusLayo

utRestoreView != null) {

/

/ tell i

t we are going to mess with i

t

focusLayo

utRestoreView.onStartTemporar

yDetach();

}

}

requestFocus();

}

/

/ Clear out old view

s

detachAllViewsFromPar

switch (mLayoutMode)

ent();

{

case LAYOUT_SET_SELEC

if (newSel != nul

sel = fillFro

TION:

l) {

mSelection(newSel.getTop(), c

hildrenTop, childrenBottom);

}

else {

sel = fillFro

mMiddle(childrenTop, children

Bottom);

}

break;

case LAYOUT_SYNC:

sel = fillSpecifi

c(mSyncPosition, mSpecificTop

)

;

break;

case LAYOUT_FORCE_BOT

TOM:

sel = fillUp(mIte

mCount - 1, childrenBottom);

adjustViewsUpOrDo

wn();

break;

case LAYOUT_FORCE_TOP

:

mFirstPosition =

0

;

sel = fillFromTop

(childrenTop);

adjustViewsUpOrDo

wn();

break;

case LAYOUT_SPECIFIC:

sel = fillSpecifi

c(reconcileSelectedPosition()

,

mSpecificTop);

break;

case LAYOUT_MOVE_SELE

CTION:

sel = moveSelecti

on(oldSel, newSel, delta, chi

ldrenTop, childrenBottom);

break;

default:

if (childCount ==

0

) {

if (!mStackFr

final int

omBottom) {

position = lookForSelectable

Position(0, true);

setSelect

edPositionInt(position);

sel = fil

lFromTop(childrenTop);

}

else {

final int

position = lookForSelectable

Position(mItemCount - 1, fals

e);

setSelect

edPositionInt(position);

sel = fil

lUp(mItemCount - 1, childrenB

ottom);

}

}

else {

if (mSelected

Position >= 0 && mSelectedPos

ition < mItemCount) {

sel = fil

lSpecific(mSelectedPosition,

o

ldSel == null ? childrenTop :

oldSel.getTop());

}

else if (mF

irstPosition < mItemCount) {

sel = fil

lSpecific(mFirstPosition,

o

ldFirst == null ? childrenTop

:

oldFirst.getTop());

else {

sel = fil

}

lSpecific(0, childrenTop);

}

}

break;

}

/

/ Flush any cached v

iews that did not get reused

above

recycleBin.scrapActiv

eViews();

if (sel != null) {

/

/ the current se

lected item should get focus

if items

/

/ are focusable

if (mItemsCanFocu

s && hasFocus() && !sel.hasFo

cus()) {

final boolean

focusWasTaken = (sel == focu

sLayoutRestoreDirectChild &&

focus

LayoutRestoreView.requestFocu

s()) || sel.requestFocus();

if (!focusWas

Taken) {

/

/ select

ed item didn't take focus, fi

ne, but still want

/

/ to mak

e sure something else outside

of the selected view

/

/ has fo

cus

final Vie

w focused = getFocusedChild()

;

if (focus

ed != null) {

focus

ed.clearFocus();

elector(sel);

}

positionS

}

}

else {

sel.setSe

mSelector

lected(false);

Rect.setEmpty();

}

else {

positionSelec

tor(sel);

}

mSelectedTop = se

l.getTop();

}

else {

if (mTouchMode >

TOUCH_MODE_DOWN && mTouchMode

TOUCH_MODE_SCROLL) {

View child =

<

getChildAt(mMotionPosition -

mFirstPosition);

if (child !=

null) positionSelector(child)

;

}

else {

mSelectedTop

=

.

0;

mSelectorRect

setEmpty();

}

/

/ even if there

is not selected position, we

may need to restore

/

/ focus (i.e. so

mething focusable in touch mo

de)

if (hasFocus() &&

focusLayoutRestoreView != nu

ll) {

focusLayoutRe

storeView.requestFocus();

}

}

/

/ tell focus view we

are done mucking with it, if

it is still in

/

/ our view hierarchy

.

if (focusLayoutRestor

eView != null

&

& focusLayou

tRestoreView.getWindowToken()

= null) {

!

focusLayoutRestor

eView.onFinishTemporaryDetach

();

}

mLayoutMode = LAYOUT_

NORMAL;

mDataChanged = false;

mNeedSync = false;

setNextSelectedPositi

onInt(mSelectedPosition);

updateScrollIndicator

s();

if (mItemCount > 0) {

checkSelectionCha

nged();

}

invokeOnItemScrollLis

tener();

}

finally {

if (!blockLayoutReque

sts) {

mBlockLayoutReque

sts = false;

}

}

}

这段代码比较长,我们挑重

点的看。首先可以确定的

是,ListVie w当中目前还没

有任何子Vie w,数据都还是

由Adapter管理的,并没有展

示到界面上,因此第19行

getChildCount()方法得到的值

肯定是0。接着在第81行会

根据dataChanged这个布尔型

的值来判断执行逻辑,

dataChanged只有在数据源发

生改变的情况下才会变成

true,其它情况都是false,因

此这里会进入到第90行的执

行逻辑,调用RecycleBin的

fillAc tive Vie ws( )方法。按理

来说,调用fillAc tive Vie w s( )

方法是为了将ListVie w的子

Vie w进行缓存的,可是目前

ListVie w中还没有任何的子

Vie w,因此这一行暂时还起

不了任何作用。

接下来在第114行会根据

mLayoutMode的值来决定布

局模式,默认情况下都是普

通模式

LAYOUT_NORMAL,因此

会进入到第140行的default语

句当中。而下面又会紧接着

进行两次if判断,childCount

目前是等于0的,并且默认

的布局顺序是从上往下,因

此会进入到第145行的

fillFr omTop( )方法,我们跟进

去瞧一瞧:

/

**

*

Fills the list from top to

bottom, starting with mFirst

Position

*

*

@param nextTop The locatio

n where the top of the first

item should be

*

*

*

drawn

@return The view that is c

urrently selected

*

/

private View fillFromTop(int

nextTop) {

mFirstPosition = Math.min

(mFirstPosition, mSelectedPos

ition);

mFirstPosition = Math.min

(mFirstPosition, mItemCount -

1

);

if (mFirstPosition < 0) {

mFirstPosition = 0;

}

return fillDown(mFirstPos

ition, nextTop);

}

从这个方法的注释中可以看

出,它所负责的主要任务就

是从mFirstPosition开始,自

顶至底去填充ListVie w。而

这个方法本身并没有什么逻

辑,就是判断了一下

mFirstPosition值的合法性,

然后调用fillDown()方法,那

么我们就有理由可以猜测,

填充ListVie w的操作是在

fillDown()方法中完成的。进

入fillDown()方法,代码如下

所示:

/

**

*

Fills the list from pos do

wn to the end of the list vie

w.

*

*

@param pos The first posit

ion to put in the list

*

*

@param nextTop The locatio

n where the top of the item a

ssociated with pos

*

*

*

should be drawn

@return The view that is c

urrently selected, if it happ

ens to be in the

*

range that we draw

.

*

/

private View fillDown(int pos

,

int nextTop) {

View selectedView = null;

int end = (getBottom() -

getTop()) - mListPadding.bott

om;

while (nextTop < end && p

os < mItemCount) {

/

/ is this the select

ed item?

boolean selected = po

s == mSelectedPosition;

View child = makeAndA

ddView(pos, nextTop, true, mL

istPadding.left, selected);

nextTop = child.getBo

ttom() + mDividerHeight;

if (selected) {

selectedView = ch

ild;

}

pos++;

}

return selectedView;

}

可以看到,这里使用了一个

while循环来执行重复逻辑,

一开始nextTop的值是第一个

子元素顶部距离整个

ListVie w顶部的像素值,pos

则是刚刚传入的

mFirstPosition的值,而end是

ListVie w底部减去顶部所得

的像素值,mItemCount则是

Adapter中的元素数量。因此

一开始的情况下nextTop必定

是小于end值的,并且pos也

是小于mItemCount值的。那

么每执行一次while循环,

pos的值都会加1,并且

nextTop也会增加,当

nextTop大于等于end时,也

就是子元素已经超出当前屏

幕了,或者pos大于等于

mItemCount时,也就是所有

Adapter中的元素都被遍历结

束了,就会跳出while循环。

那么while循环当中又做了什

么事情呢?值得让人留意的

就是第18行调用的

makeAndAddView()方法,进

入到这个方法当中,代码如

下所示:

/

**

*

Obtain the view and add it

to our list of children. The

view can be made

*

fresh, converted from an u

nused view, or used as is if

it was in the

*

*

*

recycle bin.

@param position Logical po

sition in the list

@param y Top or bottom edg

e of the view to add

@param flow If flow is tru

*

*

e, align top edge to y. If fa

lse, align bottom

*

*

edge to y.

@param childrenLeft Left e

dge where children should be

positioned

*

@param selected Is this po

sition selected?

*

@return View that was adde

d

*

/

private View makeAndAddView(i

nt position, int y, boolean f

low, int childrenLeft,

boolean selected) {

View child;

if (!mDataChanged) {

/

/ Try to use an exsi

ting view for this position

child = mRecycler.get

ActiveView(position);

if (child != null) {

/

/ Found it -- we

'

re using an existing child

/ This just need

s to be positioned

setupChild(child,

/

position, y, flow, childrenL

eft, selected, true);

return child;

}

}

/

/ Make a new view for th

is position, or convert an un

used view if possible

child = obtainView(positi

on, mIsScrap);

/

/ This needs to be posit

ioned and measured

setupChild(child, positio

n, y, flow, childrenLeft, sel

ected, mIsScrap[0]);

return child;

}

这里在第19行尝试从

RecycleBin当中快速获取一

个active vie w,不过很遗憾的

是目前RecycleBin当中还没

有缓存任何的Vie w,所以这

里得到的值肯定是null。那

么取得了null之后就会继续

向下运行,到第28行会调用

obtainView()方法来再次尝试

获取一个Vie w,这次的

obtainView()方法是可以保证

一定返回一个Vie w的,于是

下面立刻将获取到的Vie w传

入到了setupChild()方法当

中。那么obtainView()内部到

底是怎么工作的呢?我们先

进入到这个方法里面看一

下:

/

**

*

Get a view and have it sho

w the data associated with th

e specified

*

position. This is called w

hen we have already discovere

d that the view is

*

not available for reuse in

the recycle bin. The only ch

oices left are

*

converting an old view or

making a new one.

*

*

*

@param position

The position to

display

*

*

@param isScrap

Array of at lea

st 1 boolean, the first entry

will become true

*

if the returned

view was taken from the scra

p heap, false if

*

*

*

otherwise.

@return A view displaying

the data associated with the

specified position

*

/

View obtainView(int position,

boolean[] isScrap) {

isScrap[0] = false;

View scrapView;

scrapView = mRecycler.get

ScrapView(position);

View child;

if (scrapView != null) {

child = mAdapter.getV

iew(position, scrapView, this

)

;

if (child != scrapVie

mRecycler.addScra

w) {

pView(scrapView);

if (mCacheColorHi

nt != 0) {

child.setDraw

ingCacheBackgroundColor(mCach

eColorHint);

}

}

else {

isScrap[0] = true

;

dispatchFinishTem

poraryDetach(child);

}

}

else {

child = mAdapter.getV

iew(position, null, this);

if (mCacheColorHint !

=

0) {

child.setDrawingC

acheBackgroundColor(mCacheCol

orHint);

}

}

return child;

}

obtainView()方法中的代码并

不多,但却包含了非常非常

重要的逻辑,不夸张的说,

整个ListVie w中最重要的内

容可能就在这个方法里了。

那么我们还是按照执行流程

来看,在第19行代码中调用

了RecycleBin的

getScrapView()方法来尝试获

取一个废弃缓存中的Vie w,

同样的道理,这里肯定是获

取不到的,getScrapView()方

法会返回一个null。这时该

怎么办呢?没有关系,代码

会执行到第33行,调用

mAdapter的getView()方法来

去获取一个Vie w。那么

mAdapter是什么呢?当然就

是当前ListVie w关联的适配

器了。而getView()方法又是

什么呢?还用说吗,这个就

是我们平时使用ListVie w时

最最经常重写的一个方法

了,这里getView()方法中传

入了三个参数,分别是

position,null和this。

那么我们平时写ListVie w的

Adapter时,getView()方法通

常会怎么写呢?这里我举个

简单的例子:

@

Override

public View getView(int posit

ion, View convertView, ViewGr

oup parent) {

Fruit fruit = getItem(pos

ition);

View view;

if (convertView == null)

{

view = LayoutInflater

.

from(getContext()).inflate(r

esourceId, null);

}

}

else {

view = convertView;

ImageView fruitImage = (I

mageView) view.findViewById(R

.

id.fruit_image);

TextView fruitName = (Tex

tView) view.findViewById(R.id

.fruit_name);

fruitImage.setImageResour

ce(fruit.getImageId());

fruitName.setText(fruit.g

etName());

return view;

}

getView()方法接受的三个参

数,第一个参数position代表

当前子元素的的位置,我们

可以通过具体的位置来获取

与其相关的数据。第二个参

数convertView,刚才传入的

是null,说明没有

convertView可以利用,因此

我们会调用LayoutInflater的

inflate()方法来去加载一个布

局。接下来会对这个vie w进

行一些属性和值的设定,最

后将vie w返回。

那么这个Vie w也会作为

obtainView()的结果进行返

回,并最终传入到

setupChild()方法当中。其实

也就是说,第一次layout过

程当中,所有的子Vie w都是

调用LayoutInflater的inflate()

方法加载出来的,这样就会

相对比较耗时,但是不用担

心,后面就不会再有这种情

况了,那么我们继续往下

看:

/

**

*

Add a view as a child and

make sure it is measured (if

necessary) and

*

*

positioned properly.

*

@param child The view to a

dd

*

@param position The positi

on of this child

@param y The y position re

*

lative to which this view wil

l be positioned

*

@param flowDown If true, a

lign top edge to y. If false,

align bottom

*

*

edge to y.

@param childrenLeft Left e

dge where children should be

positioned

*

@param selected Is this po

sition selected?

@param recycled Has this v

*

iew been pulled from the recy

cle bin? If so it

*

does not need to be

remeasured.

*

/

private void setupChild(View

child, int position, int y, b

oolean flowDown, int children

Left,

boolean selected, boo

lean recycled) {

final boolean isSelected

=

selected && shouldShowSelec

tor();

final boolean updateChild

Selected = isSelected != chil

d.isSelected();

final int mode = mTouchMo

de;

final boolean isPressed =

mode > TOUCH_MODE_DOWN && mo

de < TOUCH_MODE_SCROLL &&

mMotionPosition =

=

position;

final boolean updateChild

Pressed = isPressed != child.

isPressed();

final boolean needToMeasu

re = !recycled || updateChild

Selected || child.isLayoutReq

uested();

/

/ Respect layout params

that are already in the view.

Otherwise make some up...

/

/ noinspection unchecked

AbsListView.LayoutParams

p = (AbsListView.LayoutParams

)

child.getLayoutParams();

if (p == null) {

p = new AbsListView.L

ayoutParams(ViewGroup.LayoutP

arams.MATCH_PARENT,

ViewGroup.Lay

outParams.WRAP_CONTENT, 0);

}

p.viewType = mAdapter.get

ItemViewType(position);

if ((recycled && !p.force

Add) || (p.recycledHeaderFoot

er &&

p.viewType == Ada

pterView.ITEM_VIEW_TYPE_HEADE

R_OR_FOOTER)) {

attachViewToParent(ch

ild, flowDown ? -1 : 0, p);

}

else {

p.forceAdd = false;

if (p.viewType == Ada

pterView.ITEM_VIEW_TYPE_HEADE

R_OR_FOOTER) {

p.recycledHeaderF

ooter = true;

}

addViewInLayout(child

,

;

flowDown ? -1 : 0, p, true)

}

if (updateChildSelected)

{

child.setSelected(isS

elected);

}

if (updateChildPressed) {

child.setPressed(isPr

essed);

}

if (needToMeasure) {

int childWidthSpec =

ViewGroup.getChildMeasureSpec

(mWidthMeasureSpec,

mListPadding.

left + mListPadding.right, p.

width);

int lpHeight = p.heig

ht;

int childHeightSpec;

if (lpHeight > 0) {

childHeightSpec =

MeasureSpec.makeMeasureSpec(

lpHeight, MeasureSpec.EXACTLY

)

;

}

else {

childHeightSpec =

MeasureSpec.makeMeasureSpec(

, MeasureSpec.UNSPECIFIED);

0

}

child.measure(childWi

dthSpec, childHeightSpec);

else {

}

cleanupLayoutState(ch

ild);

}

final int w = child.getMe

asuredWidth();

final int h = child.getMe

asuredHeight();

final int childTop = flow

Down ? y : y - h;

if (needToMeasure) {

final int childRight

=

childrenLeft + w;

final int childBottom

childTop + h;

child.layout(children

=

Left, childTop, childRight, c

hildBottom);

}

else {

child.offsetLeftAndRi

ght(childrenLeft - child.getL

eft());

child.offsetTopAndBot

tom(childTop - child.getTop()

)

;

}

if (mCachingStarted && !c

hild.isDrawingCacheEnabled())

{

child.setDrawingCache

Enabled(true);

}

}

setupChild()方法当中的代码

虽然比较多,但是我们只看

核心代码的话就非常简单

了,刚才调用obtainView()方

法获取到的子元素Vie w,这

里在第40行调用了

addViewInLayout()方法将它

添加到了ListVie w当中。那

么根据fillDown()方法中的

while循环,会让子元素Vie w

将整个ListVie w控件填满然

后就跳出,也就是说即使我

们的Adapter中有一千条数

据,ListVie w也只会加载第

一屏的数据,剩下的数据反

正目前在屏幕上也看不到,

所以不会去做多余的加载工

作,这样就可以保证

ListVie w中的内容能够迅速

展示到屏幕上。

那么到此为止,第一次

Layout过程结束。

第二次Layout

虽然我在源码中并没有找出

具体的原因,但如果你自己

做一下实验的话就会发现,

即使是一个再简单的Vie w,

在展示到界面上之前都会经

历至少两次onMeasure()和两

次onLayout()的过程。其实

这只是一个很小的细节,平

时对我们影响并不大,因为

不管是onMeasure()或者

onLayout()几次,反正都是

执行的相同的逻辑,我们并

不需要进行过多关心。但是

在ListVie w中情况就不一样

了,因为这就意味着

layoutChildren()过程会执行

两次,而这个过程当中涉及

到向ListVie w中添加子元

素,如果相同的逻辑执行两

遍的话,那么ListVie w中就

会存在一份重复的数据了。

因此ListVie w在

layoutChildren()过程当中做

了第二次Layout的逻辑处

理,非常巧妙地解决了这个

问题,下面我们就来分析一

下第二次Layout的过程。

其实第二次Layout和第一次

Layout的基本流程是差不多

的,那么我们还是从

layoutChildren()方法开始看

起:

@

Override

protected void layoutChildren

() {

final boolean blockLayout

Requests = mBlockLayoutReques

ts;

if (!blockLayoutRequests)

{

mBlockLayoutRequests

=

)

true;

}

else {

return;

}

try {

super.layoutChildren(

;

{

invalidate();

if (mAdapter == null)

resetList();

invokeOnItemScrol

lListener();

}

return;

int childrenTop = mLi

stPadding.top;

int childrenBottom =

getBottom() - getTop() - mLis

tPadding.bottom;

int childCount = getC

hildCount();

int index = 0;

int delta = 0;

View sel;

View oldSel = null;

View oldFirst = null;

View newSel = null;

View focusLayoutResto

reView = null;

/

/ Remember stuff we

will need down below

switch (mLayoutMode)

{

case LAYOUT_SET_SELEC

TION:

index = mNextSele

ctedPosition - mFirstPosition

;

if (index >= 0 &&

index < childCount) {

newSel = getC

hildAt(index);

}

break;

case LAYOUT_FORCE_TOP

:

case LAYOUT_FORCE_BOT

TOM:

case LAYOUT_SPECIFIC:

case LAYOUT_SYNC:

break;

case LAYOUT_MOVE_SELE

CTION:

default:

/

/ Remember the p

reviously selected view

index = mSelected

Position - mFirstPosition;

if (index >= 0 &&

index < childCount) {

oldSel = getC

hildAt(index);

}

/

/ Remember the p

revious first child

oldFirst = getChi

ldAt(0);

if (mNextSelected

Position >= 0) {

delta = mNext

SelectedPosition - mSelectedP

osition;

}

/

/ Caution: newSe

l might be null

newSel = getChild

At(index + delta);

}

boolean dataChanged =

mDataChanged;

if (dataChanged) {

handleDataChanged

();

}

/

/ Handle the empty s

et by removing all views that

are visible

/

/ and calling it a d

ay

{

if (mItemCount == 0)

resetList();

invokeOnItemScrol

lListener();

return;

else if (mItemCount

}

!

= mAdapter.getCount()) {

throw new Illegal

StateException("The content o

f the adapter has changed but

"

+

"ListVi

ew did not receive a notifica

tion. Make sure the content o

f "

+

"your a

dapter is not modified from a

background thread, but only

"

+

"from t

he UI thread. [in ListView("

getId() + ", " + getClass()

+

+

") with

Adapter(" + mAdapter.getClas

s() + ")]");

}

setSelectedPositionIn

t(mNextSelectedPosition);

/ Pull all children

into the RecycleBin.

/ These views will b

/

/

e reused if possible

final int firstPositi

on = mFirstPosition;

final RecycleBin recy

cleBin = mRecycler;

/

/ reset the focus re

storation

View focusLayoutResto

reDirectChild = null;

/

/ Don't put header o

r footer views into the Recyc

ler. Those are

/

/ already cached in

mHeaderViews;

if (dataChanged) {

for (int i = 0; i

childCount; i++) {

recycleBin.ad

<

dScrapView(getChildAt(i));

if (ViewDebug

.

TRACE_RECYCLER) {

ViewDebug

.

trace(getChildAt(i),

V

iewDebug.RecyclerTraceType.MO

VE_TO_SCRAP_HEAP, index, i);

}

}

else {

recycleBin.fillAc

}

tiveViews(childCount, firstPo

sition);

}

/

/ take focus back to

us temporarily to avoid the

eventual

/

/ call to clear focu

s when removing the focused c

hild below

/

/ from messing thing

s up when ViewRoot assigns fo

cus back

/

/ to someone else

final View focusedChi

ld = getFocusedChild();

if (focusedChild != n

ull) {

/

/ TODO: in some

cases focusedChild.getParent(

== null

)

/

/ we can remembe

r the focused view to restore

after relayout if the

/

/ data hasn't ch

anged, or if the focused posi

tion is a header or footer

if (!dataChanged

|

| isDirectChildHeaderOrFoote

r(focusedChild)) {

focusLayoutRe

storeDirectChild = focusedChi

ld;

/

/ remember t

he specific view that had foc

us

focusLayoutRe

storeView = findFocus();

if (focusLayo

utRestoreView != null) {

/ tell i

/

t we are going to mess with i

t

focusLayo

utRestoreView.onStartTemporar

yDetach();

}

}

requestFocus();

}

/

/ Clear out old view

s

detachAllViewsFromPar

switch (mLayoutMode)

case LAYOUT_SET_SELEC

ent();

{

TION:

if (newSel != nul

sel = fillFro

l) {

mSelection(newSel.getTop(), c

hildrenTop, childrenBottom);

}

else {

sel = fillFro

mMiddle(childrenTop, children

Bottom);

}

break;

case LAYOUT_SYNC:

sel = fillSpecifi

c(mSyncPosition, mSpecificTop

)

;

break;

case LAYOUT_FORCE_BOT

TOM:

sel = fillUp(mIte

mCount - 1, childrenBottom);

adjustViewsUpOrDo

wn();

:

break;

case LAYOUT_FORCE_TOP

mFirstPosition =

0

;

sel = fillFromTop

(childrenTop);

adjustViewsUpOrDo

wn();

break;

case LAYOUT_SPECIFIC:

sel = fillSpecifi

c(reconcileSelectedPosition()

,

mSpecificTop);

break;

case LAYOUT_MOVE_SELE

CTION:

sel = moveSelecti

on(oldSel, newSel, delta, chi

ldrenTop, childrenBottom);

break;

default:

if (childCount ==

0

) {

if (!mStackFr

omBottom) {

final int

position = lookForSelectable

Position(0, true);

setSelect

edPositionInt(position);

sel = fil

lFromTop(childrenTop);

}

else {

final int

position = lookForSelectable

Position(mItemCount - 1, fals

e);

setSelect

edPositionInt(position);

sel = fil

lUp(mItemCount - 1, childrenB

ottom);

}

}

else {

if (mSelected

Position >= 0 && mSelectedPos

ition < mItemCount) {

sel = fil

lSpecific(mSelectedPosition,

o

ldSel == null ? childrenTop :

oldSel.getTop());

}

else if (mF

irstPosition < mItemCount) {

sel = fil

lSpecific(mFirstPosition,

o

ldFirst == null ? childrenTop

:

oldFirst.getTop());

else {

}

sel = fil

lSpecific(0, childrenTop);

}

}

break;

}

/

/ Flush any cached v

iews that did not get reused

above

recycleBin.scrapActiv

eViews();

if (sel != null) {

/

/ the current se

lected item should get focus

if items

/

/ are focusable

if (mItemsCanFocu

s && hasFocus() && !sel.hasFo

cus()) {

final boolean

focusWasTaken = (sel == focu

sLayoutRestoreDirectChild &&

focus

LayoutRestoreView.requestFocu

s()) || sel.requestFocus();

if (!focusWas

Taken) {

/

/ select

ed item didn't take focus, fi

ne, but still want

/

/ to mak

e sure something else outside

of the selected view

/

/ has fo

cus

final Vie

w focused = getFocusedChild()

;

if (focus

ed != null) {

focus

ed.clearFocus();

}

positionS

elector(sel);

}

}

else {

sel.setSe

lected(false);

mSelector

Rect.setEmpty();

}

else {

positionSelec

tor(sel);

}

mSelectedTop = se

l.getTop();

}

else {

if (mTouchMode >

TOUCH_MODE_DOWN && mTouchMode

TOUCH_MODE_SCROLL) {

View child =

<

getChildAt(mMotionPosition -

mFirstPosition);

if (child !=

null) positionSelector(child)

;

}

else {

mSelectedTop

=

.

0;

mSelectorRect

setEmpty();

}

/

/ even if there

is not selected position, we

may need to restore

/

/ focus (i.e. so

mething focusable in touch mo

de)

if (hasFocus() &&

focusLayoutRestoreView != nu

ll) {

focusLayoutRe

storeView.requestFocus();

}

}

/

/ tell focus view we

are done mucking with it, if

it is still in

/

/ our view hierarchy

.

if (focusLayoutRestor

eView != null

&

& focusLayou

tRestoreView.getWindowToken()

= null) {

!

focusLayoutRestor

eView.onFinishTemporaryDetach

();

}

mLayoutMode = LAYOUT_

NORMAL;

mDataChanged = false;

mNeedSync = false;

setNextSelectedPositi

onInt(mSelectedPosition);

updateScrollIndicator

s();

if (mItemCount > 0) {

checkSelectionCha

nged();

}

invokeOnItemScrollLis

tener();

}

finally {

if (!blockLayoutReque

sts) {

mBlockLayoutReque

sts = false;

}

}

}

同样还是在第19行,调用

getChildCount()方法来获取子

Vie w的数量,只不过现在得

到的值不会再是0了,而是

ListVie w中一屏可以显示的

子Vie w数量,因为我们刚刚

在第一次Layout过程当中向

ListVie w添加了这么多的子

Vie w。下面在第90行调用了

RecycleBin的fillAc tive Vie w s( )

方法,这次效果可就不一样

了,因为目前ListVie w中已

经有子Vie w了,这样所有的

子Vie w都会被缓存到

RecycleBin的mAc tive Vie ws数

组当中,后面将会用到它

们。

接下来将会是非常非常重要

的一个操作,在第113行调

用了

detachAllViewsFromParent()

方法。这个方法会将所有

ListVie w当中的子Vie w全部

清除掉,从而保证第二次

Layout过程不会产生一份重

复的数据。那有的朋友可能

会问了,这样把已经加载好

的Vie w又清除掉,待会还要

再重新加载一遍,这不是严

重影响效率吗?不用担心,

还记得我们刚刚调用了

RecycleBin的fillAc tive Vie w s( )

方法来缓存子Vie w吗,待会

儿将会直接使用这些缓存好

的Vie w来进行加载,而并不

会重新执行一遍inflate过

程,因此效率方面并不会有

什么明显的影响。

那么我们接着看,在第141

行的判断逻辑当中,由于不

再等于0了,因此会进入到

else语句当中。而else语句中

又有三个逻辑判断,第一个

逻辑判断不成立,因为默认

情况下我们没有选中任何子

元素,mSelectedPosition应该

等于-1。第二个逻辑判断通

常是成立的,因为

mFirstPosition的值一开始是

等于0的,只要adapter中的

数据大于0条件就成立。那

么进入到fillSpe c ific ( )方法当

中,代码如下所示:

/

**

*

Put a specific item at a s

pecific location on the scree

n and then build

*

*

*

up and down from there.

@param position The refere

nce view to use as the starti

ng point

*

@param top Pixel offset fr

om the top of this view to th

e top of the

*

*

*

reference view.

@return The selected view,

or null if the selected view

is outside the

*

*

visible area.

/

private View fillSpecific(int

position, int top) {

boolean tempIsSelected =

position == mSelectedPosition

;

View temp = makeAndAddVie

w(position, top, true, mListP

adding.left, tempIsSelected);

/

/ Possibly changed again

in fillUp if we add rows abo

ve this one.

mFirstPosition = position

;

View above;

View below;

final int dividerHeight =

mDividerHeight;

if (!mStackFromBottom) {

above = fillUp(positi

on - 1, temp.getTop() - divid

erHeight);

/

/ This will correct

for the top of the first view

not touching the top of the

list

adjustViewsUpOrDown()

;

below = fillDown(posi

tion + 1, temp.getBottom() +

dividerHeight);

int childCount = getC

hildCount();

if (childCount > 0) {

correctTooHigh(ch

ildCount);

}

}

else {

below = fillDown(posi

tion + 1, temp.getBottom() +

dividerHeight);

/

/ This will correct

for the bottom of the last vi

ew not touching the bottom of

the list

adjustViewsUpOrDown()

;

above = fillUp(positi

on - 1, temp.getTop() - divid

erHeight);

int childCount = getC

hildCount();

if (childCount > 0) {

correctTooLow(ch

ildCount);

}

}

if (tempIsSelected) {

return temp;

}

else if (above != null)

{

return above;

else {

}

}

return below;

}

fillSpe c ific ( )这算是一个新方

法了,不过其实它和

fillUp( )、fillDown()方法功能

也是差不多的,主要的区别

在于,fillSpe c ific ( )方法会优

先将指定位置的子Vie w先加

载到屏幕上,然后再加载该

子Vie w往上以及往下的其它

子Vie w。那么由于这里我们

传入的position就是第一个子

Vie w的位置,于是

fillSpe c ific ( )方法的作用就基

本上和fillDown()方法是差不

多的了,这里我们就不去关

注太多它的细节,而是将精

力放在makeAndAddView() 方

法上面。再次回到

makeAndAddView()方法,代

码如下所示:

/

**

*

Obtain the view and add it

to our list of children. The

view can be made

*

fresh, converted from an u

nused view, or used as is if

it was in the

*

*

*

recycle bin.

@param position Logical po

sition in the list

*

@param y Top or bottom edg

e of the view to add

@param flow If flow is tru

*

e, align top edge to y. If fa

lse, align bottom

*

*

edge to y.

@param childrenLeft Left e

dge where children should be

positioned

*

@param selected Is this po

sition selected?

*

*

@return View that was adde

d

/

private View makeAndAddView(i

nt position, int y, boolean f

low, int childrenLeft,

boolean selected) {

View child;

if (!mDataChanged) {

/

/ Try to use an exsi

ting view for this position

child = mRecycler.get

ActiveView(position);

if (child != null) {

/

/ Found it -- we

re using an existing child

/ This just need

s to be positioned

setupChild(child,

'

/

position, y, flow, childrenL

eft, selected, true);

return child;

}

}

/

/ Make a new view for th

is position, or convert an un

used view if possible

child = obtainView(positi

on, mIsScrap);

/

/ This needs to be posit

ioned and measured

setupChild(child, positio

n, y, flow, childrenLeft, sel

ected, mIsScrap[0]);

return child;

}

仍然还是在第19行尝试从

RecycleBin当中获取Ac tive

Vie w,然而这次就一定可以

获取到了,因为前面我们调

用了RecycleBin 的

fillAc tive Vie ws( )方法来缓存

子Vie w。那么既然如此,就

不会再进入到第28行的

obtainView()方法,而是会直

接进入setupChild()方法当

中,这样也省去了很多时

间,因为如果在obtainView()

方法中又要去infalte布局的

话,那么ListVie w的初始加

载效率就大大降低了。

注意在第23行,setupChild()

方法的最后一个参数传入的

是true,这个参数表明当前

的Vie w是之前被回收过的,

那么我们再次回到

setupChild()方法当中:

/

**

*

Add a view as a child and

make sure it is measured (if

necessary) and

*

*

positioned properly.

*

@param child The view to a

dd

*

@param position The positi

on of this child

*

@param y The y position re

lative to which this view wil

l be positioned

*

@param flowDown If true, a

lign top edge to y. If false,

align bottom

*

*

edge to y.

@param childrenLeft Left e

dge where children should be

positioned

*

@param selected Is this po

sition selected?

@param recycled Has this v

*

iew been pulled from the recy

cle bin? If so it

*

does not need to be

remeasured.

*

/

private void setupChild(View

child, int position, int y, b

oolean flowDown, int children

Left,

boolean selected, boo

lean recycled) {

final boolean isSelected

=

selected && shouldShowSelec

tor();

final boolean updateChild

Selected = isSelected != chil

d.isSelected();

final int mode = mTouchMo

de;

final boolean isPressed =

mode > TOUCH_MODE_DOWN && mo

de < TOUCH_MODE_SCROLL &&

mMotionPosition =

=

position;

final boolean updateChild

Pressed = isPressed != child.

isPressed();

final boolean needToMeasu

re = !recycled || updateChild

Selected || child.isLayoutReq

uested();

/

/ Respect layout params

that are already in the view.

Otherwise make some up...

/

/ noinspection unchecked

AbsListView.LayoutParams

p = (AbsListView.LayoutParams

)

child.getLayoutParams();

if (p == null) {

p = new AbsListView.L

ayoutParams(ViewGroup.LayoutP

arams.MATCH_PARENT,

ViewGroup.Lay

outParams.WRAP_CONTENT, 0);

}

p.viewType = mAdapter.get

ItemViewType(position);

if ((recycled && !p.force

Add) || (p.recycledHeaderFoot

er &&

p.viewType == Ada

pterView.ITEM_VIEW_TYPE_HEADE

R_OR_FOOTER)) {

attachViewToParent(ch

ild, flowDown ? -1 : 0, p);

}

else {

p.forceAdd = false;

if (p.viewType == Ada

pterView.ITEM_VIEW_TYPE_HEADE

R_OR_FOOTER) {

p.recycledHeaderF

ooter = true;

}

addViewInLayout(child

,

;

flowDown ? -1 : 0, p, true)

}

if (updateChildSelected)

{

child.setSelected(isS

elected);

}

if (updateChildPressed) {

child.setPressed(isPr

essed);

}

if (needToMeasure) {

int childWidthSpec =

ViewGroup.getChildMeasureSpec

(mWidthMeasureSpec,

mListPadding.

left + mListPadding.right, p.

width);

int lpHeight = p.heig

ht;

int childHeightSpec;

if (lpHeight > 0) {

childHeightSpec =

MeasureSpec.makeMeasureSpec(

lpHeight, MeasureSpec.EXACTLY

)

;

}

else {

childHeightSpec =

MeasureSpec.makeMeasureSpec(

, MeasureSpec.UNSPECIFIED);

0

}

child.measure(childWi

dthSpec, childHeightSpec);

}

else {

cleanupLayoutState(ch

ild);

}

final int w = child.getMe

asuredWidth();

final int h = child.getMe

asuredHeight();

final int childTop = flow

Down ? y : y - h;

if (needToMeasure) {

final int childRight

childrenLeft + w;

final int childBottom

childTop + h;

child.layout(children

=

=

Left, childTop, childRight, c

hildBottom);

}

else {

child.offsetLeftAndRi

ght(childrenLeft - child.getL

eft());

child.offsetTopAndBot

tom(childTop - child.getTop()

)

;

}

if (mCachingStarted && !c

hild.isDrawingCacheEnabled())

{

child.setDrawingCache

Enabled(true);

}

}

可以看到,setupChild()方法

的最后一个参数是recycled,

然后在第32行会对这个变量

进行判断,由于recycled现在

是true,所以会执行

attachViewToParent()方法,

而第一次Layout过程则是执

行的else语句中的

addViewInLayout()方法。这

两个方法最大的区别在于,

如果我们需要向Vie wGroup

中添加一个新的子Vie w,应

该调用addViewInLayout() 方

法,而如果是想要将一个之

前detach的Vie w重新attach到

Vie wGroup上,就应该调用

attachViewToParent()方法。

那么由于前面在

layoutChildren()方法当中调

用了

detachAllViewsFromParent()

方法,这样ListVie w中所有

的子Vie w都是处于detach状

态的,所以这里

attachViewToParent()方法是

正确的选择。

经历了这样一个detach又

attach的过程,ListVie w中所

有的子Vie w又都可以正常显

示出来了,那么第二次

Layout过程结束。

滑动加载更多数据

经历了两次Layout过程,虽

说我们已经可以在ListVie w

中看到内容了,然而关于

ListVie w最神奇的部分我们

却还没有接触到,因为目前

ListVie w中只是加载并显示

了第一屏的数据而已。比如

说我们的Adapter当中有1000

条数据,但是第一屏只显示

了10条,ListVie w中也只有

10个子Vie w而已,那么剩下

的990是怎样工作并显示到

界面上的呢?这就要看一下

ListVie w滑动部分的源码

了,因为我们是通过手指滑

动来显示更多数据的。

由于滑动部分的机制是属于

通用型的,即ListVie w和

Gr idVie w都会使用同样的机

制,因此这部分代码就肯定

是写在AbsListVie w当中的

了。那么监听触控事件是在

onTouchEvent()方法当中进

行的,我们就来看一下

AbsListVie w中的这个方法:

@

Override

public boolean onTouchEvent(M

otionEvent ev) {

if (!isEnabled()) {

/

/ A disabled view th

at is clickable still consume

s the touch

/

/ events, it just do

esn't respond to them.

return isClickable()

| isLongClickable();

|

}

final int action = ev.get

Action();

View v;

int deltaY;

if (mVelocityTracker == n

ull) {

mVelocityTracker = Ve

locityTracker.obtain();

}

mVelocityTracker.addMovem

ent(ev);

switch (action & MotionEv

ent.ACTION_MASK) {

case MotionEvent.ACTION_D

OWN: {

mActivePointerId = ev

.

getPointerId(0);

final int x = (int) e

v.getX();

final int y = (int) e

v.getY();

int motionPosition =

pointToPosition(x, y);

if (!mDataChanged) {

if ((mTouchMode !

=

TOUCH_MODE_FLING) && (motio

nPosition >= 0)

&

& (getAd

apter().isEnabled(motionPosit

ion))) {

/

/ User click

ed on an actual view (and was

not stopping a

/

/ fling). It

might be a

/

/ click or a

scroll. Assume it is a click

until proven

/

/ otherwise

mTouchMode =

TOUCH_MODE_DOWN;

/

/ FIXME Debo

unce

if (mPendingC

heckForTap == null) {

mPendingC

heckForTap = new CheckForTap(

)

;

}

postDelayed(m

PendingCheckForTap, ViewConfi

guration.getTapTimeout());

}

else {

if (ev.getEdg

eFlags() != 0 && motionPositi

on < 0) {

/

/ If we

couldn't find a view to click

on, but the down

/

/ event

was touching

/

/ the ed

ge, we will bail out and try

again. This allows

ge correcting

/

/

/ the ed

/ code i

n ViewRoot to try to find a n

earby view to

/

/ select

return fa

lse;

}

if (mTouchMod

e == TOUCH_MODE_FLING) {

/

/ Stoppe

d a fling. It is a scroll.

createScr

ollingCache();

mTouchMod

e = TOUCH_MODE_SCROLL;

mMotionCo

rrection = 0;

motionPos

ition = findMotionRow(y);

reportScr

ollStateChange(OnScrollListen

er.SCROLL_STATE_TOUCH_SCROLL)

;

}

}

}

if (motionPosition >=

0

) {

/

/ Remember where

the motion event started

v = getChildAt(mo

tionPosition - mFirstPosition

)

;

mMotionViewOrigin

alTop = v.getTop();

}

mMotionX = x;

mMotionY = y;

mMotionPosition = mot

ionPosition;

mLastY = Integer.MIN_

VALUE;

break;

}

case MotionEvent.ACTION_M

OVE: {

final int pointerInde

x = ev.findPointerIndex(mActi

vePointerId);

final int y = (int) e

v.getY(pointerIndex);

deltaY = y - mMotionY

;

switch (mTouchMode) {

case TOUCH_MODE_DOWN:

case TOUCH_MODE_TAP:

case TOUCH_MODE_DONE_

WAITING:

/

/ Check if we ha

ve moved far enough that it l

ooks more like a

/

/ scroll than a

tap

startScrollIfNeed

ed(deltaY);

break;

case TOUCH_MODE_SCROL

if (PROFILE_SCROL

if (!mScrollP

L:

LING) {

rofilingStarted) {

Debug.sta

rtMethodTracing("AbsListViewS

croll");

mScrollPr

ofilingStarted = true;

}

}

if (y != mLastY)

{

deltaY -= mMo

int increment

tionCorrection;

alDeltaY = mLastY != Integer.

MIN_VALUE ? y - mLastY : delt

aY;

/

/ No need to

do all this work if we're no

t going to move

/

/ anyway

boolean atEdg

if (increment

atEdge =

e = false;

alDeltaY != 0) {

trackMotionScroll(deltaY, inc

rementalDeltaY);

}

/

/ Check to s

ee if we have bumped into the

scroll limit

if (atEdge &&

getChildCount() > 0) {

/ Treat

/

this like we're starting a ne

w scroll from the

/

/ curren

t

/

/ positi

on. This will let the user st

art scrolling back

/

/

/ into

/ conten

t immediately rather than nee

ding to scroll

/

/ back t

o the

/

/ point

where they hit the limit firs

t.

int motio

nPosition = findMotionRow(y);

if (motio

final

nPosition >= 0) {

View motionView = getChildAt

(motionPosition - mFirstPosit

ion);

mMoti

onViewOriginalTop = motionVie

w.getTop();

}

mMotionY

=

y;

mMotionPo

sition = motionPosition;

invalidat

e();

}

mLastY = y;

}

break;

}

break;

}

case MotionEvent.ACTION_U

P: {

switch (mTouchMode) {

case TOUCH_MODE_DOWN:

case TOUCH_MODE_TAP:

case TOUCH_MODE_DONE_

WAITING:

final int motionP

osition = mMotionPosition;

final View child

=

getChildAt(motionPosition -

mFirstPosition);

if (child != null

&

& !child.hasFocusable()) {

if (mTouchMod

e != TOUCH_MODE_DOWN) {

child.set

Pressed(false);

lick == null) {

}

if (mPerformC

mPerformC

lick = new PerformClick();

}

final AbsList

View.PerformClick performClic

k = mPerformClick;

performClick.

mChild = child;

performClick.

mClickMotionPosition = motion

Position;

performClick.

rememberWindowAttachCount();

mResurrectToP

osition = motionPosition;

if (mTouchMod

e == TOUCH_MODE_DOWN || mTouc

hMode == TOUCH_MODE_TAP) {

final Han

dler handler = getHandler();

if (handl

er != null) {

handl

er.removeCallbacks(mTouchMode

=

= TOUCH_MODE_DOWN ? mPendin

gCheckForTap

:

mPendingCheckForLongPres

s);

}

mLayoutMo

de = LAYOUT_NORMAL;

if (!mDat

aChanged && mAdapter.isEnable

d(motionPosition)) {

mTouc

hMode = TOUCH_MODE_TAP;

setSe

lectedPositionInt(mMotionPosi

tion);

layou

tChildren();

child

.

setPressed(true);

posit

setPr

if (m

D

ionSelector(child);

essed(true);

Selector != null) {

rawable d = mSelector.getCurr

ent();

i

f (d != null && d instanceof

TransitionDrawable) {

((TransitionDrawable) d).r

esetTransition();

}

}

postD

elayed(new Runnable() {

ublic void run() {

p

child.setPressed(false);

setPressed(false);

if (!mDataChanged) {

post(performClick);

}

mTouchMode = TOUCH_MODE_RE

}

ST;

}

, Vi

ewConfiguration.getPressedSta

teDuration());

}

else {

mTouc

hMode = TOUCH_MODE_REST;

}

return tr

ue;

}

else if (!m

DataChanged && mAdapter.isEna

bled(motionPosition)) {

post(perf

ormClick);

}

}

mTouchMode = TOUC

H_MODE_REST;

break;

case TOUCH_MODE_SCROL

L:

final int childCo

unt = getChildCount();

if (childCount >

0

) {

if (mFirstPos

ition == 0

&

& ge

tChildAt(0).getTop() >= mList

Padding.top

&

& mF

irstPosition + childCount < m

ItemCount

&

& ge

tChildAt(childCount - 1).getB

ottom() <= getHeight()

-

mListPadding.bottom) {

mTouchMod

e = TOUCH_MODE_REST;

reportScr

ollStateChange(OnScrollListen

er.SCROLL_STATE_IDLE);

}

else {

final Vel

ocityTracker velocityTracker

=

mVelocityTracker;

velocityT

racker.computeCurrentVelocity

(1000, mMaximumVelocity);

final int

initialVelocity = (int) velo

cityTracker

.

getYVelocity(mActivePointerId

)

;

if (Math.

abs(initialVelocity) > mMinim

umVelocity) {

if (m

FlingRunnable == null) {

m

FlingRunnable = new FlingRunn

able();

}

repor

tScrollStateChange(OnScrollLi

stener.SCROLL_STATE_FLING);

mFlin

gRunnable.start(-initialVeloc

ity);

}

else {

mTouc

hMode = TOUCH_MODE_REST;

repor

tScrollStateChange(OnScrollLi

stener.SCROLL_STATE_IDLE);

}

}

else {

}

mTouchMode =

TOUCH_MODE_REST;

reportScrollS

tateChange(OnScrollListener.S

CROLL_STATE_IDLE);

}

break;

}

setPressed(false);

/

/ Need to redraw sin

ce we probably aren't drawing

the selector

/

/ anymore

invalidate();

final Handler handler

=

getHandler();

if (handler != null)

{

handler.removeCal

lbacks(mPendingCheckForLongPr

ess);

}

if (mVelocityTracker

!

= null) {

recycle();

null;

mVelocityTracker.

mVelocityTracker

=

}

mActivePointerId = IN

VALID_POINTER;

if (PROFILE_SCROLLING

)

{

if (mScrollProfil

ingStarted) {

hodTracing();

Debug.stopMet

mScrollProfil

ingStarted = false;

}

}

break;

}

case MotionEvent.ACTION_C

ANCEL: {

mTouchMode = TOUCH_MO

DE_REST;

setPressed(false);

View motionView = thi

s.getChildAt(mMotionPosition

-

mFirstPosition);

if (motionView != nul

motionView.setPre

l) {

ssed(false);

}

clearScrollingCache()

;

final Handler handler

getHandler();

=

if (handler != null)

{

handler.removeCal

lbacks(mPendingCheckForLongPr

ess);

}

if (mVelocityTracker

!

= null) {

recycle();

null;

mVelocityTracker.

mVelocityTracker

=

}

mActivePointerId = IN

VALID_POINTER;

break;

}

case MotionEvent.ACTION_P

OINTER_UP: {

onSecondaryPointerUp(

final int x = mMotion

final int y = mMotion

final int motionPosit

ev);

X;

Y;

ion = pointToPosition(x, y);

if (motionPosition >=

0

) {

/

/ Remember where

the motion event started

v = getChildAt(mo

tionPosition - mFirstPosition

)

;

mMotionViewOrigin

alTop = v.getTop();

mMotionPosition =

motionPosition;

}

mLastY = y;

break;

}

}

return true;

}

这个方法中的代码就非常多

了,因为它所处理的逻辑也

非常多,要监听各种各样的

触屏事件。但是我们目前所

关心的就只有手指在屏幕上

滑动这一个事件而已,对应

的是ACTION_MOVE这个动

作,那么我们就只看这部分

代码就可以了。

可以看到,ACTION_MOVE

这个case里面又嵌套了一个

switch语句,是根据当前的

TouchMode来选择的。那这

里我可以直接告诉大家,当

手指在屏幕上滑动时,

TouchMode是等于

TOUCH_MODE_SCROLL这

个值的,至于为什么那又要

牵扯到另外的好几个方法,

这里限于篇幅原因就不再展

开讲解了,喜欢寻根究底的

朋友们可以自己去源码里找

一找原因。

这样的话,代码就应该会走

到第78行的这个case里面去

了,在这个case当中并没有

什么太多需要注意的东西,

唯一一点非常重要的就是第

92行调用的

trackMotionScroll()方法,相

当于我们手指只要在屏幕上

稍微有一点点移动,这个方

法就会被调用,而如果是正

常在屏幕上滑动的话,那么

这个方法就会被调用很多

次。那么我们进入到这个方

法中瞧一瞧,代码如下所

示:

boolean trackMotionScroll(int

deltaY, int incrementalDelta

Y) {

final int childCount = ge

tChildCount();

if (childCount == 0) {

return true;

}

final int firstTop = getC

hildAt(0).getTop();

final int lastBottom = ge

tChildAt(childCount - 1).getB

ottom();

final Rect listPadding =

mListPadding;

final int spaceAbove = li

stPadding.top - firstTop;

final int end = getHeight

() - listPadding.bottom;

final int spaceBelow = la

stBottom - end;

final int height = getHei

ght() - getPaddingBottom() -

getPaddingTop();

if (deltaY < 0) {

deltaY = Math.max(-(h

eight - 1), deltaY);

}

else {

deltaY = Math.min(hei

ght - 1, deltaY);

}

if (incrementalDeltaY < 0

)

{

incrementalDeltaY = M

ath.max(-(height - 1), increm

entalDeltaY);

}

else {

incrementalDeltaY = M

ath.min(height - 1, increment

alDeltaY);

}

final int firstPosition =

mFirstPosition;

if (firstPosition == 0 &&

firstTop >= listPadding.top

&

& deltaY >= 0) {

/

/ Don't need to move

views down if the top of the

first position

/

/ is already visible

return true;

}

if (firstPosition + child

Count == mItemCount && lastBo

ttom <= end && deltaY <= 0) {

/ Don't need to move

/

views up if the bottom of th

e last position

/

/ is already visible

return true;

}

final boolean down = incr

ementalDeltaY < 0;

final boolean inTouchMode

=

isInTouchMode();

if (inTouchMode) {

hideSelector();

}

final int headerViewsCoun

t = getHeaderViewsCount();

final int footerViewsStar

t = mItemCount - getFooterVie

wsCount();

int start = 0;

int count = 0;

if (down) {

final int top = listP

adding.top - incrementalDelta

Y;

for (int i = 0; i < c

hildCount; i++) {

final View child

=

getChildAt(i);

if (child.getBott

om() >= top) {

break;

else {

}

count++;

int position

=

>

firstPosition + i;

if (position

= headerViewsCount && positi

on < footerViewsStart) {

mRecycler

addScrapView(child);

.

}

}

}

}

else {

final int bottom = ge

tHeight() - listPadding.botto

m - incrementalDeltaY;

for (int i = childCou

nt - 1; i >= 0; i--) {

final View child

=

getChildAt(i);

if (child.getTop(

)

<= bottom) {

break;

else {

}

start = i;

count++;

int position

=

>

firstPosition + i;

if (position

= headerViewsCount && positi

on < footerViewsStart) {

mRecycler

.

addScrapView(child);

}

}

}

}

mMotionViewNewTop = mMoti

onViewOriginalTop + deltaY;

mBlockLayoutRequests = tr

ue;

if (count > 0) {

detachViewsFromParent

(start, count);

}

offsetChildrenTopAndBotto

m(incrementalDeltaY);

if (down) {

mFirstPosition += cou

nt;

}

invalidate();

final int absIncrementalD

eltaY = Math.abs(incrementalD

eltaY);

if (spaceAbove < absIncre

mentalDeltaY || spaceBelow <

absIncrementalDeltaY) {

fillGap(down);

}

if (!inTouchMode && mSele

ctedPosition != INVALID_POSIT

ION) {

final int childIndex

=

mSelectedPosition - mFirstP

osition;

if (childIndex >= 0 &

childIndex < getChildCount(

) {

&

)

positionSelector(

getChildAt(childIndex));

}

}

mBlockLayoutRequests = fa

lse;

invokeOnItemScrollListene

r();

}

awakenScrollBars();

return false;

这个方法接收两个参数,

deltaY表示从手指按下时的

位置到当前手指位置的距

离,incrementalDeltaY则表

示据上次触发event事件手指

在Y方向上位置的改变量,

那么其实我们就可以通过

incrementalDeltaY的正负值

情况来判断用户是向上还是

向下滑动的了。如第34行代

码所示,如果

incrementalDeltaY小于0,说

明是向下滑动,否则就是向

上滑动。

下面将会进行一个边界值检

测的过程,可以看到,从第

4

3行开始,当ListVie w向下

滑动的时候,就会进入一个

for循环当中,从上往下依次

获取子Vie w,第47行当中,

如果该子Vie w的bottom值已

经小于top值了,就说明这个

子Vie w已经移出屏幕了,所

以会调用RecycleBin的

addScrapView()方法将这个

Vie w加入到废弃缓存当中,

并将count计数器加1,计数

器用于记录有多少个子Vie w

被移出了屏幕。那么如果是

ListVie w向上滑动的话,其

实过程是基本相同的,只不

过变成了从下往上依次获取

子Vie w,然后判断该子Vie w

的top值是不是大于bottom值

了,如果大于的话说明子

Vie w已经移出了屏幕,同样

把它加入到废弃缓存中,并

将计数器加1。

接下来在第76行,会根据当

前计数器的值来进行一个

detach操作,它的作用就是

把所有移出屏幕的子Vie w全

部detach掉,在ListVie w的概

念当中,所有看不到的Vie w

就没有必要为它进行保存,

因为屏幕外还有成百上千条

数据等着显示呢,一个好的

回收策略才能保证ListVie w

的高性能和高效率。紧接着

在第78行调用了

offsetChildrenTopAndBottom(

)

方法,并将

incrementalDeltaY作为参数

传入,这个方法的作用是让

ListVie w中所有的子Vie w都

按照传入的参数值进行相应

的偏移,这样就实现了随着

手指的拖动,ListVie w的内

容也会随着滚动的效果。

然后在第84行会进行判断,

如果ListVie w中最后一个

Vie w的底部已经移入了屏

幕,或者ListVie w中第一个

Vie w的顶部移入了屏幕,就

会调用fillGa p( )方法,那么因

此我们就可以猜出fillGa p( )方

法是用来加载屏幕外数据

的,进入到这个方法中瞧一

瞧,如下所示:

/

**

*

Fills the gap left open by

a touch-scroll. During a tou

ch scroll,

*

children that remain on sc

reen are shifted and the othe

r ones are

*

discarded. The role of thi

s method is to fill the gap t

hus created by

performing a partial layou

*

t in the empty space.

*

*

*

@param down

true if the scr

oll is going down, false if i

t is going up

*

/

abstract void fillGap(boolean

down);

down参数用于表示ListVie w

是向下滑动还是向上滑动

的,可以看到,如果是向下

滑动的话就会调用fillDown()

方法,而如果是向上滑动的

话就会调用fillUp( )方法。那

么这两个方法我们都已经非

常熟悉了,内部都是通过一

个循环来去对ListVie w进行

填充,所以这两个方法我们

就不看了,但是填充

ListVie w会通过调用

makeAndAddView()方法来完

成,又是makeAndAddView()

方法,但这次的逻辑再次不

同了,所以我们还是回到这

个方法瞧一瞧:

void fillGap(boolean down) {

final int count = getChil

dCount();

if (down) {

final int startOffset

count > 0 ? getChildAt(cou

=

nt - 1).getBottom() + mDivide

rHeight :

getListPaddin

gTop();

fillDown(mFirstPositi

on + count, startOffset);

correctTooHigh(getChi

ldCount());

}

else {

final int startOffset

count > 0 ? getChildAt(0).

=

getTop() - mDividerHeight :

getHeight() -

getListPaddingBottom();

fillUp(mFirstPosition

-

1, startOffset);

correctTooLow(getChil

dCount());

}

}

不管怎么说,这里首先仍然

是会尝试调用RecycleBin的

getActiveView()方法来获取

子布局,只不过肯定是获取

不到的了,因为在第二次

Layout过程中我们已经从

mAc tive Vie ws中获取过了数

据,而根据RecycleBin的机

制,mAc tive Vie ws是不能够

重复利用的,因此这里返回

的值肯定是null。

既然getActiveView()方法返

回的值是null,那么就还是

会走到第28行的obtainView()

方法当中,代码如下所示:

/

**

*

Obtain the view and add it

to our list of children. The

view can be made

*

fresh, converted from an u

nused view, or used as is if

it was in the

*

*

*

recycle bin.

@param position Logical po

sition in the list

@param y Top or bottom edg

e of the view to add

@param flow If flow is tru

*

*

e, align top edge to y. If fa

lse, align bottom

*

*

edge to y.

@param childrenLeft Left e

dge where children should be

positioned

*

@param selected Is this po

sition selected?

*

*

@return View that was adde

d

/

private View makeAndAddView(i

nt position, int y, boolean f

low, int childrenLeft,

boolean selected) {

View child;

if (!mDataChanged) {

/

/ Try to use an exsi

ting view for this position

child = mRecycler.get

ActiveView(position);

if (child != null) {

/

/ Found it -- we

re using an existing child

/ This just need

s to be positioned

setupChild(child,

'

/

position, y, flow, childrenL

eft, selected, true);

return child;

}

}

/

/ Make a new view for th

is position, or convert an un

used view if possible

child = obtainView(positi

on, mIsScrap);

/

/ This needs to be posit

ioned and measured

setupChild(child, positio

n, y, flow, childrenLeft, sel

ected, mIsScrap[0]);

return child;

}

这里在第19行会调用

RecyleBin的getScrapView()方

法来尝试从废弃缓存中获取

一个Vie w,那么废弃缓存有

没有Vie w呢?当然有,因为

刚才在trackMotionScroll() 方

法中我们就已经看到了,一

旦有任何子Vie w被移出了屏

幕,就会将它加入到废弃缓

存中,而从obtainView()方法

中的逻辑来看,一旦有新的

数据需要显示到屏幕上,就

会尝试从废弃缓存中获取

Vie w。所以它们之间就形成

了一个生产者和消费者的模

式,那么ListVie w神奇的地

方也就在这里体现出来了,

不管你有任意多条数据需要

显示,ListVie w中的子Vie w

其实来来回回就那么几个,

移出屏幕的子Vie w会很快被

移入屏幕的数据重新利用起

来,因而不管我们加载多少

数据都不会出现OOM的情

况,甚至内存都不会有所增

加。

那么另外还有一点是需要大

家留意的,这里获取到了一

个scrapView,然后我们在第

2

2行将它作为第二个参数传

入到了Adapter的getView()方

法当中。那么第二个参数是

什么意思呢?我们再次看一

下一个简单的getView()方法

示例:

/

**

*

Get a view and have it sho

w the data associated with th

e specified

*

position. This is called w

hen we have already discovere

d that the view is

*

not available for reuse in

the recycle bin. The only ch

oices left are

*

converting an old view or

making a new one.

*

*

*

@param position

The position to

display

*

*

@param isScrap

Array of at lea

st 1 boolean, the first entry

will become true

*

if the returned

view was taken from the scra

p heap, false if

*

*

*

otherwise.

@return A view displaying

the data associated with the

specified position

*

/

View obtainView(int position,

boolean[] isScrap) {

isScrap[0] = false;

View scrapView;

scrapView = mRecycler.get

ScrapView(position);

View child;

if (scrapView != null) {

child = mAdapter.getV

iew(position, scrapView, this

)

;

if (child != scrapVie

w) {

mRecycler.addScra

pView(scrapView);

if (mCacheColorHi

nt != 0) {

child.setDraw

ingCacheBackgroundColor(mCach

eColorHint);

}

}

else {

isScrap[0] = true

;

dispatchFinishTem

poraryDetach(child);

}

}

else {

child = mAdapter.getV

iew(position, null, this);

if (mCacheColorHint !

=

0) {

child.setDrawingC

acheBackgroundColor(mCacheCol

orHint);

}

}

return child;

}

第二个参数就是我们最熟悉

的convertView呀,难怪平时

我们在写getView()方法是要

判断一下convertView是不是

等于null,如果等于null才调

用inflate()方法来加载布局,

不等于null就可以直接利用

convertView,因为

convertView就是我们之间利

用过的Vie w,只不过被移出

屏幕后进入到了废弃缓存

中,现在又重新拿出来使用

而已。然后我们只需要把

convertView中的数据更新成

当前位置上应该显示的数

据,那么看起来就好像是全

新加载出来的一个布局一

样,这背后的道理你是不是

已经完全搞明白了?

之后的代码又都是我们熟悉

的流程了,从缓存中拿到子

Vie w之后再调用setupChild()

方法将它重新attach到

ListVie w当中,因为缓存中

的Vie w也是之前从ListVie w

中detach掉的,这部分代码

就不再重复进行分析了。

为了方便大家理解,这里我

再附上一张图解说明:

那么到目前为止,我们就把

ListVie w的整个工作流程代

码基本分析结束了,文章比

较长,希望大家可以理解清

楚,下篇文章中会讲解我们

平时使用ListVie w时遇到的

问题,敬请期待。

第一时间获得博客更新提

醒,以及更多技术信息分

享,欢迎关注我的微信公

众号,扫一扫下方二维码

或搜索微信号guolin_blog,

即可关注。

[

]

()

http://blog.csdn.net/zhaokaiqia

ng1992/article/details/4942828

7

It’s right time to learn

Andr oid’s Framework !

前言

学习目标

写作方式

主要对象功能介绍

主要流程介绍

zygote是什么有什么作用

SystemServer是什么有什

么作用它与zygote的关系

是什么

ActivityManagerService是

什么什么时候初始化的有

什么作用

Launcher是什么什么时候

启动的

Instrumentation是什么和

Ac tivityThre a d是什么关系

如何理解AMS和

Ac tivityThre a d之间的

Binder通信

AMS接收到客户端的请求

之后会如何开启一个

Ac tivity

送给你们的彩蛋

不要使用

startActivityForResultintent

RESULT_OK

一个App的程序入口到底

是什么

整个App的主线程的消息

循环是在哪里创建的

Application是在什么时候

创建的onCreate什么时候

调用的

参考文章

Binder

zygote

ActivityThreadInstrumentati

onAMS

Launcher

结语

前言

一个App是怎么启动起来

的?

App的程序入口到底是哪

里?

Launcher到底是什么神奇

的东西?

听说还有个AMS的东西,

它是做什么的?

Binder是什么?他是如何

进行IPC通信的?

Ac tivity生命周期到底是什

么时候调用的?被谁调用

的?

等等…

你是不是还有很多类似的疑

问一直没有解决?没关系,

这篇文章将结合源码以及大

量的优秀文章,站在巨人的

肩膀上,更加通俗的来试着

解释一些问题。但是毕竟源

码繁多、经验有限,文中不

免会出现一些纰漏甚至是错

误,还恳请大家指出,互相

学习。

学习目标

1

. 了解从手机开机第一个

zygote进程创建,到点击

桌面上的图标,进入一个

App的完整流程,并且从

源码的角度了解到一个

Ac tivity的生命周期是怎么

回事

2

. 了解到

ActivityManagerServices(即

AMS)、Ac tivitySta c k、

Ac tivityThre a d、

Instrumentation等Android

framework中非常重要的

基础类的作用,及相互间

的关系

3

4

. 了解AMS与Ac tivityThre a d

之间利用Binder进行IPC通

信的过程,了解AMS和

Ac tivityThre a d在控制

Ac tivity生命周期起到的作

用和相互之间的配合

. 了解与Ac tivity相关的

framework层的其他琐碎

问题

写作方式

这篇文章我决定采用一问一

答的方式进行。

其实在这之前,我试过把每

个流程的代码调用过程,用

粘贴源代码的方式写在文章

里,但是写完一部分之后,

发现由于代码量太大,整篇

文章和老太太的裹脚布一样

—又臭又长,虽然每个重

要的操作可以显示出详细调

用过程,但是太关注于细节

反而导致从整体上不能很好

的把握。所以在原来的基础

之上进行了修改,对关键的

几个步骤进行重点介绍,力

求语言简洁,重点突出,从

而让大家在更高的层次上对

framework层有个认识,然后

结合后面我给出的参考资

料,大家就可以更加快速,

更加高效的了解这一块的整

体架构。

主要对象功能介绍

我们下面的文章将围绕着这

几个类进行介绍。可能你第

一次看的时候,印象不深,

不过没关系,当你跟随者我

读完这篇文章的时候,我会

在最后再次列出这些对象的

功能,相信那时候你会对这

些类更加的熟悉和深刻。

ActivityManagerServices,

简称AMS,服务端对象,

负责系统中所有Ac tivity的

生命周期

Ac tivityThre a d,App的真

正入口。当开启App之

后,会调用main()开始运

行,开启消息循环队列,

这就是传说中的UI线程或

者叫主线程。与

ActivityManagerServices配

合,一起完成Ac tivity的管

理工作

ApplicationThread,用来

实现

ActivityManagerService与

Ac tivityThre a d之间的交

互。在

ActivityManagerService需

要管理相关Application中

的Ac tivity的生命周期时,

通过ApplicationThread 的

代理对象与Ac tivityThre a d

通讯。

ApplicationThreadProxy,

是ApplicationThread在服

务器端的代理,负责和客

户端的ApplicationThread

通讯。AMS就是通过该代

理与Ac tivityThre a d进行通

信的。

Instrumentation,每一个应

用程序只有一个

Instrumentation对象,每个

Ac tivity内都有一个对该对

象的引用。Instrumentation

可以理解为应用进程的管

家,Ac tivityThre a d要创建

或暂停某个Ac tivity时,都

需要通过Instrumentation来

进行具体的操作。

Ac tivitySta c k,Ac tivity在

AMS的栈管理,用来记录

已经启动的Ac tivity的先后

关系,状态信息等。通过

Ac tivitySta c k决定是否需要

启动新的进程。

Ac tivityRe c ord,

Ac tivitySta c k的管理对象,

每个Ac tivity在AMS对应一

个Ac tivityRe c ord,来记录

Ac tivity的状态以及其他的

管理信息。其实就是服务

器端的Ac tivity对象的映

像。

TaskRecord,AMS抽象出

来的一个“任务”的概念,

是记录Ac tivityRe c ord的

栈,一个“Task”包含若干

个Ac tivityRe c ord。AMS用

TaskRecord确保Ac tivity启

动和退出的顺序。如果你

清楚Ac tivity的4种

launchMode,那么对这个

概念应该不陌生。

主要流程介绍

下面将按照App启动过程的

先后顺序,一问一答,来解

释一些事情。

让我们开始吧!

zygote是什么?有什么

作用?

首先,你觉得这个单词眼熟

不?当你的程序Crash的时

候,打印的红色log下面通常

带有这一个单词。

zygote意为“受精卵“。

Android是基于Linux系统

的,而在Linux中,所有的进

程都是由init进程直接或者是

间接fork出来的,zygote进程

也不例外。

在Android系统里面,zygote

是一个进程的名字。Android

是基于Linux System的,当

你的手机开机的时候,Linux

的内核加载完成之后就会启

动一个叫“ init“的进程。在

Linux System里面,所有的

进程都是由init进程fork出来

的,我们的zygote进程也不

例外。

我们都知道,每一个App其

实都是

一个单独的da lvik虚拟机

一个单独的进程

所以当系统里面的第一个

zygote进程运行之后,在这

之后再开启App,就相当于

开启一个新的进程。而为了

实现资源共用和更快的启动

速度,Android系统开启新进

程的方式,是通过fork第一

个zygote进程实现的。所以

说,除了第一个zygote进

程,其他应用所在的进程都

是zygote的子进程,这下你

明白为什么这个进程叫“受

精卵”了吧?因为就像是一

个受精卵一样,它能快速的

分裂,并且产生遗传物质一

样的细胞!

SystemServer是什么?

有什么作用?它与

zygote的关系是什么?

首先我要告诉你的是,

SystemServer也是一个进

程,而且是由zygote进程fork

出来的。

知道了SystemServer的本

质,我们对它就不算太陌生

了,这个进程是Android

Framework里面两大非常重

要的进程之一——另外一个

进程就是上面的zygote进

程。

为什么说SystemServer非常

重要呢?因为系统里面重要

的服务都是在这个进程里面

开启的,比如

ActivityManagerService、

PackageManagerService、

WindowManagerService等

等,看着是不是都挺眼熟

的?

那么这些系统服务是怎么开

启起来的呢?

在zygote开启的时候,会调

用ZygoteInit.main()进行初始

public static void main(Strin

g argv[]) {

.

..ignore some code...

/

/在加载首个zygote的时候,会

传入初始化参数,使得startSystemSe

rver = true

boolean startSystemServe

r = false;

for (int i = 1; i < argv

.

length; i++) {

if ("start-sy

stem-server".equals(argv[i]))

{

startSyst

emServer = true;

}

else if (ar

gv[i].startsWith(ABI_LIST_ARG

) {

)

abiList =

argv[i].substring(ABI_LIST_A

RG.length());

}

else if (ar

gv[i].startsWith(SOCKET_NAME_

ARG)) {

socketNam

e = argv[i].substring(SOCKET_

NAME_ARG.length());

else {

throw new

}

RuntimeException("Unknown co

mmand line argument: " + argv

[

i]);

}

}

.

..ignore some co

de...

/

/开始fork我们的System

Server进程

if (startSystemServer) {

startSystemSe

rver(abiList, socketName);

}

.

..ignore some code...

}

我们看下startSystemServer()

做了些什么

/**留着这个注释,就是为了说明Syste

mServer确实是被fork出来的

*

Prepare the arguments

and fork for the system serve

r process.

*

/

private static boolean st

artSystemServer(String abiLis

t, String socketName)

throws MethodAndA

rgsCaller, RuntimeException {

.

..ignore some code.

.

.

/

/留着这段注释,就是为了

说明上面ZygoteInit.main(String

argv[])里面的argv就是通过这种方

式传递进来的

/

* Hardcoded command

line to start the system serv

er */

String args[] = {

"

"

"

--setuid=1000",

--setgid=1000",

--setgroups=1001

,

7

0

1002,1003,1004,1005,1006,100

,1008,1009,1010,1018,1032,30

1,3002,3003,3006,3007",

"

--capabilities="

+

capabilities + "," + capab

ilities,

"

"

--runtime-init",

--nice-name=syst

em_server",

"

com.android.serv

er.SystemServer",

}

;

int pid;

try {

parsedArgs = new

ZygoteConnection.Arguments(ar

gs);

ZygoteConnection.

applyDebuggerSystemProperty(p

arsedArgs);

ZygoteConnection.

applyInvokeWithSystemProperty

(parsedArgs);

/

/确实是fuck出来的吧,我

没骗你吧~不对,是fork出来的 -_-||

|

/

* Request to for

k the system server process *

/

pid = Zygote.fork

SystemServer(

parsedArg

s.uid, parsedArgs.gid,

parsedArg

s.gids,

parsedArg

s.debugFlags,

null,

parsedArg

s.permittedCapabilities,

parsedArg

s.effectiveCapabilities);

catch (IllegalArgum

entException ex) {

throw new Runtime

}

Exception(ex);

}

/

* For child process

*

/

if (pid == 0) {

if (hasSecondZygo

te(abiList)) {

waitForSecond

aryZygote(socketName);

}

handleSystemServe

rProcess(parsedArgs);

}

return true;

}

ActivityManagerService是什

么?什么时候初始化的?有

什么作用?

ActivityManagerService,简

称AMS,服务端对象,负责

系统中所有Ac tivity的生命周

期。

ActivityManagerService进行

初始化的时机很明确,就是

在SystemServer进程开启的

时候,就会初始化

ActivityManagerService。从

下面的代码中可以看到

public final class SystemServ

er {

/

/zygote的主入口

public static void main(S

tring[] args) {

new SystemServer().ru

n();

}

public SystemServer() {

/

/ Check for factory

test mode.

mFactoryTestMode = Fa

ctoryTest.getMode();

}

private void run() {

.

/

..ignore some code..

.

/加载本地系统服务库,并

进行初始化

System.loadLibrary("a

ndroid_servers");

nativeInit();

/

/ 创建系统上下文

createSystemContext()

;

/

/初始化SystemServiceM

anager对象,下面的系统服务开启都需

要调用SystemServiceManager.sta

rtService(Class<T>),这个方法通

过反射来启动对应的服务

mSystemServiceManager

=

new SystemServiceManager(m

SystemContext);

/

/开启服务

try {

startBootstrapSer

vices();

();

startCoreServices

startOtherService

catch (Throwable ex

Slog.e("System",

s();

}

)

{

"

*

****************************

*************");

Slog.e("System",

"

************ Failure startin

g system services", ex);

throw ex;

}

.

..ignore some code..

.

}

/

/初始化系统上下文对象mSystem

Context,并设置默认的主题,mSyste

mContext实际上是一个ContextImpl

对象。调用ActivityThread.system

Main()的时候,会调用ActivityThr

ead.attach(true),而在attach()

里面,则创建了Application对象,并

调用了Application.onCreate()。

private void createSystem

Context() {

ActivityThread activi

tyThread = ActivityThread.sys

temMain();

mSystemContext = acti

vityThread.getSystemContext()

;

mSystemContext.setThe

me(android.R.style.Theme_Devi

ceDefault_Light_DarkActionBar

)

;

}

/

/在这里开启了几个核心的服务,

因为这些服务之间相互依赖,所以都放在

了这个方法里面。

private void startBootstr

apServices() {

.

..ignore some code..

.

/

/初始化ActivityManage

rService

mActivityManagerServi

ce = mSystemServiceManager.st

artService(

ActivityManag

erService.Lifecycle.class).ge

tService();

mActivityManagerServi

ce.setSystemServiceManager(mS

ystemServiceManager);

/

/初始化PowerManagerSe

rvice,因为其他服务需要依赖这个Ser

vice,因此需要尽快的初始化

mPowerManagerService

=

mSystemServiceManager.start

Service(PowerManagerService.c

lass);

/

/ 现在电源管理已经开启,

ActivityManagerService负责电源

管理功能

mActivityManagerServi

ce.initPowerManagement();

/

/ 初始化DisplayManage

rService

mDisplayManagerServic

e = mSystemServiceManager.sta

rtService(DisplayManagerServi

ce.class);

/

/初始化PackageManagerServ

ice

mPackageManagerService =

PackageManagerService.main(mS

ystemContext, mInstaller,

mFactoryTestMode != Fa

ctoryTest.FACTORY_TEST_OFF, m

OnlyCore);

.

..ignore some code...

}

}

经过上面这些步骤,我们的

ActivityManagerService对象

已经创建好了,并且完成了

成员变量初始化。而且在这

之前,调用

createSystemContext()创

建系统上下文的时候,也已

经完成了mSystemContext和

Ac tivityThre a d的创建。注

意,这是系统进程开启时的

流程,在这之后,会开启系

统的

Launcher程序,完成系统界

面的加载与显示。你是否会

好奇,我为什么说AMS是服

务端对象?下面我给你介绍

下Android系统里面的服务器

和客户端的概念。

其实服务器客户端的概念不

仅仅存在于Web开发中,在

Android的框架设计中,使用

的也是这一种模式。服务器

端指的就是所有App共用的

系统服务,比如我们这里提

到的

ActivityManagerService,和

前面提到的

PackageManagerService、

WindowManagerService等

等,这些基础的系统服务是

被所有的App公用的,当某

个App想实现某个操作的时

候,要告诉这些系统服务,

比如你想打开一个App,那

么我们知道了包名和

Ma inAc tivity类名之后就可以

打开

Intent intent = new Intent(In

tent.ACTION_MAIN);

intent.addCategory(Intent.CAT

EGORY_LAUNCHER);

ComponentName cn = new Compon

entName(packageName, classNam

e);

intent.setComponent(cn);

startActivity(intent);

但是,我们的App通过调用

sta rtAc tivity()并不能直接打

开另外一个App,这个方法

会通过一系列的调用,最后

还是告诉AMS说:“我要打

开这个App ,

我知道他的住址和名字,你

帮我打开吧!”所以是AMS

来通知zygote进程来fork一个

新进程,来开启我们的目标

App的。这就像是浏览器想

要打开一

个超链接一样,浏览器把网

页地址发送给服务器,然后

还是服务器把需要的资源文

件发送给客户端的。知道了

Android Framework的客户端

服务器架构之后,我们还需

要了解一件事情,那就是我

们的App 和

AMS(SystemServer进程)还有

zygote进程分属于三个独立

的进程,他们之间如何通信

呢?

App与AMS通过Binder进行

IPC通信,

AMS(SystemServer进程)与

zygote通过Socket进行IPC通

信。

那么AMS有什么用呢?在前

面我们知道了,如果想打开

一个App的话,需要AMS去

通知zygote进程,除此之

外,其实所有的Ac tivity的开

启、暂停、关闭都需要AMS

来控制,所以我们说,AMS

负责系统中所有Ac tivity的生

命周期。

在Android系统中,任何一个

Ac tivity的启动都是由AMS和

应用程序进程(主要是

Ac tivityThre a d)相互配合来

完成的。AMS服务统一调度

系统中所有进程的Ac tivity启

动,而每个Ac tivity的启动过

程则由其所属的进程具体来

完成。

这样说你可能还是觉得比较

抽象,没关系,下面有一部

分是专门来介绍AMS与

Ac tivityThre a d如何一起合作

控制Ac tivity的生命周期的。

Launcher是什么?什么

时候启动的?

当我们点击手机桌面上的图

标的时候,App就由

Launcher开始启动了。但

是,你有没有思考过

Launcher到底是一个什么东

西?

Launcher本质上也是一个应

用程序,和我们的App一

样,也是继承自Ac tivity

packages/apps/Launcher2/src/

com/android/launcher2/Launc

her.java

public final class Launcher e

xtends Activity

implements View.OnCli

ckListener, OnLongClickListen

er, LauncherModel.Callbacks,

View.OnTou

chListener {

}

Launcher实现了点击、长按

等回调接口,来接收用户的

输入。既然是普通的App,

那么我们的开发经验在这里

就仍然适用,比如,我们点

击图标的时候

是怎么开启的应用呢?如

果让你,你怎么做这个功能

呢?捕捉图标点击事件,然

后sta rtAc tivity()发送对应的

Intent请求呗!是的,

Launcher也是这么

做的,就是这么easy!

那么到底是处理的哪个对象

的点击事件呢?既然

Launcher是App,并且有界

面,那么肯定有布局文件

呀,是的,我找到了布局文

件launcher.xml

<

FrameLayout

xmlns:android="http://sch

emas.android.com/apk/res/andr

oid"

xmlns:launcher="http://sc

hemas.android.com/apk/res/com

.

android.launcher"

android:id="@+id/launcher

>

"

<

com.android.launcher2.Dr

agLayer

android:id="@+id/drag

android:layout_width=

match_parent"

android:layout_height

_

"

layer"

=

"match_parent"

android:fitsSystemWin

dows="true">

<

!-- Keep these behin

d the workspace so that they

are not visible when

we go into AllAp

ps -->

<

include

android:id="@+id/

dock_divider"

layout="@layout/w

orkspace_divider"

android:layout_ma

rginBottom="@dimen/button_bar

height"

_

android:layout_gr

avity="bottom" />

<

include

android:id="@+id/

paged_view_indicator"

layout="@layout/s

croll_indicator"

android:layout_gr

avity="bottom"

android:layout_ma

rginBottom="@dimen/button_bar

height" />

_

<

!-- The workspace co

ntains 5 screens of cells -->

com.android.launcher

.Workspace

<

2

android:id="@+id/

workspace"

android:layout_wi

dth="match_parent"

android:layout_he

ight="match_parent"

android:paddingSt

art="@dimen/workspace_left_pa

dding"

android:paddingEn

d="@dimen/workspace_right_pad

ding"

android:paddingTo

p="@dimen/workspace_top_paddi

ng"

android:paddingBo

ttom="@dimen/workspace_bottom

_

padding"

launcher:defaultS

creen="2"

launcher:cellCoun

tX="@integer/cell_count_x"

launcher:cellCoun

tY="@integer/cell_count_y"

launcher:pageSpac

ing="@dimen/workspace_page_sp

acing"

launcher:scrollIn

dicatorPaddingLeft="@dimen/wo

rkspace_divider_padding_left"

launcher:scrollIn

dicatorPaddingRight="@dimen/w

orkspace_divider_padding_righ

t">

<

include android:

id="@+id/cell1" layout="@layo

ut/workspace_screen" />

<

include android:

id="@+id/cell2" layout="@layo

ut/workspace_screen" />

<

include android:

id="@+id/cell3" layout="@layo

ut/workspace_screen" />

<

include android:

id="@+id/cell4" layout="@layo

ut/workspace_screen" />

<

include android:

id="@+id/cell5" layout="@layo

ut/workspace_screen" />

<

/com.android.launche

r2.Workspace>

.

<

..ignore some code...

/com.android.launcher2.D

ragLayer>

</FrameLayout>

为了方便查看,我删除了很

多代码,从上面这些我们应

该可以看出一些东西来:

Launcher大量使用标签来实

现界面的复用,而且定义了

很多的自定义控

件实现界面效果,

dock_divider从布局的参数声

明上可以猜出,是底部操作

栏和上面图标布局的分割

线,而paged_view_indicator

则是页面指示器,

和App首次进入的引导页下

面的界面引导是一样的道

理。当然,我们最关心的是

Workspace这个布局,因为

注释里面说在这里面包含了

5

个屏幕的单元

格,想必你也猜到了,这个

就是在首页存放我们图标的

那五个界面(不同的ROM会

做不同的DIY,数量不固

定)。

接下来,我们应该打开

workspace_screen布局,看

看里面有什么东东。

workspace_screen.xml

<

com.android.launcher2.CellLa

yout

xmlns:android="http://sch

emas.android.com/apk/res/andr

oid"

xmlns:launcher="http://sc

hemas.android.com/apk/res/com

.

android.launcher"

android:layout_width="wra

p_content"

android:layout_height="wr

ap_content"

android:paddingStart="@di

men/cell_layout_left_padding"

android:paddingEnd="@dime

n/cell_layout_right_padding"

android:paddingTop="@dime

n/cell_layout_top_padding"

android:paddingBottom="@d

imen/cell_layout_bottom_paddi

ng"

android:hapticFeedbackEna

bled="false"

launcher:cellWidth="@dime

n/workspace_cell_width"

launcher:cellHeight="@dim

en/workspace_cell_height"

launcher:widthGap="@dimen

/

workspace_width_gap"

launcher:heightGap="@dime

n/workspace_height_gap"

launcher:maxGap="@dimen/w

orkspace_max_gap" />

里面就一个CellLayout,也是

一个自定义布局,那么我们

就可以猜到了,既然可以存

放图标,那么这个自定义的

布局很有可能是继承自

Vie wGroup或者

是其子类,实际上,

CellLayout确实是继承自

Vie wGroup。在CellLayout里

面,只放了一个子Vie w,那

就是

ShortcutAndWidgetContainer

。从名字也

可以看出来,

ShortcutAndWidgetContainer

这个类就是用来存放快捷图

标和Widge t小部件的,那么

里面放的是什么对象呢?

在桌面上的图标,使用的是

Bubble Te xtVie w对象,这个

对象在Te xtVie w的基础之

上,添加了一些特效,比如

你长按移动图标的时候,图

标位置会出现一个背景(不同

版本的效果不同),所以我们

找到Bubble Te xtVie w对象的

点击事件,就可以找到

Launcher如何开启一个App

了。

除了在桌面上有图标之外,

在程序列表中点击图标,也

可以开启对应的程序。这里

的图标使用的不是

Bubble Te xtVie w对象,而是

PagedViewIcon对象,我们如

果找到它的点击事件,就也

可以找到Launcher如何开启

一个App 。

其实说这么多,和今天的主

题隔着十万八千里,上面这

些东西,你有兴趣就看,没

兴趣就直接跳过,不知道不

影响这篇文章阅读。

Bubble Te xtVie w的点击事件

在哪里呢?我来告诉你:在

Launcher. onClick(View v)里

面。

/

**

*

Launches the intent re

ferred by the clicked shortcu

t

*

/

public void onClick(View

v) {

.

..ignore some code

.

..

Object tag = v.getTa

if (tag instanceof Sh

g();

ortcutInfo) {

/

/ Open shortcut

final Intent inte

nt = ((ShortcutInfo) tag).int

ent;

int[] pos = new i

nt[2];

v.getLocationOnSc

reen(pos);

intent.setSourceB

ounds(new Rect(pos[0], pos[1]

,

pos[0] +

v.getWidth(), pos[1] + v.getH

eight()));

/

/开始开启Activity咯~

boolean success =

startActivitySafely(v, inten

t, tag);

if (success && v

instanceof BubbleTextView) {

mWaitingForRe

sume = (BubbleTextView) v;

mWaitingForRe

sume.setStayPressed(true);

}

}

else if (tag instan

ceof FolderInfo) {

//如果点击的是图标文

件夹,就打开文件夹

if (v instanceof

FolderIcon) {

FolderIcon fi

(FolderIcon) v;

handleFolderC

=

lick(fi);

}

}

else if (v == mAllA

ppsButton) {

.

}

..ignore some code..

.

}

从上面的代码我们可以看

到,在桌面上点击快捷图标

的时候,会调用

startActivitySafely(v, intent

,

tag);

那么从程序列表界面,点击

图标的时候会发生什么呢?

实际上,程序列表界面使用

的是

AppsCustomizePagedView对

象,所以我在这个类里面找

到了

onClic k(Vie w v)。

com.android.launcher2.AppsC

ustomizeP agedView.java

/

**

*

The Apps/Customize page th

at displays all the applicati

ons, widgets, and shortcuts.

*

/

public class AppsCustomizePag

edView extends PagedViewWithD

raggableItems implements

View.OnClickListener,

View.OnKeyListener, DragSour

ce,

PagedViewIcon.Pressed

Callback, PagedViewWidget.Sho

rtPressListener,

LauncherTransitionabl

e {

@

Override

public void onClick(View

v) {

.

..ignore some code.

.

.

if (v instanceof Page

dViewIcon) {

mLauncher.updateW

allpaperVisibility(true);

mLauncher.startAc

tivitySafely(v, appInfo.inten

t, appInfo);

}

else if (v instance

of PagedViewWidget) {

..ignore so

.

me code..

}

}

}

可以看到,调用的是

mLauncher.startActivitySafely

(v, appInfo.intent, appInfo);

和上面一样!这叫什么?这

叫殊途同归!

所以咱们现在又明白了一件

事情:不管从哪里点击图

标,调用的都是

Launcher. startActivitySafely()

下面我们就可以一步步的

来看一下

La unc he r. sta rtAc tivitySa fe ly

()到底做了什么事情。

boolean startActivitySafely(V

iew v, Intent intent, Object

tag) {

boolean success = fal

se;

try {

success = startAc

tivity(v, intent, tag);

}

catch (ActivityNotF

oundException e) {

Toast.makeText(th

is, R.string.activity_not_fou

nd, Toast.LENGTH_SHORT).show(

)

;

Log.e(TAG, "Unabl

e to launch. tag=" + tag + "

intent=" + intent, e);

}

return success;

}

调用了startActivity(v,

intent, tag)

boolean startActivity(View v,

Intent intent, Object tag) {

intent.addFlags(Inten

t.FLAG_ACTIVITY_NEW_TASK);

try {

boolean useLaunch

Animation = (v != null) &&

!

intent.h

asExtra(INTENT_EXTRA_IGNORE_L

AUNCH_ANIMATION);

if (useLaunchAnim

ation) {

if (user == n

ull || user.equals(android.os

.

Process.myUserHandle())) {

startActi

vity(intent, opts.toBundle())

;

}

else {

launcherA

pps.startMainActivity(intent.

getComponent(), user,

i

ntent.getSourceBounds(),

o

pts.toBundle());

}

}

else {

if (user == n

ull || user.equals(android.os

.

Process.myUserHandle())) {

startActi

vity(intent);

}

else {

launcherA

pps.startMainActivity(intent.

getComponent(), user,

i

ntent.getSourceBounds(), null

)

;

}

}

return true;

}

catch (SecurityExce

ption e) {

.

}

..

return false;

}

这里会调用

Ac tivity. sta r tAc tivity( inte nt,

opts.toBundle()),这个方

法熟悉吗?这就是我们经

常用到的

Ac tivity. sta r tAc tivity( Inte nt)

的重载函数。

而且由于设置了

intent.addFlags(Intent.FLAG_

ACTIVITY_NEW_TASK);

所以这个Ac tivity会添加到

一个新的Task栈中,而

且,sta rtAc tivity()调用的

其实是

startActivityForResult()这

个方法。

@

Override

public void startActivity

(Intent intent, @Nullable Bun

dle options) {

if (options != null)

{

startActivityForR

esult(intent, -1, options);

}

else {

/

/ Note we want t

o go through this call for co

mpatibility with

/

/ applications t

hat may have overridden the m

ethod.

startActivityForR

esult(intent, -1);

}

}

所以我们现在明确了,

Launcher中开启一个

App,其实和我们在

Ac tivity中直接

sta rtAc tivity()基本一样,

都是调用了

Ac tivity. sta r tAc tivityFor Re s

ult()。

Instrumentation是什

么?和ActivityThread

是什么关系?

还记得前面说过的

Instrumentation对象吗?每个

Ac tivity都持有Instrumentation

对象的一个引用,但是整个

进程只会存在一个

Instrumentation对象。当

startActivityForResult()调用之

后,实际上还是调用了

mInstrumentation.execStartAc

tivity( )

public void startActivityForR

esult(Intent intent, int requ

estCode, @Nullable Bundle opt

ions) {

if (mParent == null)

{

Instrumentation.A

ctivityResult ar =

mInstrumentat

ion.execStartActivity(

this, mMa

inThread.getApplicationThread

(), mToken, this,

intent, r

equestCode, options);

if (ar != null) {

mMainThread.s

endActivityResult(

mToken, m

EmbeddedID, requestCode, ar.g

etResultCode(),

ar.getRes

ultData());

}

.

..ignore some co

de...

ll) {

}

else {

if (options != nu

/

/当现在的Act

ivity有父Activity的时候会调用,但

是在startActivityFromChild()内

部实际还是调用的mInstrumentation

.

execStartActivity()

mParent.start

ActivityFromChild(this, inten

t, requestCode, options);

}

else {

mParent.start

ActivityFromChild(this, inten

t, requestCode);

}

}

.

..ignore some code.

.

.

}

下面是

mInstrumentation.execStartAc

tivity( )的实现

public ActivityResult execSta

rtActivity(

Context who, IBin

der contextThread, IBinder to

ken, Activity target,

Intent intent, in

t requestCode, Bundle options

)

{

IApplicationThread wh

oThread = (IApplicationThread

)

contextThread;

.

..ignore some co

de...

try {

intent.migrateExt

raStreamToClipData();

intent.prepareToL

eaveProcess();

int result = Acti

vityManagerNative.getDefault(

)

.

startActivit

y(whoThread, who.getBasePacka

geName(), intent,

inten

t.resolveTypeIfNeeded(who.get

ContentResolver()),

token

,

target != null ? target.mEm

beddedID : null,

reque

stCode, 0, null, options);

checkStartActivit

yResult(result, intent);

}

catch (RemoteExcept

ion e) {

}

}

return null;

所以当我们在程序中调用

sta rtAc tivity()的 时候,实际

上调用的是Instrumentation的

相关的方法。

Instrumentation意为“仪器”,

我们先看一下这个类里面包

含哪些方法吧

我们可以看到,这个类里面

的方法大多数和Application

和Ac tivity有关,是的,这个

类就是完成对Application和

Ac tivity初始化和生命周期的

工具类。比如说,我单独挑

一个callActivityOnCreate() 让

你看看

public void callActivityOnCre

ate(Activity activity, Bundle

icicle) {

prePerformCreate(acti

vity);

activity.performCreat

e(icicle);

postPerformCreate(act

ivity);

}

activity.performCreate(icicle);

这一行代码熟悉吗?这一行

里面就调用了传说中的

Ac tivity的入口函数

onCreate(),不信?接着往下

Activity.performCreate()

final void performCreate(Bund

le icicle) {

onCreate(icicle);

mActivityTransitionSt

ate.readState(icicle);

performCreateCommon()

;

}

没骗你吧,onCreate在这里

调用了吧。但是有一件事情

必须说清楚,那就是这个

Instrumentation类这么重要,

为啥我在开发的过程中,没

有发现他的踪迹呢?

是的,Instrumentation这个类

很重要,对Ac tivity生命周期

方法的调用根本就离不开

他,他可以说是一个大管

家,但是,这个大管家比较

害羞,是一个女的,管内不

管外,是老板娘 ~

那么你可能要问了,老板是

谁呀?

老板当然是大名鼎鼎的

Ac tivityThre a d了!

Ac tivityThre a d你都没听说

过?那你肯定听说过传说中

的UI线程吧?是的,这就是

UI线程。我们前面说过,

App和AMS是通过Binder传

递信息的,那么

Ac tivityThre a d就是专门与

AMS的外交工作的。

AMS说:“ActivityThread,

你给我暂停一个Ac tivity!”

Ac tivityThre a d就说:“没问

题!”然后转身和

Instrumentation说:“老婆,

AMS让暂停一个Ac tivity,我

这里忙着呢,你快去帮我把

这事办了把~”

于是,Instrumentation就去把

事儿搞定了。

所以说,AMS是董事会,负

责指挥和调度的,

Ac tivityThre a d是老板,虽然

说家里的事自己说了算,但

是需要听从AMS的指挥,而

Instrumentation则是老板娘,

负责家里的大事小事,但是

一般不抛头露面,听一家之

主Ac tivityThre a d的安排。

如何理解AMS和

ActivityThread之间的

Binder通信?

前面我们说到,在调用

sta rtAc tivity()的时候,实际

上调用的是

mInstrumentation.execStartAct

ivity()

但是到这里还没完呢!里面

又调用了下面的方法

ActivityManagerNative.getDefa

ult()

.

startActivity

这里的

ActivityManagerNative.getDef

ault返回的就是

ActivityManagerService的远

程接口,即

ActivityManagerP roxy。

怎么知道的呢?往下看

public abstract class Activit

yManagerNative extends Binder

implements IActivityManager

{

/

/从类声明上,我们可以看到Activit

yManagerNative是Binder的一个子

类,而且实现了IActivityManager接

static public IActivityManag

er getDefault() {

return gDefault.get()

;

}

/

/通过单例模式获取一个IActivityM

anager对象,这个对象通过asInterf

ace(b)获得

private static final Singlet

on<IActivityManager> gDefault

=

new Singleton<IActivityMan

ager>() {

protected IActivityMa

nager create() {

IBinder b = Servi

ceManager.getService("activit

y");

if (false) {

Log.v("Activi

tyManager", "default service

binder = " + b);

}

IActivityManager

am = asInterface(b);

if (false) {

Log.v("Activi

tyManager", "default service

=

" + am);

}

}

return am;

}

;

}

/

/最终返回的还是一个ActivityMana

gerProxy对象

static public IActivityManage

r asInterface(IBinder obj) {

if (obj == null) {

return null;

}

IActivityManager in =

(IActivityManager

)

obj.queryLocalInterface(desc

riptor);

if (in != null) {

return in;

}

/

/这里面的Binder类型的obj参

数会作为ActivityManagerProxy的

成员变量保存为mRemote成员变量,负

责进行IPC通信

return new ActivityMa

nagerProxy(obj);

}

}

再看

ActivityManagerP roxy.startAct

ivity( ),在这里面做的事情就

是IPC通信,利用Binder对

象,调用transact(),把所有

需要的参数封装成Parcel对

,向AMS发送数据进行通

信。

public int startActivity(IApp

licationThread caller, String

callingPackage, Intent inten

t,

String resolvedTy

pe, IBinder resultTo, String

resultWho, int requestCode,

int startFlags, P

rofilerInfo profilerInfo, Bun

dle options) throws RemoteExc

eption {

Parcel data = Parcel.

obtain();

Parcel reply = Parcel

.

obtain();

data.writeInterfaceTo

ken(IActivityManager.descript

or);

data.writeStrongBinde

r(caller != null ? caller.asB

inder() : null);

data.writeString(call

ingPackage);

intent.writeToParcel(

data, 0);

data.writeString(reso

lvedType);

data.writeStrongBinde

r(resultTo);

data.writeString(resu

ltWho);

data.writeInt(request

Code);

data.writeInt(startFl

ags);

if (profilerInfo != n

ull) {

data.writeInt(1);

profilerInfo.writ

eToParcel(data, Parcelable.PA

RCELABLE_WRITE_RETURN_VALUE);

}

else {

data.writeInt(0);

}

if (options != null)

{

data.writeInt(1);

options.writeToPa

rcel(data, 0);

}

else {

data.writeInt(0);

}

mRemote.transact(STAR

T_ACTIVITY_TRANSACTION, data,

reply, 0);

reply.readException()

;

int result = reply.re

adInt();

reply.recycle();

data.recycle();

return result;

}

Binder本质上只是一种底层

通信方式,和具体服务没有

关系。为了提供具体服务,

Server必须提供一套接口函

数以便Client通过远程访问使

用各种服务。

这时通常采用Proxy设计模

式:将接口函数定义在一个

抽象类中,Server和Client都

会以该抽象类为基类实现所

有接口函数,所不同的是

Server端是真正

的功能实现,而Client端是对

这些函数远程调用请求的包

装。

为了更方便的说明客户端和

服务器之间的Binder通信,

下面以

ActivityManagerServices和他

在客户端的代理类

ActivityManagerP roxy为例。

ActivityManagerServices和

ActivityManagerP roxy都实现

了同一个接口——

IAc tivityMa na ge r。

class ActivityManagerProxy im

plements IActivityManager{}

public final class ActivityMa

nagerService extends Activity

ManagerNative{}

public abstract class Activit

yManagerNative extends Binder

implements IActivityManager{}

虽然都实现了同一个接口,

但是代理对象

ActivityManagerP roxy并不会

对这些方法进行真正地实

现,ActivityManagerP roxy只

是通过这种方式对方法

的参数进行打包(因为都实现

了相同接口,所以可以保证

同一个方法有相同的参数,

即对要传输给服务器的数据

进行打包),真正实现的是

ActivityManagerService。

但是这个地方并不是直接由

客户端传递给服务器,而是

通过Binder驱动进行中转。

其实我对Binder驱动并不熟

悉,我们就把他当做一个中

转站就OK,客户端调用

ActivityManagerP roxy接口里

面的方法,把数据传送给

Binder驱动,然后Binder驱动

就会把这些东西转发给服务

器的

ActivityManagerServices,由

ActivityManagerServices去真

正的实施具体的操作。

但是Binder只能传递数据,

并不知道是要调用

ActivityManagerServices的哪

个方法,所以在数据中会添

加方法的唯一标识码,比如

前面的sta rtAc tivity()方法:

public int startActivity(IApp

licationThread caller, String

callingPackage, Intent inten

t,

String resolvedTy

pe, IBinder resultTo, String

resultWho, int requestCode,

int startFlags, P

rofilerInfo profilerInfo, Bun

dle options) throws RemoteExc

eption {

Parcel data = Parcel.

obtain();

Parcel reply = Parcel

.

.

obtain();

.

..ignore some code..

mRemote.transact(STAR

T_ACTIVITY_TRANSACTION, data,

reply, 0);

reply.readException()

;

int result = reply.re

adInt();

reply.recycle();

data.recycle();

return result;

}

上面的

START_ACTIVITY_TRANS

ACTION就是方法标示,data

是要传输给Binder驱动的数

据,reply则接受操作的返回

值。

客户端:

ActivityManagerP roxy

=

====>Binder驱动=====>

ActivityManagerService:服

务器

而且由于继承了同样的公共

接口类,

ActivityManagerP roxy提供了

与ActivityManagerService一

样的函数原型,使用户感觉

不出Server是运行在本地还

是远端,从而可以更加方便

的调用这些重要的系统服

务。

但是!这里Binder通信是单

方向的,即从

ActivityManagerP roxy指向

ActivityManagerService的,

如果AMS想要通知

Ac tivityThre a d做一些事情,

应该咋办呢?

还是通过Binder通信,不过

是换了另外一对,换成了

ApplicationThread和

ApplicationThreadProxy。

客户端:ApplicationThread

<

=====Binder驱动<=====

ApplicationThreadProxy:服

务器

他们也都实现了相同的接口

IApplicationThread

private class ApplicationThre

ad extends ApplicationThreadN

ative {}

public abstract class Appli

cationThreadNative extends Bi

nder implements IApplicationT

hread{}

class ApplicationThreadProx

y implements IApplicationThre

ad {}

剩下的就不必多说了吧,和

前面一样。

AMS接收到客户端的

请求之后,会如何开

启一个Activity ?

OK,至此,点击桌面图标

调用sta rtAc tivity(),终于把

数据和要开启Ac tivity的请求

发送到了AMS了。说了这么

多,其实这些都在一瞬间完

成了,下面咱们研究下AMS

到底做了什么。

注:前方有高能的方法调用

链,如果你现在累了,请先

喝杯咖啡或者是上趟厕所休

息下

AMS收到sta r tAc tivity的请求

之后,会按照如下的方法链

进行调用

调用sta rtAc tivity()

@

Override

public final int startAct

ivity(IApplicationThread call

er, String callingPackage,

Intent intent, St

ring resolvedType, IBinder re

sultTo, String resultWho, int

requestCode,

int startFlags, P

rofilerInfo profilerInfo, Bun

dle options) {

return startActivityA

sUser(caller, callingPackage,

intent, resolvedType, result

To,

resultWho, reques

tCode, startFlags, profilerIn

fo, options,

UserHandle.getCal

lingUserId());

}

调用sta rtAc tivityAsUse r()

@

Override

public final int startAct

ivityAsUser(IApplicationThrea

d caller, String callingPacka

ge,

Intent intent, St

ring resolvedType, IBinder re

sultTo, String resultWho, int

requestCode,

int startFlags, P

rofilerInfo profilerInfo, Bun

dle options, int userId) {

..ignore some co

.

de...

return mStackSupervis

or.startActivityMayWait(calle

r, -1, callingPackage, intent

,

resolvedType,

null, null, resultTo, result

Who, requestCode, startFlags,

profilerInfo,

null, null, options, userId,

null, null);

}

在这里又出现了一个新对象

Ac tivitySta c kSupe rvisor,通

过这个类可以实现对

Ac tivitySta c k的部分操作。

final int startActivityMayWai

t(IApplicationThread caller,

int callingUid,

String callingPac

kage, Intent intent, String r

esolvedType,

IVoiceInteraction

Session voiceSession, IVoiceI

nteractor voiceInteractor,

IBinder resultTo,

String resultWho, int reques

tCode, int startFlags,

ProfilerInfo prof

ilerInfo, WaitResult outResul

t, Configuration config,

Bundle options, i

nt userId, IActivityContainer

iContainer, TaskRecord inTas

k) {

.

..ignore some co

int res = start

de...

ActivityLocked(caller, intent

resolvedType, aInfo,

voiceSess

,

ion, voiceInteractor, resultT

o, resultWho,

requestCo

de, callingPid, callingUid, c

allingPackage,

realCalli

ngPid, realCallingUid, startF

lags, options,

component

Specified, null, container, i

nTask);

.

..ignore some co

de...

}

继续调用

startActivityLocked()

final int startActivityLocked

(IApplicationThread caller,

Intent intent, St

ring resolvedType, ActivityIn

fo aInfo,

IVoiceInteraction

Session voiceSession, IVoiceI

nteractor voiceInteractor,

IBinder resultTo,

String resultWho, int reques

tCode,

int callingPid, i

nt callingUid, String calling

Package,

int realCallingPi

d, int realCallingUid, int st

artFlags, Bundle options,

boolean component

Specified, ActivityRecord[] o

utActivity, ActivityContainer

container,

TaskRecord inTask

)

{

err = startActi

vityUncheckedLocked(r, source

Record, voiceSession, voiceIn

teractor,

startFlags, tru

e, options, inTask);

if (err < 0) {

notifyActivityDra

wnForKeyguard();

}

return err;

}

调用

startActivityUncheckedLocked

(),此时要启动的Ac tivity已经

通过检验,被认为是一个正

当的启动请求。

终于,在这里调用到了

Ac tivitySta c k的

sta rtAc tivityLoc ke d(Ac tivityRe

cord r, boolean

newTask,boolean doResume,

boolean keepCurTransition,

Bundle options) 。

Ac tivityRe c ord代表的就是要

开启的Ac tivity对象,里面分

装了很多信息,比如所在的

Ac tivityTa sk等,如果这是首

次打开应用,那么这个

Ac tivity会被放到Ac tivityTa sk

的栈顶,

final int startActivityUnchec

kedLocked(ActivityRecord r, A

ctivityRecord sourceRecord,

IVoiceInteraction

Session voiceSession, IVoiceI

nteractor voiceInteractor, in

t startFlags,

boolean doResume,

Bundle options, TaskRecord i

nTask) {

de...

.

..ignore some co

targetStack.start

ActivityLocked(r, newTask, do

Resume, keepCurTransition, op

tions);

.

..ignore some co

de...

return ActivityM

anager.START_SUCCESS;

}

调用的是

Ac tivitySta c k. sta rtAc tivityLoc k

ed()

final void startActivityLocke

d(ActivityRecord r, boolean n

ewTask,

boolean doResume,

boolean keepCurTransition, B

undle options) {

/

/ActivityRecord中存储

的TaskRecord信息

TaskRecord rTask = r.

task;

.

..ignore some code.

.

.

/

/如果不是在新的Activit

yTask(也就是TaskRecord)中的话,

就找出要运行在的TaskRecord对象

TaskRecord task = null;

if (!newTask) {

boolean startIt =

true;

for (int taskNdx

mTaskHistory.size() - 1; ta

skNdx >= 0; --taskNdx) {

task = mTaskH

istory.get(taskNdx);

if (task.getT

opActivity() == null) {

=

/

/ task中

的所有Activity都结束了

continue;

}

if (task == r

.

task) {

/

/ 找到了

if (!star

tIt) {

task.

addActivityToTop(r);

InHistory();

r.put

mWind

owManager.addAppToken(task.mA

ctivities.indexOf(r), r.appTo

ken,

r.task.taskId, mStackId, r

.

info.screenOrientation, r.fu

llscreen,

(r.info.flags & ActivityIn

fo.FLAG_SHOW_ON_LOCK_SCREEN)

= 0,

!

r.userId, r.info.configCha

nges, task.voiceSession != nu

ll,

r.mLaunchTaskBehind);

if (V

ALIDATE_TOKENS) {

v

alidateAppTokensLocked();

}

Activ

ityOptions.abort(options);

retur

n;

}

break;

}

else if (ta

sk.numFullscreen > 0) {

startIt =

false;

}

}

}

.

..ignore some code...

/ Place a new activi

/

ty at top of stack, so it is

next to interact

/

/ with the user.

task = r.task;

task.addActivityToTop

(r);

;

task.setFrontOfTask()

.

..ignore some code..

.

if (doResume) {

mStackSupervisor.

resumeTopActivitiesLocked(thi

s, r, options);

}

}

靠!这来回折腾什么呢!从

Ac tivitySta c kSupe rvisor到

Ac tivitySta c k,又调回

Ac tivitySta c kSupe rvisor,这

到底是在折腾什么玩意

啊!!!

淡定…淡定…我知道你也在

心里骂娘,世界如此美妙,

你却如此暴躁,这样不好,

不好 …

来来来,咱们继续哈,刚才

说到哪里了?哦,对,咱们

一起看下

StackSupervisor. resumeTopAc

tivitie sLoc ke d( this, r, options)

boolean resumeTopActivitiesLo

cked(ActivityStack targetStac

k, ActivityRecord target,

Bundle targetOpti

ons) {

if (targetStack == nu

ll) {

targetStack = get

FocusedStack();

}

/

/ Do targetStack fir

st.

e;

boolean result = fals

if (isFrontStack(targ

etStack)) {

result = targetSt

ack.resumeTopActivityLocked(t

arget, targetOptions);

}

.

..ignore some code

.

..

return result;

}

我…已无力吐槽了,又调回

Ac tivitySta c k去了…

Ac tivitySta c k. re sume TopAc tivi

tyLocked()

final boolean resumeTopActivi

tyLocked(ActivityRecord prev,

Bundle options) {

if (inResumeTopActivi

ty) {

/

/ Don't even sta

rt recursing.

return false;

}

boolean result = fals

try {

e;

/

/ Protect agains

t recursion.

ty = true;

inResumeTopActivi

result = resumeTo

pActivityInnerLocked(prev, op

tions);

}

finally {

inResumeTopActivi

ty = false;

}

return result;

}

咱们坚持住,看一下

Ac tivitySta c k. re sume TopAc tivi

tyInnerLocked()到底进行了

什么操作

final boolean resumeTopActivi

tyInnerLocked(ActivityRecord

prev, Bundle options) {

.

..ignore some code

.

..

/

/找出还没结束的首个Activi

tyRecord

ActivityRecord next = to

pRunningActivityLocked(null);

/

/如果一个没结束的Activity都

没有,就开启Launcher程序

if (next == null) {

ActivityOptions.a

bort(options);

if (DEBUG_STATES)

Slog.d(TAG, "resumeTopActivi

tyLocked: No more activities

go home");

if (DEBUG_STACK)

mStackSupervisor.validateTopA

ctivitiesLocked();

/

/ Only resume ho

me if on home display

final int returnT

askType = prevTask == null ||

!

prevTask.isOverHomeStack()

?

HOME_ACTI

VITY_TYPE : prevTask.getTaskT

oReturnTo();

return isOnHomeDi

splay() &&

mStackSup

ervisor.resumeHomeStackTask(r

eturnTaskType, prev);

}

/

/先需要暂停当前的Activi

ty。因为我们是在Lancher中启动main

Activity,所以当前mResumedActiv

ity!=null,调用startPausingLo

cked()使得Launcher进入Pausing状

if (mResumedActivit

y != null) {

pausing |= startP

ausingLocked(userLeaving, fal

se, true, dontWaitForPause);

if (DEBUG_STATES)

Slog.d(TAG, "resumeTopActivi

tyLocked: Pausing " + mResume

dActivity);

}

}

在这个方法里,prev.app为记

录启动Lancher进程的

ProcessRecord,

prev.app.thread为Lancher进

程的远程调用接口

IApplicationThead,所以可以

调用

prev.app.thread.schedulePause

Ac tivity,到Lancher进程暂停

指定Ac tivity 。

final boolean startPausingLoc

ked(boolean userLeaving, bool

ean uiSleeping, boolean resum

ing,

boolean dontWait)

{

if (mPausingActivity

= null) {

!

completePauseLock

ed(false);

}

.

..ignore some code...

if (prev.app != null

&

& prev.app.thread != null)

try {

mService.upda

teUsageStats(prev, false);

prev.app.thre

ad.schedulePauseActivity(prev

.

appToken, prev.finishing,

userL

eaving, prev.configChangeFlag

s, dontWait);

}

catch (Exceptio

n e) {

mPausingActiv

mLastPausedAc

mLastNoHistor

ity = null;

tivity = null;

yActivity = null;

}

}

else {

mPausingActivity

=

null;

mLastPausedActivi

mLastNoHistoryAct

ty = null;

ivity = null;

}

.

..ignore some code...

}

在Lancher进程中消息传递,

调用

ActivityThread.handlePauseAc

tivity( ),最终调用

ActivityThread.performPause

Ac tivity( )暂停指定Ac tivity。

接着通

过前面所说的Binder通信,

通知AMS已经完成暂停的操

作。

ActivityManagerNative.getDefa

ult().activityPaused(token).

上面这些调用过程非常复

杂,源码中各种条件判断让

人眼花缭乱,所以说如果你

没记住也没关系,你只要记

住这个流程,理解了Android

在控制Ac tivity

生命周期时是如何操作,以

及是通过哪几个关键的类进

行操作的就可以了,以后遇

到相关的问题之道从哪块下

手即可,这些过程我虽然也

是撸了一遍,但

还是记不清。最后来一张高

清无码大图,方便大家记

忆:

请戳这里(图片3.3M,请用

电脑观看 )

送给你们的彩蛋

不要使用

startActivityForResult(i

ntent,RESULT _ OK)

这是因为sta rtAc tivity()是这

样实现的

public void startActivity(Int

ent intent, @Nullable Bundle

options) {

if (options != null)

{

startActivityForR

esult(intent, -1, options);

}

else {

/

/ Note we want t

o go through this call for co

mpatibility with

/

/ applications t

hat may have overridden the m

ethod.

startActivityForR

esult(intent, -1);

}

}

public static final int RESUL

T_OK = -1;

所以

startActivityForResult(intent

,RESULT_OK) = startActivity()

你不可能从onAc tivityRe sult( )

里面收到任何回调。而这个

问题是相当难以被发现的,

就是因为这个坑,我工作一

年多来第一次加班到9点

(ˇˍˇ)

一个App的程序入口到

底是什么?

是ActivityThread.main()。

整个App的主线程的消

息循环是在哪里创建

的?

是在Ac tivityThre a d初始化的

时候,就已经创建消息循环

了,所以在主线程里面创建

Handler不需要指定Looper,

而如果在其他线程使用

Handler,则需要单独使用

Looper.prepare()和

Looper.loop()创建消息循

环。

public static void main(Strin

g[] args) {

.

..ignore some code

.

..

Looper.prepareMainLoope

ActivityThread thread

r();

=

new ActivityThread();

thread.attach(false);

if (sMainThreadHandle

r == null) {

sMainThreadHandle

r = thread.getHandler();

}

AsyncTask.init();

if (false) {

Looper.myLooper()

.

setMessageLogging(new

LogPrinte

r(Log.DEBUG, "ActivityThread"

)

);

}

Looper.loop();

.

..ignore some code

.

..

}

Application是在什么时候创

建的?onCreate()什么时候调

用的?

也是在ActivityThread. main()

的时候,再具体点呢,就是

在thread.attach(false)的时

候。

看你的表情,不信是吧!凯

子哥带你溜溜 ~

我们先看一下

ActivityThread.attach()

private void attach(boolean s

ystem) {

sCurrentActivityThrea

d = this;

mSystemThread = syste

m;

/

/普通App进这里

if (!system) {

.

..ignore some co

de...

RuntimeInit.setAp

plicationObject(mAppThread.as

Binder());

final IActivityMa

nager mgr = ActivityManagerNa

tive.getDefault();

try {

mgr.attachApp

lication(mAppThread);

}

catch (RemoteEx

ception ex) {

/

/ Ignore

}

}

else {

/这个分支在System

Server加载的时候会进入,通过调用

/ private void

createSystemContext() {

ActivityTh

/

/

/

/

read activityThread = Activit

yThread.systemMain() ;

/

/}

/

/ public static

ActivityThread systemMain()

{

/

/

if (!Activi

tyManager.isHighEndGfx()) {

Hardwar

/

/

eRenderer.disable(true);

/

/

/

/

} else {

Hardwar

eRenderer.enableForegroundTri

mming();

/

/

/

/

}

ActivityThr

ead thread = new ActivityThre

ad();

/

/

/

/

thread.atta

ch(true);

/

return thre

ad;

/

}

}

}

这里需要关注的就是

mgr.attachApplication(mAppT

hread),这个就会通过Binder

调用到AMS里面对应的方法

@

Override

public final void attachA

pplication(IApplicationThread

thread) {

synchronized (this) {

int callingPid =

Binder.getCallingPid();

final long origId

=

Binder.clearCallingIdentit

y();

attachApplication

Locked(thread, callingPid);

Binder.restoreCal

lingIdentity(origId);

}

}

然后就是

private final boolean attachA

pplicationLocked(IApplication

Thread thread,

int pid) {

thread.bindAppli

cation(processName, appInfo,

providers, app.instrumentatio

nClass,

profilerI

nfo, app.instrumentationArgum

ents, app.instrumentationWatc

her,

app.instr

umentationUiAutomationConnect

ion, testMode, enableOpenGlTr

ace,

isRestric

tedBackupMode || !normalMode,

app.persistent,

new Confi

guration(mConfiguration), app

compat, getCommonServicesLoc

ked(),

.

mCoreSett

ingsObserver.getCoreSettingsL

ocked());

}

thread是IApplicationThread ,

实际上就是

ApplicationThread在服务端

的代理类

ApplicationThreadProxy,然

后又通过IPC就会调用到

ApplicationThread的对应方

private class ApplicationThre

ad extends ApplicationThreadN

ative {

public final void bindAppli

cation(String processName, Ap

plicationInfo appInfo,

List<Provider

Info> providers, ComponentNam

e instrumentationName,

ProfilerInfo

profilerInfo, Bundle instrume

ntationArgs,

IInstrumentat

ionWatcher instrumentationWat

cher,

IUiAutomation

Connection instrumentationUiC

onnection, int debugMode,

boolean enabl

eOpenGlTrace, boolean isRestr

ictedBackupMode, boolean pers

istent,

Configuration

config, CompatibilityInfo co

mpatInfo, Map<String, IBinder

>

services,

Bundle coreSe

ttings) {

.

..ignore so

me code...

AppBindData data

data.processName

=

new AppBindData();

=

processName;

data.appInfo = ap

pInfo;

data.providers =

providers;

data.instrumentat

ionName = instrumentationName

;

data.instrumentat

ionArgs = instrumentationArgs

;

data.instrumentat

ionWatcher = instrumentationW

atcher;

data.instrumentat

ionUiAutomationConnection = i

nstrumentationUiConnection;

data.debugMode =

debugMode;

data.enableOpenGl

Trace = enableOpenGlTrace;

data.restrictedBa

ckupMode = isRestrictedBackup

Mode;

data.persistent =

persistent;

data.config = con

fig;

compatInfo;

data.compatInfo =

data.initProfiler

Info = profilerInfo;

sendMessage(H.BIN

D_APPLICATION, data);

}

}

我们需要关注的其实就是最

后的sendMessage(),里面有

函数的编号

H.BIND_APPLICATION,然

后这个Messge会被H这个

Handler处理

private class H extends Handl

er {

.

..ignore some code...

public static final int

BIND_APPLICATION = 110

;

.

..ignore some code...

public void handleMessag

e(Message msg) {

switch (msg.what) {

.

..ignore some code..

case BIND_APPLICATIO

Trace.tra

.

N:

ceBegin(Trace.TRACE_TAG_ACTIV

ITY_MANAGER, "bindApplication

"

);

AppBindDa

ta data = (AppBindData)msg.ob

j;

handleBin

dApplication(data);

Trace.tra

ceEnd(Trace.TRACE_TAG_ACTIVIT

Y_MANAGER);

break;

.

}

..ignore some code..

.

}

最后就在下面这个方法中,

完成了实例化,拨那个企鹅

通过

mInstrumentation.callApplicati

onOnCreate实现了onCreate()

的调用。

private void handleBindApplic

ation(AppBindData data) {

try {

.

..ignore some cod

Application app =

e...

data.info.makeApplication(da

ta.restrictedBackupMode, null

)

;

mInitialApplicati

..ignore some cod

on = app;

e...

.

try {

mInstrumentat

ion.onCreate(data.instrumenta

tionArgs);

}

catch (Exception

e) {

}

try {

mInstrumentat

ion.callApplicationOnCreate(a

pp);

}

catch (Exceptio

}

n e) {

}

finally {

StrictMode.setThr

eadPolicy(savedPolicy);

}

}

data.info是一个LoadeApk对

象。

LoadeApk.data.info.makeAppl

ication()

public Application makeApplic

ation(boolean forceDefaultApp

Class,

Instrumentation i

nstrumentation) {

if (mApplication != n

ull) {

return mApplicati

on;

}

Application app = nul

l;

String appClass = mAp

plicationInfo.className;

if (forceDefaultAppCl

ass || (appClass == null)) {

appClass = "andro

id.app.Application";

}

try {

java.lang.ClassLo

ader cl = getClassLoader();

if (!mPackageName

.

equals("android")) {

initializeJav

aContextClassLoader();

}

ContextImpl appCo

ntext = ContextImpl.createApp

Context(mActivityThread, this

)

;

app = mActivityTh

read.mInstrumentation.newAppl

ication(

cl, appCl

ass, appContext);

appContext.setOut

erContext(app);

}

catch (Exception e)

}

{

mActivityThread.mAllA

pplications.add(app);

mApplication = app;

/

/传进来的是null,所以这里不

会执行,onCreate在上一层执行

if (instrumentation !

=

null) {

try {

instrumentati

on.callApplicationOnCreate(ap

p);

}

catch (Exceptio

n e) {

}

}

.

..ignore some code..

.

}

return app;

}

所以最后还是通过

Instrumentation.makeApplicati

on()实例化的,这个老板娘

真的很厉害呀!

static public Application new

Application(Class<?> clazz, C

ontext context)

throws Instantiat

ionException, IllegalAccessEx

ception,

ClassNotFoundExce

ption {

Application app = (Ap

plication)clazz.newInstance()

;

app.attach(context);

return app;

}

而且通过反射拿到

Application对象之后,直接

调用attach(),所以attach()调

用是在onCreate()之前的。

参考文章

下面的这些文章都是这方面

比较精品的,希望你抽出时

间研究,这可能需要花费很

长时间,但是如果你想进阶

中高级开发者,这一步是

必须的。

再次感谢下面这些文章的作

者的分享精神。

Binder

Android Bander设计与实

现 - 设计篇

zygote

Android系统进程Zygote启

动过程的源代码分析

Android 之 zygote 与进程

创建

Zygote浅谈

ActivityThread、

Instrumentation、AMS

Android

Ac tivity. sta r tAc tivity流程简

Android应用程序进程启动

过程的源代码分析

框架层理解Ac tivity生命周

期(APP启动过程)

Android应用程序窗口设计

框架介绍

ActivityManagerService分

析一:AMS的启动

Android应用程序窗口设计

框架介绍

Launcher

Android 4.0 Launcher源码

分析系列(一)

Android Launcher分析和修

改9——Launcher启动APP

流程

结语

OK,到这里,这篇文章算

是告一段落了,我们再回头

看看一开始的几个问题,你

还困惑吗?

一个App是怎么启动起来

的?

App的程序入口到底是哪

里?

Launcher到底是什么神奇

的东西?

听说还有个AMS的东西,

它是做什么的?

Binder是什么?他是如何

进行IPC通信的?

Ac tivity生命周期到底是什

么时候调用的?被谁调用

的?

再回过头来看看这些类,你

还迷惑吗?

ActivityManagerServices,

简称AMS,服务端对象,

负责系统中所有Ac tivity的

生命周期

Ac tivityThre a d,App的真

正入口。当开启App之

后,会调用main()开始运

行,开启消息循环队列,

这就是传说中的UI线程或

者叫主线程。与

ActivityManagerServices配

合,一起完成Ac tivity的管

理工作

ApplicationThread,用来

实现

ActivityManagerService与

Ac tivityThre a d之间的交

互。在

ActivityManagerService需

要管理相关Application中

的Ac tivity的生命周期时,

通过ApplicationThread 的

代理对象与Ac tivityThre a d

通讯。

ApplicationThreadProxy,

是ApplicationThread在服

务器端的代理,负责和客

户端的ApplicationThread

通讯。AMS就是通过该代

理与Ac tivityThre a d进行通

信的。

Instrumentation,每一个应

用程序只有一个

Instrumentation对象,每个

Ac tivity内都有一个对该对

象的引用。Instrumentation

可以理解为应用进程的管

家,Ac tivityThre a d要创建

或暂停某个Ac tivity时,都

需要通过Instrumentation来

进行具体的操作。

Ac tivitySta c k,Ac tivity在

AMS的栈管理,用来记录

已经启动的Ac tivity的先后

关系,状态信息等。通过

Ac tivitySta c k决定是否需要

启动新的进程。

Ac tivityRe c ord,

Ac tivitySta c k的管理对象,

每个Ac tivity在AMS对应一

个Ac tivityRe c ord,来记录

Ac tivity的状态以及其他的

管理信息。其实就是服务

器端的Ac tivity对象的映

像。

TaskRecord,AMS抽象出

来的一个“任务”的概念,

是记录Ac tivityRe c ord的

栈,一个“Task”包含若干

个Ac tivityRe c ord。AMS用

TaskRecord确保Ac tivity启

动和退出的顺序。如果你

清楚Ac tivity的4种

launchMode,那么对这个

概念应该不陌生。

如果你还感到迷惑的话,就

把这篇文章多读几遍吧,信

息量可能比较多,需要慢慢

消化 ~

尊重原创,转载请注明:

From 凯子哥

http://blog.csdn.net/zhaokai

qiang1992) 侵权必究!

关注我的微博,可以获得更

多精彩内容

工匠若

水 http://blog.csdn.net/yanbob

er 转载烦请注明出处,尊重

分享成果】

1

背景

Android异步处理机制一直都

是Android的一个核心,也是

应用工程师面试的一个知识

点。前面我们分析了Handler

异步机制原理(不了解的可

以阅读我的《Android异步消

息处理机制详解及源码分

析》文章),这里继续分析

Android的另一个异步机制

AsyncTask的原理。

当使用线程和Handler组合实

现异步处理时,当每次执行

耗时操作都创建一条新线程

进行处理,性能开销会比较

大。为了提高性能我们使用

AsyncTask实现异步处理(其

实也是线程和handler组合实

现),因为其内部使用了

java提供的线程池技术,有

效的降低了线程创建数量及

限定了同时运行的线程数,

还有一些针对性的对池的优

化操作。所以说AsyncTask是

Android为我们提供的方便编

写异步任务的工具类。

工匠若

水 http://blog.csdn.net/yanbob

er 转载烦请注明出处,尊重

分享成果】

2

实例演示

先看下使用AsyncTask模拟下

载的效果图:

看下代码,如下:

public class MainActivity ext

ends Activity {

@

Override

protected void onCreate(B

undle savedInstanceState) {

super.onCreate(savedI

nstanceState);

setContentView(R.layo

ut.activity_main);

new TestAsyncTask(thi

s).execute();

}

static final class TestAs

yncTask extends AsyncTask<Voi

d, Integer, Boolean> {

/

/如上三个泛型参数从左到

右含义依次为:

/

/1. 在执行AsyncTask时

需要传入的参数,可用于在后台任务中使

用。

/

/2. 后台任务执行时,如果

需要在界面上显示当前的进度,则使用这

个。

/

/3. 当任务执行完毕后,如

果需要对结果进行返回,则使用这个。

private Context mCont

ext = null;

private ProgressDialo

g mDialog = null;

private int mCount =

0

;

public TestAsyncTask(

Context context) {

mContext = contex

t;

}

/

/在后台任务开始执行之间

调用,用于进行一些界面上的初始化操作

protected void onPreE

xecute() {

super.onPreExecut

e();

mDialog = new Pro

gressDialog(mContext);

mDialog.setMax(10

0

);

mDialog.setProgre

ssStyle(ProgressDialog.STYLE_

HORIZONTAL);

mDialog.show();

}

/

/这个方法中的所有代码都

会在子线程中运行,我们应该在这里去处

理所有的耗时任务

protected Boolean doI

nBackground(Void... params) {

while (mCount < 1

0

0) {

publishProgre

ss(mCount);

mCount += 20;

try {

Thread.sl

eep(1000);

}

catch (Inte

rruptedException e) {

e.printSt

ackTrace();

}

}

return true;

}

/

/当在后台任务中调用了pub

lishProgress(Progress...)方法

后,这个方法就很快会被调用

protected void onProg

ressUpdate(Integer... values)

{

super.onProgressU

pdate(values);

mDialog.setProgre

ss(values[0]);

}

/

/当后台任务执行完毕并通

过return语句进行返回时,这个方法就

很快会被调用

protected void onPost

Execute(Boolean aBoolean) {

super.onPostExecu

te(aBoolean);

if (aBoolean && m

Dialog != null && mDialog.isS

howing()) {

mDialog.dismi

ss();

}

}

}

}

可以看见Android帮我们封装

好的AsyncTask还是很方便使

用的,咱们不做过多说明。

接下来直接分析源码。

工匠若

水 http://blog.csdn.net/yanbob

er 转载烦请注明出处,尊重

分享成果】

3

2

Android5.1.1(API

2)AsyncTa sk源码分

通过源码可以发现AsyncTask

是一个抽象类,所以我们在

在上面使用时需要实现它。

那怎么下手分析呢?很简

单,我们就依据上面示例的

流程来分析源码,具体如

下。

3-1 AsyncTask实例化源码分

/

**

*

Creates a new asynchro

nous task. This constructor m

ust be invoked on the UI thre

ad.

*

/

public AsyncTask() {

mWorker = new WorkerR

unnable<Params, Result>() {

public Result cal

l() throws Exception {

mTaskInvoked.

set(true);

Process.setTh

readPriority(Process.THREAD_P

RIORITY_BACKGROUND);

/

/noinspectio

n unchecked

return postRe

sult(doInBackground(mParams))

;

}

}

;

mFuture = new FutureT

ask<Result>(mWorker) {

Override

@

protected void do

ne() {

try {

postResul

tIfNotInvoked(get());

}

catch (Inte

rruptedException e) {

android.u

til.Log.w(LOG_TAG, e);

}

catch (Exec

utionException e) {

throw new

RuntimeException("An error o

ccured while executing doInBa

ckground()",

e

.

getCause());

}

catch (Canc

ellationException e) {

postResul

tIfNotInvoked(null);

}

}

}

;

}

看见注释没有,AsyncTask的

实例化只能在UI线程中。然

后整个构造函数就只初始化

了两个AsyncTask类的成员变

量(mWorker和mFuture)。

mWorker

为匿名内部类的实例对象

WorkerRunnable(实现了

Ca lla ble接口),mFuture为

匿名内部类的实例对象

FutureTask,传入了mWorker

作为形参(重写了

FutureTask类的done方

法)。

3-2 AsyncTask的execute方法

源码分析

正如上面实例一样,得到

AsyncTask实例化对象之后就

执行了execute方法,所以看

下execute方法的源码,如

下:

public final AsyncTask<Params

Progress, Result> execute(P

arams... params) {

return executeOnExecu

,

tor(sDefaultExecutor, params)

;

}

可以看见,execute调运了

executeOnExecutor方法,

executeOnExecutor方法除过

传入了params形参以外,还

传入了一个static的

SerialExecutor对象

SerialExecutor实现了

Executor接口)。继续看下

executeOnExecutor源码,如

下:

public final AsyncTask<Params

,

Progress, Result> executeOn

Executor(Executor exec,

Params... params)

{

if (mStatus != Status

PENDING) {

.

{

switch (mStatus)

case RUNNING:

throw new

IllegalStateException("Canno

t execute task:"

+

"

the task is already runnin

g.");

case FINISHED

:

throw new

IllegalStateException("Canno

t execute task:"

+

"

the task has already been

executed "

+

"

(a task can be executed onl

y once)");

}

}

mStatus = Status.RUNN

ING;

onPreExecute();

mWorker.mParams = par

exec.execute(mFuture)

ams;

;

return this;

}

首先判断AsyncTask异步任务

的状态,当处于RUNNING

和FINISHED时就报

IllegalStateException非法状态

异常。由此可以看见一个

AsyncTask的

execute方法只能被调运一

次。接着看见17行

onPreExecute();没有?看下

这个方法源码,如下:

/

**

*

Runs on the UI thread

before {@link #doInBackground

}

.

*

*

*

*

@see #onPostExecute

@see #doInBackground

/

protected void onPreExecu

te() {

}

空方法,而且通过注释也能

看见,这不就是我们

AsyncTask中第一个执行的方

法吗?是的。

回过头继续往下看,看见20

行exec.execute(mFuture);代

码没?exec就是形参出入的

上面定义的static

SerialExecutor对象

SerialExecutor实现了

Executor接口),所以

execute就是SerialExecutor静

态内部类的方法喽,在执行

execute方法时还传入了

AsyncTask构造函数中实例化

的第二个成员变量mFuture。

我们来看下SerialExecutor静

态内部类的代码,如下:

private static class SerialEx

ecutor implements Executor {

final ArrayDeque<Runn

able> mTasks = new ArrayDeque

<

Runnable>();

Runnable mActive;

public synchronized v

oid execute(final Runnable r)

{

mTasks.offer(new

Runnable() {

public void r

un() {

try {

r.run

();

}

}

finally

sched

{

uleNext();

}

}

);

if (mActive == nu

ll) {

scheduleNext(

}

)

;

}

protected synchronize

d void scheduleNext() {

if ((mActive = mT

asks.poll()) != null) {

THREAD_POOL_E

XECUTOR.execute(mActive);

}

}

}

在源码中可以看见,

SerialExecutor在AsyncTask中

是以常量的形式被使用的,

所以在整个应用程序中的所

有AsyncTask实例都会共用同

一个

SerialExecutor对象。接着可

以看见,SerialExecutor是使

用ArrayDeque这个队列来管

理Runnable对象的,如果我

们一次性启动了很多个任

务,首先在第一次运行

execute()方法的时候会调用

ArrayDeque的offer()方法将

传入的Runnable对象添加到

队列的最后,然后判断

mAc tive对象是不是等于

null,第一次运行是null,然

后调用scheduleNext()方法,

在这个方法中会从队列的头

部取值,并赋值给mAc tive对

象,然后调用

THREAD_POOL_EXECUTO

R去执行取出的取出的

Runnable对象。之后如果再

有新的任务被执行时就等待

上一个任务执行完毕后才会

得到执行,所以说同一时刻

只会有一个线程正在执行,

其余的均处于等待状态,这

就是SerialExecutor类的核心

作用。

我们再来看看上面用到的

THREAD_POOL_EXECUTO

R与execute,如下:

public abstract class AsyncTa

sk<Params, Progress, Result>

{

.

.....

private static final int

CPU_COUNT = Runtime.getRuntim

e().availableProcessors();

private static final int

CORE_POOL_SIZE = CPU_COUNT +

1

;

private static final int

MAXIMUM_POOL_SIZE = CPU_COUNT

*

2 + 1;

private static final int

KEEP_ALIVE = 1;

private static final Thre

adFactory sThreadFactory = ne

w ThreadFactory() {

private final AtomicI

nteger mCount = new AtomicInt

eger(1);

public Thread newThre

ad(Runnable r) {

return new Thread

(r, "AsyncTask #" + mCount.ge

tAndIncrement());

}

}

;

private static final Bloc

kingQueue<Runnable> sPoolWork

Queue =

new LinkedBlockin

gQueue<Runnable>(128);

/

**

*

An {@link Executor} th

at can be used to execute tas

ks in parallel.

*

/

public static final Execu

tor THREAD_POOL_EXECUTOR

=

new ThreadPoolE

xecutor(CORE_POOL_SIZE, MAXIM

UM_POOL_SIZE, KEEP_ALIVE,

TimeUnit.

SECONDS, sPoolWorkQueue, sThr

eadFactory);

.

.....

}

看见没有,实质就是在一个

线程池中执行,这个

THREAD_POOL_EXECUTO

R线程池是一个常量,也就

是说整个App中不论有多少

AsyncTask都只有这

一个线程池。也就是说上面

SerialExecutor类中execute()

方法的所有逻辑就是在子线

程中执行,注意

SerialExecutor的execute方法

有一个Runnable参数

这个参数就是mFuture对

象,所以我们看下

FutureTask类的run()方法,

如下源码:

public void run() {

if (state != NEW ||

!

UNSAFE.compareAn

dSwapObject(this, runnerOffse

t,

null, Thread.curr

entThread()))

return;

try {

Callable<V> c = c

allable;

if (c != null &&

state == NEW) {

V result;

boolean ran;

try {

result =

c.call();

e;

ran = tru

}

catch (Thro

result =

wable ex) {

null;

ran = fal

setExcept

se;

ion(ex);

}

if (ran)

set(resul

t);

}

}

finally {

/ runner must be

/

non-null until state is sett

led to

/

/ prevent concur

rent calls to run()

runner = null;

/ state must be

/

re-read after nulling runner

to prevent

/

/ leaked interru

pts

int s = state;

if (s >= INTERRUP

TING)

handlePossibl

eCancellationInterrupt(s);

}

}

看见没有?第7行的c =

callable;其实就是AsyncTask

构造函数中实例化

FutureTask对象时传入的参

数mWorker。12行看见

result = c.call(); 没

有?

其实就是调运

WorkerRunnable类的c all方

法,所以我们回到AsyncTask

构造函数的WorkerRunnable

匿名内部内中可以看见如

下:

mWorker = new WorkerRunnable<

Params, Result>() {

public Result cal

l() throws Exception {

mTaskInvoked.

set(true);

Process.setTh

readPriority(Process.THREAD_P

RIORITY_BACKGROUND);

/

/noinspectio

n unchecked

return postRe

sult(doInBackground(mParams))

;

}

}

;

看见没有?在postResult()方

法的参数里面,我们可以看

见doInBackground()方法。所

以这验证了我们上面例子中

使用的AsyncTask,首先在主

线程执

行onPreExecute方法,接着

在子线程执行

doInBackground方法,所以

这也就是为什么我们可以在

doInBackground()方法中去处

理耗时操作的原因了

,接着等待doInBackground

方法耗时操作执行完毕以后

将返回值传递给了

postResult()方法。所以我们

来看下postResult这个方法的

源码,如下:

private Result postResult(Res

ult result) {

@

SuppressWarnings("un

checked")

Message message = get

Handler().obtainMessage(MESSA

GE_POST_RESULT,

new AsyncTask

Result<Result>(this, result))

;

message.sendToTarget(

)

;

return result;

}

先看下这个getHandler拿到的

是哪个Handler吧,如下:

private static class Internal

Handler extends Handler {

public InternalHandle

r() {

super(Looper.getM

ainLooper());

}

@

SuppressWarnings({"u

nchecked", "RawUseOfParameter

izedType"})

@

Override

public void handleMes

sage(Message msg) {

AsyncTaskResult<?

result = (AsyncTaskResult<?

) msg.obj;

>

>

switch (msg.what)

{

case MESSAGE_

POST_RESULT:

/

/ There

is only one result

result.mT

ask.finish(result.mData[0]);

break;

case MESSAGE_

POST_PROGRESS:

result.mT

ask.onProgressUpdate(result.m

Data);

break;

}

}

}

看见没有,拿到的是

MainLooper,也就是说在在

UI线程中的Handler(不清楚

的请阅读《Android异步消息

处理机制详解及源码分析》

文章)。所以

上面的方法其实就是将子线

程的数据发送到了UI来处

理,也就是通过

MESSAGE_P OST_RESULT

在handleMessage来处理。所

以我们继续看

handleMessage中的

result.mTask.finish(result.mDa

ta[0]);就会发现finish的代码

如下:

private void finish(Result re

sult) {

if (isCancelled()) {

onCancelled(resul

t);

}

else {

onPostExecute(res

ult);

}

mStatus = Status.FINI

SHED;

}

看见没有?依据返回值true

与false回调AsyncTask的

onPostExecute或者

onCancelled方法。

到此是不是会好奇

onProgressUpdate方法啥时候

调运的呢?继续往下看可以

发现handleMessage方法中的

MESSAGE_POST_PROGRES

S不就是回调我们UI Thread

中的onProgressUpdate方法

吗?那怎么样才能让他回调

呢?追踪

MESSAGE_POST_PROGRES

S消息你会发现如下:

protected final void publishP

rogress(Progress... values) {

if (!isCancelled()) {

getHandler().obta

inMessage(MESSAGE_POST_PROGRE

SS,

new Async

TaskResult<Progress>(this, va

lues)).sendToTarget();

}

}

额,没意思了。这不就是我

们上面例子中的在子线程的

doInBackground耗时操作中

调运通知回调

onProgressUpdate的方法么。

看见没有,AsyncTa sk的实质

就是Handler异步消息处理机

制(不清楚的请阅读

《Android异步消息处理机制

详解及源码分析》文章),

只是对线程做了优化处理和

封装而已。

【工匠若

水 http://blog.csdn.net/yanbob

er 转载烦请注明出处,尊重

分享成果】

4

为当年低版本

AsyncTa sk的臭名正身

接触Android比较久的可能都

知道,在Android 3.0之前是

并没有SerialExecutor这个类

的(上面有分析)。那些版

本的代码是直接创建了指定

大小的线程池常量来执行

task的。其中

MAXIMUM_POOL_SIZE =

128;,所以那时候如果我们

应用中一个界面需要同时创

建的Async Task线程大于

128(批量获取数据,譬如

照片浏览瀑布流一次加载)

程序直接就挂了。所以当时

的AsyncTask因为这个原因臭

名昭著。

回过头来看看现在高版本的

AsyncTa sk,是不是没有这个

问题了吧?因为现在是顺序

执行的。而且更劲爆的是现

在的Async Task还直接提供了

客户化实现Executor接口功

能,使用如下方法执行

Async Task即可使用自定义

Executor,如下:

public final AsyncTask<Params

,

Progress, Result> executeOn

Executor(Executor exec,

Params... params)

{

.

.....

return this;

}

可以看出,在3.0以上版中

Async Task已经不存在那个臭

名昭著的Bug了,所以可以

放心使用了,妈妈再也不用

担心我的AsyncTa sk出Bug

了。

【工匠若

水 http://blog.csdn.net/yanbob

er 转载烦请注明出处,尊重

分享成果】

5

AsyncTa sk与Handler

异步机制对比

前面文章也分析过Handler

了,这里也分析了

AsyncTa sk,现在把他们两拽

一起来比较比较。具体如

下:

1. Async Task是对Handler与

Thread的封装。

2

. Async Task在代码上比

Handler要轻量级别,但实

际上比Handler更耗资源,

因为AsyncTask底层是一

个线程池,而Handler仅仅

就是发送了一个消息队

列。但是,如果异步任务

的数据特别庞大,

Async Task线程池比

Handler节省开销,因为

Handler需要不停的new

Thread执行。

3

. AsyncTa sk的实例化只能

在主线程,Handler可以随

意,只和Looper有关系。

6

AsyncTa sk总结

到此整个Android的

AsyncTa sk已经分析完毕,相

信你现在对于AsyncTask会有

一个很深入的理解与认识

了。

【工匠若

水 http://blog.csdn.net/yanbob

er 转载烦请注明出处,尊重

分享成果】

版权声明:本文为博主原创

文章,未经博主允许不得转

载。

http://www.tuicool.com/article

s/22iMZj

说说 PendingIntent 的内部机

侯 亮

1

概述

在Android中,我们常常使用

PendingIntent来表达一种“留

待日后处理”的意思。从这

个角度来说,PendingIntent

可以被理解为一种特殊的异

步处理机制。不过,单就命

名而言,PendingIntent其实

具有一定误导性,因为它既

不继承于Intent,也不包含

Intent,它的核心可以粗略地

汇总成四个字——“异步激

发”。

很明显,这种异步激发常常

是要跨进程执行的。比如说

A进程作为发起端,它可以

从系统“获取”一个

PendingIntent,然后A进程可

以将PendingIntent对象通过

binder机制“传递”给B进程,

再由B进程在未来某个合适

时机,“回调”PendingIntent

对象的send()动作,完成激

发。

在Android系统中,最适合做

集中性管理的组件就是

AMS(Ac tivity Manager

Service)啦,所以它义不容

辞地承担起管理所有

PendingIntent的职责。这样

我们就可以画出如下示意

图:

注意其中的第4步“递送相应

的intent”。这一步递送的

intent是从何而来的呢?简单

地说,当发起端获取

PendingIntent时,其实是需

要同时提供若干intent的。这

些intent和PendingIntent只是

配套的关系,而不是聚合的

关系,它们会被缓存在AMS

中。日后,一旦处理端将

PendingIntent的“激发”语义

传递到AMS,AMS就会尝试

找到与这个PendingIntent对

应的若干intent,并递送出

去。

当然,以上说的只是大概情

况,实际的技术细节会更复

杂一点儿。下面我们就来谈

谈细节。

2

PendingIntent的技术

细节

2

.1 发起端获取PendingIntent

我们先要理解,所谓的“发

起端获取PendingIntent”到底

指的是什么。难道只是简单

new一个PendingIntent对象

吗?当然不是。此处的“获

取”动作其实还含有向

AMS“注册”intent的语义。

在P endingIntent.java文件中,

我们可以看到有如下几个比

较常见的静态函数:

public static

PendingIntent getActivity (

Context context, int

requestCode, Intent intent,

int flags)

public static

PendingIntent getBroadcast

(Context context, int

requestCode, Intent intent,

int flags)

public static

PendingIntent getService (

Context context, int

requestCode, Intent intent,

int flags)

public static

PendingIntent getActivities

(Context context, int

requestCode, Intent[]

intents, int flags)

public static

PendingIntent getActivities

(Context context, int

requestCode, Intent[]

intents, int flags, Bundle

options)

它们就是我们常用的获取

PendingIntent的动作了。

坦白说,这几个函数的命名

可真不怎么样,所以我们简

单解释一下。上面的

ge tAc tivity( )的意思其实是,

获取一个PendingIntent对

象,而且该对象日后激发时

所做的事情是启动一个新

a c tivity。也就是说,当它异

步激发时,会执行类似

Context.startActivity()那样的

动作。相应地,

getBroadcast()和getService()

所获取的PendingIntent对象

在激发时,会分别执行类似

Context..sendBroadcast()和

Context.startService()这样的

动作。至于最后两个

ge tAc tivitie s(),用得比较

少,激发时可以启动几个

a c tivity。

我们以ge tAc tivity( )的代码来

说明问题:

public static PendingIntent g

etActivity(Context context, i

nt requestCode,

Intent intent, int

flags, Bundle options)

{

String packageName = cont

ext.getPackageName();

String resolvedType = int

ent != null ?

int

ent.resolveTypeIfNeeded(conte

xt.getContentResolver()) : nu

ll;

try

{

intent.setAllowFds(fa

lse);

IIntentSender target

=

ActivityManagerNative.getDe

fault().getIntentSender(

ActivityManager.INTENT

_

,

SENDER_ACTIVITY, packageName

null, null, requestCod

e, new Intent[] { intent },

resolvedType != null ?

new String[] { resolvedType

: null,

}

:

flags, options);

return target != null

?

new PendingIntent(target)

null;

}

catch (RemoteException e)

{

}

return null;

}

其中那句new

PendingIntent(target)创建了

PendingIntent对象,其重要

性自不待言。然而,这个对

象的内部核心其实是由上面

那个getIntentSender()函数得

来的。而这个IIntentSender核

心才是我们真正需要关心的

东西。

说穿了,此处的IIntentSender

对象是个binder代理,它对

应的binder实体是AMS中的

PendingIntentRecord对象。

PendingIntent对象构造之

时,IIntentSender代理作为参

数传进来,并记录在

PendingIntent的mTa rge t域。

日后,当PendingIntent执行

异步激发时,其内部就是靠

这个mTa rge t域向AMS传递语

义的。

我们前文说过,

PendingIntent常常会经由

binder机制,传递到另一个

进程去。而binder机制可以

保证,目标进程得到的

PendingIntent的mTa rge t域也

是合法的IIntentSender代理,

而且和发起端的IIntentSender

代理对应着同一个

PendingIntentRecord实体。

示意图如下:

2

.2 AMS里的

PendingIntentRecord

那么PendingIntentRecord 里

又有什么信息呢?它的定义

截选如下:

class PendingIntentRecord ext

ends IIntentSender.Stub

{

final ActivityManagerServ

ice owner;

final Key key; // 最关键

的key 域

final int uid;

final WeakReference<Pendi

ngIntentRecord> ref;

boolean sent = false;

boolean canceled = false;

String stringName;

.

. . . . .

}

请注意其中那个key域。这

里的Key是个

PendingIntentRecord的内嵌

类,其定义截选如下:

final static class Key

{

final int type;

final String packageName;

final ActivityRecord acti

vity;

final String who;

final int requestCode;

final Intent requestInten

// 注意!

t;

final String requestResol

vedType;

final Bundle options;

Intent[] allIntents;

注意!记录了当初获取PendingInte

/

/

nt时,用户所指定的所有intent

String[] allResolvedTypes

;

final int flags;

final int hashCode;

.

.

. . . . .

. . . . .

}

请注意其中的allIntents[]数组

域以及requestIntent域。前者

记录了当初获取

PendingIntent时,用户所指

定的所有intent(虽然一般情

况下只会指定一个intent,但

类似ge tAc tivitie s()这样的函

数还是可以指定多个intent

的),而后者可以粗浅地理

解为用户所指定的那个intent

数组中的最后一个intent。现

在大家应该清楚异步激发时

用到的intent都存在哪里了

吧。

Key的构造函数截选如下:

Key(int _t, String _p, Activi

tyRecord _a, String _w,

int _r, Intent[] _i, Stri

ng[] _it, int _f, Bundle _o)

{

type = _t;

packageName = _p;

activity = _a;

who = _w;

requestCode = _r;

requestIntent = _i != nul

l ? _i[_i.length-1] : null;

/

/ intent数组中的最后一个

requestResolvedType = _it

!

= null ? _it[_it.length-1]

null;

:

allIntents = _i;

// 所

有intent

allResolvedTypes = _it;

flags = _f;

options = _o;

.

. . . . .

}

Key不光承担着记录信息的

作用,它还承担“键值”的作

用。

2.3 AMS中的

PendingIntentRecord总表

在AMS中,管理着系统中所

有的PendingIntentRecord 节

点,所以需要把这些节点组

织成一张表:

final HashMap<PendingIntentRe

cord.Key, WeakReference<Pendi

ngIntentRecord>>

m

IntentSenderRecords

这张哈希映射表的键值类型

就是刚才所说的

PendingIntentRecord.Key。

以后每当我们要获取

PendingIntent对象时,

PendingIntent里的mTa rge t是

这样得到的:AMS会先查

mIntentSenderRecords表,如

果能找到符合的

PendingIntentRecord节点,

则返回之。如果找不到,就

创建一个新的

PendingIntentRecord节点。

因为PendingIntentRecord 是

个binder实体,所以经过

binder机制传递后,客户进

程拿到的就是个合法的

binder代理。如此一来,前

文的示意图可以进一步修改

成下图:

.4 AMS里的

2

getIntentSender()函数

现在,我们回过头继续说前

文的ge tAc tivity( ),以及其调

用的getIntentSender()。我们

先列一遍ge tAc tivity( )的原

型:

public static PendingIntent g

etActivity(Context context, i

nt requestCode,

Intent intent, int

flags, Bundle options)

context参数是调用方的上

下文。

requestCode是个简单的整

数,起区分作用。

intent是异步激发时将发出

的intent 。

flags可以包含一些既有的

标识,比如

FLAG_ONE_SHOT、

FLAG_NO_CREATE、

FLAG_CANCEL_CURREN

T、

FLAG_UP DATE_CURREN

T等等。不少同学对这个

域不是很清楚,我们后文

会细说。

options可以携带一些额外

的数据。

ge tAc tivity( )的代码很简单,

其参数基本上都传给了

getIntentSender()。

IIntentSender target = Activi

tyManagerNative.getDefault().

getIntentSender(. . . . . .)

getIntentSender()的原型大体

是这样的:

public IIntentSender getInten

tSender(int type,

String packageNam

e, IBinder token, String resu

ltWho,

int requestCode,

Intent[] intents, String[] re

solvedTypes,

int flags, Bundle

options) throws RemoteExcept

ion;

其参数比ge tAc tivity( )要多一

些,我们逐个说明。

type参数表明PendingIntent的

类型。ge tAc tivity( )和

ge tAc tivitie s()动作里指定的

类型值是

INTENT_SENDER_ACTIVIT

Y,getBroadcast() 和

getService()和动作里指定的

类型值分别是

INTENT_SENDER_BROAD

CAST和

INTENT_SENDER_SERVIC

E。另外,在Ac tivity. ja va文

件中,我们还看到一个

createPendingResult()函数,

这个函数表达了发起方的

a c tivity日后希望得到result回

馈的意思,所以其内部调用

getIntentSender()时指定的类

型值为

INTENT_SENDER_ACTIVIT

Y_RESULT。

packageName参数表示发起

端所属的包名。

token参数是个指代回馈目标

方的代理。这是什么意思

呢?我们常用的

ge tAc tivity( )、getBroadcast()

和getService()中,只是把这

个参数简单地指定为null,

表示这个PendingIntent激发

时,是不需要发回什么回馈

的。不过当我们希望获取类

型为

INTENT_SENDER_ACTIVIT

Y_RESULT的PendingIntent

时,就需要指定token参数

了。具体可参考

createPendingResult()的代

码:

public PendingIntent createPe

ndingResult(int requestCode,

Intent data, int flags)

{

String packageName = getP

ackageName();

try

{

data.setAllowFds(fals

e);

IIntentSender target

=

ActivityManagerNative.getDe

fault().getIntentSender(

ActivityManager.INTENT

_

SENDER_ACTIVITY_RESULT,

packageName,

mParent == null ? mTok

en : mParent.mToken,

mEmbeddedID, requestCo

de, new Intent[] { data },

null, flags, null);

return target != null

?

new PendingIntent(target)

null;

:

}

catch (RemoteException

e) {

/

/ Empty

}

return null;

}

看到了吗?传入的token为

Ac tivity的mToken或者其

mParent.mToken。说得简单

点儿,AMS内部可以根据这

个token找到其对应的

Ac tivityRe c ord,日后当

PendingIntent激发时,AMS

可以根据这个Ac tivityRe c ord

确定出该向哪个目标进程的

哪个Ac tivity发出result语义。

resultWho参数和token参数息

息相关,一般也是null啦。

在createPendingResult()中,

其值为Ac tivity的

mEmbeddedID字符串。

requestCode参数是个简单的

整数,可以在获取

PendingIntent时由用户指

定,它可以起区分的作用。

intents数组参数是异步激发

时希望发出的intent。对于

ge tAc tivity( )、getBroadcast()

和getService()来说,都只会

指定一个intent而已。只有

ge tAc tivitie s()会尝试一次传

入若干intent 。

resolvedTypes参数基本上和

intent是相关的。一般是这样

得到的:

String resolvedType = intent

!

= null ? intent.resolveTypeI

fNeeded(

context.getCo

ntentResolver()) : null;

这个值常常和intent内部的

mData URI有关系,比如最

终的值可能是URI对应的

MIME类型。

flags参数可以指定

PendingIntent的一些行为特

点。它的取值是一些既有的

比特标识的组合。目前可用

的标识有:

FLAG_ONE_SHOT、

FLAG_NO_CREATE、

FLAG_CANCEL_CURRENT

FLAG_UP DATE_CURRENT

等等。有时候,flags中还可

以附带若干FILL_IN_XXX标

识。我们把常见的标识定义

列举如下:

【PendingIntent中】

public static final int FLAG_

ONE_SHOT = 1<<30;

public static final int FLAG_

NO_CREATE = 1<<29;

public static final int FLAG_

CANCEL_CURRENT = 1<<28;

public static final int FLAG_

UPDATE_CURRENT = 1<<27;

Intent中】

public static final int FILL_

IN_ACTION = 1<<0;

public static final int FILL_

IN_DATA = 1<<1;

public static final int FILL_

IN_CATEGORIES = 1<<2;

public static final int FILL_

IN_COMPONENT = 1<<3;

public static final int FILL_

IN_PACKAGE = 1<<4;

public static final int FILL_

IN_SOURCE_BOUNDS = 1<<5;

public static final int FILL_

IN_SELECTOR = 1<<6;

public static final int FILL_

IN_CLIP_DATA = 1<<7;

这些以FILL_IN_打头的标志

位,主要是在intent对象的

fillIn()函数里起作用:

public int fillIn(Intent othe

r, int flags)

我们以FILL_IN_ACTION 为

例来说明,当一个当我们执

行类似

srcIntent.fillIn(otherIntent, ...)

的句子时,如果otherIntent的

mAction域不是null值,那么

fillIn()会在以下两种情况

下,用otherIntent的mAction

域值为srcIntent的mAction域

赋值:

1

) 当srcIntent的mAction域

值为null时;

) 如果fillIn的flags参数里携

2

带了FILL_IN_ACTION标志

位,那么即便srcIntent的

mAction已经有值了,此时也

会用otherIntent的mAction域

值强行替换掉srcIntent的

mAction域值。

其他FILL_IN_标志位和

FILL_IN_ACTION的处理方

式类似,我们不再赘述。

options参数可以携带一

些额外数据。

2

.4.1 getIntentSender()

函数

getIntentSender()函数摘

录如下:

public IIntentSender getInten

tSender(int type, String pack

ageName,

IBinder token, String

resultWho,

int requestCode, Inte

nt[] intents,

String[] resolvedType

s,

int flags, Bundle opt

ions)

{

.

. . . . .

/

/ 先判断intents数组,可以用

伪代码checkIntents(intents)来表

/

.

/ checkIntents(intents);

. . . . .

int callingUid = Binder.g

etCallingUid();

. . . . .

.

if (callingUid != 0 && ca

llingUid != Process.SYSTEM_UI

D)

{

int uid = AppGlobals.

getPackageManager().getPackag

eUid(packageName,

UserId.getUserId(calling

Uid));

if (!UserId.isSameApp

(callingUid, uid))

{

.

. . . . .

throw new Securit

yException(msg);

}

}

.

. . . . .

return getIntentSenderLoc

ked(type, packageName, Binder

.

getOrigCallingUid(),

token, resultWho,

requestCode, intents, resolv

edTypes, flags, options);

. . . . .

.

}

getIntentSender()函数中有一

段逐条判断intents[]的代码,

我用伪代码

checkIntents(intents)来表示,

这部分对应的实际代码如

下:

for (int i=0; i<intents.lengt

h; i++)

{

Intent intent = intents[i

]

;

if (intent != null)

{

if (intent.hasFileDes

criptors())

{

throw new Illegal

ArgumentException("File descr

iptors passed in Intent");

}

if (type == ActivityM

anager.INTENT_SENDER_BROADCAS

T &&

(intent.getFlags(

)

&Intent.FLAG_RECEIVER_BOOT_U

PGRADE) != 0)

{

throw new Illegal

ArgumentException("Can't use

FLAG_RECEIVER_BOOT_UPGRADE he

re");

}

intents[i] = new Inte

nt(intent);

}

}

这段代码说明在获取

PendingIntent对象时,intent

中是不能携带文件描述符

的。而且如果这个

PendingIntent是那种要发出

广播的PendingIntent,那么

intent中也不能携带

FLAG_RECEIVER_BOOT_U

P GRADE标识

符。“BOOT_UPGRADE” 应

该是“启动并升级”的意思,

它不能使用PendingIntent。

getIntentSender()中最核

心的一句应该是调用

getIntentSenderLocked()的那

句。

2

.4.2

getIntentSenderLocked()

函数

getIntentSenderLocked()

的代码截选如下:

【frameworks/base/services/ja

va/com/android/server/am/Acti

vityManagerService.java】

IIntentSender getIntentSender

Locked(int type, String packa

geName, int callingUid,

IBinder token, String

resultWho,

int requestCode, Inten

t[] intents,

String[] resolvedTypes

int flags,

,

{

Bundle options)

.

/

. . . . .

/ 如果是INTENT_SENDER_ACT

IVITY_RESULT类型,那么要判断toke

n所

/

/ 代表的activity是否还在ac

tivity栈中

.

/

. . . . .

/ 整理flags中的信息

.

. . . . .

PendingIntentRecord.Key k

ey = new PendingIntentRecord.

Key(type, packageName,

activity, resultWho,

requestCode, intents,

resolvedTypes, flags, opt

ions);

/

/ 尽力从哈希映射表中查找key

对应的PendingIntentRecord,如果

找不到就创建一个新的节点。

WeakReference<PendingInte

ntRecord> ref;

ref = mIntentSenderRecord

s.get(key);

PendingIntentRecord rec =

ref != null ? ref.get() : nu

ll;

if (rec != null)

{

/

/ 找到了匹配的PendingI

ntent,现在考虑要不要更新它,或者取

消它。

if (!cancelCurrent)

{

if (updateCurrent

)

{

/

/ 如果明确指定

了FLAG_UPDATE_CURRENT,那么更新

找到的节点

if (rec.key.r

equestIntent != null) {

rec.key.r

equestIntent.replaceExtras(in

tents != null ?

i

ntents[intents.length - 1] :

null);

}

if (intents !

intents[i

=

null) {

ntents.length-1] = rec.key.re

questIntent;

rec.key.a

llIntents = intents;

rec.key.a

llResolvedTypes = resolvedTyp

es;

}

else {

rec.key.a

llIntents = null;

rec.key.a

llResolvedTypes = null;

}

}

/

/ 凡是能找到对应的

节点,而且又不取消该节点的,那么就r

eturn这个节点

return rec;

}

/

/ 如果PendingIntent的

标志中带有FLAG_CANCEL_CURRENT,

则从哈希映射表中删除之

rec.canceled = true;

mIntentSenderRecords.

remove(key);

}

if (noCreate)

{

/

/ 如果明确表示了不创建新

节点,也就是说标志中带有FLAG_NO_C

REATE ,

/

/ 那么不管是不是Cancel

了PendingIntent,此时一概直接返回

return rec;

}

/

/ 从哈希映射表中找不到,而且

又没有写明FLAG_NO_CREATE,此时创

建一个新节点

rec = new PendingIntentRe

cord(this, key, callingUid);

mIntentSenderRecords.put(

key, rec.ref);

if (type == ActivityManag

er.INTENT_SENDER_ACTIVITY_RES

ULT)

{

/

/ 如果intent需要返回结

果,那么修改token对应的ActivityR

ecord

/

/ 的pendingResults域

if (activity.pendingR

esults == null)

{

activity.pendingR

esults = new HashSet<WeakRefe

rence<PendingIntentRecord>>()

;

}

activity.pendingResul

ts.add(rec.ref);

}

return rec;

}

上面这段代码主要做的事情

有:

1) 将传进来的多个参数信

息整理成一个

PendingIntentRecord.Key对象

(key);

) 尝试从

2

mIntentSenderRecords总表中

查找和key相符的

PendingIntentRecord节点;

3) 根据flags参数所含有的

意义,对得到的

PendingIntentRecord进行加

工。有时候修改之,有时候

删除之。

4

) 如果在总表中没有找到

对应的PendingIntentRecord

节点,或者根据flags的语义

删除了刚找到的节点,那么

此时的默认行为是创建一个

新的PendingIntentRecord 节

点,并插入总表。除非flags

中明确指定了

FLAG_NO_CREATE,此时

不会创建新节点。

2

.4.3 说说flags

从getIntentSenderLocked()的

代码中,我们终于搞明白了

flags中那些特定比特值的意

义了。我们现在总结一下。

应该说这些flags比特值基本

上都是在围绕着

mIntentSenderRecords总表说

事的。其中,

FLAG_CANCEL_CURRENT

的意思是,当我们获取

PendingIntent时,如果可以

从总表中查到一个相符的已

存在的PendingIntentRecord

节点的话,那么需要把这个

节点从总表中清理出去。而

在没有指定

FLAG_CANCEL_CURRENT

的大前提下,如果用户指定

FLAG_UP DATE_CURRENT

标识,那么会用新的intents

参数替掉刚查到的

PendingIntentRecord中的旧

intents。

而不管是刚清理了已存在的

PendingIntentRecord,还是

压根儿就没有找到符合的

PendingIntentRecord,只要

用户没有明确指定

FLAG_NO_CREATE标识,

系统就会尽力创建一个新的

PendingIntentRecord节点,

并插入总表。

至于FLAG_ONE_SHOT标识

嘛,它并没有在

getIntentSenderLocked()中露

脸儿。它的名字

是“FLAG_ONE_SHOT”,也

就是“只打一枪”的意思,那

么很明显,这个标识起作用

的地方应该是在“激发”函数

里。在最终的激发函数

(sendInner())里,我们可

以看到下面的代码:

【frameworks/base/services/ja

va/com/android/server/am/Pen

dingIntentRecord. java】

int sendInner(int code, Inten

t intent, String resolvedType

,

IIntentReceiver finis

hedReceiver, String requiredP

ermission,

IBinder resultTo, Str

ing resultWho, int requestCod

e,

int flagsMask, int fl

agsValues, Bundle options)

{

synchronized(owner) {

if (!canceled)

{

sent = true;

if ((key.flags &

PendingIntent.FLAG_ONE_SHOT)

!

= 0) {

owner.cancelI

ntentSenderLocked(this, true)

;

canceled = tr

ue;

}

.

.

. . . . .

. . . . .

}

}

return ActivityManager.ST

ART_CANCELED;

}

意思很简单,一进行激发就

把相应的

PendingIntentRecord节点从

总表中清理出去,而且把

PendingIntentRecord的

canceled域设为true。这样,

以后即便外界再调用send()

动作都没用了,因为再也无

法进入if (!canceled)判断了。

2

.4.4 将

PendingIntentRecord节

点插入总表

接下来

getIntentSenderLocked()函数

new了一个

PendingIntentRecord节点,

并将之插入

mIntentSenderRecords总表

中。

2

.5 PendingIntent的激

发动作

下面我们来看PendingIntent

的激发动作。在前文我们已

经说过,当需要激发

PendingIntent之时,主要是

通过调用PendingIntent的

send()函数来完成激发动作

的。PendingIntent提供了多

个形式的send()函数,然而

这些函数的内部其实调用的

是同一个send(),其函数原

型如下:

public void send(Context cont

ext, int code, Intent intent,

OnFinishe

d onFinished, Handler handler

,

String requiredPermission)

throws Ca

nceledException

该函数内部最关键的一句

是:

int res = mTarget.send(code,

intent, resolvedType,

onFinishe

d != null ? new FinishedDispa

tcher(this, onFinished, handl

er) : null,

requiredP

ermission);

我们前文已经介绍过这个

mTa rge t域了,它对应着AMS

中的某个

PendingIntentRecord。

所以我们要看一下

PendingIntentRecord一侧的

send()函数,其代码如下:

public int send(int code, Int

ent intent, String resolvedTy

pe,

IIntentRec

eiver finishedReceiver, Strin

g requiredPermission)

{

return sendInner(code, in

tent, resolvedType, finishedR

eceiver,

requiredPermi

ssion, null, null, 0, 0, 0, n

ull);

}

其中sendInner()才是真正做

激发动作的函数。

sendInner()完成的主要逻辑

动作有:

1) 如果当前

PendingIntentRecord节点已

经处于canceled域为true的状

态,那么说明这个节点已经

被取消掉了,此时

sendInner()不会做任何实质

上的激发动作,只是简单地

return

Ac tivityMa na ge r. START_CAN

CELED而已。

2) 如果当初在创建这个节

点时,使用者已经指定了

FLAG_ONE_SHOT标志位的

话,那么此时sendInner()会

把这个PendingIntentRecord

节点从AMS中的总表中摘

除,并且把canceled域设为

true。而后的操作和普通激

发时的动作是一致的,也就

是说也会走下面的第3)

步。

3) 关于普通激发时应执行

的逻辑动作是,根据当初创

建PendingIntentRecord节点

时,用户指定的type类型,

进行不同的处理。这个type

其实就是我们前文所说的

INTENT_SENDER_ACTIVIT

Y、

INTENT_SENDER_BROAD

CAST、

INTENT_SENDER_SERVIC

E等类型啦,大家如有兴

趣,可自己参考本文一开始

所说的ge tAc tivity( )、

getBroadcast()、getService()

等函数的实现代码。

现在还有一个问题是,既然

我们在当初获取

PendingIntent时,已经指定

了日后激发时需要递送的

intent(或intent数组),那

么为什么send()动作里还有

一个intent参数呢?它们的关

系又是什么呢?我猜想,

PendingIntent机制的设计者

是希望给激发端一个修

改“待激发的intent”的机会。

比如当初我们获取

PendingIntent对象时,如果

在flags里设置了

FILL_IN_ACTION标志位,

那么就说明我们允许日后在

某个激发点,用新的intent的

mAction域值,替换掉我们最

初给的intent的mAction域

值。如果一开始没有设置

FILL_IN_ACTION标志位,

而且在最初的intent里已经有

了非空的mAction域值的话,

那么即使在激发端又传入了

新intent,它也不可能修改用

新intent的mAction域值替换

旧intent的mAction域值。

细心的读者一定记得,当初

获取PendingIntent对象时,

我们可是向AMS端传递了一

个intent数组噢,虽然一般情

况下这个数组里只有一个

intent元素,但有时候我们也

是有可能一次性传递多个

intent的。比如ge tAc tivitie s( )

函数就可以一次传递多个

intent。可是现在激发动作

send()却只能传递一个intent

参数,这该如何处理呢?答

案很简单,所传入的intent只

能影响已有的intent数组的最

后一个intent元素。大家可以

看看sendInner 里

allIntents[allIntents.length-1] =

finalIntent;一句。

Ok,intent说完了,下面就

该做具体的激发了。我们以

简单的

INTENT_SENDER_BROAD

CAST型PendingIntentRecord

来说明,此时的激发动作就

是发送一个广播:

owner.broadcastIntentInPackag

e(key.packageName, uid, final

Intent, resolvedType,

finishedReceiver, code, nul

l, null,

requiredPermission, (finish

edReceiver != null),

false, UserId.getUserId(uid

)

);

至于其他类型的

PendingIntentRecord的激发

动作,大家可以自行查阅代

码,它们的基本代码格局都

是差不多的。

3

小结

本文是基于我早先的一点儿

笔记整理而成的。当时为了

搞清楚PendingIntent的机

理,也查阅了一些网上的相

关文章,只是都不大满足我

的要求,后来只好自己看代

码,终于得了些自己的浅

见。现在把我过去的一点儿

认识整理出来,希望能对学

习PendingIntent的同学有点

儿帮助。

1

1

. 基本概念

.1 Instrumentation是什

么?

顾名思义,仪器仪表,用于

在应用程序中进行“测

量”和“管理”工作。一个应用

程序中只有一个

Instrumentation实例对象,且

每个Ac tivity都有此对象的引

用。Instrumentation将在任何

应用程序运行前初始化,可

以通过它监测系统与应用程

序之间的所有交互,即类似

于在系统与应用程序之间安

装了个“窃听器”。

当Ac tivityThre a d 创建

(callActivityOnCreate)、暂

停、恢复某个Ac tivity时,通

过调用此对象的方法来实

现,如:

1

) 创

建: c a llAc tivityOnCre a te

2

) 暂

停: c a llAc tivityOnP a use

) 恢

3

复: c a llAc tivityOnRe sume

Instrumentation和

Ac tivityThre a d的关系,类似

于老板与经理的关系,老板

负责对外交流(如与Ac tivity

Manager Service),

Instrumentation负责管理并完

成老板交待的任务。

它通过以下两个成员变量来

对当前应用进程中的Ac tivity

进行管理:

private List<ActivityWaiter>

mWaitingActivities;

private List<ActivityMonitor

>

mActivityMonitors;

其功能函数下表所示:

功能

函数

addMonito

removeMo

monitor)

增加删除

Monitor

newApplic

Context co

newActivit

classNam

callActivity

Bundle ici

callActivity

callActivity

callActivity

callActivity

activity)

Application与

Activity生命周

期控制

callActivity

callActivity

onCreate(

start()

onStart()

finish(int r

onDestroy

Instrumentation

生命周期控制

sendChar

sendPoint

sendTrack

event)

sendTrack

event)

发送用户操控

信息到当前窗

startActivi

用Context

runOnMai

waitForIdl

同步操作

2

. Android应用程序启

动过程(MainActivity)

即Ma inAc tivity的启动过程,

在此过程中,将创建一个新

的进程来执行此

Ma inAc tivity 。

Android应用程序从Launcher

启动流程如下所示:

/

***************************

*

*

****************************

********

*

Launcher通过Binder告诉Acti

vityManagerService ,

*

*

它将要启动一个新的Activity;

**************************

*

*

****************************

*******/

Launcher.startActivitySafely

>

-

Launcher.startActivity->

/

/

/要求在新的Task中启动此Activi

ty

/intent.addFlags(Intent.FL

AG_ACTIVITY_NEW_TASK)

Activity.startActivity->

Activity.startActivityForRe

sult->

Instrumentation.execStartAc

tivity->

/

/ ActivityManagerNative.g

etDefault()返回AMS Proxy接口

ActivityManagerNative.getD

efault().startActivity->

ActivityManagerProxy.start

Activity->

ActivityManagerService.st

artActivity-> (AMS)

ActivityManagerService.st

artActivityAsUser->

ActivityStack.startActiv

ityMayWait->

ActivityStack.resolveAct

ivity(获取ActivityInfo)

/

/aInfo.name为main Act

ivity,如:com.my.test.MainAct

ivity

/

/aInfo.applicationInf

o.packageName为包名,如com.my.t

est

ActivityStack.startActiv

ityLocked->

/

/ProcessRecord caller

App; 调用者即Launcher信息

/

/ActivityRecord sourc

eRecord; Launcher Activity相

关信息

/

/ActivityRecord r=new

ActivityRecord(...),将要创建

的Activity相关信息

ActivityStack.startActiv

ityUncheckedLocked->

/

/Activity启动方式:Activ

ityInfo.LAUNCH_MULTIPLE/LAUNC

H_SINGLE_INSTANCE/

/

/

Activity

Info.LAUNCH_SINGLE_TASK/LAUNC

H_SINGLE_TOP)

/

/ 创建一个新的task,即Tas

kRecord,并保存在ActivityRecord

task 中

.

/

/r.setTask(new TaskRec

ord(mService.mCurTask, r.info

,

intent), null, true)

/

/ 把新创建的Activity放在

栈顶

ActivityStack.startActi

vityLocked->

ActivityStack.resumeTop

ActivityLocked->

ActivityStack.startPaus

ingLocked (使Launcher进入Pause

d状态)->

/

**********************

*

*

****************************

*************

*

AMS通过Binder通知Laun

cher进入Paused状态

*

*********************

*

*

****************************

************/

ApplicationThreadProxy

schedulePauseActivity->

.

/

/private class Applic

ationThread extends Applicati

onThreadNative

ApplicationThread.sche

dulePauseActivity->

ActivityThread.queueO

rSendMessage->

/

/ 调用Activity.onUse

rLeaveHint

/

/

/ 调用Activity.onPau

/ 通知activity manag

se

er我进入了pause状态

ActivityThread.handle

PauseActivity->

/

********************

*

*

****************************

***************

*

Launcher通过Binder

告诉AMS,它已经进入Paused状态

*******************

*

*

*

****************************

**************/

ActivityManagerProxy.

activityPaused->

ActivityManagerServic

e.activityPaused->

ActivityStack.activit

yPaused->(把Activity状态修改为P

AUSED)

ActivityStack.complet

ePauseLocked->

/

/ 参数为代表Launcher这

个Activity的ActivityRecord

/

/ 使用栈顶的Activity进

入RESUME状态

ActivityStack.resumeT

opActivityLokced->

/

/topRunningActivit

yLocked将刚创建的放于栈顶的activi

ty取回来

/

/ 即在ActivityStac

k.startActivityUncheckedLocke

d中创建的

/

********************

*

*

****************************

***************

*

AMS创建一个新的进程,

用来启动一个ActivityThread实例,

*

即将要启动的Activity

就是在这个ActivityThread实例中运

*

*******************

*

*

****************************

**************/

ActivityStack.startSp

ecificActivityLocked->

/

/ 创建对应的ProcessRe

cord

ActivityManagerServi

ce.startProcessLocked->

/

/ 启动一个新的进程

/

/ 新的进程会导入andr

oid.app.ActivityThread类,并且

执行它的main函数,

/

/ 即实例化ActivityT

hread, 每个应用有且仅有一个Activi

tyThread实例

Process.start("andr

oid.app.ActivityThread",...)-

>

/

/ 通过zygote机制创建

一个新的进程

Process.startViaZyg

ote->

/

/ 这个函数在进程中创建

一个ActivityThread实例,然后调用

/

/ 它的attach函数,接

着就进入消息循环

ActivityThread.main

-

>

/

******************

*

*

****************************

*****************

*

ActivityThread通

过Binder将一个ApplicationThrea

d类的Binder对象

*

传递给AMS,以便AMS

通过此Binder对象来控制Activity整

个生命周期

*

*****************

*

*

****************************

****************/

ActivityThread.atta

ch->

IActivityManager.at

tachApplication(mAppThread)->

ActivityManagerProx

y.attachApplication->

ActivityManagerServ

ice.attachApplication->

/

/ 把在ActivityMana

gerService.startProcessLocked

中创建的ProcessRecord取出来

ActivityManagerServ

ice.attachApplicationLocked->

/

******************

*

*

****************************

*****************

*

AMS通过Binder通知

ActivityThread一切准备OK,它可以

真正启动新的Activity了

*

*****************

*

*

****************************

****************/

/

/ 真正启动Activity

ActivityStack.realS

tartActivityLocked->

ApplicationThreadPr

oxy.scheduleLaunchActivity->

ApplicationThread.s

cheduleLaunchActivity->

ActivityThread.hand

leLaunchActivity->

/ 加载新的Activit

y类,并执行它的onCreate

ActivityThread.pe

rformLaunchActivity

/

/

*1) Instrumenta

tion.newActivity: 加载新类,即

创建Activity对象;

2

) ActivityCli

entRecord.packageInfo.makeApp

lication:创建Application对象;

<

LoadedApk.

makeApplication>

3

) Activity.at

tach(Context context, Activit

yThread aThread,

Instrume

ntation instr, IBinder token,

int ident,

Applicat

ion application, Intent inten

t, ActivityInfo info,

CharSequ

ence title, Activity parent,

String id,

NonConfi

gurationInstances lastNonConf

igurationInstances,

Configur

ation config):把Application

attach到Activity, 即把Activtiy

相关信息设置到新

创建的Activity中

4

) Instrumenta

tion.callActivityOnCreate:调

用onCreate;*/

/

/ 使用Activity进

入RESUMED状态,并调用onResume

ActivityThread.ha

ndleResumeActivity

3

.

ActivityManagerService

3

.1 类中关键信息

public final class ActivityM

anagerService extends Activit

yManagerNative

implements Watchdog.

Monitor, BatteryStatsImpl.Bat

teryCallback {

.

/

..

/ Maximum number of rec

ent tasks that we can remembe

r.

static final int MAX_REC

ENT_TASKS = 20;

public ActivityStack mMa

inStack; // 管理Activity堆栈

/

/ Whether we should sho

w our dialogs (ANR, crash, et

c) or just perform their

/

/ default actuion autom

atically. Important for devi

ces without direct input

/

/ devices.

private boolean mShowDia

logs = true;

/

**

*

Description of a requ

est to start a new activity,

which has been held

*

due to app switches b

eing disabled.

*

/

static class PendingActi

vityLaunch {

ActivityRecord r;

ActivityRecord sourc

eRecord;

}

int startFlags;

/

**

*

Activity we have told

the window manager to have k

ey focus.

*

/

ActivityRecord mFocusedA

ctivity = null;

/

**

*

List of intents that

were used to start the most r

ecent tasks.

*

/

final ArrayList<TaskReco

rd> mRecentTasks = new ArrayL

ist<TaskRecord>();

/

**

*

Process management.

*

/

final ProcessList mProce

ssList = new ProcessList();

/

**

*

All of the applicatio

ns we currently have running

organized by name.

*

The keys are strings

of the application package na

me (as

*

returned by the packa

ge manager), and the keys are

ApplicationRecord

*

objects.

*

/

final ProcessMap<Process

Record> mProcessNames = new P

rocessMap<ProcessRecord>();

/

**

*

The currently running

isolated processes.

*

/

final SparseArray<Proces

sRecord> mIsolatedProcesses =

new SparseArray<ProcessRecor

d>();

.

..

public static final Cont

ext main(int factoryTest) { /

/

main入口函数

AThread thr = new AT

hread();

thr.start();

synchronized (thr) {

while (thr.mServ

ice == null) {

try {

thr.wait

();

}

catch (Int

erruptedException e) {

}

}

}

ActivityManagerServi

ce m = thr.mService;

mSelf = m;

ActivityThread at =

ActivityThread.systemMain();

mSystemThread = at;

Context context = at

.

getSystemContext();

context.setTheme(and

roid.R.style.Theme_Holo);

m.mContext = context

;

m.mFactoryTest = fac

toryTest;

m.mMainStack = new A

ctivityStack(m, context, true

)

; // 创建ActivityStack

m.mBatteryStatsServi

ce.publish(context);

m.mUsageStatsService

publish(context);

.

synchronized (thr) {

thr.mReady = tru

thr.notifyAll();

e;

}

m.startRunning(null,

null, null, null);

return context;

}

}

3

.2 家族图谱

4

. ActivityStack-真正做

事的家伙

ActivityManagerService使用

它来管理系统中所有的

Ac tivitie s的状态,Ac tivitie s使

用stack的方式进行管理。它

是真正负责做事的家伙,很

勤快的,但外界无人知道!

4

.1 类中关键信息

/

**

*

State and management of a

single stack of activities.

*

/

final class ActivityStack {

final ActivityManagerSer

vice mService;

final boolean mMainStack

;

final Context mContext;

enum ActivityState {

INITIALIZING,

RESUMED,

PAUSING,

PAUSED,

STOPPING,

STOPPED,

FINISHING,

DESTROYING,

DESTROYED

}

/

**

*

The back history of a

ll previous (and possibly sti

ll

*

running) activities.

It contains HistoryRecord ob

jects.

*

/

final ArrayList<Activity

Record> mHistory = new ArrayL

ist<ActivityRecord>();

/

**

*

Used for validating a

pp tokens with window manager

.

*

/

final ArrayList<IBinder>

mValidateAppTokens = new Arr

ayList<IBinder>();

/

**

*

List of running activ

ities, sorted by recent usage

.

*

The first entry in th

e list is the least recently

used.

*

It contains HistoryRe

cord objects.

*

/

final ArrayList<Activity

Record> mLRUActivities = new

ArrayList<ActivityRecord>();

/

**

*

List of activities th

at are waiting for a new acti

vity

*

to become visible bef

ore completing whatever opera

tion they are

*

*

supposed to do.

/

final ArrayList<Activity

Record> mWaitingVisibleActivi

ties

=

new ArrayList<

ActivityRecord>();

/

**

*

List of activities th

at are ready to be stopped, b

ut waiting

*

for the next activity

to settle down before doing

so. It contains

*

HistoryRecord objects

.

*

/

final ArrayList<Activity

Record> mStoppingActivities

new ArrayList<

ActivityRecord>();

=

/

**

*

List of activities th

at are in the process of goin

g to sleep.

*

/

final ArrayList<Activity

Record> mGoingToSleepActiviti

es

=

new ArrayList<

ActivityRecord>();

/

**

*

When we are in the pr

ocess of pausing an activity,

before starting the

*

next one, this variab

le holds the activity that is

currently being paused.

*

/

ActivityRecord mPausingA

ctivity = null;

/

**

*

This is the last acti

vity that we put into the pau

sed state. This is

*

used to determine if

we need to do an activity tra

nsition while sleeping,

*

when we normally hold

the top activity paused.

*

/

ActivityRecord mLastPaus

edActivity = null;

/

**

*

Current activity that

is resumed, or null if there

is none.

*

/

ActivityRecord mResumedA

ctivity = null;

/

**

*

This is the last acti

vity that has been started.

It is only used to

*

identify when multipl

e activities are started at o

nce so that the user

*

can be warned they ma

y not be in the activity they

think they are.

*

/

ActivityRecord mLastStar

tedActivity = null;

/

*

**

Set to indicate whethe

r to issue an onUserLeaving c

allback when a

*

newly launched activi

ty is being brought in front

of us.

*

/

boolean mUserLeaving = f

alse;

ActivityStack(ActivityMa

nagerService service, Context

context, boolean mainStack)

{

mService = service;

mContext = context;

mMainStack = mainSta

ck;

.

..

}

.

..

}

4

.2 家族图谱

5

. ProcessRecord

记录了一个进程的相关信

息。

5

.1 类中关键信息

/

**

*

Full information about a

particular process that

*

*

is currently running.

/

class ProcessRecord {

final ApplicationInfo in

fo; // all about the first ap

p in the process

final boolean isolated;

/

/ true if this is a spec

ial isolated process

final int uid;

/

/ uid of process; may be

different from 'info' if iso

lated

final int userId;

/

/ user of process.

final String processName

;

// name of the process

IApplicationThread threa

d; // the actual proc... ma

y be null only if

/

/ 'persistent' is true (

in which case we

/

/ are in the process of

launching the app)

/

/ 是ApplicationThread对象

的远程接口,

// 通过此接口通知Activity进

入对应的状态

int pid;

/ The process of this ap

plication; 0 if none

/

ApplicationInfo instrume

ntationInfo; // the applicati

on being instrumented

BroadcastRecord curRecei

ver;// receiver currently run

ning in the app

/

/ contains HistoryRecor

d objects

final ArrayList<Activity

Record> activities = new Arra

yList<ActivityRecord>();

/

/ all ServiceRecord run

ning in this process

final HashSet<ServiceRec

ord> services = new HashSet<S

erviceRecord>();

/

/ services that are cur

rently executing code (need t

o remain foreground).

final HashSet<ServiceRec

ord> executingServices

=

new HashSet<S

erviceRecord>();

/

/ All ConnectionRecord

this process holds

final HashSet<ConnectionR

ecord> connections

=

new HashSet<Co

nnectionRecord>();

/

/ all IIntentReceivers

that are registered from this

process.

final HashSet<ReceiverLi

st> receivers = new HashSet<R

eceiverList>();

/

/ class (String) -> Con

tentProviderRecord

final HashMap<String, Co

ntentProviderRecord> pubProvi

ders

=

new HashMap<St

ring, ContentProviderRecord>(

)

;

/

/ All ContentProviderRe

cord process is using

final ArrayList<ContentP

roviderConnection> conProvide

rs

=

new ArrayList<

ContentProviderConnection>();

boolean persistent;

/

/ always keep this appli

cation running?

boolean crashing;

/ are we in the process

of crashing?

Dialog crashDialog;

/ dialog being displayed

due to crash.

boolean notResponding;

/ does the app have a no

/

/

/

t responding dialog?

Dialog anrDialog;

/

/ dialog being displayed

due to app not resp.

boolean removed;

/

/ has app package been r

emoved from device?

boolean debugging;

/

/ was app launched for d

ebugging?

boolean waitedForDebugge

r; // has process show wait

for debugger dialog?

Dialog waitDialog;

/

/ current wait for debug

ger dialog

ProcessRecord(BatterySta

tsImpl.Uid.Proc _batteryStats

,

IApplicationThread _thread,

ApplicationInfo

_

info, String _processName, i

nt _uid) {

batteryStats = _batt

eryStats;

info = _info;

isolated = _info.uid

!

= _uid;

uid = _uid;

userId = UserHandle.

getUserId(_uid);

processName = _proce

ssName;

pkgList.add(_info.pa

ckageName);

thread = _thread;

maxAdj = ProcessList

HIDDEN_APP_MAX_ADJ;

hiddenAdj = clientHid

.

denAdj = emptyAdj = ProcessLi

st.HIDDEN_APP_MIN_ADJ;

curRawAdj = setRawAd

j = -100;

curAdj = setAdj = -1

0

0;

persistent = false;

removed = false;

}

.

..

}

5

. 2 家族图谱

6

. IApplicationThread接

口AMS->Application

IApplicationThread为AMS作

为客户端访问Application服

务器端的Binder接口。当创

建Application时,将把此

Binder对象传递给AMS,然

后AMS把它保存在

mProcessNames.ProcessReco

rd.thread中。当需要通知

Application工作时,则调用

IApplicationThread中对应的

接口函数。

其相互关系如下图所示:

前几天凯子哥写的

Framework层的解析文章

Ac tivity启动过程全解

析》,反响还不错,这说

明“写让大家都能看懂的

Framework解析文章”的思

想是基本正确的。

我个人觉得,深入分析的

文章必不可少,但是对于

更多的Android开发者——

即只想做应用层开发,不

想了解底层实现细节——

来说,“整体上把握,重要

环节深入“是更好的学习方

式。因为这样既可以有完

整的知识体系,又不会在

浩瀚的源码世界里迷失兴

趣和方向。

所以呢,今天凯子哥又带

来一篇文章,接着上一篇

的结尾,重点介绍Ac tivity

开启之后,Android系统对

界面的一些操作及相关知

识。

本期关键字

学习目标

写作方式

进入正题

onCreate中的

setContentView到底做了什

么为什么不能在

setContentView之后设置某

些Window属性标志

Ac tivity中的findVie w ById

本质上是在做什么

Window和PhoneWindow是

什么关系WindowManager

是做什么的

Ac tivity中Window类型的

成员变量mWindow是什么

时候初始化的

PhoneWindowsetContentVi

ew到底发生了什么

如何验证上一个问题

我们通过setContentView设

置的界面为什么在

onResume之后才对用户可

见呢

ViewManagerWindowMana

ge rWindowMa na ge rImplWi

ndowManagerGlobal到底

都是些什么玩意

ViewRootImpl是什么有什

么作用ViewRootImpl如何

与WMS通信

从什么时候开始绘制整个

Ac tivity的Vie w树的

Window的类型有几种分别

在什么情况下会使用到哪

一种

为什么使用PopWindow的

时候不设置背景就不能触

发事件

在Ac tivity中使用Dia log的

时候为什么有时候会报错

Una ble to add window

token is not va lid is your

a c tivity running

为什么Toa st需要由系统统

一控制在子线程中为什么

不能显示Toast

结语

参考文章

本期关键字

Window

PhoneWindow

WindowManager

WindowManagerImpl

WindowManagerGlobal

RootViewImpl

DecorView

Dia log

PopWindow

Toast

学习目标

了解Android中Ac tivity界面

显示的流程,涉及到的关

键类,以及关键流程

解决在开发中经常遇到的

问题,并在源码的角度弄

清楚其原因

了解Framework层与

Window相关的一些概念和

细节

写作方式

老样子,咱们还是和上次一

样,采用一问一答的方式进

行学习,毕竟“带着问题学

习”才是比较高效的学习方

式。

进入正题

话说,在上次的文章中,我

们解析到了从手机开机第一

个zygote进程开启,到App的

第一个Ac tivity的onCreate()结

束,那么我们这里就接着上

次留下的茬,从第一个

Ac tivity的onCreate()开始说

起。

onCreate()中的

setContentView()到底

做了什么?为什么不

能在setContentView()

之后设置某些Window

属性标志?

一个最简单的onCreate()如

下:

@

Override

public void onCreate(Bund

le savedInstanceState) {

super.onCreate(savedI

nstanceState);

setContentView(R.layo

ut.activity_main);

}

通过上面几行简单的代码,

我们的App就可以显示在

a c tivity_ma in. xml文件中设计

的界面了,那么这一切到底

是怎么做到的呢?

我们跟踪一下源码,然后就

在Ac tivity的源码中找到了3

个setContentView()的重载函

数:

public void setContentView(in

t layoutResID) {

getWindow().setConten

tView(layoutResID);

initWindowDecorAction

Bar();

}

public void setContentVie

w(View view) {

getWindow().setConten

tView(view);

initWindowDecorAction

Bar();

}

public void setContentVie

w(View view, ViewGroup.Layout

Params params) {

getWindow().setConten

tView(view, params);

initWindowDecorAction

Bar();

}

我们上面用到的就是第一个

方法。虽然setContentView()

的重载函数有3种,但是我

们可以发现,内部做的事情

都是基本一样的。首先是调

getWindow()获取到一个对

象,然后调用了这个对象的

相关方法。咱们先来看一

下,getWindow()到底获取到

了什么对象。

private Window mWindow;

public Window getWindow() {

return mWindow;

}

喔,原来是一个Window对

象,你现在可能不知道

Window到底是个什么玩意,

但是没关系,你只要能猜到

它肯定和咱们的界面现实有

关系就得了,毕

竟叫“Window”么,Windows

系统的桌面不是

叫“Windows”桌面么,差不

多的东西,反正是用来显示

界面的就得了。

那么

initWindowDecorActionBar()

函数是做什么的呢?

写了这么多程序,看名字也

应该能猜出八九不离十了,

init是初始化,Window是窗

口,Decor是装饰,

ActionBar就更不用说了,所

以这个方法应该就是”初始

化装饰在窗口上的

ActionBar”,来,咱们看一

下代码实现:

/

**

*

Creates a new ActionBa

r, locates the inflated Actio

nBarView,

*

initializes the Action

Bar with the view, and sets m

ActionBar.

*

/

private void initWindowDe

corActionBar() {

Window window = getWi

ndow();

/

/ Initializing the w

indow decor can change window

feature flags.

/

/ Make sure that we

have the correct set before p

erforming the test below.

window.getDecorView()

if (isChild() || !win

;

dow.hasFeature(Window.FEATURE

_

ACTION_BAR) || mActionBar !=

null) {

return;

}

mActionBar = new Wind

owDecorActionBar(this);

mActionBar.setDefault

DisplayHomeAsUpEnabled(mEnabl

eDefaultActionBarUp);

mWindow.setDefaultIco

n(mActivityInfo.getIconResour

ce());

mWindow.setDefaultLog

o(mActivityInfo.getLogoResour

ce());

}

哟,没想到这里第一行代码

就又调用了getWindow(),接

着往下调用了

window.getDecorView(),从

注释中我们知道,在调用这

个方法之后,Window

的特征标志就被初始化了,

还记得如何让Ac tivity全屏

吗?

@

Override

public void onCreate(Bund

le savedInstanceState) {

super.onCreate(savedIn

stanceState);

requestWindowFeature(Wind

ow.FEATURE_NO_TITLE);

getWindow().setFlags(Wind

owManager.LayoutParams.FILL_P

ARENT,

Windo

wManager.LayoutParams.FILL_PA

RENT);

setContentView(R.layout.a

ctivity_main);

}

而且这两行代码必须在

setContentView()之前调用,

知道为啥了吧?因为在这里

就把Window的相关特征标志

给初始化了,在

setContentView()之后调

用就不起作用了!

如果你还不确定的话,我们

可以再看下

window.getDecorView()的部

分注释

/

**

*

Note that calling this

function for the first time

locks in"

"

*

various window charact

eristics as described in

*

{@link #setContentView

(View, android.view.ViewGroup

LayoutParams)}

.

*

/

public abstract View getD

ecorView();

注意,这个方法第一次调

用的时候,会锁定在

setContentView()中描述的

各种Window特征”

所以说,这也同样解释了为

什么在setContentView()之后

设置Window的一些特征标

志,会不起作用。如果以后

遇到类似问题,可以往这方

面想一下。

Activity中的

findViewById()本质上

是在做什么?

在上一个问题里面,咱们提

到了一个很重要的类——

Window,下面先简单看一下

这个类的几个方法:

public abstract class Window

{

public abstract void setC

ontentView(int layoutResID);

public abstract void setC

ontentView(View view);

public abstract void setC

ontentView(View view, ViewGro

up.LayoutParams params);

public View findViewById(

int id) {

return getDecorView()

.

}

findViewById(id);

}

哇塞,有个好眼熟的方法,

findViewById()!

是的,你每次在Ac tivity中用

的这个方法,其实间接调用

了Window类里面的方法!

public View findViewById(int

id) {

return getWindow().fi

ndViewById(id);

}

不过,findViewById()的最终

实现是在Vie w及其子类里面

的,所以getDecorView()获

取到的肯定是一个Vie w对象

或者是Vie w的子类对象:

public abstract View getDecor

View();

Ac tivity、Window中的

findViewById()最终调用的,

其实是Vie w的

findViewById()。

public class View implements

Drawable.Callback, KeyEvent.C

allback,

AccessibilityEventSou

rce {

public final View findVie

wById(int id) {

if (id < 0) {

return null;

}

return findViewTr

aversal(id);

}

protected View findVi

ewTraversal(int id) {

if (id == mID) {

return this;

}

return null;

}

}

但是,很显然,最终调用的

肯定不是Vie w类里面的

findViewTraversal(),因为这

个方法只会返回自身。

而且,findViewById()是final

修饰的,不可被重写,所以

说,肯定是调用的被子类重

写的findViewTraversal(),再

联想到,我们的界面上有很

多的Vie w,那么既能作为

Vie w的容器,又是Vie w的子

类的类是什么呢?很显然,

是Vie wGroup !

public abstract class ViewGro

up extends View implements Vi

ewParent, ViewManager {

@

Override

protected View findViewTr

aversal(int id) {

if (id == mID) {

return this;

}

final View[] where =

mChildren;

final int len = mChil

drenCount;

for (int i = 0; i < l

en; i++) {

View v = where[i]

;

if ((v.mPrivateFl

ags & PFLAG_IS_ROOT_NAMESPACE

== 0) {

)

v = v.findVie

wById(id);

if (v != null

)

}

{

return v;

}

}

}

return null;

}

所以说,在onCreate()中调用

findViewById()对控件进行绑

定的操作,实质上是通过在

某个Vie w中查找子Vie w实现

的,这里你先记住,这个

Vie w叫做

DecorView,而且它位于用

户窗口的最下面一层。

Window和

PhoneWindow是什么关

系?WindowManager

是做什么的?

话说,咱们前面介绍Window

的时候,只是简单的介绍了

下findViewById(),还没有详

细的介绍下这个类,下面咱

们一起学习一下。

前面提到过,Window是一个

抽象类,抽象类肯定是不能

实例化的,所以咱们需要使

用的是它的实现类,Window

的实现类有哪些呢?咱们从

Dash中看下Window类的文档

Window只有一个实现类,就

是PhoneWindow!所以说这

里扯出了PhoneWindow这个

类。

而且文档还说,这个类的一

个实例,也就是

PhoneWindow,应该被添加

到Window Manager中,作为

顶层的Vie w,所以,这里又

扯出了一个WindowManager

的概念。

除此之外,还说这个类提供

了标准的UI策略,比如背

景,标题区域,和默认的按

键处理等等,所以说,咱们

还知道了Window和

PhoneWindow这两个类的作

用!

所以说,看文档多重要呀!

OK,现在咱们已经知道了

Window和唯一的实现类

PhoneWindow,以及他们的

作用。而且我们还知道了

WindowManager,虽然不知

道干嘛的,但是从名字也可

以猜出是管理Window的,而

且还会把Window添加到里面

去,在下面的模块中,我会

详细的介绍WindowManager

这个类。

Activity中,Window类

型的成员变量

mWindow是什么时候

初始化的?

在每个Ac tivity中都有一个

Window类型的对象

mWindow,那么是什么时候

初始化的呢?

是在attach()的时候。

还记得attach()是什么时候调

用的吗?是在

ActivityThread.performLaunc

hAc tivity()的时候:

private Activity performLaunc

hActivity(ActivityClientRecor

d r, Intent customIntent) {

Activity activity = null

;

try {

java.lang.ClassLo

ader cl = r.packageInfo.getCl

assLoader();

activity = mInstr

umentation.newActivity(

cl, compo

nent.getClassName(), r.intent

)

;

}

catch (Exceptio

n e) {

.

..ignore some c

ode...

}

try {

.

..ignore some code..

.

activity.attach(appCo

ntext, this, getInstrumentati

on(), r.token,

r.ide

nt, app, r.intent, r.activity

Info, title, r.parent,

r.emb

eddedID, r.lastNonConfigurati

onInstances, config,

r.voi

ceInteractor);

.

..ignore some code..

.

}

}

}

catch (Exception e) {

return activity;

在attach()里面做了些什么

呢?

public class Activity extends

ContextThemeWrapper

implements LayoutInfl

ater.Factory2,

Window.Callback, KeyE

vent.Callback,

OnCreateContextMenuLi

stener, ComponentCallbacks2,

Window.OnWindowDismis

sedCallback {

private Window mWindow;

final void attach(Context

context, ActivityThread aThr

ead,

Instrumentation i

nstr, IBinder token, int iden

t,

Application appli

cation, Intent intent, Activi

tyInfo info,

CharSequence titl

e, Activity parent, String id

,

NonConfigurationI

nstances lastNonConfiguration

Instances,

Configuration con

fig, IVoiceInteractor voiceIn

teractor) {

.

/

..ignore some c

ode...

/就是在这里实例化

了Window对象

mWindow = Polic

yManager.makeNewWindow(this);

/设置各种回调

/

mWindow.setCallba

ck(this);

mWindow.setOnWind

owDismissedCallback(this);

mWindow.getLayout

Inflater().setPrivateFactory(

this);

/

/这就是传说中的UI

线程,也就是ActivityThread所在的

开启了消息循环机制的线程,所以在A

ctiivty所在线程中使用Handler不需

要使用Loop开启消息循环。

mUiThread = Thre

ad.currentThread();

.

..ignore some c

ode...

/

/终于见到了前面提到

的WindowManager,可以看到,Wind

owManager属于一种系统服务

mWindow.setWindow

Manager(

(WindowManage

r)context.getSystemService(Co

ntext.WINDOW_SERVICE),

mToken, mComp

onent.flattenToString(),

(info.flags &

ActivityInfo.FLAG_HARDWARE_A

CCELERATED) != 0);

if (mParent != null

)

{

mWindow.set

Container(mParent.getWindow()

)

;

}

mWindowManager

=

mWindow.getWindowManager();

}

}

attach()是Ac tivity实例化之

后,调用的第一个函数,在

这个时候,就实例化了

Window。那么这个

PolicyManager是什么玩意?

mWindow = PolicyManager.makeN

ewWindow(this);

来来来,咱们一起

RTFSC(Read The Fucking

Source Code) !

public final class PolicyMana

ger {

private static final Stri

ng POLICY_IMPL_CLASS_NAME =

"

com.android.internal

.

policy.impl.Policy";

private static final IPol

icy sPolicy;

static {

/

/ Pull in the actual

implementation of the policy

at run-time

try {

Class policyClass

=

Class.forName(POLICY_IMPL_

CLASS_NAME);

sPolicy = (IPolic

y)policyClass.newInstance();

catch (ClassNotFoun

dException ex) {

throw new Runtime

}

Exception(

POLICY_IM

PL_CLASS_NAME + " could not b

e loaded", ex);

}

catch (Instantiatio

nException ex) {

throw new Runtime

Exception(

POLICY_IM

PL_CLASS_NAME + " could not b

e instantiated", ex);

}

catch (IllegalAcces

sException ex) {

throw new Runtime

Exception(

POLICY_IM

PL_CLASS_NAME + " could not b

e instantiated", ex);

}

}

private PolicyManager() {

}

public static Window make

NewWindow(Context context) {

return sPolicy.makeNe

wWindow(context);

}

}

“Policy”是“策略”的意思,所

以就是一个策略管理器,采

用了策略设计模式。而

sP olic y是一个IP olic y类型,

IP olic y实际上是一个接口

public interface IPolicy {}

所以说,sP olic y的实际类型

是在静态代码块里面,利用

反射进行实例化的P olicy类

型。静态代码块中的代码在

类文件加载进类加载器之后

就会执行,

sP olic y就实现了实例化。

那我们看下在P olicy里面实

际上是做了什么

public class Policy implement

s IPolicy {

/

/看见PhoneWindow眼熟么?还

有DecorView,眼熟么?这就是前面所

说的那个位于最下面的View,findVie

wById()就是在它里面找的

private static final Stri

ng[] preload_classes = {

"

com.android.internal

policy.impl.PhoneLayoutInfla

ter",

.

"

com.android.internal

.

.

policy.impl.PhoneWindow",

"

com.android.internal

policy.impl.PhoneWindow$1",

com.android.internal

"

.

policy.impl.PhoneWindow$Dial

ogMenuCallback",

"

com.android.internal

.

policy.impl.PhoneWindow$Deco

rView",

"

com.android.internal

policy.impl.PhoneWindow$Pane

lFeatureState",

.

"

com.android.internal

.

policy.impl.PhoneWindow$Pane

lFeatureState$SavedState",

}

;

/

/由于性能方面的原因,在当前Po

licy类加载的时候,会预加载一些特定

的类

static {

for (String s : pr

eload_classes) {

try {

Class.forName

(s);

}

catch (ClassNot

FoundException ex) {

Log.e(TAG, "C

ould not preload class for ph

one policy: " + s);

}

}

}

/

/终于找到PhoneWindow了,我

没骗你吧,前面咱们所说的Window终于

可以换成PhoneWindow了~

public Window makeNewWind

ow(Context context) {

return new PhoneWindo

w(context);

}

}

P honeWindow. setContentView

()到底发生了什么?

上面说了这么多,实际上只

是追踪到了

P honeWindow. setContentView

(),下面看一下到底在这里

执行了什么:

@

Override

public void setContentVie

w(int layoutResID) {

if (mContentParent =

=

null) {

}

installDecor();

else if (!hasFeatur

e(FEATURE_CONTENT_TRANSITIONS

) {

)

mContentParent.re

moveAllViews();

}

if (hasFeature(FEATUR

E_CONTENT_TRANSITIONS)) {

final Scene newSc

ene = Scene.getSceneForLayout

(mContentParent, layoutResID,

getContex

t());

transitionTo(newS

cene);

}

else {

mLayoutInflater.i

nflate(layoutResID, mContentP

arent);

}

final Callback cb = g

etCallback();

if (cb != null && !is

Destroyed()) {

cb.onContentChang

ed();

}

}

@

Override

public void setContentVie

w(View view) {

setContentView(view,

new ViewGroup.LayoutParams(MA

TCH_PARENT, MATCH_PARENT));

}

@

Override

public void setContentVie

w(View view, ViewGroup.Layout

Params params) {

if (mContentParent ==

null) {

installDecor();

}

else if (!hasFeatur

e(FEATURE_CONTENT_TRANSITIONS

)

) {

mContentParent.re

moveAllViews();

}

if (hasFeature(FEATUR

E_CONTENT_TRANSITIONS)) {

view.setLayoutPar

ams(params);

final Scene newSc

ene = new Scene(mContentParen

t, view);

transitionTo(newS

cene);

}

else {

mContentParent.ad

dView(view, params);

}

final Callback cb = g

etCallback();

if (cb != null && !is

Destroyed()) {

cb.onContentChang

ed();

}

}

当我们第一次调用

serContentView()的时候,

mContentParent是没有进行

过初始化的,所以会调用

installDecor()。

为什么能确定

mContentParent是没有初始

化的呢?因为

mContentParent就是在

installDecor()里面赋值的

private void installDecor() {

if (mDecor == null) {

mDecor = generate

Decor();

.

..

}

if (mContentParent =

mContentParent =

=

null) {

generateLayout(mDecor);

}

}

在generateDecor()做了什

么?返回了一个DecorView

对象。

protected DecorView generate

Decor() {

return new DecorV

iew(getContext(), -1);

}

还记得前面推断出的,

DecorView是一个Vie wGroup

的结论吗?看下面,

DecorView继承自

FrameLayout,所以咱们的推

论是完全正确的。而且

DecorView是PhoneWindow

的私有内部类,这两个类关

系紧密!

public class PhoneWindow exte

nds Window implements MenuBui

lder.Callback {

private final class Decor

View extends FrameLayout impl

ements RootViewSurfaceTaker {

}

}

咱们再看一下在对

mContentParent赋值的

generateLayout(mDecor)做了

什么

protected ViewGroup generateL

ayout(DecorView decor) {

.

..判断并设置了一堆的标志位..

.

/

/这个是我们的界面将要采用的基

础布局xml文件的id

int layoutResource;

/

/根据标志位,给layoutResou

rce赋值

if ((features & (1 << FE

ATURE_SWIPE_TO_DISMISS)) != 0

)

{

layoutResource =

R.layout.screen_swipe_dismiss

;

}

..我们设置不同的主题以及样式

.

会采用不同的布局文件...

else {

/

/我们在下面代码验证的时

候,就会用到这个布局,记住它哦

layoutResource =

R.layout.screen_simple;

}

/

/要开始更改mDecor啦~

mDecor.startChanging(

)

;

/

/将xml文件解析成View对

象,至于LayoutInflater是如何将xm

l解析成View的,咱们后面再说

View in = mLayoutInfl

ater.inflate(layoutResource,

null);

/

/decor和mDecor实际上是

同一个对象,一个是形参,一个是成员变

decor.addView(in, new

ViewGroup.LayoutParams(MATCH

PARENT, MATCH_PARENT));

mContentRoot = (ViewG

_

roup) in;

/

/这里的常量ID_ANDROID_CON

TENT就是 public static final

int ID_ANDROID_CONTENT = com.

android.internal.R.id.content

;

/

/而且,由于是直接执行的find

ViewById(),所以本质上还是调用的m

Decor.findViewById()。而在上面

的decor.addView()执行之前,deco

r里面是空白的,所以我们可以断定,la

youtResource所指向的xml布局文件

内部,一定存在一个叫做“content”的

ViewGroup

ViewGroup contentPare

nt = (ViewGroup)findViewById(

ID_ANDROID_CONTENT);

if (contentParent ==

null) {

throw new Runtime

Exception("Window couldn't fi

nd content container view");

}

.

.....

mDecor.finishChanging

();

/

/最后把id为content的一

个ViewGroup返回了

return contentParent;

}

当上的代码执行之后,

mDecor和mContentParent就

初始化了,往下就会执行下

面的代码,利用

LayoutInflater把咱们传进来

的layoutResID转化成

Vie w对象,然后添加到id为

content的mContentParent中

mLayoutInflater.inflate(layou

tResID, mContentParent);

所以到目前为止,咱们已经

知道了以下几个事实,咱们

总结一下:

DecorView是

PhoneWindow的内部类,

继承自FrameLayout,是

最底层的界面

PhoneWindow是Window的

唯一子类,他们的作用就

是提供标准UI,标题,背

景和按键操作

在DecorView中会根据用

户选择的不同标志,选择

不同的xml文件,并且这

些布局会被添加到

DecorView中

在DecorView中,一定存

在一个叫做“content”的

Vie wGroup,而且我们在

xml文件中声明的布局文

件,会被添加进去

既然是事实,那么怎么才能

验证一下呢?

如何验证上一个问题

首先,说明一下运行条件:

/

/主题

name="AppTheme" parent="@andr

oid:style/Theme.Holo.Light.No

ActionBar"

/

/编译版本

android {

compileSdkVersion 19

buildToolsVersion '19.1.0

'

defaultConfig {

applicationId "com.so

cks.uitestapp"

minSdkVersion 15

targetSdkVersion 19

versionCode 1

versionName "1.0"

}

}

dependencies {

compile fileTree(include:

'*.jar'], dir: 'libs')

compile 'com.android.supp

[

ort:appcompat-v7:19.1.0'

}

/

/Activity代码

public class MainActivity ext

ends Activity {

@

Override

protected void onCreate(B

undle savedInstanceState) {

super.onCreate(savedI

nstanceState);

setContentView(R.layo

ut.activity_main);

}

}

/

<

"

<

:

/activity_main.xml

?xml version="1.0" encoding=

utf-8"?>

TextView xmlns:android="http

//schemas.android.com/apk/re

s/android"

android:layout_width="mat

ch_parent"

android:layout_height="ma

tch_parent"

android:gravity="center"

android:text="Hello World

!

"

android:textSize="20sp" />

OK,咱们的软件已经准备

好了,采用的是最简单的布

局,界面效果如下:

下面用Hierarchy看一下树状

结构:

第一层,就是上面的

DecorView,里面有一个线

性布局,上面的是

Vie wStub,下面就是id为

content的Vie wGroup,是一

个FrameLayout。而我们通过

setContentView()设置的布

局,就是Te xtVie w了。

能不能在源码里面找到源文

件呢?当然可以,这个布局

就是screen_simple.xml

frameworks/base/core/res/res/l

ayout/screen_simple.xml

<

LinearLayout xmlns:android="

http://schemas.android.com/ap

k/res/android"

android:layout_width="mat

ch_parent"

android:layout_height="ma

tch_parent"

android:fitsSystemWindows

"true"

android:orientation="vert

ical">

=

<

ViewStub android:id="@+i

d/action_mode_bar_stub"

android:inflate

dId="@+id/action_mode_bar"

android:layout=

"

@layout/action_mode_bar"

android:layout_

width="match_parent"

android:layout_

height="wrap_content"

android:theme="

attr/actionBarTheme" />

FrameLayout

android:id="@android

?

<

:

id/content"

android:layout_width

"match_parent"

android:layout_heigh

t="match_parent"

android:foregroundIn

sidePadding="false"

android:foregroundGr

=

avity="fill_horizontal|top"

android:foreground="

?

android:attr/windowContentOv

erlay" />

/LinearLayout>

<

所以,即使你不调用

setContentView(),在一个空

Ac tivity上面,也是有布局

的。而且肯定有一个

DecorView,一个id为content

的FrameLayout。

你可以采用下面的方式获取

到DecorView,但是你不能

获取到一个DecorView实

例,只能获取到

Vie wGroup 。

下面贴上这个图,你就可以

看明白了(转自 工匠若水)

ViewGroup view = (ViewGroup)

getWindow().getDecorView();

我们通过setContentView()设

置的界面,为什么在

onResume()之后才对用户可

见呢?

有开发经验的朋友应该知

道,我们的界面元素在

onResume()之后才对用户是

可见的,这是为啥呢?

那我们就追踪一下,

onResume()是什么时候调用

的,然后看看做了什么操作

就Ok了。

这一下,我们又要从

Ac tivityThre a d开始说起了,

不熟悉的快去看前一篇文章

《Ac tivity启动过程全解析》]

(http://blog.csdn.net/zhaokaiqi

ang1992/article/details/494282

87)。

话说,前文说到,我们想要

开启一个Ac tivity的时候,

Ac tivityThre a d的

handleLaunchActivity()会在

Handler中被调用

private void handleLaunchActi

vity(ActivityClientRecord r,

Intent customIntent) {

/

/就是在这里调用了Activity.a

ttach()呀,接着调用了Activity.o

nCreate()和Activity.onStart()

生命周期,但是由于只是初始化了mDeco

r,添加了布局文件,还没有把

/

/mDecor添加到负责UI显示的Ph

oneWindow中,所以这时候对用户来说

是不可见的

Activity a = performLaunc

hActivity(r, customIntent);

.

.....

if (a != null) {

/这里面执行了Activity.onRe

sume()

handleResumeActivity(r.to

/

ken, false, r.isForward,

!

ty.mFinished && !r.startsNotR

esumed);

r.activi

if (!r.activity.mFinished

&

& r.startsNotResumed) {

try {

r.activit

y.mCalled = false;

/

/执行Act

ivity.onPause()

mInstrume

ntation.callActivityOnPause(r

.

activity);

}

}

}

}

所以说,

ActivityThread.handleLaunch

Ac tivity执行完之后,Ac tivity

的生命周期已经执行了4个

(onCreate、onStart()、

onResume、onPause())。

下面咱们重点看下

handleResumeActivity()做了

什么

final void handleResumeActivi

ty(IBinder token,

boolean clearHide

,

boolean isForward, boolean

reallyResume) {

/

/这个时候,Activit

y.onResume()已经调用了,但是现在

界面还是不可见的

ActivityClientRec

ord r = performResumeActivity

(token, clearHide);

if (r != null) {

final Activit

y a = r.activity;

if (r.windo

w == null && !a.mFinished &&

willBeVisible) {

r.window = r.

activity.getWindow();

View decor =

r.window.getDecorView();

/

/decor对用户

不可见

decor.setVisi

bility(View.INVISIBLE);

ViewManager w

m = a.getWindowManager();

WindowManager

.

LayoutParams l = r.window.ge

tAttributes();

a.mDecor = de

/这里记住这个W

cor;

/

indowManager.LayoutParams的ty

pe为TYPE_BASE_APPLICATION,后

面介绍Window的时候会见到

l.type = Wind

owManager.LayoutParams.TYPE_B

ASE_APPLICATION;

if (a.mVisibl

eFromClient) {

a.mWindow

Added = true;

/

/终于被添

加进WindowManager了,但是这个时候

还是不可见的

wm.addVie

w(decor, l);

}

if (!r.activi

ty.mFinished && willBeVisible

&

& r.acti

vity.mDecor != null && !r.hid

eForNow) {

/

/在这里

,执行了重要的操作!

if (r.ac

tivity.mVisibleFromClient) {

r

.

activity.makeVisible();

}

}

}

从上面的分析中我们知道,

其实在onResume()执行之

后,界面还是不可见的,当

我们执行了

Ac tivity. ma ke Visible ()方法之

后,界面才对我们是可见的

if (!mWindowAdded) {

ViewManager wm =

getWindowManager();

wm.addView(mDecor

,

)

getWindow().getAttributes()

;

mWindowAdded = tr

ue;

}

mDecor.setVisibility(

View.VISIBLE);

OK,其实讲到了这里,关

于Ac tivity中的界面显示应该

算是告一段落了,我们知道

了Ac tivity的生命周期方法的

调用时机,还知道了一个最

简单的Ac tivity

的界面的构成,并了解了

Window、PhoneWindow、

DecorView、

WindowManager的存在。

但是我还是感觉不过瘾,因

为上面只是在流程上大体上

过了一遍,对于Window、

WindowManager的深入了解

还不够,所以下面就开始讲

解Window、WindowManager

等相关类的稍微高级点的知

识。

前面看累了的朋友,可以上

个厕所,泡个咖啡,休息下

继续往下看。

ViewMa na ger、

WindowManager、

WindowManagerImpl、

WindowManagerGlobal

到底都是些什么玩

意?

WindowManager其实是一个

接口,和Window一样,起作

用的是它的实现类

public interface WindowManage

r extends ViewManager {

/

/对这个异常熟悉么?当你往已

经销毁的Activity中添加Dialog的时

候,就会抛这个异常

public static class BadT

okenException extends Runtime

Exception {

public BadTokenEx

ception() {

}

public BadTokenExcept

ion(String name) {

super(name);

}

}

/

/其实WindowManager里面80

的代码是用来描述这个内部静态类的

%

public static class Lay

outParams extends ViewGroup.L

ayoutParams

implements Parcel

able {

}

}

WindowManager继承自

ViewManager这个接口,从

注释和方法我们可以知道,

这个就是用来描述可以对

Ac tivity中的子Vie w进行添加

和移除能力的接口。

/

** Interface to let you add

and remove child views to an

Activity. To get an instance

*

of this class, call {@lin

k android.content.Context#get

SystemService(java.lang.Strin

g) Context.getSystemService()

}

.

*

/

public interface ViewManager

{

public void addView(View

view, ViewGroup.LayoutParams

params);

public void updateVie

wLayout(View view, ViewGroup.

LayoutParams params);

public void removeVie

w(View view);

}

那么我们在使用

WindowManager的时候,到

底是在使用哪个类呢?

是WindowManagerImpl。

public final class WindowMana

gerImpl implements WindowMana

ger {}

怎么知道的呢?那我们还要

从Activity.attach()说起

话说,在attach()里面完成了

mWindowManager的初始化

final void attach(Context con

text, ActivityThread aThread,

Instrumentation i

nstr, IBinder token, int iden

t,

Application appli

cation, Intent intent, Activi

tyInfo info,

CharSequence titl

e, Activity parent, String id

,

NonConfigurationI

nstances lastNonConfiguration

Instances,

Configuration con

fig, IVoiceInteractor voiceIn

teractor) {

mWindow.setWindow

Manager(

(WindowManage

r)context.getSystemService(Co

ntext.WINDOW_SERVICE),

mToken, mComp

onent.flattenToString(),

(info.flags &

ActivityInfo.FLAG_HARDWARE_A

CCELERATED) != 0);

mWindowManager =

mWindow.getWindowManager();

}

那我们只好看下

(WindowManager)context.get

SystemService(Context.WIND

OW_SERVICE)是什么玩意

了。

这里要说明的是,context是

一个ContextImpl对象,这里

先记住就好,以后再细说。

class ContextImpl extends Con

text {

/

/静态代码块,完成各种系统服务的注

static {

.

.....

registerService(WINDOW_S

ERVICE, new ServiceFetcher()

{

Display mDefa

ultDisplay;

public Object

getService(ContextImpl ctx)

{

Display d

isplay = ctx.mDisplay;

if (displ

ay == null) {

if (m

DefaultDisplay == null) {

D

isplayManager dm = (DisplayMa

nager)ctx.getOuterContext().

getSystemService(Conte

xt.DISPLAY_SERVICE);

m

DefaultDisplay = dm.getDispla

y(Display.DEFAULT_DISPLAY);

}

displ

ay = mDefaultDisplay;

}

/

/没骗你吧

return ne

w WindowManagerImpl(display);

}

});

.

.....

}

@

Override

public Object getSystemSe

rvice(String name) {

ServiceFetcher fetche

r = SYSTEM_SERVICE_MAP.get(na

me);

return fetcher == nul

l ? null : fetcher.getService

(this);

}

}

要注意的是,这里返回的

WindowManagerImpl对象,

最终并不是和我们的Window

关联的,而且这个方法是有

可能返回null的,所以在

Window. setWindowManager()

的时候,进行了处理

public void setWindowManager

(WindowManager wm, IBinder ap

pToken, String appName,

boolean hardwareA

ccelerated) {

mAppToken = appToken;

mAppName = appName;

mHardwareAccelerated

=

hardwareAccelerated

| SystemProp

|

erties.getBoolean(PROPERTY_HA

RDWARE_UI, false);

/

/重试一遍

if (wm == null) {

wm = (WindowManag

er)mContext.getSystemService(

Context.WINDOW_SERVICE);

}

/

/设置parentWindow,创

建真正关联的WindowManagerImpl对

mWindowManager = ((Wi

ndowManagerImpl)wm).createLoc

alWindowManager(this);

}

public final class Window

ManagerImpl implements Window

Manager {

/

/最终调用的这个构造

private WindowManager

Impl(Display display, Window

parentWindow) {

mDisplay = displa

y;

mParentWindow = p

arentWindow;

}

public WindowManagerImpl

createLocalWindowManager(Wind

ow parentWindow) {

return new Window

ManagerImpl(mDisplay, parentW

indow);

}

}

所以说,每一个Ac tivity都有

一个PhoneWindow成员变

量,并且也都有一个

WindowManagerImpl,而

且,PhoneWindow 和

WindowManagerImpl

在Activity.attach()的时候进行

了关联。

插一张类图(转自工匠若

水)

知道了这些,那下面的操作

就可以直接看

WindowManagerImpl了。

其实WindowManagerImpl 这

个类也没有什么看头,为啥

这么说呢?因为他其实是代

理模式中的代理。是谁的代

理呢?是

WindowMa na ge rGloba l。

public final class WindowMana

gerImpl implements WindowMana

ger {

private final WindowManag

erGlobal mGlobal = WindowMana

gerGlobal.getInstance();

private final Display mDi

splay;

private final Window mPar

entWindow;

@

Override

public void addView(View

view, ViewGroup.LayoutParams

params) {

mGlobal.addView(view,

params, mDisplay, mParentWin

dow);

}

@

Override

public void updateViewLay

out(View view, ViewGroup.Layo

utParams params) {

mGlobal.updateViewLay

out(view, params);

}

@

Override

public void removeView(Vi

ew view) {

mGlobal.removeView(vi

ew, false);

}

@

Override

public void removeViewImm

ediate(View view) {

mGlobal.removeView(vi

ew, true);

}

}

从上面的代码中可以看出

来,WindowManagerImpl 里

面对ViewManager接口内方

法的实现,都是通过代理

WindowMa na ge rGloba l的方

法实现的,

所以重点转移到了

WindowMa na ge rGloba l这个

类。

还记得前面我们的

DecorView被添加到了

WindowManager吗?

wm.addView(decor, l);

其实最终调用的是

WindowManagerGlobal. addVi

ew();

public final class WindowMana

gerGlobal {

private static IWindowMan

ager sWindowManagerService;

private static IWindo

wSession sWindowSession;

private final ArrayList<V

iew> mViews = new ArrayList<V

iew>();

private final ArrayLi

st<ViewRootImpl> mRoots = new

ArrayList<ViewRootImpl>();

private final ArrayLi

st<WindowManager.LayoutParams

>

mParams =

new ArrayList<Win

dowManager.LayoutParams>();

/

例模式

/WindowManagerGlobal是单

private static WindowMana

gerGlobal sDefaultWindowManag

er;

public static WindowManag

erGlobal getInstance() {

synchronized (WindowM

anagerGlobal.class) {

if (sDefaultWindo

wManager == null) {

sDefaultWindo

wManager = new WindowManagerG

lobal();

}

return sDefaultWi

ndowManager;

}

}

public void addView(View

view, ViewGroup.LayoutParams

params,

Display display,

Window parentWindow) {

final WindowMan

ager.LayoutParams wparams = (

WindowManager.LayoutParams)pa

rams;

.

.....

synchronized

(mLock) {

root;

ViewRootImpl

root = new Vi

ewRootImpl(view.getContext(),

display);

view.setLayou

tParams(wparams);

mViews.add(view

mRoots.add(ro

mParams.add(w

)

;

ot);

params);

}

.

.....

try {

/注意下这个方法,

/

因为下面介绍ViewRootImpl的时候会

用到

root.setView(

view, wparams, panelParentVie

w);

}

}

}

catch (RuntimeEx

ception e) {

}

我们看到,

WindowMa na ge rGloba l是单

例模式,所以在一个App里

面只会有一个

WindowMa na ge rGloba l实

例。在

WindowMa na ge rGloba l里面

护了三个集合,分别存放添

加进来的Vie w(实际上就是

DecorView),布局参数

params,和刚刚实例化的

ViewRootImpl对象,

WindowMa na ge rGloba l到底

干嘛的呢?

其实,

WindowMa na ge rGloba l是和

WindowManagerService(即

WMS)通信的。

还记得在上一篇文章中我们

介绍Ac tivityThre a d和AMS之

间的IBinder通信的吗?是

的,这里也是IBinder通信。

public static IWindowSession

getWindowSession() {

synchronized (WindowM

anagerGlobal.class) {

if (sWindowSessio

n == null) {

try {

Input

MethodManager imm = InputMeth

odManager.getInstance();

IWind

owManager windowManager = get

WindowManagerService();

sWind

owSession = windowManager.ope

nSession(

.

.

....

}

catch

(RemoteException e) {

Log.e(TAG

"Failed to open window sess

,

ion", e);

}

}

return sWindowSes

sion;

}

}

public static IWindowManager

getWindowManagerService() {

synchronized (WindowM

anagerGlobal.class) {

if (sWindowManage

rService == null) {

/

/ServiceMa

nager是用来管理系统服务的,比如AMS

WMS等,这里就获取到了WMS的客户端

代理对象

sWindowManage

rService = IWindowManager.Stu

b.asInterface(

Servi

ceManager.getService("window"

)

);

}

return sWindowMan

agerService;

}

}

首先通过上面的方法获取到

IBinder对象,然后转化成了

WMS在本地的代理对象

IWindowManager,然后通过

openSession()初始化了

sWindowSession

对象。这个对象是干什么的

呢?

Session“是会话的意思,这

个类就是为了实现与WMS的

会话的,谁和WMS的对话

呢?WindowManagerGlobal

类内部并没有用这个类呀!

是ViewRootImpl与WMS的对

话。

ViewRootImpl是什么?

有什么作用?

ViewRootImpl如何与

WMS通信

你还记得么?在前面将

WindowManagerGlobal. addVi

ew()的时候,实例化了一个

ViewRootImpl,然后添加到

了一个集合里面,咱们先看

下ViewRootImpl的构造函数

public final class ViewRootIm

pl implements ViewParent,

View.AttachInfo.Callb

acks, HardwareRenderer.Hardwa

reDrawCallbacks {

public ViewRootImpl(Co

ntext context, Display displa

y) {

mContext = contex

t;

/

/获取WindowSessi

on

mWindowSession =

WindowManagerGlobal.getWindow

Session();

mDisplay = displa

y;

.

.....

mWindow = new W(t

his);

/

/默认不可见

mViewVisibility =

View.GONE;

/

/这个数值就是屏幕宽

度的dp总数

mDensity = contex

t.getResources().getDisplayMe

trics().densityDpi;

mChoreographer =

Choreographer.getInstance();

mDisplayManager =

(DisplayManager)context.getS

ystemService(Context.DISPLAY_

SERVICE);

}

}

在这个构造方法里面,主要

是完成了各种参数的初始

化,并且最关键的,获取到

了前面介绍的

WindowSession,那么你可能

好奇了,这个ViewRootImpl

到底有什么作用呢?

ViewRootImpl负责管理视图

树和与WMS交互,与WMS

交互是通过WindowSession。

而且ViewRootImpl也负责UI

界面的布局与渲染,负责把

一些事件分发至Ac tivity,以

便Ac tivity可以截获事件。大

多数情况下,它管理Ac tivity

顶层视图DecorView,它相

当于MVC模型中的

Controller。

WindowSession是

ViewRootImpl获取之后,主

动和WMS通信的,但是我们

在前面的文章知道,客户端

和服务器需要互相持有对方

的代理引用,才能实现双向

通信,那么WMS是怎么得到

ViewRootImpl的通信代理的

呢?

是在Vie wRootImpl. se tVie w()

的时候。

还记得不?在上面介绍

WindowManagerGlobal. addVi

ew()的时候,我还重点说了

下,在这个方法的try代码块

中,调用了

Vie wRootImpl. se tVie w(),下

面咱们看下这个方法干嘛

了:

public void setView(View view

,

WindowManager.LayoutParams

attrs, View panelParentView)

{

synchronized (this) {

if (mView == nul

mView = view

l) {

;

int res;

requestLayou

t();

try {

res = mWi

ndowSession.addToDisplay(mWin

dow, mSeq, mWindowAttributes,

g

etHostVisibility(), mDisplay.

getDisplayId(),

m

AttachInfo.mContentInsets, mI

nputChannel);

}

catc

h (RemoteException e) {

throw new RuntimeExcepti

on("Adding window failed", e)

;

}

fin

ally {

}

}

}

}

为了突出重点,我简化了很

多代码,从上面可以看出

来,是

mWindowSession.addToDispla

y()这个方法把mWindow传递

给我WMS,WMS就持有了

当前Vie w Rootlmpl的代理,

就可以调用W对象让

Vie w Rootlmpl做一些事情

了。

这样,双方都有了对方的接

口,WMS中的Session注册到

WindowMa na ge rGloba l的成

员WindowSession中,

ViewRootImpl::W注册到

WindowState中的成员

mClient中。前者是为了App

改变Vie w结构时请求WMS为

其更新布局。后者代表了

App端的一个添加到WMS中

的Vie w,每一个像这样通过

WindowManager接口中

addView()添加的窗口都有一

个对应的ViewRootImpl,也

有一个相应的

ViewRootImpl::W。它可以理

解为是ViewRootImpl中暴露

给WMS的接口,这样WMS

可以通过这个接口和App端

通信。

另外源码中很多地方采用了

这种将接口隐藏为内部类的

方式,这样可以实现六大设

计原则之一——接口最小原

则。

从什么时候开始绘制

整个Activity的View树

的?

注意前面代码中的

requestLayout();因为这个方

法执行之后,我们的

ViewRootImpl才开始绘制整

个Vie w树!

@

Override

public void requestLayout

() {

if (!mHandlingLayoutI

nLayoutRequest) {

checkThread();

mLayoutRequested

=

true;

scheduleTraversal

s();

}

}

void scheduleTraversals() {

if (!mTraversalSchedu

led) {

mTraversalSchedul

ed = true;

/

/暂停UI线程消息队列

对同步消息的处理

mTraversalBarrier

mHandler.getLooper().postS

=

yncBarrier();

/

/向Choreographer

注册一个类型为CALLBACK_TRAVERSA

L的回调,用于处理UI绘制

mChoreographer.po

stCallback(

Choreogra

pher.CALLBACK_TRAVERSAL, mTra

versalRunnable, null);

if (!mUnbufferedI

nputDispatch) {

scheduleConsu

meBatchedInput();

}

notifyRendererOfFr

amePending();

}

}

Choreographer就是一个消

息处理器,根据vsync 信号 来

计算frame“

解释起来比较麻烦,我们暂

时不展开讨论,你只要知

道,当回调被触发之后,

mTraversalRunnable对象的

run()就会被调用

final class TraversalRunnable

implements Runnable {

@

Override

public void run() {

doTraversal();

}

}

doTraversal()中最关键的,就

是调用了

performTraversals(),然后就

开始mesure,layout,draw了,

这里面的具体逻辑本篇文章

不讲,因为重点是

Ac tivity的界面显示流程,这

一块属于Vie w的,找时间单

独拿出来说

void doTraversal() {

if (mTraversalSchedul

ed) {

mTraversalSchedul

ed = false;

mHandler.getLoope

r().removeSyncBarrier(mTraver

salBarrier);

if (mProfile) {

Debug.startMe

thodTracing("ViewAncestor");

}

Trace.traceBegin(

Trace.TRACE_TAG_VIEW, "perfor

mTraversals");

try {

performTraver

sals();

}

finally {

Trace.traceEn

d(Trace.TRACE_TAG_VIEW);

}

if (mProfile) {

Debug.stopMet

mProfile = fa

hodTracing();

lse;

}

}

}

来回倒腾了这么多,终于看

见界面了,让我哭会 T^T

Window的类型有几

种?分别在什么情况

下会使用到哪一种?

Window的类型是根据

WindowManager.LayoutPara

ms的type属性相关的,根据

类型可以分为三类:

取值在

FIRST_AP P LICATION_WI

NDOW与

LAST_AP P LICATION_WI

NDOW之间(1-99),是常

用的顶层应用程序窗口,

须将token设置成Ac tivity的

token,比如前面开启

Window的时候设置的类型

即为

TYPE_APPLICATION

在FIRST_SUB_WINDOW

LAST_SUB_WINDOW(10

00-1999)之间,与顶层窗

口相关联,需将token设置

成它所附着宿主窗口的

token,比如PopupWindow

就是

TYPE_APPLICATION_PA

NEL

取值在

FIRST_SYSTEM_WINDO

W和

LAST_SYSTEM_WINDO

W(2000-2999)之间,不能

用于应用程序,使用时需

要有特殊权限,它是特定

的系统功能才能使用,比

如Toa st就是

TYPE_TOAST=2005,所

以不需要特殊权限

下面是所有的Type说明

/

/WindowType:开始应用程序窗口

public static final i

nt FIRST_APPLICATION_WINDOW =

1

;

/

/WindowType:所有程序

窗口的base窗口,其他应用程序窗口都

显示在它上面

public static final i

nt TYPE_BASE_APPLICATION = 1

;

/

/WindowType:普通应用

程序窗口,token必须设置为Activity

的token来指定窗口属于谁

public static final i

nt TYPE_APPLICATION

=

2

;

/

/WindowType:应用程序

启动时所显示的窗口,应用自己不要使用

这种类型,它被系统用来显示一些信息,

直到应用程序可以开启自己的窗口为止

public static final i

nt TYPE_APPLICATION_STARTING

=

3;

/

/WindowType:结束应用

程序窗口

public static final i

nt LAST_APPLICATION_WINDOW =

99;

/

/WindowType:SubWind

ows子窗口,子窗口的Z序和坐标空间都

依赖于他们的宿主窗口

public static final i

nt FIRST_SUB_WINDOW

1000;

=

//WindowType: 面板窗口

,显示于宿主窗口的上层

public static final i

nt TYPE_APPLICATION_PANEL =

FIRST_SUB_WINDOW;

/

/WindowType:媒体窗口

(例如视频),显示于宿主窗口下层

public static final i

nt TYPE_APPLICATION_MEDIA =

FIRST_SUB_WINDOW+1;

/

/WindowType:应用程序

窗口的子面板,显示于所有面板窗口的上

public static final i

nt TYPE_APPLICATION_SUB_PANEL

=

FIRST_SUB_WINDOW+2;

/WindowType:对话框,

/

类似于面板窗口,绘制类似于顶层窗口,

而不是宿主的子窗口

public static final i

nt TYPE_APPLICATION_ATTACHED_

DIALOG = FIRST_SUB_WINDOW+3;

/

/WindowType:媒体信息

显示在媒体层和程序窗口之间,需要实

现半透明效果

public static final i

nt TYPE_APPLICATION_MEDIA_OVE

RLAY = FIRST_SUB_WINDOW+4;

/

/WindowType:子窗口结

public static final i

nt LAST_SUB_WINDOW = 1

99;

9

/

/WindowType:系统窗口

非应用程序创建

public static final i

nt FIRST_SYSTEM_WINDOW = 2

00;

0

/

/WindowType:状态栏,

只能有一个状态栏,位于屏幕顶端,其他

窗口都位于它下方

public static final i

nt TYPE_STATUS_BAR

IRST_SYSTEM_WINDOW;

= F

/

/WindowType:搜索栏,

只能有一个搜索栏,位于屏幕上方

public static final i

nt TYPE_SEARCH_BAR = F

IRST_SYSTEM_WINDOW+1;

/WindowType:电话窗口

它用于电话交互(特别是呼入),置于

所有应用程序之上,状态栏之下

public static final i

/

nt TYPE_PHONE

=

FIRST_SYSTEM_WINDOW+2;

/

/WindowType:系统提示

,出现在应用程序窗口之上

public static final i

nt TYPE_SYSTEM_ALERT

IRST_SYSTEM_WINDOW+3;

= F

/

/WindowType:锁屏窗口

public static final i

nt TYPE_KEYGUARD = F

IRST_SYSTEM_WINDOW+4;

/

/WindowType:信息窗口

用于显示Toast

public static final i

nt TYPE_TOAST

=

FIRST_SYSTEM_WINDOW+5;

/

/WindowType:系统顶层

窗口,显示在其他一切内容之上,此窗口

不能获得输入焦点,否则影响锁屏

public static final i

nt TYPE_SYSTEM_OVERLAY

IRST_SYSTEM_WINDOW+6;

= F

/

/WindowType:电话优先

当锁屏时显示,此窗口不能获得输入焦

点,否则影响锁屏

public static final i

nt TYPE_PRIORITY_PHONE = F

IRST_SYSTEM_WINDOW+7;

/WindowType:系统对话

/

public static final i

nt TYPE_SYSTEM_DIALOG

FIRST_SYSTEM_WINDOW+8;

=

/

/WindowType:锁屏时显

示的对话框

public static final i

nt TYPE_KEYGUARD_DIALOG

FIRST_SYSTEM_WINDOW+9;

=

/

/WindowType:系统内部

错误提示,显示于所有内容之上

public static final i

nt TYPE_SYSTEM_ERROR

= F

IRST_SYSTEM_WINDOW+10;

/

/WindowType:内部输入

法窗口,显示于普通UI之上,应用程序

可重新布局以免被此窗口覆盖

public static final i

nt TYPE_INPUT_METHOD

= F

IRST_SYSTEM_WINDOW+11;

/

/WindowType:内部输入

法对话框,显示于当前输入法窗口之上

public static final i

nt TYPE_INPUT_METHOD_DIALOG=

FIRST_SYSTEM_WINDOW+12;

/

/WindowType:墙纸窗口

public static final i

nt TYPE_WALLPAPER

FIRST_SYSTEM_WINDOW+13;

/WindowType:状态栏的

=

/

滑动面板

public static final i

nt TYPE_STATUS_BAR_PANEL = F

IRST_SYSTEM_WINDOW+14;

/

/WindowType:安全系统

覆盖窗口,这些窗户必须不带输入焦点,

否则会干扰键盘

public static final i

nt TYPE_SECURE_SYSTEM_OVERLAY

=

FIRST_SYSTEM_WINDOW+15;

/

/WindowType:拖放伪窗

口,只有一个阻力层(最多),它被放置

在所有其他窗口上面

public static final i

nt TYPE_DRAG

= F

IRST_SYSTEM_WINDOW+16;

/

/WindowType:状态栏下

拉面板

public static final i

nt TYPE_STATUS_BAR_SUB_PANEL

FIRST_SYSTEM_WINDOW+17;

/WindowType:鼠标指针

public static final i

=

/

nt TYPE_POINTER = FIRST_SYSTE

M_WINDOW+18;

/

/WindowType:导航栏(有

别于状态栏时)

public static final i

nt TYPE_NAVIGATION_BAR = FIRS

T_SYSTEM_WINDOW+19;

/

/WindowType:音量级别

的覆盖对话框,显示当用户更改系统音量

大小

public static final i

nt TYPE_VOLUME_OVERLAY = FIRS

T_SYSTEM_WINDOW+20;

/

/WindowType:起机进度

框,在一切之上

public static final i

nt TYPE_BOOT_PROGRESS = FIRST

SYSTEM_WINDOW+21;

/WindowType:假窗,消

费导航栏隐藏时触摸事件

public static final i

_

/

nt TYPE_HIDDEN_NAV_CONSUMER =

FIRST_SYSTEM_WINDOW+22;

/

/WindowType:梦想(屏保

窗口,略高于键盘

public static final i

)

nt TYPE_DREAM = FIRST_SYSTEM_

WINDOW+23;

/

/WindowType:导航栏面

板(不同于状态栏的导航栏)

public static final i

nt TYPE_NAVIGATION_BAR_PANEL

FIRST_SYSTEM_WINDOW+24;

/WindowType:univers

e背后真正的窗户

public static final i

=

/

nt TYPE_UNIVERSE_BACKGROUND =

FIRST_SYSTEM_WINDOW+25;

/

/WindowType:显示窗口

覆盖,用于模拟辅助显示设备

public static final i

nt TYPE_DISPLAY_OVERLAY = FIR

ST_SYSTEM_WINDOW+26;

/

/WindowType:放大窗口

覆盖,用于突出显示的放大部分可访问性

放大时启用

public static final i

nt TYPE_MAGNIFICATION_OVERLAY

=

FIRST_SYSTEM_WINDOW+27;

/WindowType:......

public static final i

nt TYPE_KEYGUARD_SCRIM

FIRST_SYSTEM_WINDOW+29;

/

=

public static final i

nt TYPE_PRIVATE_PRESENTATION

=

FIRST_SYSTEM_WINDOW+30;

public static final i

nt TYPE_VOICE_INTERACTION = F

IRST_SYSTEM_WINDOW+31;

public static final i

nt TYPE_ACCESSIBILITY_OVERLAY

=

FIRST_SYSTEM_WINDOW+32;

/WindowType:系统窗口

/

结束

public static final i

nt LAST_SYSTEM_WINDOW

999;

=

2

为什么使用PopWindow的时

候,不设置背景就不能触发

事件?

我们在使用PopupWindow的

时候,会发现如果不给

PopupWindow设置背景,那

么就不能触发点击返回事

件,有人认为这个是B UG,

其实并不是的。

我们以下面的方法为例,其

实所有的显示方法都有下面

的流程:

public void showAtLocation(IB

inder token, int gravity, int

x, int y) {

if (isShowing() || mC

ontentView == null) {

return;

}

mIsShowing = true;

mIsDropdown = false;

WindowManager.LayoutP

arams p = createPopupLayout(t

oken);

p.windowAnimations =

computeAnimationResource();

/

/在这里会根据不同的设置

配置不同的LayoutParams属性

preparePopup(p);

if (gravity == Gravit

y.NO_GRAVITY) {

gravity = Gravity

TOP | Gravity.START;

.

}

p.gravity = gravity;

p.x = x;

p.y = y;

if (mHeightMode < 0)

p.height = mLastHeight = mHei

ghtMode;

if (mWidthMode < 0) p

.

width = mLastWidth = mWidthM

ode;

invokePopup(p);

}

我们重点看下preparePopup()

private void preparePopup(Win

dowManager.LayoutParams p) {

/

/根据背景的设置情况进行

不同的配置

if (mBackground != nu

ll) {

final ViewGroup.L

ayoutParams layoutParams = mC

ontentView.getLayoutParams();

int height = View

Group.LayoutParams.MATCH_PARE

NT;

/

/如果设置了背景,就用

一个PopupViewContainer对象来包

裹之前的mContentView,并设置背景

PopupViewContaine

r popupViewContainer = new Po

pupViewContainer(mContext);

PopupViewContaine

r.LayoutParams listParams = n

ew PopupViewContainer.LayoutP

arams(

ViewGroup

.

LayoutParams.MATCH_PARENT, h

eight

)

;

popupViewContaine

r.setBackground(mBackground);

popupViewContaine

r.addView(mContentView, listP

arams);

mPopupView = popu

pViewContainer;

}

else {

mPopupView = mCon

tentView;

}

}

为啥包了一层

PopupViewContainer,就可

以处理按钮点击事件了?因

为PopupWindow没有相关事

件回调,也没有重写按键和

触摸方法,所以接收不

到对应的信号

public class PopupWindow {}

而PopupViewContainer则可

以,因为它重写了相关方法

private class PopupViewContai

ner extends FrameLayout {

@

Override

public boolean dispat

chKeyEvent(KeyEvent event) {

if (event.getKeyC

ode() == KeyEvent.KEYCODE_BAC

K) {

if (getKeyDis

patcherState() == null) {

return su

per.dispatchKeyEvent(event);

}

if (event.get

Action() == KeyEvent.ACTION_D

OWN

&

& ev

ent.getRepeatCount() == 0) {

KeyEvent.

DispatcherState state = getKe

yDispatcherState();

if (state

!

= null) {

state

.

startTracking(event, this);

}

return tr

ue;

}

else if (ev

ent.getAction() == KeyEvent.A

CTION_UP) {

/

/back键

消失

KeyEvent.

DispatcherState state = getKe

yDispatcherState();

if (state

!

= null && state.isTracking(

event) && !event.isCanceled()

)

{

dismi

retur

ss();

n true;

}

}

return super.

dispatchKeyEvent(event);

else {

return super.

}

dispatchKeyEvent(event);

}

}

@

Override

public boolean dispat

chTouchEvent(MotionEvent ev)

{

if (mTouchInterce

ptor != null && mTouchInterce

ptor.onTouch(this, ev)) {

return true;

}

return super.disp

atchTouchEvent(ev);

}

@

Override

public boolean onTouc

hEvent(MotionEvent event) {

final int x = (in

t) event.getX();

final int y = (in

t) event.getY();

/

/触摸在外面就消失

if ((event.getAct

ion() == MotionEvent.ACTION_D

OWN)

&

& ((x <

0

) || (x >= getWidth()) || (y

<

{

0) || (y >= getHeight())))

dismiss();

return true;

}

else if (event.

getAction() == MotionEvent.AC

TION_OUTSIDE) {

dismiss();

return true;

}

else {

return super.

onTouchEvent(event);

}

}

}

在Ac tivity中使用Dia log的时

候,为什么有时候会报

错“Unable to add window –

token is not va lid; is your

a c tivity running?” ?

这种情况一般发生在什么时

候?一般发生在Ac tivity进入

后台,Dia log没有主动

Dismiss掉,然后从后台再次

进入App的时候。

为什么会这样呢?

还记得前面说过吧,子窗口

类型的Window,比如

Dia log,想要显示的话,比

如保证appToken与Ac tivity保

持一致,而当Ac tivity销毁,

再次回来的时候,Dia log试

图重新创建,调用

ViewRootImp的setView()的

时候就会出问题,所以记得

在Ac tivity不可见的时候,主

动Dismiss掉Dia log。

if (res < WindowManagerGlobal

.

ADD_OKAY) {

switch (res) {

case

WindowManagerGlobal.ADD_BAD_A

PP_TOKEN:

case

WindowManagerGlobal.ADD_BAD_S

UBWINDOW_TOKEN:

t

hrow new WindowManager.BadTok

enException(

"

Unable to add window -- t

oken " + attrs.token

+

" is not valid; is your

activity running?");

case

WindowManagerGlobal.ADD_NOT_A

PP_TOKEN:

t

hrow new WindowManager.BadTok

enException(

"

Unable to add window -- t

oken " + attrs.token

+

" is not for an applicat

ion");

case

WindowManagerGlobal.ADD_APP_E

XITING:

t

hrow new WindowManager.BadTok

enException(

"

Unable to add window -- a

pp for token " + attrs.token

+

" is exiting");

case

WindowManagerGlobal.ADD_DUPLI

CATE_ADD:

t

hrow new WindowManager.BadTok

enException(

"

Unable to add window -- w

indow " + mWindow

" has already been added

case

+

"

);

WindowManagerGlobal.ADD_START

ING_NOT_NEEDED:

/

/

/

Silently ignore -- we would

have just removed it

/

right away, anyway.

r

eturn;

case

WindowManagerGlobal.ADD_MULTI

PLE_SINGLETON:

t

hrow new WindowManager.BadTok

enException(

"

Unable to add window " +

mWindow +

"

-- another window of thi

s type already exists");

case

WindowManagerGlobal.ADD_PERMI

SSION_DENIED:

t

hrow new WindowManager.BadTok

enException(

"

Unable to add window " +

mWindow +

"

-- permission denied for

this window type");

case

WindowManagerGlobal.ADD_INVAL

ID_DISPLAY:

t

hrow new WindowManager.Invali

dDisplayException(

"

Unable to add window " +

mWindow +

"

-- the specified display

can not be found");

}

throw new

RuntimeException(

"

Unab

le to add window -- unknown e

rror code " + res);

}

}

为什么Toa st需要由系统统一

控制,在子线程中为什么不

能显示Toa st?

首先Toa st也属于窗口系统,

但是并不是属于App的,是

由系统同一控制的。

关于这一块不想说太多,具

体实现机制请参考后面的文

章。

为了看下面的内容,你需要

知道以下几件事情:

1

. Toa st的显示是由系统Toast

服务控制的,与系统之间

的通信方式是Binder

2. 整个Toa st系统会维持最多

50个Toa st的队列,依次显

3

. 负责现实工作的是Toa st的

内部类TN,它负责最终的

显示与隐藏操作

4. 负责给系统Toa st服务发送

内容的是

INotificationManager的实

现类,它负责在

Toast.show()里面把TN对

象传递给系统消息服务,

service.enqueueToast(pkg,

tn, mDuration);这样Toast

服务就持有客户端的代

理,可以通过TN来控制每

个Toa st的显示与隐藏。

再来张图(转自工匠若水

ok,现在假如你知道上面这

些啦,那么我们下面就看为

什么在子线程使用

Toast.show()会提示

"

No Looper; Looper.prepare()

wasn't called on this thread."

原因很简单,因为TN在操作

Toa st的时候,是通过Handler

做的

@

Override

public void show() {

if (localLOGV) Lo

g.v(TAG, "SHOW: " + this);

mHandler.post(mSh

ow);

}

@

Override

public void hide() {

if (localLOGV) Lo

g.v(TAG, "HIDE: " + this);

mHandler.post(mHi

de);

}

所以说,TN初始化的线程必

须为主线程,在子线程中使

用Handler,由于没有消息队

列,就会造成这个问题。

结语

上面写了这么多,你可能看

了前面忘了后面,下面,凯

子哥给你总结一下,这篇文

章到底讲了什么东西:

每个Ac tivity,都至少有一

个Window,这个Window

实际类型为

PhoneWindow,当Ac tivity

中有子窗口,比如Dia log

的时候,就会出现多个

Window。Ac tivity的

Window是我们控制的,状

态栏和导航栏的Window由

系统控制。

在DecorView的里面,一

定有一个id为content的

FraneLayout的布局容器,

咱们自己定义的xml布局

都放在这里面。

Ac tivity的Window里面有

一个DecorView,它使继

承自FrameLayout的一个

自定义控件,作为整个

Vie w层的容器,及Vie w树

的根节点。

Window是虚拟的概念,

DecorView才是看得见,

摸得着的东西,

Ac tivity. se tConte ntVie w()实

际调用的是

P hone Window. se tConte ntVi

ew(),在这里面实现了

DecorView的初始化和id为

content的FraneLayout的布

局容器的初始化,并且会

根据主题等配置,选择不

同的xml文件。而且在

Ac tivity. se tConte ntVie w()之

后,Window的一些特征位

将被锁定。

Ac tivity. findVie wById( )实

际上调用的是DecorView

的findvie wById(),这个方

法在Vie w中定义,但是是

fina l的,实际起作用的是

在Vie wGroup中被重写的

findViewTraversal()方法。

Ac tivity的mWindow成员变

量是在attach()的时候被初

始化的,attach()是Ac tivity

被通过反射手段实例化之

后调用的第一个方法,在

这之后生命周期方法才会

依次调用

在onResume()刚执行之

后,界面还是不可见的,

只有执行完

Ac tivity. ma ke Visible (),

DecorView才对用户可见

ViewManager这个接口里

面就三个接口,添加、移

除和更新,实现这个接口

的有WindowManager和

Vie wGroup,但是他们两

个面向的对象是不一样

的,WindowManager实现

的是对Window的操作,而

Vie wGroup则是对Vie w的

增、删、更新操作。

WindowManagerImpl是

WindowManager的实现

类,但是他就是一个代理

类,代理的是

WindowMa na ge rGloba l,

WindowMa na ge rGloba l一

个App里面就有一个,因

为它是单例的,它里面管

理了App中所有打开的

DecorView,Conte ntVie w

和PhoneWindow的布局参

WindowManager.LayoutPar

ams,而且

WindowMa na ge rGloba l这

个类是和WMS通信用的,

是通过IWindowSession对

象完成这个工作的,而

IWindowSession一个App只

有一个,但是每个

ViewRootImpl都持有对

IWindowSession的引用,

所以ViewRootImpl可以和

WMS喊话,但是WMS怎

么和ViewRootImpl喊话

呢?是通过

ViewRootImpl::W这个内部

类实现的,而且源码中很

多地方采用了这种将接口

隐藏为内部类的方式,这

样可以实现六大设计原则

之一——接口最小原则,

这样ViewRootImpl和WMS

就互相持有对方的代理,

就可以互相交流了

ViewRootImpl这个类每个

Ac tivity都有一个,它负责

和WMS通信,同时相应

WMS的指挥,还负责

Vie w界面的测量、布局和

绘制工作,所以当你调用

View. invalidate()和

View.requestLayout()的时

候,都会把事件传递到

ViewRootImpl,然后

ViewRootImpl计算出需要

重绘的区域,告诉WMS,

WMS再通知其他服务完成

绘制和动画等效果,当

然,这是后话,咱们以后

再说。

Window分为三种,子窗

口,应用窗口和系统窗

口,子窗口必须依附于一

个上下文,就是Ac tivity,

因为它需要Ac tivity的

appToken,子窗口和

Ac tivity的WindowManager

是一个的,都是根据

appToken获取的,描述一

个Window属于哪种类型,

是根据LayoutParam.type

决定的,不同类型有不同

的取值范围,系统类的的

Window需要特殊权限,当

然Toa st比较特殊,不需要

权限

PopupWindow使用的时

候,如果想触发按键和触

摸事件,需要添加一个背

景,代码中会根据是否设

置背景进行不同的逻辑判

Dia log在Ac tivity不可见的

时候,要主动dismiss掉,

否则会因为appToken为空

crash

Toa st属于系统窗口,由系

统服务

NotificationManagerService

统一调度,

NotificationManagerService

中维持着一个集合

ArrayList,最多存放50个

Toa st,但是

NotificationManagerService

只负责管理Toa st,具体的

现实工作由Toa st::TN来实

最后来一张Android的窗口管

理框架(转自ariesjzj

OK,关于Ac tivity的界面显

示就说到这里吧,本篇文章

大部分的内容来自于阅读下

面参考文章之后的总结和思

考,想了解更详细的可以研

究下。

下次再见,拜拜 ~

参考文章

Android应用Ac tivity、

Dia log、PopWindow、

Toa st窗口添加机制及源码

分析

Android应用

setContentView与

LayoutInflater加载解析机

制源码分析

Android 4.4(KitKat)窗口管

理子系统- 体系框架

Android 之Window、

WindowManager 与窗口管

图解Android - Android GUI

系统(1) - 概论

尊重原创,转载请注明:

From 凯子哥

http://blog.csdn.net/zhaokai

qiang1992) 侵权必究!

关注我的微博,可以获得更

多精彩内容

http://m.blog.csdn.net/blog/yhc

13429826359/19977429

1

生命周期图

2

主要类图调用

上面类图关系中包含两个进

程,一个是应用程序进程,

另一个是AMS进程,所以会

涉及到进程间通信,android

进程间通信用的是Binder通

信。

2

.1客户进程

ØActivityThread

可以看到该类有一个main方

法,其实它是android一个应

用程序的入口,每启动一个

应用进程,都会创建

Ac tivityThre a d与之对应的实

例,是应用程序的UI线程,

Android进程启动时会建立消

息循环。负责管理应用程序

的生命周期,执行系统广播

及其ActivityManagerService

请求执行的操作。属于客户

端对象。

ØApplicationThread&Applicat

inThreadNative

ApplicationThread用来实现

ActivityManagerService与

Ac tivityThre a d之间的交互。

在ActivityManagerService需

要管理相关Application中的

Ac tivity的生命周期时,通过

ApplicationThread与

Ac tivityThre a d通讯,

ApplicationThreadNative是

ApplicationThread在客户端

的实现。

ØApplicationThreadProxy

ApplicationThreadProxy是

ApplicationThread在服务器

端的代理。负责和服务器端

的ApplicatingThreadNative通

讯。

AMS就是通过该代理与

Ac tivityThre a d进行通信的。

Ø Ac tivity& Intrumentation

Ac tivity是应用程序真正做事

情的类,每一个应用程序只

有一个Instrumentation对象,

每个Ac tivity内都有一个对该

对象的引用。Instrumentation

可以理解为应用进程的管

家,Ac tivityThre a d要创建或

暂停某个Ac tivity时,都需要

通过Instrumentation。通俗的

理解,Instrumentation与

Ac tivityThre a d的区别,前者

像是一个“家庭”里的“管

家”,后者是负责创建这

个“家庭”,并负责对外打交

道,比如接收AMS的通知

等。

2

.2 AMS进程

这里说的AMS进程,实际指

的是System_server进程,

System_server进程起来的时

候启动AMS服务,AMS实际

是ActivityManagerService的

缩写。

ØActivityManagerService

管理Ac tivity的生命周期

ØAc tivityMa na ge rNa tive

ActivityManagerService在服

务器端的实现,客户端的请

求调用ActivityManagerP roxy

后,通过IBinder,最终会在

ActivityManagerNative中实

现。ActivityManagerNative再

通过调用

ActivityManagerService的相

关功能,以完成客户端请

求。

ØActivityManagerP roxy

ActivityManagerService的在

客户端的代理。负责和服务

器端的Ac tivityMa na ge rNa tive

通讯。

ØAc tivitySta c k

Ac tivity在AMS的栈管理,用

来记录已经启动的Ac tivity的

先后关系,状态信息等。通

过Ac tivitySta c k决定是否需要

启动新的进程。

ØAc tivityRe c ord

Ac tivitySta c k的管理对象,每

个Ac tivity在AMS对应一个

Ac tivityRe c ord来记录Ac tivity

的状态以及其他的管理信

息。

ØTaskRecord

AMS抽象出来的一个“任

务”的概念,是记录

Ac tivityRe c ord的栈,一

个“Task”包含若干个

Ac tivityRe c ord。AMS用

TaskRecord确保Ac tivity启动

和退出的顺序。

ØProcessRecord

一个Apk文件运行时会对应

一个进程,ProcessRecord正

是记录一个进程中的相关信

息。

3

startActivity流程

在Android系统中,应用程序

是由Ac tivity组成的,因此,

应用程序的启动过程实际上

就是应用程序中的默认

Ac tivity的启动过程。启动

Android应用程序中的Ac tivity

的两种情景,第一,在

android设备屏幕中点击应用

程序图标的情景就会引发

Android应用程序中的默认

Ac tivity的启动,从而把应用

程序启动起来,这种启动方

式的特点是会启动一个新的

进程来加载相应的Ac tivity。

第二,应用程序内部启动非

默认Ac tivity的过程的源代

码,这种非默认Ac tivity一般

是在原来的进程和任务中启

动的。在Android的Ac tivity管

理机制中,当退出Ac tivity的

时候,在某些情况下并没有

立即把该Ac tivity杀死,而是

将其暂时保存起来,当第二

次调用sta r tAc tivity启动该

Ac tivity的时候,就不需要再

创建该Ac tivity的实例,直接

恢复Ac tivity即可。

3

.1调用流程图

对用户来讲,启动一个

Ac tivity有以下几种方式:

Ø在应用程序中调用

sta rtAc tivity()启动指定的

Ac tivity

Ø在Home程序中点击一个应

用图标,启动新的Ac tivity

Ø按“Back”键,结束当前

Ac tivity,自动启动上一个

Ac tivity

Ø长按“Home”键,显示当前

列表中,从中选则一个启动

对于AMS内部讲,启动一个

Ac tivity有三种方式,如上图

中的①②③分支:

目标Ac tivity的对象已经存

在,那么直接resume该

Ac tivity

目标Ac tivity所在的进程不

存在,那么需要创建进程,

并在新的进程中启动该

Ac tivity

目标Ac tivity所在进程已经

存在,那么直接在已存在进

程中启动该Ac tivity

3

.2在新的进程中启动

以在Home程序中点击一个

应用图标,启动Ma inAc tivity

为例子,介绍如下。

时序图如下图:

以上时序图包含35步骤调

用,下面逐一讲解:

3

.2.1(1~4),Launcher

中发送startActivity请

在Android系统中,应用程序

是由Launcher启动起来的,

其实,Launcher本身也是一

个应用程序,其它的应用程

序安装后,就会Launcher的

界面上出现一个相应的图

标,点击这个图标时,

Launcher就会对应的应用程

序启动起来。

Launcher继承与Ac tivity,

Ac tivity类的有个成员变量

mInstrumentation是,它的类

型是Intrumentation,它用来

监控应用程序和系统的交

互。

Instrumentation.execStartActiv

ity:

publicActivityResult execStartAc

Context who, IBinder contextThr

Activitytarget,

Intent intent, int requestCode,

IApplicationThread whoThread =

(IApplicationThread)contextThrea

……

try {

intent.setAllowFds(false);

intent.migrateExtraStreamToClip

int result = ActivityManagerNat

.

startActivity(whoThread, inten

intent.resolveTypeIfNeeded(who.g

token, target != null ? target.

requestCode, 0, null, null, opt

checkStartActivityResult(result

}

}

catch (RemoteException e) {

return null;

}

这里的

ActivityManagerNative.getDef

ault返回

ActivityManagerService的远

程接口,即

ActivityManagerP roxy接口。

3

.2.2(5-8) AMS接收客

户端startActivity请求

客户端通过Binder调用,最

终调用到

Ac tivitySta c k. sta rtAc tivityLoc k

ed:

final intstartActivityLocked(IAp

caller,

Intent intent, String resolvedT

aInfo, IBinderresultTo,

String resultWho, int requestCo

int callingPid, int callingUid,

Bundleoptions,

boolean componentSpecified, Act

outActivity){

ProcessRecord callerApp = null;

if (caller != null) {

callerApp = mService.getRecordF

if (callerApp != null) {

callingPid = callerApp.pid;

callingUid = callerApp.info.uid

}

}

else {

}

.

ActivityRecord sourceRecord = n

ActivityRecord resultRecord = n

….

int launchFlags = intent.getFla

….

if((launchFlags&Intent.FLAG_ACTI

0

&& sourceRecord != null){

// Transfer the result target f

activity to thenew

/ one being started, including

/

….

}

ActivityRecord r = new Activity

callerApp,callingUid,

intent, resolvedType, aInfo, mS

resultRecord, resultWho,

requestCode,componentSpecified);

if (outActivity != null) {

outActivity[0] = r;

}

err = startActivityUncheckedLoc

startFlags, true, options);

……

return err;

}

startActivityLock()主要做了

一下几件事:

①处理传进来的参数caller,

得到调用者的进程信息,并

保存在callerApp变量中,这

里就是Launcher应用程序的

进程信息了。

②处理

FLAG_ACTIVITY_FORWAR

D_RESULT标志。该标志的

特殊作用,就是能跨Ac tivity

传Result,比如A1->A2,A2

带该标志启动A3,那么A3调

用setResult,然后finish(),结

果将直接返回到A1

③创建一个临时的

Ac tivityRe c ord对象,该对象

只为了后面调用过程中的各

种对比,不一定会最终加入

到mHistory列表中。

④判断

mP e ndingAc tivityLa unc he s列

表是否有等待的Ac tivity要启

动,如果有先启动等待的

Ac tivity

⑤调用

startActivityUncheckedLocked

()方法。此时要启动的

Ac tivity已经通过检验,被人

认为是一个正当的启动请

求。

3

.2.3(9)创建新的Ta sk

调用

Ac tivitySta c k. sta rtAc tivityUnc

heckedLocked()处理Task问

题,因为这里我们是新启动

一个apk,所以将创建新的

Task,newTask=true,并调

Ac tivitySta c k. sta rtAc tivityLoa c

ked():

privatefinal void

startActivityLocked(ActivityReco

r, booleannewTask,

boolean doResume, boolean

keepCurTransition) {

……

if (!newTask) {

…….

}

}

/

/ Place a new activity at top

stack, so it is next tointeract

/ with the user.

/

if (addPos < 0) {

addPos = NH;

}

……

mHistory.add(addPos, r);

r.putInHistory();

r.frontOfTask = newTask;

……

if (doResume) {

resumeTopActivityLocked(null);

}

}

注意AtivitySta c k中有两个

startActivityLoacked()方法,

这里调用的是带四个参数

的,即

sta rtAc tivityLoc ke d(Ac tivityRe

cord r,

booleannewTask,boolean

doResume,

booleankeepCurTransition),

其中,r为将要启动的

Ac tivity,newTask=true,

doResume=true,在这个方法

中,将r放到mHistory的最后

面doResume=true,所以调用

re sume TopAc tivityLoc ke d(null

)。关于Task的概念比较复

杂,这里先不讲解。

3

.2.4:(10)运行mHistory

中最后一个

ActivityRecord

Ac tivitySta c k.

re sume TopAc tivityLoc ke d(null

)

finalboolean

resumeTopActivityLocked(Activity

// Find the first activity that

finishing.

ActivityRecord next = topRunnin

(null);

if (next == null) {

/

/ There are no more activities

startup the

/ Launcher...

/

if (mMainStack) {

return mService.startHomeActivi

}

}

next.delayedResume = false;

// If the top activity is the r

nothing todo.

if (mResumedActivity == next &&

ActivityState.RESUMED) {

/ Make sure we have executed a

transitions, sincethere

/ should be nothing left to do

/

/

mService.mWindowManager.executeA

mNoAnimActivities.clear();

return false;

}

/

/ If we are sleeping, and ther

activity, and thetop

/ activity is paused, well tha

/

we want.

if ((mService.mSleeping ||

mService.mShuttingDown)

&

& mLastPausedActivity == next&

=ActivityState.PAUSED) {

/ Make sure we have executed a

=

/

transitions, sincethere

// should be nothing left to do

mService.mWindowManager.executeA

mNoAnimActivities.clear();

returnfalse;

}

/

/ The activity may be waiting

that is nolonger

/ appropriate for it.

/

mStoppingActivities.remove(next

mGoingToSleepActivities.remove(

next.sleeping = false;

mWaitingVisibleActivities.remov

……

if (mPausingActivity != null) {

if (DEBUG_SWITCH) Slog.v(TAG, "

pausing=" +mPausingActivity);

return false;

}

..

/ We need to start pausing the

activity so the topone

/ can be resumed...

/

/

if (mResumedActivity != null) {

if (DEBUG_SWITCH) Slog.v(TAG, "

need to startpausing");

startPausingLocked(userLeaving,

return true;

}

}

…..

调用

re sume TopAc tivityLoc ke d(null

)

启动真正的Ac tivity。

①调用

topRunningActivityLocked()方

法取出当前正在运行的

Ac tivityRe c ord对象

②判断mHistory中是否有记

录,如果没有就意味着还没

有启动任何的Ac tivity,需要

首先调用

mService. startHomeActivityLo

cked()方法启动所谓的“主界

面程序”。当然我们这里

mHistroy已经有记录了。

③判断正在执行的Ac tivity是

否和目标Ac tivity一样,如果

一样,则直接返回。

④判断当前系统是否处于休

眠涨停,如果是,则返回。

这里继续往下执行。

⑤从mStoppingActivities、

mWa itingVisible Ac tivitie s和

mGoingToSle e pAc tivitie s中删

除目标对象,因为接下来将

要被启动。

⑥判断当前是否在暂停某个

Ac tivity,如果是则还不能运

行。这里

mP a usingAc tivity= null,所以

继续往下执行。

⑦判断当前是否有Ac tivity在

运行,如果有则先需要暂停

当前的Ac tivity。因为我们是

在Lancher中启动

ma inAc tivity,所以当前

mResumedActivity!=null,

所有调用

startPausingLocked(userLeavi

ng, false);

3

.2.5(11~16)暂停当前

运行Activity

①调用

ActivityStack.startPausingLoc

ked()暂停当前Ac tivity。

②判断运行当前Ac tivity的进

程是否存在。在这里if

(prev.app != null&&

prev.app.thread !=null)为真。

其中,prev.app为记录启动

Lancher进程的

ProcessRecord ,

prev.app.thread为Lancher进

程的远程调用接口

IApplicationThead,所以可以

调用

prev.app.thread.schedulePause

Ac tivity,到Lancher进程暂停

指定Ac tivity。

③在Lancher进程中消息传

递,调用

ActivityThread.handlePauseAc

tivity( ),最终调用

ActivityTHread.performPause

Ac tivity暂停指定Ac tivity。接

着通过Binder通信,通知

AMS已经完成暂停

ActivityManagerNative.getDef

ault().activityPaused(token).

3

.2.6(17~20) AMS处理

暂停Activity事情

在Launcher通过Binder进程

间通信机制通知AMS,它已

经准备就绪进入Paused状

态,在

ActivityStack.completePauseL

ocked()中完成暂停:

private final void

completePauseLocked(){

ActivityRecord prev =

mPausingActivity;

if (DEBUG_PAUSE) Slog.v(TAG,

"

Complete pause: " + prev);

if (prev != null) {

if (prev.finishing) {

prev =

finishCurrentActivityLoc

ked(prev,FINISH_AFTER_VISIBLE);

}

else if (prev.app != null) {

if (prev.configDestroy) {

destroyActivityLocked(prev,

true, false,"pause-config");

} else {

mStoppingActivities.add(prev);

if (mStoppingActivities.size()

3) {

>

/

/ If we already have a few

activities waiting to stop,

/ then give up on things

going idle and start clearing

/ them out.

/

/

if (DEBUG_PAUSE) Slog.v(TAG,

To many pending stops,

forcingidle");

"

scheduleIdleLocked();

}

checkReadyForSleepLocked();

else {

}

}

}

else {

if (DEBUG_PAUSE) Slog.v(TAG,

App died during pause, not

"

stopping:" + prev);

prev = null;

}

mPausingActivity = null;

}

if (!mService.isSleeping()) {

resumeTopActivityLocked(prev);

}

else {

checkReadyForSleepLocked();

}

}

给prev赋值

mP a usingAc tivity,即上一个

被执行的Ac tivity,即Launcer

如果prev的finishing为

true,说明上一个Ac tivity已

经完成,因此需要调用

finishCurrentActivityLocked()

执行相关操作。一般的流程

不会为true,这个条件似乎

只有内存回收的时候才会被

执行。

③将mP a usingAc tivity变量置

为空

④调用

re sume TopAc tivityLoc ke d方

法正式启动目标Ac tivity,即

Ma inAc tivity

3

.2.7:(21~23)正式启

动目标Activity

调用

Ac ivitySta c k. re sume TopAc tivit

yLocked:

final

booleanresumeTopActivityLocked(A

prev) {

ActivityRecord next =

topRunningActivityLocked(null);

……

if (mResumedActivity != null) {

if (DEBUG_SWITCH) Slog.v(TAG, "

need to startpausing");

startPausingLocked(userLeaving,

return true;

}

if (next.app != null &&next.app

null) {

}

startSpecificActivityLocked(nex

true);

}

return true;

}

①该方法在3.2.4步骤中调用

过,那时是因为

mRe sume dAc tivity != null,

有Ac tivity正在运行,所以去

执行了startPausingLocked暂

停Laucher去了。这时候,

mResumedActivity=null,所

以继续往下执行。

②判断讲要启动的Ac tivity的

客户进程是否存在,这里

next.app != null

&&next.app.thread != null为

false

③调用

ActivityStack.startSpecificActi

vityLoc ke d

private final voidstartSpecificA

ked(ActivityRecord r,

boolean andResume, boolean chec

// Is this activity's applicati

running?

ProcessRecord app

=

mService.getProcessRecordLocked

r.info.applicationInfo.uid);

……

if (app != null && app.thread !

……

realStartActivityLocked(r, app,

checkConfig);

return;

}

mService.startProcessLocked(r.p

r.info.applicationInfo,true, 0,

"

}

activity", r.intent.getCompone

④客户进程不存在,app!=

null && app.thread !=null为

false,所以调用

mService.startProcessLocked(

)

fork一个新的进程。

3

.2.8(24) fork一个新的

进程

①AMS通过Socket通信,向

Zygote发送一个创建进程请

求,Zygote创建新进程。

创建好进程后,调用

ActivityThread.main()。到

此,我们到了新了一个进程

中,也是程序的入口出。

调用ActivityThread.attach()

开始新的应用程序,接着同

过Binder通信通知AMS,新

的进程已经创建好了,可以

开始新的程序了。

3

.2.9(26~28) AMS准备

执行目标Activity

目标进程启动后,报告给

AMS,自己已经启动完毕可

以启动Ac tivity了,这里通过

IPC调用AMS的

attachApplication方法完成。

ActivityManagerService.attach

Application():

public final

voidattachApplication(IApplicati

thread) {

synchronized (this) {

int callingPid = Binder.getCall

final long origId =

Binder.clearCallingIdentity();

attachApplicationLocked(thread,c

Binder.restoreCallingIdentity(o

}

}

根据Binde r. ge tCa llingP id(),

或得客户进程pid,并调用

attachApplicationLocked(IApp

licationThreadthread,int pid)

在attachApplicationLocked

中,根据pid找到对应的

ProcessRecord对象,如果找

不到说明改pid客户进程是一

个没经过AMS允许的进程。

private final

booleanattachApplicationLocked(I

thread,

int pid) {

ProcessRecord app;

if (pid != MY_PID && pid>= 0) {

synchronized (mPidsSelfLocked)

app = mPidsSelfLocked.get(pid);

}

}

if (app == null) {

……

returnfalse;

}

/

/ If this application record i

to aprevious

/ process, clean it up now.

if (app.thread != null) {

/

handleAppDiedLocked(app, true,

}

……

app.thread = thread;

app.curAdj = app.setAdj = -100;

app.curSchedGroup =

Process.THREAD_GROUP_DEFAULT;

app.setSchedGroup

=

Process.THREAD_GROUP_BG_NONINTE

app.forcingToForeground = null;

app.foregroundServices = false;

app.hasShownUi = false;

app.debugging = false;

……

ensurePackageDexOpt(app.instrum

null

?

:

app.instrumentationInfo.packa

app.info.packageName);

……

// See if the top visible activ

run in thisprocess...

ActivityRecord hr

=

mMainStack.topRunningActivityLo

if (hr != null && normalMode){

if (hr.app == null && app.info.

hr.info.applicationInfo.uid

&processName.equals(hr.process

&

if (mMainStack.realStartActivit

true,true))

……

return true;

}

③为ProcessRecordapp对象

内部变量赋值

④确保目标程序(APK)文

件已经被转换为了odex文

件。Android中安装程序是

APK文件,实际上是一个zip

文件。

⑤调用

Ac tivitySta c k. re a lSta rtAc tivity

Locked通知客户进程运行指

定Ac tivity.

⑥调用

ApplicationThread.scheduleLa

unc hAc tivity,启动指定

Ac tivity。

3

.2.10:(29~35)客户进

程启动指定Activity

AMS通过IPC通行,通知客

户进程启动指定Ac tivity

①调用

ApplicationThread.scheduleLa

unc hAc tivity

②经过Handler消息传动,调

ActivityThread.handleLaunch

Ac tivity( )

调用

ActivityThread.performLaunc

hAc tivity()完成Ac tivity的加

载,并最终调用Ac tivity生命

周期的onCreate()方法

④performLaunchActivity返

回,继续调用

ActivityThread.handleResume

Ac tivity(),该方法内部又调用

ActivityThread.performResum

eActivity(),其内部仅仅调用

了目标Ac tivity的onResume()

方法。到此Ac tivity启动完

成。

添加一个IdleHandler对

象,因为在一般情况下,该

步骤执行完毕后,Ac tivity就

会进入空闲状态,所以就可

以进行内存回收。

3

.3在已有进程中启动

在已有的进程中启动

Ac tivity,也就是在一个应用

程序中启动内部Ac tivity,其

过程跟3.2小节大致一样,这

里我们不会像3.2小节详细分

析每一步骤,我们只看差别

的地方。这里以启动

subAc tivity为例子。时序图

如下:

以上时序图包含29步骤调

用,下面逐一讲解:

3

.3.1(1~3)在

MainActivity启动

Activity

这一步跟3.2.1小节一样

3

.3.2(4~7) AMS接收客

户端startActivity请求

这一步跟3.2.2小节一样

3

.3.3(8)不需要创建新

的Ta sk

调用

Ac tivitySta c k. sta rtAc tivityUnc

heckedLocked()处理Task问

题,因为这里我们是在已有

应用中sta r tAc tivity,也不设

置标志要在新的Task中启动

Ac tivity,所以不创建新的

Task,newTask=false,并调

Ac tivitySta c k. sta rtAc tivityLoa c

ked():

privatefinal void

startActivityLocked(ActivityReco

r, booleannewTask,

boolean doResume, boolean

keepCurTransition) {

……

if (!newTask) {

…….

}

}

// Place a new activity at top

stack, so it is next tointeract

// with the user.

if (addPos < 0) {

addPos = NH;

}

……

mHistory.add(addPos, r);

r.putInHistory();

r.frontOfTask = newTask;

if (doResume) {

resumeTopActivityLocked(null);

}

}

注意AtivitySta c k中有两个

startActivityLoacked()方法,

这里调用的是带四个参数

的,即

sta rtAc tivityLoc ke d(Ac tivityRe

cord r,

booleannewTask,boolean

doResume,

booleankeepCurTransition),

其中,r为将要启动的

Ac tivity,newTask=false,

doResume=true,在这个方法

中,将r放到mHistory的最后

面doResume=true,所以调用

re sume TopAc tivityLoc ke d(null

)

3

.3.4(9)准备启动

mHistory中最后一个

Activity

这一步跟3.2.4小节一样

3

.3.5(10~15)暂停

MainActivity

这一步跟3.2.5小节一样

3

.3.6(16~19) AMS处理

暂停MainActivity

这一步跟3.2.6小节一样

3

.3.7(20~22)正式启动

目标Activity

调用

Ac ivitySta c k. re sume TopAc tivit

yLocked:

final

booleanresumeTopActivityLocked(A

prev) {

ActivityRecord next =

topRunningActivityLocked(null);

……

if (mResumedActivity != null) {

if (DEBUG_SWITCH) Slog.v(TAG, "

need to startpausing");

startPausingLocked(userLeaving,

return true;

}

if (next.app != null &&next.app

null) {

}

startSpecificActivityLocked(nex

true);

}

return true;

}

①调用

startSpecificActivityLocked(ne

xt, true,true)

private final voidstartSpecificA

ked(ActivityRecord r,

boolean andResume, boolean chec

// Is this activity's applicati

running?

ProcessRecord app

=mService.getProcessRecordLocked

r.info.applicationInfo.uid);

……

if (app != null && app.thread !

……

realStartActivityLocked(r, app,

checkConfig);

return;

}

mService.startProcessLocked(r.p

r.info.applicationInfo,true, 0,

"

activity", r.intent.getCompone

}

②subAc tivity进程已经存

在,app != null&& app.thread

!=null为true,所以调用

re a lSta rtAc tivityLoc ke d。

finalboolean

realStartActivityLocked(Activity

r,

ProcessRecord app, boolean andR

booleancheckConfig)

throws RemoteException {

..

app.thread.scheduleLaunchActivi

Intent(r.intent),r.appToken,

System.identityHashCode(r), r.i

new

Configuration(mService.mConfigur

r.compat, r.icicle, results,

newIntents,!andResume,

mService.isNextTransitionForwar

profileFile,profileFd,

profileAutoStop);

……

return true;

}

③调用

ApplicationThread.scheduleLa

unc hAc tivity,启动指定

Ac tivity。

3

.3.8(23~29)客户进程

启动指定Activity

这一步跟3.2.10是一样的

3

.4在已有的

ActivityRecord中恢复

指定Activity

经过上面3.2和3.3小节,现

在对Ac tivity的启动流程应该

是比较清晰的了,这一节就

简单的讲下恢复Ac tivity的流

程。当Ac tivityRe c ord已经记

录有一个Ac tivity,如果再次

调用sta r tAc tivity,并没有标

志要创建Ac tivity新的实例,

那么就可以直接恢复该

Ac tivity。

①启动一个Ac tivity,跟前面

3.2节一样,都需要暂停当前

正在运行的Ac tivity,暂停流

程这里就不讲了,完成暂停

后,调用

Ac tivitySta c k. re sume TopAc tivi

tyLocked()。

②因为AMS和ActivityTHread

的IPC通信,

re sume TopAc tivityLoc ke d会

被反复调用几次,每次都会

根据一些变量值的差异,走

不同的流程。

finalboolean resumeTopActivityLo

prev) {

/

/ Find the first activity that

ActivityRecord next = topRunnin

(null);

……

if (mResumedActivity != null) {

if (DEBUG_SWITCH) Slog.v(TAG, "

startpausing");

startPausingLocked(userLeaving,

return true;

}

if (next.app != null &&next.app

if (DEBUG_SWITCH) Slog.v(TAG, "

next);

+

……

try {

……

next.app.thread.scheduleResumeAc

mService.isNextTransitionForwar

checkReadyForSleepLocked();

}

/

catch (Exception e) {

/ Whoops, need to restart this

startSpecificActivityLocked(nex

return true;

}

}

else {

return true;

}

③这里,mRe sume dAc tivity =

null,不走暂停流程。

④next.app != null

&&next.app.thread != null为

true,调用

ApplicationThead.scheduleRes

umeActivity(),到客户进程恢

复指定Ac tivity。

⑤经过消息传递,调用

ActivityTHread.handleResume

Ac tivity( )

⑥调用

ActivityTHread.performResu

me Ac tivity()正在恢复

Ac tivity,接着回调Ac tivity的

onResume()方法。

4

stop停止Activity

前面几节汇总,A启动到B

时,需要先暂停A,然后再

启动B。什么时候停止(stop)

或者销毁(Destory)呢?

4

.1从暂停到停止全过

4按Home键回到桌面

5

按Back键回到上一个

Ac tivity

6

长按Home键

应用程序包的安装是

androi d的特点

APK为AndroidPackage的缩

Android应用安装有如下四种

方式:

1.系统应用安装――开机时

完成,没有安装界面

2

.网络下载应用安装――通

过market应用完成,没有安

装界面

3.ADB工具安装――没有安

装界面。

4

.第三方应用安装――通过

SD卡里的APK文件安装,有

安装界面,由

packageinstaller.apk应用处理

安装及卸载过程的界面。

应用安装的流程及路径

应用安装涉及到如下几个目

录:

system/app ---------------系统

自带的应用程序,获得

adb root权限才能删除

data/app ---------------用户程

序安装的目录。安装时

apk文件复制到此目

data/data ---------------存放应

用程序的数据

data/dalvik-cache--------将apk

中的dex文件安装到da lvik-

cache目录下(dex文件是

da lvik虚拟机的可执行文件,

其大小约为原始apk文件大

小的四分之一 )

安装过程:

复制APK安装包到data/app目

录下,解压并扫描安装包,

把dex文件(Da lvik字节码)保

存到dalvik-cache目录,并

data/data目录下创建对应的

应用数据目录。

卸载过程:

删除安装过程中在上述三个

目录下创建的文件及目录。

安装应用的过程解析

一.开机安装

PackageManagerService处理

各种应用的安装,卸载,管

理等工作,开机时由

systemServer启动此服务

(源文件路径:

android\frameworks\base\servi

ces\java\com\android\server\P

ackageManagerService.java)

PackageManagerService服务

启动的流程:

1

.首先扫描安

装“system\framework”目录下

的jar包

/

/ Find base frameworks (res

ource packages without code).

mFrameworkInstall

Observer = new AppDirObserver

(

mFrameworkDir

.

getPath(), OBSERVER_EVENTS,

true);

mFrameworkInstall

Observer.startWatching();

scanDirLI(mFramew

orkDir, PackageParser.PARSE_I

S_SYSTEM

|

Package

Parser.PARSE_IS_SYSTEM_DIR,

scanMode

|

SCAN_NO_DEX, 0);

2.扫描安装系统system/app的

应用程序

/

/ Collect all system packag

es.

mSystemAppDir = ne

w File(Environment.getRootDir

ectory(), "app");

mSystemInstallObse

rver = new AppDirObserver(

mSystemAppDir.

getPath(), OBSERVER_EVENTS, t

rue);

mSystemInstallObse

rver.startWatching();

scanDirLI(mSystemA

ppDir, PackageParser.PARSE_IS

_

SYSTEM

|

PackageP

arser.PARSE_IS_SYSTEM_DIR, sc

anMode, 0);

3

.制造商的目录

下/vendor/app应用包

/

/ Collect all vendor packag

es.

mVendorAppDir = n

ew File("/vendor/app");

mVendorInstallObs

erver = new AppDirObserver(

mVendorAppDir

.

getPath(), OBSERVER_EVENTS,

true);

mVendorInstallObs

erver.startWatching();

scanDirLI(mVendor

AppDir, PackageParser.PARSE_I

S_SYSTEM

|

Package

Parser.PARSE_IS_SYSTEM_DIR, s

canMode, 0);

4.扫描“data\app”目录,即用

户安装的第三方应用

scanDirLI(mAppInstallDir, 0,

scanMode, 0);

5

.扫描" data\app-private"目

录,即安装DRM保护的APK

文件(一个受保护的歌曲或

受保 护的视频是使

用DRM 保护的文件)

scanDirLI(mDrmAppPrivateInst

allDir, PackageParser.PARSE_F

ORWARD_LOCK,

scanMode

,

0);

扫描方法的代码清单

private void scanDirLI(File

dir, int flags, int scanMode,

long currentTime) {

String[] files = dir

.

list();

if (files == null) {

Log.d(TAG, "No f

iles in app dir " + dir);

return;

}

if (false) {

Log.d(TAG, "Scan

ning app dir " + dir);

}

int i;

for (i=0; i<files.le

ngth; i++) {

File file = new

File(dir, files[i]);

if (!isPackageFi

lename(files[i])) {

/

/ Ignore en

tries which are not apk's

continue;

}

PackageParser.Pa

ckage pkg = scanPackageLI(fil

e,

flags|Pa

ckageParser.PARSE_MUST_BE_APK

,

scanMode, currentTime);

/ Don't mess ar

/

ound with apps in system part

ition.

if (pkg == null

&

& (flags & PackageParser.PAR

SE_IS_SYSTEM) == 0 &&

mLastSca

nError == PackageManager.INST

ALL_FAILED_INVALID_APK) {

/

/ Delete th

e apk

Slog.w(TAG,

"

Cleaning up failed install o

f " + file);

file.delete(

)

;

}

}

}

并且从该扫描方法中可以看

出调用了scanPackageLI()

priva te PackageParser.Packag

e scanPackageLI(File scanFile,

int parseFlags, int scanMode, l

ong currentTime)

跟踪scanPackageLI()方法

后发现,程序经过很多次的

if e lse 的筛选,最后判定可

以安装后调用

了mInsta lle r. insta ll

if (mInstaller != null) {

int ret

=

mInstaller.install(pkgName,

useEncryptedFSDir, pkg.appl

icationInfo.uid,pkg.applicati

onInfo.uid);

if(ret <

0

) {

/

/ E

rror from installer

mLas

PackageManage

r.INSTALL_FAILED_INSUFFICIENT

STORAGE;

tScanError =

_

retu

rn null;

}

}

mInstaller.install() 通过

LocalSocketAddress address

new LocalSocketAddress(

=

"

installd", L

ocalSocketAddress.Namespace.R

ESERVED);

指挥installd在C语言的文件

中完成工作

PackageManagerService小

节:1)从apk, xml中载入

pacakge信息, 存储到内部成

员变量中, 用于后面的查

找. 关键的方法是

scanPackageLI().

2

)各种查询操作, 包括

query Intent操作.

)install package和

3

delete package的操作. 还有

后面的关键方法是

installPackageLI().

二、从网络上下载应用:

下载完成后,会自动调用

Packagemanager的安装方法

installP ackage()

/

Called when a downloaded p

ackage installation has been c

onfirmed by the user /

由英文注释可见

PackageManagerService类的

installP ackage()函数为安

装程序入口。

public void installPackage(

final Uri package

URI, final IPackageInstallObs

erver observer, final int fla

gs,

final String inst

allerPackageName) {

mContext.enforceCalli

ngOrSelfPermission(

android.Manif

est.permission.INSTALL_PACKAG

ES, null);

Message msg = mHandle

r.obtainMessage(INIT_COPY);

msg.obj = new Install

Params(packageURI, observer,

flags,

installerPack

ageName);

mHandler.sendMessage(

msg);

}

其中是通过PackageHandler

的实例

mhandler.sendMessage(msg

)把信息发给继承Handler的

类HandleMessage()方法

class PackageHandler extends

Handler{

*

****************省略若干****

***************

public void handleM

*

essage(Message msg) {

try {

doHandleMess

age(msg);

}

finally {

Process.setT

hreadPriority(Process.THREAD_

PRIORITY_BACKGROUND);

}

}

*****************省略若干

*

*

*********************

}

把信息发给

doHandleMessage()方法,方法

中用switch()语句进行判

定传来Message

void doHandleMessage(Messag

e msg) {

switch (msg.what

)

{

case INIT_CO

if (DEBU

PY: {

G_SD_INSTALL) Log.i(TAG, "ini

t_copy");

HandlerP

arams params = (HandlerParams

)

msg.obj;

int idx

=

mPendingInstalls.size();

if (DEBU

G_SD_INSTALL) Log.i(TAG, "idx

" + idx);

=

/

/ If a

bind was already initiated we

dont really

/

/ need

to do anything. The pending i

nstall

/

/ will

be processed later on.

if (!mBo

und) {

/

/ I

f this is the only one pendin

g we might

/

/ h

ave to bind to the service ag

ain.

if (

!

connectToService()) {

Slog.e(TAG, "Failed to bind t

o media container service");

params.serviceError();

return;

}

el

se {

/

/ Once we bind to the servic

e, the first

/

/ pending request will be pr

ocessed.

mPendingInstalls.add(idx, par

ams);

}

else {

}

mPen

dingInstalls.add(idx, params)

;

/

/ A

lready bound to the service.

Just make

/

ure we trigger off processing

the first request.

if (

/ s

idx == 0) {

mHandler.sendEmptyMessage(MCS

_

BOUND);

}

}

break;

}

case MCS_BOU

ND: {

if (DEBU

G_SD_INSTALL) Log.i(TAG, "mcs

_

bound");

if (msg.

mCon

obj != null) {

tainerService = (IMediaContai

nerService) msg.obj;

}

if (mCon

tainerService == null) {

/

/ S

omething seriously wrong. Bai

l out

Slog

.

e(TAG, "Cannot bind to media

container service");

for

(HandlerParams params : mPend

ingInstalls) {

mPendingInstalls.remove(0);

/

/ Indicate service bind erro

r

params.serviceError();

}

mPen

dingInstalls.clear();

}

else i

f (mPendingInstalls.size() >

) {

0

Hand

lerParams params = mPendingIn

stalls.get(0);

if (

params != null) {

params.startCopy();

}

}

else {

/

/ S

hould never happen ideally.

Slog

.

w(TAG, "Empty queue");

}

break;

}

*

***************

省略若干**********************

}

}

public final boolean sendMess

age (Message msg)

public final boolean sendEmpt

yMessage (int what)

两者参数有别。

然后调用抽象类

HandlerParams中的一个

startCopy()方法

abstract class HandlerParams

{

final void startCopy() {

*

**************若干if语句判

定否这打回handler消息*******

handleReturnCode();

}

}

handleReturnCode()复写

了两次其中有一次是删除时

要调用的,只列出安装调用

的一个方法

@

Override

void handleReturnCode

() {

/

/ If mArgs is nu

ll, then MCS couldn't be reac

hed. When it

/

/ reconnects, it

will try again to install. A

t that point, this

/

/ will succeed.

if (mArgs != null

processPendin

)

{

gInstall(mArgs, mRet);

}

}

这时可以清楚的看

见processPendingInstall()

被调用。

其中run()方法如下

run(){

synchronized (mInstallLock)

{

*

***

*

*******省略*****************

inst

allPackageLI(args, true, res)

;

}

}

instaPacakgeLI()args,res参

数分析

//InstallArgs 是在

PackageService定义的

static abstract class InstallArgs

静态抽象类。

static abstract class Instal

lArgs {

*

***************************

*

*

****************************

***********

其中定义了flag标志,packageURL

创建文件,拷贝apk,修改包名称,

还有一些删

除文件的清理,释放存储函数。

*

***********************

*

****************************

*

***************

}

class PackageInstalledInfo

{

String name;

int uid;

PackageParser.Packag

e pkg;

int returnCode;

PackageRemovedInfo r

emovedInfo;

}

private void installPackageL

I(InstallArgs args,

boolean newInstall

,

PackageInstalledInfo res) {

int pFlags = args.flag

s;

String installerPackag

eName = args.installerPackage

Name;

File tmpPackageFile =

new File(args.getCodePath());

boolean forwardLocked

=

((pFlags & PackageManager.I

NSTALL_FORWARD_LOCK) != 0);

boolean onSd = ((pFlag

s & PackageManager.INSTALL_EX

TERNAL) != 0);

boolean replace = fals

e;

int scanMode = (onSd ?

0

: SCAN_MONITOR) | SCAN_FOR

CE_DEX | SCAN_UPDATE_SIGNATUR

E

|

(newInstall

SCAN_NEW_INSTALL : 0);

/ Result object to be

returned

?

/

res.returnCode = Packa

geManager.INSTALL_SUCCEEDED;

/

/ Retrieve PackageSet

tings and parse package

int parseFlags = Packa

geParser.PARSE_CHATTY |

(forwardLocked ? Packa

geParser.PARSE_FORWARD_LOCK :

) |

0

(onSd ? PackageParser.

PARSE_ON_SDCARD : 0);

parseFlags |= mDefPars

eFlags;

PackageParser pp = new

PackageParser(tmpPackageFile

getPath());

.

pp.setSeparateProcesse

s(mSeparateProcesses);

final PackageParser.Pa

ckage pkg = pp.parsePackage(t

mpPackageFile,

null, mMetrics

,

parseFlags);

if (pkg == null) {

res.returnCode = p

p.getParseError();

return;

}

String pkgName = res.n

ame = pkg.packageName;

if ((pkg.applicationIn

fo.flags&ApplicationInfo.FLAG

TEST_ONLY) != 0) {

if ((pFlags&Packag

eManager.INSTALL_ALLOW_TEST)

= 0) {

_

=

res.returnCode

PackageManager.INSTALL_FAI

=

LED_TEST_ONLY;

return;

}

}

if (GET_CERTIFICATES &

&

,

!pp.collectCertificates(pkg

parseFlags)) {

res.returnCode = p

p.getParseError();

return;

}

/

/ Get rid of all refe

rences to package scan path v

ia parser.

pp = null;

String oldCodePath = n

ull;

boolean systemApp = fa

lse;

synchronized (mPackage

s) {

/

/ Check if instal

ling already existing package

if ((pFlags&Packag

eManager.INSTALL_REPLACE_EXIS

TING) != 0) {

String oldName

=

mSettings.mRenamedPackages

.

get(pkgName);

if (pkg.mOrigi

nalPackages != null

&

& pkg

.

mOriginalPackages.contains(o

ldName)

&

& mPa

ckages.containsKey(oldName))

{

/

/ This pa

ckage is derived from an orig

inal package,

/

/ and thi

s device has been updating fr

om that original

/

/ name.

We must continue using the or

iginal name, so

/

/ rename

the new package here.

pkg.setPac

pkgName =

replace =

kageName(oldName);

pkg.packageName;

true;

}

else if (mPa

ckages.containsKey(pkgName))

{

/

/ This pa

ckage, under its official nam

e, already exists

/

/ on the

device; we should replace it.

replace =

true;

}

}

PackageSetting ps

=

mSettings.mPackages.get(pkg

Name);

if (ps != null) {

oldCodePath =

mSettings.mPackages.get(pkgNa

me).codePathString;

if (ps.pkg !=

null && ps.pkg.applicationInf

o != null) {

systemApp

=

(ps.pkg.applicationInfo.fla

gs &

Ap

plicationInfo.FLAG_SYSTEM) !=

0

;

}

}

}

if (systemApp && onSd)

{

/

/ Disable updates

to system apps on sdcard

Slog.w(TAG, "Canno

t install updates to system a

pps on sdcard");

res.returnCode = P

ackageManager.INSTALL_FAILED_

INVALID_INSTALL_LOCATION;

return;

}

if (!args.doRename(res

.

returnCode, pkgName, oldCode

Path)) {

res.returnCode = P

ackageManager.INSTALL_FAILED_

INSUFFICIENT_STORAGE;

return;

}

/

/ Set application obj

ects path explicitly after th

e rename

setApplicationInfoPath

s(pkg, args.getCodePath(), ar

gs.getResourcePath());

pkg.applicationInfo.na

tiveLibraryDir = args.getNati

veLibraryPath();

if (replace) {

replacePackageLI(p

kg, parseFlags, scanMode,

installerP

ackageName, res);

else {

installNewPackageL

}

I(pkg, parseFlags, scanMode,

installerP

ackageName,res);

}

}

最后判断如果以前不存在那

么调用

installNewPackageLI()

private void installNewPacka

geLI(PackageParser.Package pk

g,

int parseFlags,i

nt scanMode,

String installer

PackageName, PackageInstalled

Info res) {

*

**********************

省略若干**********************

*

**************************

PackageParser.Packag

e newPackage = scanPackageLI(

pkg, parseFlags, scanMode,

System.curren

tTimeMillis());

*

**********************

省略若干**********************

*

***************************

}

最后终于回到了和开机安装

一样的地方.与开机方式安装

调用统一方法。

三、从ADB工具安装

其入口函数源文件为pm. java

(源文件路径:

android\frameworks\base\cmd

s\pm\src\com\android\comman

ds\pm\pm.java)

其中

\system\framework\pm.jar 包

管理库

包管理脚

本\system\bin\pm 解析

showUsage就是使用方法

private static void showUsage

() {

System.err.println("u

sage: pm [list|path|install|u

ninstall]");

System.err.println("

pm list packages [-f]")

;

System.err.println("

pm list permission-grou

ps");

System.err.println("

pm list permissions [-g

]

[-f] [-d] [-u] [GROUP]");

System.err.println("

pm list instrumentation

-f] [TARGET-PACKAGE]");

System.err.println("

pm list features");

[

System.err.println("

pm path PACKAGE");

System.err.println("

pm install [-l] [-r] [-

t] [-i INSTALLER_PACKAGE_NAME

]

[-s] [-f] PATH");

System.err.println("

pm uninstall [-k] PACKA

GE");

System.err.println("

pm enable PACKAGE_OR_CO

MPONENT");

System.err.println("

pm disable PACKAGE_OR_C

OMPONENT");

System.err.println("

pm setInstallLocation [

0

/auto] [1/internal] [2/exter

nal]");

*

*********************

省略*************************

*

}

安装时候会调

用runInstall()方法

private void runInstall() {

int installFlags = 0;

String installerPackage

Name = null;

String opt;

while ((opt=nextOption(

)

) != null) {

if (opt.equals("-l"

)

) {

installFlags |=

PackageManager.INSTALL_FORWA

RD_LOCK;

}

else if (opt.equa

ls("-r")) {

installFlags |=

PackageManager.INSTALL_REPLA

CE_EXISTING;

}

else if (opt.equa

ls("-i")) {

installerPackag

eName = nextOptionData();

if (installerPa

ckageName == null) {

System.err.

println("Error: no value spec

ified for -i");

showUsage()

;

return;

}

}

else if (opt.equa

installFlags |=

ls("-t")) {

PackageManager.INSTALL_ALLOW

TEST;

_

}

else if (opt.equa

ls("-s")) {

/

/ Override if

-

s option is specified.

installFlags |=

PackageManager.INSTALL_EXTER

NAL;

}

else if (opt.equa

ls("-f")) {

/

/ Override if

-

s option is specified.

installFlags |=

PackageManager.INSTALL_INTER

NAL;

}

else {

System.err.prin

tln("Error: Unknown option: "

+

opt);

}

showUsage();

return;

}

String apkFilePath = ne

xtArg();

System.err.println("\tp

kg: " + apkFilePath);

if (apkFilePath == null

)

{

System.err.println(

"

)

Error: no package specified"

;

showUsage();

return;

}

PackageInstallObserver

obs = new PackageInstallObser

ver();

try {

mPm.installPackage(

Uri.fromFile(new File(apkFile

Path)), obs, installFlags,

installerPa

ckageName);

synchronized (obs)

{

while (!obs.fin

ished) {

try {

obs.wai

t();

}

catch (In

terruptedException e) {

}

}

if (obs.result

=

= PackageManager.INSTALL_SUC

CEEDED) {

System.out.

println("Success");

else {

System.err.

}

println("Failure ["

+

i

nstallFailureToString(obs.res

ult)

+

"

]

");

}

}

}

catch (RemoteExceptio

n e) {

System.err.println(

e.toString());

System.err.println(

PM_NOT_RUNNING_ERR);

}

}

其中的

PackageInstallObserver obs =

new PackageInstallObserver();

mPm.installPackage(U

ri.fromFile(new File (a pkFile P a

th)), obs, installFlags,

installerPackage

Name);

如果安装成功

obs.result == PackageManage

r. INSTALL_SUCCEEDED)

又因为有

IPackageManage mPm;

mPm = IpackageManage

r.Stub.asInterface(ServiceMan

ager.getService("package"));

Stub是接口IPackageManage

的静态抽象类,asInterface是

返回IPackageManager代理的

静态方法。

因为

class PackageManagerService

extends IPackageManager.Stu

b

所以mPm.installPackage 调

/

Called when a downloade

d package installation has bee

n confirmed by the user /

public vo id installP ackage(

final Uri packageURI,

final IPackageInstallObserver

observer, final int flags,final St

ring installerPackageName)

这样就是从网络下载安装的

入口了。

四,从SD卡安装

系统调用

P ackageInstallerActivity.java (

/home/zhongda/androidSRC/v

ortex-8inch-for-

hoperun/packages/apps/Packa

geInstaller/src/com/android/pa

ckageinstaller)

进入这个Ac tivity会判断信息

是否有错,然后调用

priva te vo id initiateInstall()

判断是否曾经有过同名包的

安装,或者包已经安装

通过后执行

priva te vo id startInstallConfirm

() 点击OK按钮后经过一系列

的安装信息的判断Intent跳转

public class InstallAppProgre

ss extends Activity implement

s View.OnClickListener, OnCan

celListener

public void onCreate(Bundl

e icicle) {

super.onCreate(icicle

)

;

Intent intent = getIn

mAppInfo = intent.get

tent();

ParcelableExtra(PackageUtil.I

NTENT_ATTR_APPLICATION_INFO);

mPackageURI = intent.

getData();

initView();

}

方法中调用了initVie w()方

public void initView() {

requestWindowFeature(W

indow.FEATURE_NO_TITLE);

setContentView(R.layou

t.op_progress);

int installFlags = 0;

PackageManager pm = ge

tPackageManager();

try {

PackageInfo pi = p

m.getPackageInfo(mAppInfo.pac

kageName,

PackageMan

ager.GET_UNINSTALLED_PACKAGES

)

;

if(pi != null) {

installFlags |

=

PackageManager.INSTALL_REPL

ACE_EXISTING;

}

}

catch (NameNotFoundE

xception e) {

}

if((installFlags & Pac

kageManager.INSTALL_REPLACE_E

XISTING )!= 0) {

Log.w(TAG, "Replac

ing package:" + mAppInfo.pack

ageName);

}

PackageUtil.AppSnippet

as = PackageUtil.getAppSnipp

et(this, mAppInfo,

mPackageURI);

mLabel = as.label;

PackageUtil.initSnippe

tForNewApp(this, as, R.id.app

_

snippet);

mStatusTextView = (Tex

tView)findViewById(R.id.cente

r_text);

mStatusTextView.setTex

t(R.string.installing);

mProgressBar = (Progre

ssBar) findViewById(R.id.prog

ress_bar);

mProgressBar.setIndete

rminate(true);

/

/ Hide button till pr

ogress is being displayed

mOkPanel = (View)findV

iewById(R.id.buttons_panel);

mDoneButton = (Button)

findViewById(R.id.done_button

)

;

mLaunchButton = (Butto

n)findViewById(R.id.launch_bu

tton);

mOkPanel.setVisibility

(View.INVISIBLE);

String installerPackag

eName = getIntent().getString

Extra(

Intent.EXTRA_I

NSTALLER_PACKAGE_NAME);

PackageInstallObserver

observer = new PackageInstal

lObserver();

pm.installPackage(mPac

kageURI, observer, installFla

gs, installerPackageName);

}

方法最后我们可以看到再次

调用安装接口完成安装。

Android的构建过程涉及到许

多工具和流程,并会产生一

系列中间件,最终生成一个

APK文件,可以根据官方提

供的流程图来具体了解构建

的过程。

通常的构建过程就是如上图

所示,下面是具体描述:

1

.AAPT(Android Asset Packa

ging Tool)工具会打包应用中

的资源文件,如

AndroidManifest.xml、layout

布局中的xml等,并将xml文

件编译为二进制形式,当然

assets文件夹中的文件不会被

编译,图片及raw文件夹中

的资源也会保持原来的形

态,需要注意的是raw文件

夹中的资源也会生成资源

id。AAPT编译完成之后会生

成R.java文件。

2

.AIDL工具会将所有的a idl

接口转化为java接口。

3

.所有的java代码,包括

R.java与a idl文件都会被Java

编译器编译成.class文件。

4

.Dex工具会将上述产生

的.class文件及第三库及其

他.class文件编译成.dex文件

dex文件是Da lvik虚拟机可

以执行的格式),dex文件

最终会被打包进APK文件。

5

.ApkBuilder工具会将编译过

的资源及未编译过的资源

如图片等)以及.dex文件

打包成APK文件。

6

.生成APK文件后,需要对

其签名才可安装到设备,平

时测试时会使用

debug keystore,当正式发布

应用时必须使用release版的

keystore对应用进行签名。

7

.如果对APK正式签名,还

需要使用z ipa lign工具对APK

进行对齐操作,这样做的好

处是当应用运行时会减少内

存的开销。

在构建APK的过程中,当

APK过大,应用中的方法数

量超过65536限制的时候,

可能会报如下错误:

Una ble to execute dex: metho

d ID not in [0, 0xffff] : 65536

。一旦遇到上述错误,就需

要使用MultiDex方案来解

决,但官方的MultiDex方案

有一些限制,因此还得使用

各种策略填坑才行,具体可

参考网上不少较优秀的解决

方案。

参考文

档:https://de ve lope r. a ndroid.

com/sdk/installing/studio-

build.html#detailed-build

版权声明:本文为博主原创

文章,未经博主允许不得转

载。

目录(?)[+]

工匠若

水http://blog.csdn.net/yanbob

er 转载烦请注明出处,尊重

分享成果】

1

背景

还记得前面《Android应用

setContentView与

LayoutInflater加载解析机制

源码分析》这篇文章吗?我

们有分析到Ac tivity中界面加

载显示的基本流程原理,记

不记得最终分析结果就是下

面的关系:

看见没有,如上图中id为

content的内容就是整个Vie w

树的结构,所以对每个具体

Vie w对象的操作,其实就是

个递归的实现。

前面《Android触摸屏事件派

发机制详解与源码分析一

(View篇)》文章的3-1小节说

过Android中的任何一个布

局、任何一个控件其实都是

直接或间接继承自Vie w实现

的,当然也包括我们后面一

步一步引出的自定义控件也

不例外,所以说这些Vie w应

该都具有相同的绘制流程与

机制才能显示到屏幕上(因

为他们都具备相同的父类

Vie w,可能每个控件的具体

绘制逻辑有差异,但是主流

程都是一样的)。经过总结

发现每一个Vie w的绘制过程

都必须经历三个最主要的过

程,也就是measure、layout

和draw。

既然一个Vie w的绘制主要流

程是这三步,那一定有一个

开始地方呀,就像一个类从

main函数执行一样呀。对于

Vie w的绘制开始调运地方这

里先给出结论,本文后面会

反过来分析原因的,先往下

看就行。具体结论如下:

整个Vie w树的绘图流程是在

ViewRootImpl类的

performTraversals()方法(这

个方法巨长)开始的,该函

数做的执行过程主要是根据

之前设置的状态,判断是否

重新计算视图大小

(measure)、是否重新放置视

图的位置(layout)、以及是否

重绘(draw),其核心也就是

通过判断来选择顺序执行这

三个方法中的哪个,如下:

private void performTraversal

s() {

.

/

.....

/最外层的根视图的widthM

easureSpec和heightMeasureSpec

由来

/

/lp.width和lp.height

在创建ViewGroup实例时等于MATCH_P

ARENT

int childWidthMeasure

Spec = getRootMeasureSpec(mWi

dth, lp.width);

int childHeightMeasur

eSpec = getRootMeasureSpec(mH

eight, lp.height);

.

.....

mView.measure(childWi

dthMeasureSpec, childHeightMe

asureSpec);

.

.....

mView.layout(0, 0, mV

iew.getMeasuredWidth(), mView

.

getMeasuredHeight());

.

.....

mView.draw(canvas);

.....

.

}

/

**

*

Figures out the measur

e spec for the root view in a

window based on it's

*

*

*

*

layout params.

@param windowSize

The availab

le width or height of the win

dow

*

*

*

@param rootDimension

The layout

params for one dimension (wid

th or height) of the

*

window.

*

*

@return The measure sp

ec to use to measure the root

view.

*

/

private static int getRoo

tMeasureSpec(int windowSize,

int rootDimension) {

int measureSpec;

switch (rootDimension

)

{

case ViewGroup.Layout

Params.MATCH_PARENT:

/

/ Window can't r

esize. Force root view to be

windowSize.

measureSpec = Mea

sureSpec.makeMeasureSpec(wind

owSize, MeasureSpec.EXACTLY);

break;

.

.....

}

return measureSpec;

}

可以看见这个方法的注释说

是用来测Root Vie w的。上面

传入参数后这个函数走的是

MATCH_PARENT,使用

MeasureSpec.makeMeasureS

pec方法组

装一个MeasureSpec,

MeasureSpec的specMode等

于EXACTLY,specSize等于

windowSize,也就是为何根

视图总是全屏的原因。

其中的mVie w就是Vie w对

象。如下就是整个流程的大

致流程图:

如下我们就依据Vie w绘制的

这三个主要流程进行详细剖

析(基于Android5.1.1 API 22

源码进行分析)。

工匠若

水http://blog.csdn.net/yanbob

er 转载烦请注明出处,尊重

分享成果】

2

View绘制流程第一

步:递归measure源码

分析

整个Vie w树的源码measure

流程图如下:

2

-1 measure源码分析

先看下Vie w的measure方法

源码,如下:

/

**

*

*

<p>

This is called to find

out how big a view should be

.

The parent

supplies constraint in

*

formation in the width and he

ight parameters.

*

*

*

*

</p>

<p>

The actual measurement

work of a view is performed

in

*

{@link #onMeasure(int,

int)}, called by this method

Therefore, only

.

*

{@link #onMeasure(int,

int)} can and must be overri

dden by subclasses.

*

*

*

*

</p>

@param widthMeasureSpe

c Horizontal space requiremen

ts as imposed by the

*

*

parent

@param heightMeasureSp

ec Vertical space requirement

s as imposed by the

*

*

*

parent

@see #onMeasure(int, i

nt)

*

/

/

/final方法,子类不可重写

public final void measure

(int widthMeasureSpec, int he

ightMeasureSpec) {

.

/

.....

/回调onMeasure()方法

onMeasure(widthMeasur

eSpec, heightMeasureSpec);

.

.....

}

看见注释信息没有,他告诉

你了很多重要信息。为整个

Vie w树计算实际的大小,然

后设置实际的高和宽,每个

Vie w控件的实际宽高都是由

父视图和自身

决定的。实际的测量是在

onMeasure方法进行,所以

在Vie w的子类需要重写

onMeasure方法,这是因为

measure方法是fina l的,不允

许重载,所以

Vie w子类只能通过重载

onMeasure来实现自己的测

量逻辑。

这个方法的两个参数都是父

Vie w传递过来的,也就是代

表了父vie w的规格。他由两

部分组成,高16位表示

MODE,定义在MeasureSpec

类(Vie w的内部类)中,有

三种类型,

MeasureSpec.EXACTLY表示

确定大小,

MeasureSpec.AT_MOST表示

最大大小,

MeasureSpec.UNSPECIFIED

不确定。低16位表示size,

也就是父Vie w的大小。对于

系统Window类的DecorVIew

对象Mode一般都为

MeasureSpec.EXACTLY ,

而size分别对应屏幕宽高。

对于子Vie w来说大小是由父

Vie w和子Vie w共同决定的。

在这里可以看出measure方法

最终回调了Vie w的

onMeasure方法,我们来看

下Vie w的onMeasure源码,

如下:

/

**

*

*

<p>

Measure the view and i

ts content to determine the m

easured width and the

*

measured height. This

method is invoked by {@link #

measure(int, int)} and

*

should be overriden by

subclasses to provide accura

te and efficient

*

measurement of their c

ontents.

*

*

*

*

</p>

<p>

<strong>CONTRACT:</str

ong> When overriding this met

hod, you

*

<em>must</em> call {@l

ink #setMeasuredDimension(int

int)} to store the

measured width and hei

,

*

ght of this view. Failure to

do so will trigger an

*

<code>IllegalStateExce

ption</code>, thrown by

*

{@link #measure(int, i

nt)}. Calling the superclass'

*

{@link #onMeasure(int,

int)} is a valid use.

*

*

*

*

</p>

<p>

The base class impleme

ntation of measure defaults t

o the background size,

*

unless a larger size i

s allowed by the MeasureSpec.

Subclasses should

*

override {@link #onMea

sure(int, int)} to provide be

tter measurements of

*

*

*

*

*

their content.

</p>

<p>

If this method is over

ridden, it is the subclass's

responsibility to make

*

sure the measured heig

ht and width are at least the

view's minimum height

*

and width ({@link #get

SuggestedMinimumHeight()} and

{@link #getSuggestedMi

nimumWidth()}).

*

*

*

*

</p>

@param widthMeasureSpe

c horizontal space requiremen

ts as imposed by the parent.

*

The requirements are encode

d with

*

{

@link android.view.View.Me

asureSpec}.

*

@param heightMeasureSp

ec vertical space requirement

s as imposed by the parent.

*

The requirements are encode

d with

*

{

@link android.view.View.Me

asureSpec}.

*

*

*

*

@see #getMeasuredWidth

()

@see #getMeasuredHeigh

@see #setMeasuredDimen

t()

sion(int, int)

@see #getSuggestedMini

*

mumHeight()

*

@see #getSuggestedMini

mumWidth()

*

@see android.view.View

.

.

MeasureSpec#getMode(int)

*

@see android.view.View

MeasureSpec#getSize(int)

*

/

/

/View的onMeasure默认实现

方法

protected void onMeasure(

int widthMeasureSpec, int hei

ghtMeasureSpec) {

setMeasuredDimension(

getDefaultSize(getSuggestedMi

nimumWidth(), widthMeasureSpe

c),

getDefaultSiz

e(getSuggestedMinimumHeight()

,

heightMeasureSpec));

}

看见没有,其实注释已经很

详细了(自定义Vie w重写该

方法的指导操作注释都有说

明),不做过多解释。

对于非Vie wGroup的Vie w而

言,通过调用上面默认的

onMeasure即可完成Vie w的

测量,当然你也可以重载

onMeasure并调用

setMeasuredDimension来设置

任意大小的布局,但一般不

这么做,因为这种做法不太

好,至于为何不好,后面分

析完你就明白了。

我们可以看见onMeasure默

认的实现仅仅调用了

setMeasuredDimension,

setMeasuredDimension函数是

一个很关键的函数,它对

Vie w的成员变量

mMeasuredWidth和

mMeasuredHeight变量赋值,

measure的主要目的就是对

Vie w树中的每个Vie w的

mMeasuredWidth和

mMeasuredHeight进行赋值,

所以一旦这两个变量被赋值

意味着该Vie w的测量工作结

束。既然这样那我们就看看

设置的默认尺寸大小吧,可

以看见setMeasuredDimension

传入的参数都是通过

getDefaultSize返回的,所以

再来看下getDefaultSize方法

源码,如下:

public static int getDefaultS

ize(int size, int measureSpec

)

{

int result = size;

/通过MeasureSpec解析获

取mode与size

/

int specMode = Measur

eSpec.getMode(measureSpec);

int specSize = Measur

eSpec.getSize(measureSpec);

switch (specMode) {

case MeasureSpec.UNSP

ECIFIED:

result = size;

break;

case MeasureSpec.AT_M

OST:

case MeasureSpec.EXAC

TLY:

result = specSize

;

break;

}

return result;

}

看见没有,如果specMode等

于AT_MOST或EXACTLY就

返回specSize,这就是系统

默认的规格。

回过头继续看上面

onMeasure方法,其中

getDefaultSize参数的

widthMeasureSpec和

heightMeasureSpec都是由父

Vie w传递进来的。

getSuggestedMinimumWidth

getSuggestedMinimumHeight

都是Vie w的方法,具体如

下:

protected int getSuggestedMin

imumWidth() {

return (mBackground =

=

null) ? mMinWidth : max(mMi

nWidth, mBackground.getMinimu

mWidth());

}

protected int getSuggeste

dMinimumHeight() {

return (mBackground =

=

null) ? mMinHeight : max(mM

inHeight, mBackground.getMini

mumHeight());

}

看见没有,建议的最小宽度

和高度都是由Vie w的

Background尺寸与通过设置

Vie w的miniXXX属性共同决

定的。

到此一次最基础的元素Vie w

的measure过程就完成了。上

面说了Vie w实际是嵌套的,

而且measure是递归传递的,

所以每个Vie w都需要

measure。实际能够嵌套的

Vie w一般都是Vie wGroup的

子类,所以在Vie wGroup中

定义了measureChildren,

measureChild,

measureChildWithMargins方

法来对子视图进行测量,

measureChildren内部实质只

是循环调用measureChild,

measureChild和

measureChildWithMargins的

区别就是是否把margin和

padding也作为子视图的大

小。如下我们以Vie wGroup

中稍微复杂的

measureChildWithMargins方

法来分析:

/

**

*

Ask one of the childre

n of this view to measure its

elf, taking into

*

account both the Measu

reSpec requirements for this

view and its padding

*

and margins. The child

must have MarginLayoutParams

The heavy lifting is

*

done in getChildMeasur

@param child The child

eSpec.

*

*

to measure

*

@param parentWidthMeas

ureSpec The width requirement

s for this view

*

@param widthUsed Extra

space that has been used up

by the parent

*

horizontally (p

ossibly by other children of

the parent)

*

@param parentHeightMea

sureSpec The height requireme

nts for this view

*

@param heightUsed Extr

a space that has been used up

by the parent

*

vertically (pos

sibly by other children of th

e parent)

*

/

protected void measureChi

ldWithMargins(View child,

int parentWidthMe

asureSpec, int widthUsed,

int parentHeightM

easureSpec, int heightUsed) {

/

/获取子视图的LayoutPar

ams

final MarginLayoutPar

ams lp = (MarginLayoutParams)

child.getLayoutParams();

/

/

/调整MeasureSpec

/通过这两个参数以及子视

图本身的LayoutParams来共同决定子

视图的测量规格

final int childWidthM

easureSpec = getChildMeasureS

pec(parentWidthMeasureSpec,

mPaddingLeft

+

mPaddingRight + lp.leftMarg

in + lp.rightMargin

+

wid

thUsed, lp.width);

final int childHeight

MeasureSpec = getChildMeasure

Spec(parentHeightMeasureSpec,

mPaddingTop +

mPaddingBottom + lp.topMargi

n + lp.bottomMargin

+

hei

ghtUsed, lp.height);

/调运子View的measure方

/

法,子View的measure中会回调子Vie

w的onMeasure方法

child.measure(childWi

dthMeasureSpec, childHeightMe

asureSpec);

}

关于该方法的参数等说明注

释已经描述的够清楚了。该

方法就是对父视图提供的

measureSpec参数结合自身的

LayoutParams参数进行了调

整,然后再

来调用child.measure()方法,

具体通过方法

getChildMeasureSpec来进行

参数调整。所以我们继续看

下getChildMeasureSpec方法

代码,如下:

public static int getChildMea

sureSpec(int spec, int paddin

g, int childDimension) {

/

/获取当前Parent View的

Mode和Size

int specMode = Measur

eSpec.getMode(spec);

int specSize = Measur

eSpec.getSize(spec);

/

/获取Parent size与pad

ding差值(也就是Parent剩余大小)

若差值小于0直接返回0

int size = Math.max(0

specSize - padding);

/定义返回值存储变量

,

/

int resultSize = 0;

int resultMode = 0;

/

/依据当前Parent的Mode

进行switch分支逻辑

switch (specMode) {

/ Parent has imposed

an exact size on us

/

/

/默认Root View的Mode

就是EXACTLY

case MeasureSpec.EXAC

if (childDimensio

/如果child的l

TLY:

n >= 0) {

/

ayout_wOrh属性在xml或者java中给

予具体大于等于0的数值

/

/设置child的s

ize为真实layout_wOrh属性值,mod

e为EXACTLY

resultSize =

resultMode =

childDimension;

MeasureSpec.EXACTLY;

}

else if (childD

imension == LayoutParams.MATC

H_PARENT) {

/

/如果child的l

ayout_wOrh属性在xml或者java中给

予MATCH_PARENT

/

/ Child want

s to be our size. So be it.

/设置child的s

ize为size,mode为EXACTLY

resultSize =

/

size;

resultMode =

MeasureSpec.EXACTLY;

}

else if (childD

imension == LayoutParams.WRAP

_

CONTENT) {

/

/如果child的l

ayout_wOrh属性在xml或者java中给

予WRAP_CONTENT

/

/设置child的s

ize为size,mode为AT_MOST

/

s to determine its own size.

It can't be

/ Child want

/

/ bigger tha

n us.

size;

resultSize =

resultMode =

MeasureSpec.AT_MOST;

}

break;

.

/

}

/

.....

/其他Mode分支类似

/将mode与size通过Meas

ureSpec方法整合为32位整数返回

return MeasureSpec.ma

keMeasureSpec(resultSize, res

ultMode);

}

可以看见,

getChildMeasureSpec的逻辑

是通过其父Vie w提供的

MeasureSpec参数得到

specMode和specSize,然后

根据计算出来的specMode以

子Vie w的

childDimension(layout_width

或layout_height)来计算自身

的measureSpec,如果其本身

包含子视图,则计算出来的

measureSpec将作

为调用其子视图measure函数

的参数,同时也作为自身调

用setMeasuredDimension的参

数,如果其不包含子视图则

默认情况下最终会调用

onMeasure

的默认实现,并最终调用到

setMeasuredDimension。

所以可以看见onMeasure的

参数其实就是这么计算出来

的。同时从上面的分析可以

看出来,最终决定Vie w的

measure大小是Vie w的

setMeasuredDimension方法,

所以我们可以通过

setMeasuredDimension设定死

值来设置Vie w的

mMeasuredWidth和

mMeasuredHeight的大小,但

是一个好的自定义Vie w应该

会根据子视图的measureSpec

来设置mMeasuredWidth和

mMeasuredHeight的大小,这

样的灵活性更大,所以这也

就是上面分析onMeasure时

说Vie w的onMeasure最好不

要重写死值的原因。

可以看见当通过

setMeasuredDimension方法最

终设置完成Vie w的measure

之后Vie w的mMeasuredWidth

和mMeasuredHeight成员才会

有具体的数值,所以如果我

们自定义的Vie w或者使用现

成的Vie w想通过

getMeasuredWidth()和

getMeasuredHeight()方法来

获取Vie w测量的宽高,必须

保证这两个方法在

onMeasure流程之后被调用

才能返回有效值。

还记得前面《Android应用

setContentView与

LayoutInflater加载解析机制

源码分析》文章3-3小节探讨

的inflate方法加载一些布局

显示时指定的大小失效问题

吗?当时只给出了结论,现

在给出了详细原因分析,我

想不需要再做过多解释了

吧。

至此整个Vie w绘制流程的第

一步就分析完成了,可以看

见,相对来说还是比较复杂

的,接下来进行小结。

2

-2 measure原理总结

通过上面分析可以看出

measure过程主要就是从顶层

父Vie w向子Vie w递归调用

view.measure方法(measure

中又回调onMeasure方法)

的过程。具体measure核心主

要有如下几点:

MeasureSpec(Vie w的内

部类)测量规格为int型,

值由高16位规格模式

specMode和低16位具体尺

寸specSize组成。其中

specMode只有三种值:

MeasureSpec.EXACTLY //确定模式

父View希望子View的大小是确定的,

由specSize决定;

MeasureSpec.AT_MOST //最多模式

父View希望子View的大小最多是spe

cSize指定的值;

MeasureSpec.UNSPECIFIED //未

指定模式,父View完全依据子View的设

计值来决定;

Vie w的measure方法是final

的,不允许重载,Vie w子

类只能重载onMeasure来

完成自己的测量逻辑。

最顶层DecorView测量时

的MeasureSpec是由

ViewRootImpl中

getRootMeasureSpec方法

确定的(LayoutParams宽

高参数均为

MATCH_PARENT,

specMode是EXACTLY,

specSize为物理屏幕大

小)。

Vie wGroup类提供了

measureChild,

measureChild和

measureChildWithMargins

方法,简化了父子Vie w的

尺寸计算。

只要是Vie wGroup的子类

就必须要求LayoutParams

继承子

MarginLayoutParams,否

则无法使用layout_margin

参数。

Vie w的布局大小由父Vie w

和子Vie w共同决定。

使用Vie w的

getMeasuredWidth()和

getMeasuredHeight()方法

来获取Vie w测量的宽高,

必须保证这两个方法在

onMeasure流程之后被调

用才能返回有效值。

工匠若

水http://blog.csdn.net/yanbob

er 转载烦请注明出处,尊重

分享成果】

3

View绘制流程第二

步:递归layout源码分

在上面的背景介绍就说过,

当ViewRootImpl的

performTraversals中measure

执行完成以后会接着执行

mVie w. la yout,具体如下:

private void performTraversal

s() {

.

.....

mView.layout(0, 0, mView.

getMeasuredWidth(), mView.get

MeasuredHeight());

.

.....

}

可以看见layout方法接收四

个参数,这四个参数分别代

表相对Parent的左、上、

右、下坐标。而且还可以看

见左上都为0,右下分别为

上面刚刚测量的

width和height。至此又回归

到Vie w的layout(int l, int t, int

r, int b)方法中去实现具体逻

辑了,所以接下来我们开始

分析Vie w的layout过程。

整个Vie w树的layout递归流

程图如下:

3

-1 layout源码分析

layout既然也是递归结构,

那我们先看下Vie wGroup的

layout方法,如下:

@

Override

public final void layout(

int l, int t, int r, int b) {

.....

super.layout(l, t, r,

.

b);

.

.....

}

看着没有?Vie wGroup的

layout方法实质还是调运了

Vie w父类的layout方法,所

以我们看下Vie w的layout源

码,如下:

public void layout(int l, int

t, int r, int b) {

.

/

.....

/实质都是调用setFrame

方法把参数分别赋值给mLeft、mTop、

mRight和mBottom这几个变量

/

/判断View的位置是否发生

过变化,以确定有没有必要对当前的Vie

w进行重新layout

boolean changed = isL

ayoutModeOptical(mParent) ?

setOpticalFra

me(l, t, r, b) : setFrame(l,

t, r, b);

/

/需要重新layout

if (changed || (mPriv

ateFlags & PFLAG_LAYOUT_REQUI

RED) == PFLAG_LAYOUT_REQUIRED

)

{

/

/回调onLayout

onLayout(changed,

l, t, r, b);

.

.....

}

.

.....

}

看见没有,类似measure过

程,lauout调运了onLayout方

法。

对比上面Vie w的layout和

Vie wGroup的layout方法可以

发现,Vie w的layout方法是

可以在子类重写的,而

Vie wGroup的layout是不能在

子类重写的,言外之意就是

说Vie wGroup中只能通过重

写onLayout方法。那我们接

下来看下Vie wGroup的

onLayout方法,如下:

@

Override

protected abstract void o

nLayout(boolean changed,

int l, int t, int

r, int b);

看见没有?Vie wGroup的

onLayout()方法竟然是一个

抽象方法,这就是说所有

Vie wGroup的子类都必须重

写这个方法。所以在自定义

Vie wGroup控件

中,onLayout配合onMeasure

方法一起使用可以实现自定

义Vie w的复杂布局。自定义

Vie w首先调用onMeasure进

行测量,然后调用onLayout

方法动

态获取子Vie w和子Vie w的测

量大小,然后进行layout布

局。重载onLayout的目的就

是安排其children在父Vie w的

具体位置,重载onLayout通

常做法

就是写一个for循环调用每一

个子视图的layout(l, t, r, b)函

数,传入不同的参数l, t, r, b

来确定每个子视图在父视图

中的显示位置。

再看下Vie w的onLayout方法

源码,如下:

protected void onLayout(boole

an changed, int left, int top

,

int right, int bottom) {

}

我勒个去!是一个空方法,

没啥可看的。

既然这样那我们只能分析一

个现有的继承Vie wGroup的

控件了,就拿LinearLayout来

说吧,如下是LinearLayout中

onLayout的一些代码:

public class LinearLayout ext

ends ViewGroup {

@

Override

protected void onLayout(b

oolean changed, int l, int t,

int r, int b) {

if (mOrientation == V

ERTICAL) {

layoutVertical(l,

t, r, b);

}

else {

layoutHorizontal(

l, t, r, b);

}

}

}

看见没有,LinearLayout的

layout过程是分Vertical和

Horizontal的,这个就是xml

布局的orientation属性设置

的,我们为例说明

Vie wGroup的onLayout

重写一般步骤就拿这里的

V ERTICAL模式来解释吧,

如下是layoutVertical方法源

码:

void layoutVertical(int left,

int top, int right, int bott

om) {

final int paddingLeft

=

mPaddingLeft;

int childTop;

int childLeft;

/

/ Where right end of

child should go

/

/计算父窗口推荐的子View

宽度

final int width = rig

ht - left;

/计算父窗口推荐的子View

/

右侧位置

int childRight = widt

h - mPaddingRight;

/

/

/ Space available fo

/child可使用空间大小

r child

int childSpace = widt

h - paddingLeft - mPaddingRig

ht;

/

/通过ViewGroup的getCh

ildCount方法获取ViewGroup的子Vi

ew个数

final int count = get

VirtualChildCount();

/

/获取Gravity属性设置

final int majorGravit

y = mGravity & Gravity.VERTIC

AL_GRAVITY_MASK;

final int minorGravit

y = mGravity & Gravity.RELATI

VE_HORIZONTAL_GRAVITY_MASK;

/

/依据majorGravity计算

childTop的位置值

switch (majorGravity)

{

case Gravity.BOTTO

/ mTotalLengt

M:

/

h contains the padding alread

y

childTop = mPa

ddingTop + bottom - top - mTo

talLength;

break;

/

/ mTotalLengt

h contains the padding alread

y

case Gravity.CENTE

R_VERTICAL:

childTop = mPa

ddingTop + (bottom - top - mT

otalLength) / 2;

break;

case Gravity.TOP:

default:

childTop = mPa

ddingTop;

break;

}

/

/重点!!!开始遍历

for (int i = 0; i < c

ount; i++) {

final View child

getVirtualChildAt(i);

=

)

if (child == null

{

childTop += m

easureNullChild(i);

}

else if (child.

getVisibility() != GONE) {

/

/LinearLayou

t中其子视图显示的宽和高由measure过

程来决定的,因此measure过程的意义

就是为layout过程提供视图显示范围的

参考值

final int chi

ldWidth = child.getMeasuredWi

dth();

final int chi

ldHeight = child.getMeasuredH

eight();

/

/获取子View的

LayoutParams

final LinearL

ayout.LayoutParams lp =

(Line

arLayout.LayoutParams) child.

getLayoutParams();

int gravity =

lp.gravity;

if (gravity <

0

) {

gravity =

minorGravity;

}

final int lay

outDirection = getLayoutDirec

tion();

final int abs

oluteGravity = Gravity.getAbs

oluteGravity(gravity, layoutD

irection);

/

/依据不同的abs

oluteGravity计算childLeft位置

switch (absol

uteGravity & Gravity.HORIZONT

AL_GRAVITY_MASK) {

case Grav

ity.CENTER_HORIZONTAL:

child

Left = paddingLeft + ((childS

pace - childWidth) / 2)

+

lp.leftMargin - lp.right

Margin;

break

;

case Grav

child

ity.RIGHT:

Left = childRight - childWidt

h - lp.rightMargin;

break

;

case Grav

ity.LEFT:

default:

child

Left = paddingLeft + lp.leftM

argin;

break

;

}

if (hasDivide

rBeforeChildAt(i)) {

childTop

+

= mDividerHeight;

}

childTop += l

/通过垂直排列

p.topMargin;

/

计算调运child的layout设置child的

位置

setChildFrame

(child, childLeft, childTop +

getLocationOffset(child),

child

Width, childHeight);

childTop += c

hildHeight + lp.bottomMargin

+

)

getNextLocationOffset(child

;

i += getChild

renSkipCount(child, i);

}

}

}

从上面分析的Vie wGroup子

类LinearLayout的onLayout实

现代码可以看出,一般情况

下layout过程会参考measure

过程中计算得到的

mMeasuredWidth

和mMeasuredHeight来安排子

Vie w在父Vie w中显示的位

置,但这不是必须的,

measure过程得到的结果可能

完全没有实际用处,特别是

对于一些自定

义的Vie wGroup,其子Vie w

的个数、位置和大小都是固

定的,这时候我们可以忽略

整个measure过程,只在

layout函数中传入的4个参数

来安排每个子

Vie w的具体位置。到这里就

不得不提ge tWidth()、

getHeight()和

getMeasuredWidth()、

getMeasuredHeight()这两对

方法之间的区别(上面分析

measure过程已经说过

getMeasuredWidth()、

getMeasuredHeight()必须在

onMeasure之后使用才有

效)。可以看出来ge tWidth( )

与getHeight()方法必须在

layout(int l, int t, int r, int b)执

行之后才有效。那我们看下

Vie w源码中这些方法的实现

吧,如下:

public final int getMeasuredW

idth() {

return mMeasuredWidth

&

MEASURED_SIZE_MASK;

}

public final int getMeasu

redHeight() {

return mMeasuredHeigh

t & MEASURED_SIZE_MASK;

}

public final int getWidth

() {

return mRight - mLeft

;

}

public final int getHeigh

t() {

return mBottom - mTop

;

}

public final int getLeft(

)

{

return mLeft;

}

public final int getRight

() {

return mRight;

}

public final int getTop()

{

return mTop;

}

public final int getBotto

m() {

return mBottom;

}

这也解释了为什么有些情况

下ge tWidth()和

getMeasuredWidth()以及

getHeight()和

getMeasuredHeight()会得到

不同的值,所以这里不做过

多解释。

到此整个Vie w的layout过程

分析就算结束了,接下来进

行一些总结工作。

3

-2 layout原理总结

整个layout过程比较容易理

解,从上面分析可以看出

layout也是从顶层父Vie w向

子Vie w的递归调用

vie w. la yout方法的过程,即

父Vie w根据上一步measure

子Vie w所得到的布局大小和

布局参数,将子Vie w放在合

适的位置上。具体layout核

心主要有以下几点:

Vie w. la yout方法可被重

载,ViewGroup. layout为

fina l的不可重载,

ViewGroup. onLayout为

abstract的,子类必须重载

实现自己的位置逻辑。

measure操作完成后得到

的是对每个Vie w经测量过

的measuredWidth和

measuredHeight,layout操

作完成之后得到的是对每

个Vie w进行位置分配后的

mLeft、mTop、mRight、

mBottom,这些值都是相

对于父Vie w来说的。

凡是layout_XXX的布局属

性基本都针对的是包含子

Vie w的Vie wGroup的,当

对一个没有父容器的Vie w

设置相关layout_XXX属性

是没有任何意义的(前面

Android应用

setContentView与

LayoutInflater加载解析机

制源码分析》也有提到

过)。

使用Vie w的ge tWidth()和

getHeight()方法来获取

Vie w测量的宽高,必须保

证这两个方法在onLayout

流程之后被调用才能返回

有效值。

【工匠若

水http://blog.csdn.net/yanbob

er 转载烦请注明出处,尊重

分享成果】

4

View绘制流程第三

步:递归draw源码分

在上面的背景介绍就说过,

当ViewRootImpl的

performTraversals中measure

和layout执行完成以后会接

着执行mVie w. la yout,具体

如下:

private void performTraversal

s() {

.

.....

final Rect dirty = mDirty

;

.

.....

canvas = mSurface.lockCan

vas(dirty);

.....

mView.draw(canvas);

.....

.

.

}

draw过程也是在

ViewRootImpl的

performTraversals()内部调运

的,其调用顺序在measure()

和layout()之后,这里的

mVie w对于Actiity来说就是

PhoneWindow.DecorView,

ViewRootImpl中的代码会创

建一个Canvas对象,然后调

用Vie w的draw()方法来执行

具体的绘制工。所以又回归

到了

Vie wGroup与Vie w的树状递

归draw过程。

先来看下Vie w树的递归draw

流程图,如下:

如下我们详细分析这一过

程。

4

-1 draw源码分析

由于Vie wGroup没有重写

Vie w的draw方法,所以如下

直接从Vie w的draw方法开始

分析:

public void draw(Canvas canv

as) {

.

/

.....

*

*

Draw traversal per

forms several drawing steps w

hich must be executed

*

in the appropriate

order:

*

*

1. Draw the b

2. If necessa

ackground

*

ry, save the canvas' layers t

o prepare for fading

*

*

*

3. Draw view'

4. Draw child

5. If necessa

s content

ren

ry, draw the fading edges and

restore layers

*

6. Draw decor

ations (scrollbars for instan

ce)

*

/

/

/ Step 1, draw the b

ackground, if needed

.....

.

if (!dirtyOpaque) {

drawBackground(ca

nvas);

}

/

/ skip step 2 & 5 if

possible (common case)

.

/

.....

/ Step 2, save the c

anvas' layers

.....

if (drawTop) {

canvas.saveLa

.

yer(left, top, right, top + l

ength, null, flags);

}

.

.....

/

/ Step 3, draw the c

ontent

if (!dirtyOpaque) onD

raw(canvas);

/

/ Step 4, draw the c

hildren

dispatchDraw(canvas);

/

/ Step 5, draw the f

ade effect and restore layers

.....

if (drawTop) {

matrix.setScale(1

fadeHeight * topFadeStrengt

h);

.

,

matrix.postTransl

ate(left, top);

fade.setLocalMatr

ix(matrix);

p.setShader(fade)

canvas.drawRect(l

;

eft, top, right, top + length

,

p);

}

.

.....

/

/ Step 6, draw decor

ations (scrollbars)

onDrawScrollBars(canv

as);

.

.....

}

看见整个Vie w的draw方法很

复杂,但是源码注释也很明

显。从注释可以看出整个

draw过程分为了6步。源码

注释说

(”skip step 2 & 5 if possible

(common case)”)第2和5步

可以跳过,所以我们接下来

重点剩余四步。如下:

第一步,对Vie w的背景进行

绘制。

可以看见,draw方法通过调

运drawBackground(canvas);

方法实现了背景绘制。我们

来看下这个方法源码,如

下:

private void drawBackground(C

anvas canvas) {

/

/获取xml中通过android:

background属性或者代码中setBack

groundColor()、setBackgroundR

esource()等方法进行赋值的背景Draw

able

final Drawable backgr

ound = mBackground;

.

/

.....

/根据layout过程确定的V

iew位置来设置背景的绘制区域

if (mBackgroundSizeCh

anged) {

background.setBou

nds(0, 0, mRight - mLeft, mB

ottom - mTop);

mBackgroundSizeCh

anged = false;

rebuildOutline();

}

.

.....

/调用Drawable的dr

aw()方法来完成背景的绘制工作

background.draw(c

/

anvas);

.

.....

}

第三步,对Vie w的内容进行

绘制。

可以看到,这里去调用了一

下Vie w的onDraw()方法,所

以我们看下Vie w的onDraw方

法(Vie wGroup也没有重写

该方法),如下:

/

**

*

Implement this to do y

our drawing.

*

*

@param canvas the canv

as on which the background wi

ll be drawn

*

/

protected void onDraw(Can

vas canvas) {

}

可以看见,这是一个空方

法。因为每个Vie w的内容部

分是各不相同的,所以需要

由子类去实现具体逻辑。

第四步,对当前Vie w的所有

子Vie w进行绘制,如果当前

的Vie w没有子Vie w就不需要

进行绘制。

我们来看下Vie w的draw方法

中的dispatchDraw(canvas);方

法源码,可以看见如下:

/

**

*

Called by draw to draw

the child views. This may be

overridden

*

by derived classes to

gain control just before its

children are drawn

*

(but after its own vie

w has been drawn).

*

@param canvas the canv

as on which to draw the view

*

/

protected void dispatchDr

aw(Canvas canvas) {

}

看见没有,Vie w的

dispatchDraw()方法是一个空

方法,而且注释说明了如果

Vie w包含子类需要重写他,

所以我们有必要看下

Vie wGroup的dispatchDraw

方法源码(这也就是刚刚说

的对当前Vie w的所有子Vie w

进行绘制,如果当前的Vie w

没有子Vie w就不需要进行绘

制的原因,因为如果是Vie w

调运该方法

是空的,而Vie wGroup才有

实现),如下:

@

Override

protected void dispatchDr

aw(Canvas canvas) {

.....

final int childrenCou

nt = mChildrenCount;

final View[] children

.

=

mChildren;

.....

for (int i = 0; i < c

hildrenCount; i++) {

.....

.

.

if ((child.mViewF

lags & VISIBILITY_MASK) == VI

SIBLE || child.getAnimation()

!

= null) {

more |= drawC

hild(canvas, child, drawingTi

me);

}

}

.

/

.....

/ Draw any disappear

ing views that have animation

s

if (mDisappearingChil

dren != null) {

.

.....

for (int i = disa

ppearingCount; i >= 0; i--) {

.....

.

more |= drawC

hild(canvas, child, drawingTi

me);

}

.....

}

.

}

可以看见,Vie wGroup确实

重写了Vie w的dispatchDraw()

方法,该方法内部会遍历每

个子Vie w,然后调用

drawChild()方法,我们可以

看下

Vie wGroup的drawChild方

法,如下:

protected boolean drawChild(C

anvas canvas, View child, lon

g drawingTime) {

return child.draw(can

vas, this, drawingTime);

}

可以看见drawChild()方法调

运了子Vie w的draw()方法。

所以说Vie wGroup类已经为

我们重写了dispatchDraw()的

功能实现,我们一般不需要

重写该方法

,但可以重载父类函数实现

具体的功能。

第六步,对Vie w的滚动条进

行绘制。

可以看到,这里去调用了一

下Vie w的onDrawScrollBars()

方法,所以我们看下Vie w的

onDrawScrollBars(canvas);方

法,如下:

/

**

*

<p>Request the drawing

of the horizontal and the ve

rtical scrollbar. The

*

scrollbars are painted

only if they have been awake

ned first.</p>

*

*

@param canvas the canv

as on which to draw the scrol

lbars

*

*

@see #awakenScrollBars

(int)

*

/

protected final void onDr

awScrollBars(Canvas canvas) {

/

/绘制ScrollBars分析不

是我们这篇的重点,所以暂时不做分析

.....

.

}

可以看见其实任何一个Vie w

都是有(水平垂直)滚动条

的,只是一般情况下没让它

显示而已。

到此,Vie w的draw绘制部分

源码分析完毕,我们接下来

进行一些总结。

4

-2 draw原理总结

可以看见,绘制过程就是把

Vie w对象绘制到屏幕上,整

个draw过程需要注意如下细

节:

如果该Vie w是一个

Vie wGroup,则需要递归

绘制其所包含的所有子

Vie w。

Vie w默认不会绘制任何内

容,真正的绘制都需要自

己在子类中实现。

Vie w的绘制是借助onDraw

方法传入的Canvas类来进

行的。

区分Vie w动画和

Vie wGroup布局动画,前

者指的是Vie w自身的动

画,可以通过setAnimation

添加,后者是专门针对

Vie wGroup显示内部子视

图时设置的动画,可以在

xml布局文件中对

Vie wGroup设置

layoutAnimation属性(譬

如对LinearLayout设置子

Vie w在显示时出现逐行、

随机、下等显示等不同动

画效果)。

在获取画布剪切区(每个

Vie w的draw中传入的

Canvas)时会自动处理掉

padding,子Vie w获取

Canvas不用关注这些逻

辑,只用关心如何绘制即

可。

默认情况下子Vie w的

ViewGroup. drawChild绘制

顺序和子Vie w被添加的顺

序一致,但是你也可以重

Vie wGroup. ge tChildDra win

gOrder()方法提供不同顺

序。

工匠若

水http://blog.csdn.net/yanbob

er 转载烦请注明出处,尊重

分享成果】

5

View的invalidate和

postInvalidate方法源码

分析

你可能已经看见了,在上面

分析Vie w的三步绘制流程中

最后都有调运一个叫

inva lida te的方法,这个方法

是啥玩意?为何出现频率这

么高?很简单,我们拿出来

分析分析不就得了。

5

-1 invalidate方法源码

分析

来看一下Vie w类中的一些

inva lida te方法(Vie wGroup没

有重写这些方法),如下:

/

**

*

Mark the area defined

by dirty as needing to be dra

wn. If the view is

*

visible, {@link #onDra

w(android.graphics.Canvas)} w

ill be called at some

*

*

*

point in the future.

<p>

This must be called fr

om a UI thread. To call from

a non-UI thread, call

*

{@link #postInvalidate

()}.

*

*

<p>

<b>WARNING:</b> In API

1

9 and below, this method ma

y be destructive to

*

*

*

{@code dirty}.

@param dirty the recta

ngle representing the bounds

of the dirty region

*

/

/

/看见上面注释没有?public,

只能在UI Thread中使用,别的Threa

d用postInvalidate方法,View是可

见的才有效,回调onDraw方法,针对局

部View

public void invalidate(Re

ct dirty) {

final int scrollX = m

ScrollX;

ScrollY;

final int scrollY = m

/

/实质还是调运invalidat

eInternal方法

invalidateInternal(di

rty.left - scrollX, dirty.top

scrollY,

-

dirty.right -

scrollX, dirty.bottom - scro

llY, true, false);

}

/

**

*

Mark the area defined

by the rect (l,t,r,b) as need

ing to be drawn. The

*

coordinates of the dir

ty rect are relative to the v

iew. If the view is

*

visible, {@link #onDra

w(android.graphics.Canvas)} w

ill be called at some

*

*

*

point in the future.

<p>

This must be called fr

om a UI thread. To call from

a non-UI thread, call

*

{@link #postInvalidate

()}.

*

*

@param l the left posi

tion of the dirty region

@param t the top posit

ion of the dirty region

*

*

@param r the right pos

ition of the dirty region

*

@param b the bottom po

sition of the dirty region

*

/

/

/看见上面注释没有?public,

只能在UI Thread中使用,别的Threa

d用postInvalidate方法,View是可

见的才有效,回调onDraw方法,针对局

部View

public void invalidate(in

t l, int t, int r, int b) {

final int scrollX = m

ScrollX;

final int scrollY = m

ScrollY;

/

/实质还是调运invalidat

eInternal方法

invalidateInternal(l

scrollX, t - scrollY, r - s

-

crollX, b - scrollY, true, fa

lse);

}

/

**

*

Invalidate the whole v

iew. If the view is visible,

{@link #onDraw(android

*

.

graphics.Canvas)} will be ca

lled at some point in

*

*

*

the future.

<p>

This must be called fr

om a UI thread. To call from

a non-UI thread, call

*

{@link #postInvalidate

()}.

*

/

/

/看见上面注释没有?public,

只能在UI Thread中使用,别的Threa

d用postInvalidate方法,View是可

见的才有效,回调onDraw方法,针对整

个View

public void invalidate()

{

/

/invalidate的实质还是

调运invalidateInternal方法

invalidate(true);

}

/

**

*

This is where the inva

lidate() work actually happen

s. A full invalidate()

*

causes the drawing cac

he to be invalidated, but thi

s function can be

*

called with invalidate

Cache set to false to skip th

at invalidation step

*

for cases that do not

need it (for example, a compo

nent that remains at

*

the same dimensions wi

th the same content).

*

*

@param invalidateCache

Whether the drawing cache fo

r this view should be

*

invalidated

as well. This is usually tru

e for a full

*

invalidate,

but may be set to false if t

he View's contents or

*

dimensions

have not changed.

*

/

/

/看见上面注释没有?default

的权限,只能在UI Thread中使用,别

的Thread用postInvalidate方法,V

iew是可见的才有效,回调onDraw方法

针对整个View

void invalidate(boolean i

nvalidateCache) {

/

/实质还是调运invalidateInt

ernal方法

invalidateInternal(0,

, mRight - mLeft, mBottom -

0

mTop, invalidateCache, true)

;

}

/

/!!!!!!看见没有,这是所

有invalidate的终极调运方法!!!

!!

void invalidateInternal(i

nt l, int t, int r, int b, bo

olean invalidateCache,

boolean fullInval

idate) {

.

.....

/ Propagate the

/

damage rectangle to the paren

t view.

final AttachInfo

ai = mAttachInfo;

final ViewParent

p = mParent;

if (p != null &&

ai != null && l < r && t < b)

{

final Rect da

mage = ai.mTmpInvalRect;

/

/设置刷新区域

damage.set(l,

t, r, b);

/

/传递调运Pare

nt ViewGroup的invalidateChild

方法

p.invalidateC

hild(this, damage);

}

.

.....

}

看见没有,Vie w的

inva lida te(invalidateInternal

)方法实质是将要刷新区域

直接传递给了父Vie wGroup

的inva lida te Child方法,在

inva lida te中,调

用父Vie w的inva lida te Child,

这是一个从当前向上级父

Vie w回溯的过程,每一层的

父Vie w都将自己的显示区域

与传入的刷新Rect做交集 。

所以我们看

下Vie wGroup的

inva lida te Child方法,源码如

下:

public final void invalidateC

hild(View child, final Rect d

irty) {

ViewParent parent = t

his;

final AttachInfo atta

chInfo = mAttachInfo;

.....

do {

.

.

/

.....

/循环层层上级调运,

直到ViewRootImpl会返回null

parent = parent.i

nvalidateChildInParent(locati

on, dirty);

.

.....

}

while (parent != nu

ll);

}

这个过程最后传递到

ViewRootImpl的

inva lida te ChildInP a re nt方法结

束,所以我们看下

ViewRootImpl的

inva lida te ChildInP a re nt方法,

如下:

@

Override

public ViewParent invalid

ateChildInParent(int[] locati

on, Rect dirty) {

.

/

.....

/View调运invalidate最

终层层上传到ViewRootImpl后最终触

发了该方法

scheduleTraversals();

.

.....

return null;

}

看见没有?这个

ViewRootImpl类的

inva lida te ChildInP a re nt方法直

接返回了null,也就是上面

Vie wGroup中说的,层层上

级传递到ViewRootImpl的

inva lida te ChildInP a re nt方法结

束了那个do while循环。看见

这里调运的

scheduleTraversals这个方法

吗?scheduleTraversals会通

过Handler的

Runnable发送一个异步消

息,调运doTraversal方法,

然后最终调用

performTraversals()执行重

绘。开头背景知识介绍说过

的,performTraversals就

是整个Vie w数开始绘制的起

始调运地方,所以说Vie w调

运inva lida te方法的实质是层

层上传到父级,直到传递到

ViewRootImpl后触发了

scheduleTraversals

方法,然后整个Vie w树开始

重新按照上面分析的Vie w绘

制流程进行重绘任务。

到此Vie w的inva lida te方法原

理就分析完成了。

5

-2 postInvalidate方法

源码分析

上面分析inva lida te方法时注

释中说该方法只能在UI

Thread中执行,其他线程中

需要使用postInva lida te方

法,所以我们来分析分析

postInva lida te这个方法源

码。如下:

public void postInvalidate()

{

postInvalidateDelayed

(0);

}

继续看下他的调运方法

postInvalidateDelayed,如

下:

public void postInvalidateDel

ayed(long delayMilliseconds)

{

/

/ We try only with t

he AttachInfo because there's

no point in invalidating

/

/ if we are not atta

ched to our window

final AttachInfo atta

chInfo = mAttachInfo;

/

/核心,实质就是调运了Vie

wRootImpl.dispatchInvalidateD

elayed方法

if (attachInfo != nul

l) {

attachInfo.mViewR

ootImpl.dispatchInvalidateDel

ayed(this, delayMilliseconds)

;

}

}

我们继续看他调运的

ViewRootImpl类的

dispatchInvalidateDelayed方

法,如下源码:

public void dispatchInvalidat

eDelayed(View view, long dela

yMilliseconds) {

Message msg = mHandle

r.obtainMessage(MSG_INVALIDAT

E, view);

mHandler.sendMessageD

elayed(msg, delayMilliseconds

)

;

}

看见没有,通过

ViewRootImpl类的Handler发

送了一条MSG_INVALIDATE

消息,继续追踪这条消息的

处理可以发现:

public void handleMessage(Mes

sage msg) {

.

.....

switch (msg.what) {

case MSG_INVALIDATE:

((View) msg.obj).inva

lidate();

break;

.....

.

}

.

.....

}

看见没有,实质就是又在UI

Thread中调运了Vie w的

invalidate();方法,那接下来

Vie w的invalidate();方法我们

就不说了,上名已经分析过

了。

到此整个Vie w的

postInva lida te方法就分析完

成了。

5

-3 invalidate与

postInvalidate方法总结

依据上面对Vie w的inva lida te

分析我总结绘制如下流程

图:

依据上面对Vie w的

postInva lida te分析我总结绘

制如下流程图:

关于这两个方法的具体流程

和原理上面也分析过了,流

程图也给出了,相信已经很

明确了,没啥需要解释的

了。所以我们对其做一个整

体总结,归纳出重点如下:

inva lida te系列方法请求重绘

Vie w树(也就是draw方

法),如果Vie w大小没有发

生变化就不会调用layout过

程,并且只绘制那些“需要

重绘的”View,也就是哪个

Vie w(Vie w只绘制该Vie w,

Vie wGroup绘制整个

ViewGroup)请求inva lida te系

列方法,就绘制该Vie w。

常见的引起inva lida te方法操

作的原因主要有:

直接调用inva lida te方法.请

求重新draw,但只会绘制

调用者本身。

触发setSelection方法。请

求重新draw,但只会绘制

调用者本身。

触发se tVisibility方法。 当

Vie w可视状态在

INVISIBLE转换VISIBLE

时会间接调用inva lida te方

法,继而绘制该Vie w。当

Vie w的可视状态在

INVISIBLE\VISIBLE 转换

为GONE状态时会间接调

用requestLayout和

inva lida te方法,同时由于

Vie w树大小发生了变化,

所以会请求measure过程

以及draw过程,同样只绘

制需要“重新绘制”的视

图。

触发setEnabled方法。请

求重新draw,但不会重新

绘制任何Vie w包括该调用

者本身。

触发requestFocus方法。请

求Vie w树的draw过程,只

绘制“需要重绘”的Vie w。

5

-4 通过invalidate方法

分析结果回过头去解

决一个背景介绍中的

疑惑

分析完inva lida te后需要你回

过头去想一个问题。还记不

记得这篇文章的开头背景介

绍,我们说整个Vie w绘制流

程的最初代码是在

ViewRootImpl类的

performTraversals()方法中开

始的。上面当时只是告诉你

了这个结论,至于这个

ViewRootImpl类的

performTraversals()方法为何

会被触发没有说明原因。现

在我们就来分析一下这个触

发的源头。

让我们先把大脑思考暂时挪

回到《Android应用

setContentView与

LayoutInflater加载解析机制

源码分析》这篇博文的

setContentView机制分析中

(不清楚的请点击先看这篇

文章再回过头来继续看)。

我们先来看下那篇博文分析

的PhoneWindow的

setContentView方法源码,如

下:

@

Override

public void setContentVie

w(View view, ViewGroup.Layout

Params params) {

.

/

.....

/如果mContentParent为

空进行一些初始化,实质mContentPar

ent是通过findViewById(ID_ANDRO

ID_CONTENT);获取的id为content的

FrameLayout的布局(不清楚的请先看

Android应用setContentView与L

ayoutInflater加载解析机制源码分析

文章)

if (mContentParent ==

null) {

installDecor();

}

.

/

.....

/把我们的view追加到mCo

ntentParent

mContentParent.addVie

w(view, params);

.....

.

}

这个方法是Ac tivity中

setContentView的实现,我们

继续看下这个方法里调运的

addView方法,也就是

Vie wGroup的addView方法,

如下:

public void addView(View chil

d) {

addView(child, -1);

}

public void addView(View

child, int index) {

.

.....

addView(child, index,

params);

}

public void addView(View

child, int index, LayoutParam

s params) {

.

/

.....

/该方法稍后后面会详细分

requestLayout();

/重点关注!!!

invalidate(true);

.....

/

.

}

看见addView调运inva lida te方

法没有?这不就真相大白

了。当我们写一个Ac tivity

时,我们一定会通过

setContentView方法将我们要

展示的界面传入

该方法,该方法会讲我们界

面通过addView追加到id为

content的一个

FrameLayout(Vie wGroup)

中,然后addView方法中通

过调运invalidate(true)

去通知触发ViewRootImpl类

的performTraversals()方法,

至此递归绘制我们自定义的

所有布局。

【工匠若

水http://blog.csdn.net/yanbob

er 转载烦请注明出处,尊重

分享成果】

6

View的requestLayout

方法源码分析

6

-1 requestLayout方法

分析

和inva lida te类似,其实在上

面分析Vie w绘制流程时或多

或少都调运到了这个方法,

而且这个方法对于Vie w来说

也比较重要,所以我们接下

来分析一下他。如下Vie w的

requestLayout源码:

public void requestLayout() {

.

.....

if (mParent != null &

&

!mParent.isLayoutRequested(

)

) {

/

/

/由此向ViewParent

/从这个View开始向

请求布局

上一直requestLayout,最终到达Vie

wRootImpl的requestLayout

mParent.requestLa

yout();

}

.

.....

}

看见没有,当我们触发Vie w

的requestLayout时其实质就

是层层向上传递,直到

ViewRootImpl为止,然后触

发ViewRootImpl的

requestLayout方法,

如下就是ViewRootImpl的

requestLayout方法:

@

Override

public void requestLayout

() {

if (!mHandlingLayoutI

nLayoutRequest) {

checkThread();

mLayoutRequested

=

true;

/

/View调运request

Layout最终层层上传到ViewRootImp

l后最终触发了该方法

scheduleTraversal

s();

}

}

看见没有,类似于上面分析

的inva lida te过程,只是设置

的标记不同,导致对于Vie w

的绘制流程中触发的方法不

同而已。

6

-2 requestLayout方法

总结

可以看见,这些方法都是大

同小异。对于requestLayout

方法来说总结如下:

requestLayout()方法会调用

measure过程和layout过程,

不会调用draw过程,也不会

重新绘制任何Vie w包括该调

用者本身。

7

View绘制流程总结

至此整个关于Android应用程

序开发中的Vie w绘制机制及

相关重要方法都已经分析完

毕。关于各个方法的总结这

里不再重复,直接通过该文

章前面的目录索引到相应方

法的总结小节进行查阅即

可。

工匠若

水http://blog.csdn.net/yanbob

er 转载烦请注明出处,尊重

分享成果】

「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论