Android 42 由Context引发的思考.docx

上传人:b****5 文档编号:11634311 上传时间:2023-03-29 格式:DOCX 页数:19 大小:133.75KB
下载 相关 举报
Android 42 由Context引发的思考.docx_第1页
第1页 / 共19页
Android 42 由Context引发的思考.docx_第2页
第2页 / 共19页
Android 42 由Context引发的思考.docx_第3页
第3页 / 共19页
Android 42 由Context引发的思考.docx_第4页
第4页 / 共19页
Android 42 由Context引发的思考.docx_第5页
第5页 / 共19页
点击查看更多>>
下载资源
资源描述

Android 42 由Context引发的思考.docx

《Android 42 由Context引发的思考.docx》由会员分享,可在线阅读,更多相关《Android 42 由Context引发的思考.docx(19页珍藏版)》请在冰豆网上搜索。

Android 42 由Context引发的思考.docx

Android42由Context引发的思考

Android4.2由Context引发的思考

目录(?

)[-]

1问题描述

2复现步骤

3期望结果

4实际结果

5问题分析

6Dialog显示流程图

7重要方法

71addWindowToListInOrderLocked

72updateFocusedWindowLocked

8解决方案

9总结

10知识点

11注

最近在做类似于三星S4的那种皮套(后面简称SmartCover),具有可操作的窗口,一方面用户可以保护手机屏幕,另一方面用户可以直接在SmartCover上接听电话,非常方便。

在开发过程中发现一个问题,虽然最终解决但还是记录一下,好记性不如烂笔头啊。

转载请务必注明出处:

问题描述:

SmartCover滑动控件失效

复现步骤:

1.当前正在通话,长按Power键弹出关机对话框

2.关闭SmartCover

3.滑动挂断按钮,挂断当前通话

期望结果:

正常滑动挂断按钮并能挂断当前通话

实际结果:

无法滑动挂断按钮

问题分析:

在关机对话框(GlobalActions)弹出之后,SmartCover就无法接收点击事件了,同时打开SmartCover后会发现之前的点击事件传到了关机对话框上。

SmartCover的显示是基于Dialog的,是一个自定Dialog。

虽然能够正常显示,却没法获取点击事件,那这就得从Android的事件派发来分析问题了!

我们知道在Android中事件的派发可以分为两类:

按键事件和触摸事件

无论是哪种事件,这些消息首先都会被硬件设备所检测到,比如我们点击Home键,对硬件来说只会检测到Down/Up,这些硬件消息会被转换成统一的系统消息,并向上逐层传递。

接下来,Android的窗口管理系统(WmS)会根据窗口的状态,判断用户正在与哪个窗口进行交互,然后把消息派发给该窗口。

在Android系统中,所有的窗口都是有WmS创建的,因此WmS可以查出所有窗口的状态,包括窗口大小、位置、是否具有焦点等等。

当底层消息传递上来时,如果是按键消息,则直接派发给当前窗口;如果是触摸消息,则WmS会根据触摸消息判断用户触摸区域属于哪个窗口,并将消息传递给该窗口。

最后这些消息如何处理就是窗口自己的事儿了,也就是View的处理逻辑了,dispatchEvent,onTouchEvent等等。

当问题发生时,我们可以点击屏幕,但消息没有传递给SmartCover,反而传递给了关机对话框,因此问题的大致原因可能是WmS在派发消息时出现了问题。

既然问题可能出现在WmS中,自然需要查看WmS的Log,因为WmS的Log属于systemlog,而Android默认打印的log也就是adblogcat直接输出的log是mainlog,所以需要加-b指定buffer,同时我们需要过滤WindowManager这个TAG(WmS的LogTAG是WindowManager),所以在终端中输入:

adblogcat-bsystem-s"WindowManager"

然后按照复现步骤操作一遍并查看log。

当在通话界面长按Power键之后会出现以下Log:

[plain]viewplaincopy

12V/WindowManager(536):

ChangingfocusfromWindow{424ce3f0u0com.android.phone

13/com.android.phone.InCallScreen}toWindow{4232b120u0GlobalActions}

也就是说这时候的焦点从InCallScreen变到了GlobalActions上,GlobalActions就是关机对话框,这里没有问题。

但当我们关闭SmartCover时,WmS没有焦点变化的Log输出。

对比以没有关机对话框的Log:

[plain]viewplaincopy

14V/WindowManager(536):

ChangingfocusfromWindow{424ce3f0u0com.android.phone

15/com.android.phone.InCallScreen}toWindow{4232b120u0com.android.phone/com.and

16roid.phone.InCallScreen}

我们可以看到如果没有关机对话框,在通话界面关闭SmartCover时会有焦点改变,只是焦点还在InCallScreen上,毕竟SmartCover弹出的Dialog是基于InCallScreen的(Context是InCallScreen),因此焦点还是属于InCallScreen这个Activity。

仔细想一下,这个逻辑也是对的,比如我们的一个Activity要弹出一个Dialog,但当我们Pause它时,它虽然会照常弹出Dialog但此时的Dialog是不具有焦点的,也不能接收任何点击事件。

为什么SmartCover的Dialog能够正常显示呢?

那是因为SmartCover的WindowManager.LayoutParams对象type属性被我们自定义了,定义了一个比关机对话框还高的等级,因此SmartCover能够显示却不能获取焦点,从而无法接收WmS派发的点击事件。

也就是说SmartCover并没有添加到最顶层,系统认为当前最顶层的Window任然是GlobalActions(关机对话框)。

将WmS中Debug值DEBUG_FOCUS修改为true,查看焦点切换的Log信息,如下:

先接通电话,后弹出关机对话框

[plain]viewplaincopy

17V/WindowManager(536):

AddingwindowWindow{4211a128u0GlobalActions}at3of

184

19V/WindowManager(536):

Lookingforfocus:

4=Window{4242da68u0StatusBar},fl

20ags=25165896,canReceive=false

21V/WindowManager(536):

Lookingforfocus:

3=Window{4211a128u0GlobalActions}

22,flags=8519682,canReceive=true

我们看到先Add了一个Window(即GlobalActions关机对话框),然后寻找到焦点在GlobalActions上(canReceive=true),此时我们关闭SmartCover再次查看Log输出如下:

[java]viewplaincopy

23V/WindowManager(536):

AddingwindowWindow{42b22850u0com.android.phone/com.a

24ndroid.phone.InCallScreen}at3of5

25V/WindowManager(536):

Lookingforfocus:

5=Window{4242da68u0StatusBar},fl

26ags=25165896,canReceive=false

27V/WindowManager(536):

Lookingforfocus:

4=Window{423e1f78u0GlobalActions}

28,flags=8519682,canReceive=true

29V/WindowManager(536):

Foundfocus@4=Window{423e1f78u0GlobalActions}

可以看到关闭SmartCover后的确也弹出了显示界面,即这里的Addwindow(InCallScreen),只是在Lookingforfocus的时候还是找到的GlobalActions(关机对话框),即这里的4,而没有继续找到我们想要的3。

如果不弹出关机对话框则显示的Log如下:

[java]viewplaincopy

30V/WindowManager(536):

AddingwindowWindow{41f756d0u0com.android.phone/com.a

31ndroid.phone.InCallScreen}at3of4

32V/WindowManager(536):

Lookingforfocus:

4=Window{4242da68u0StatusBar},fl

33ags=25165896,canReceive=false

34V/WindowManager(536):

Lookingforfocus:

3=Window{41f756d0u0com.android.ph

35one/com.android.phone.InCallScreen},flags=23592960,canReceive=true

36V/WindowManager(536):

Foundfocus@3=Window{41f756d0u0com.android.phone/c

37om.android.phone.InCallScreen}

38V/WindowManager(536):

ChangingfocusfromWindow{424ce3f0u0com.android.phone

39/com.android.phone.InCallScreen}toWindow{41f756d0u0com.android.phone/com.and

40roid.phone.InCallScreen}

从这里我们可以看到,如果没有关机GlobalActions(关机对话框)则焦点的获取是正常的,关机对话实际上也是一个Dialog,那么为什么它能获取到焦点呢?

Dialog显示流程图:

这里大致画一下Dialog显示流程,如图1:

图1

最终可以看到Dialog是在WmS中进行的显示添加,看一下其中几个重要的方法。

重要方法:

(1).addWindowToListInOrderLocked

该方法会根据appWindowToken的值不同,从而添加到不同的layer。

[java]viewplaincopy

41privatevoidaddWindowToListInOrderLocked(WindowStatewin,booleanaddToToken){

42finalIWindowclient=win.mClient;

43finalWindowTokentoken=win.mToken;

44finalDisplayContentdisplayContent=win.mDisplayContent;

45

46finalWindowListwindows=win.getWindowList();

47finalintN=windows.size();

48finalWindowStateattached=win.mAttachedWindow;

49inti;

50WindowListtokenWindowList=getTokenWindowsOnDisplay(token,displayContent);

51if(attached==null){

52inttokenWindowsPos=0;

53intwindowListPos=tokenWindowList.size();

54if(token.appWindowToken!

=null){//如果appWindowToken不为null

55intindex=windowListPos-1;

56if(index>=0){

57//......省略

58}else{

59//......省略

60}else{

61intnewIdx=findIdxBasedOnAppTokens(win);

62//thereisawindowabovethisoneassociatedwiththesame

63//apptokennotethatthewindowcouldbeafloatingwindow

64//thatwascreatedlaterorawindowatthetopofthelistof

65//windowsassociatedwiththistoken.

66if(DEBUG_FOCUS||DEBUG_WINDOW_MOVEMENT||DEBUG_ADD_REMOVE){

67Slog.v(TAG,"Addingwindow"+win+"at"

68+(newIdx+1)+"of"+N);

69}

70windows.add(newIdx+1,win);

71if(newIdx<0){

72//Nowindowfromtokenfoundonwin'sdisplay.

73tokenWindowsPos=0;

74}else{

75tokenWindowsPos=indexOfWinInWindowList(

76windows.get(newIdx),token.windows)+1;

77}

78mWindowsChanged=true;

79}

80}

81//......省略

82}else{

微软雅黑">//如果appWindowToken为null

83//Figureoutwherewindowshouldgo,basedonlayer.

84///M:

Ignorethewallpaperwindowwhenaddingwindow@{

85finalintmyLayer=win.mBaseLayer;

86WindowStatelocalWindow;

87for(i=N-1;i>=0;i--){

88localWindow=windows.get(i);

89if(localWindow.mBaseLayer<=myLayer

90&&localWindow.mAttrs.type!

=TYPE_WALLPAPER){

91///@}

92break;

93}

94}

95i++;

96if(DEBUG_FOCUS||DEBUG_WINDOW_MOVEMENT||DEBUG_ADD_REMOVE)Slog.v(

97TAG,"Addingwindow"+win+"at"

98+i+"of"+N);

99windows.add(i,win);

100mWindowsChanged=true;

101}

102

103//......省略

104}

105}

在该方法中,会根据appWindowToken值是否为null采用不同的添加window策略,如果appWindowToken!

=null则最终会执行:

[java]viewplaincopy

106intnewIdx=findIdxBasedOnAppTokens(win);

107if(DEBUG_FOCUS||DEBUG_WINDOW_MOVEMENT||DEBUG_ADD_REMOVE){

108Slog.v(TAG,"Addingwindow"+win+"at"

109+(newIdx+1)+"of"+N);

110}

111windows.add(newIdx+1,win);

文章前面的Log信息的确也是这样输出的“xxxat3of4”“xxxat3of5”,继续查看这里的findIdxBaseOnAppTokens方法:

[java]viewplaincopy

112privateintfindIdxBasedOnAppTokens(WindowStatewin){

113WindowListwindows=win.getWindowList();

114for(intj=windows.size()-1;j>=0;j--){

115WindowStatewentry=windows.get(j);

116if(wentry.mAppToken==win.mAppToken){

117returnj;

118}

119}

120return-1;

121}

该方法中会去获取所有的WindowList,然后用一个for循环找到每一个Window的mAppToken,与当前需要添加的窗口的mAppToken进行比较。

这简单的说明一下,Android添加Window的顺序有点类似于做奶油蛋糕,一层一层的添加,最顶层永远是当前具有焦点的窗口。

通过该for循环,可以找到当前系统中是否具有和需要添加的Window相同mAppToken的窗口,如果有则返回对应的层级给它。

有点绕,可以简单的这么说,当前系统的窗口我们可以用一个5层的奶油蛋糕来表示,顺序从底向上依次是:

红->白->绿->黄->蓝,现在我想给这个蛋糕加一个绿色的巧克力上去(这里颜色相当于系统中的mAppToken),我们看到(for循环查找)当前已经有一层绿色了,那么我们就把这个绿色的巧克力放到(Addwindow)绿色这一层。

如果appWindowToken==null呢,那执行以下代码:

[java]viewplaincopy

122finalintmyLayer=win.mBaseLayer;

123WindowStatelocalWindow;

124for(i=N-1;i>=0;i--){

125localWindow=windows.get(i);

126if(localWindow.mBaseLayer<=myLayer

127&&localWindow.mAttrs.type!

=TYPE_WALLPAPER){

128///@}

129break;

130}

131}

132i++;

133if(DEBUG_FOCUS||DEBUG_WINDOW_MOVEMENT||DEBUG_ADD_REMOVE)Slog.v(

134TAG,"Addingwindow"+win+"at"

135+i+"of"+N);

136windows.add(i,win);

这里的N实际为windows.size(),而windows就是WindowList对象,相当于还是得出当前的Window数量,然后判断最顶层Window的下一层Window是否是BaseLayer以及属性是否是TYPE_WALLPAPER,如果不是则break掉。

然后将需要增加的Window(需要显示的View)添加到最顶层。

还是用前面奶油蛋糕的例子吧,比如现在依旧是5层,从底向上依次是:

红->白->绿->黄->蓝,此时我们需要添加一个黑色的巧克力(这里颜色相当于系统中的mAppToken)在这个蛋糕上,那么我们先判断已有的每一层蛋糕,是否是蛋糕的托盘并具有图案(这里判断我们是否是想给蛋糕加上一个托盘,如果是托盘我们需要放在蛋糕的最下面),首先判断黄色这层然后依次向下。

如果都不满足则把我们需要添加的黑色巧克力放在最上面,即蓝色那层。

通过以上分析我们可以很清楚的知道,如果appWindowToken的值不同,则会将我们的Dialog添加到不同的Layer上,如果该Layer此时已经没有Focus了,那么我们先添加的Dialog也不会具有Focus,系统也不会执行Focuschange。

那么在什么情况下appWindowToken==null,什么情况下appWindowToken!

=null。

到这里终于可以请出本文的主角Context了......o(╯□╰)o......

Context在开发中经常被用到,Context一般理解为上下文,也可以理解为场景,比如当收到一条短信时,Context就包括当前的界面以及后台的数据。

Context是一个abstract类,Activity以及Service都是它的子类,为了便于使用又定义了一个包装类ContextWrapper,而真正实现了Context的是ContextImpl类,应用程序中调用的各种Context方法其实都来自它。

我们知道获取Context的方法有Activity.this、Service.this、getApplicationContext方法。

那么它们有什么不同呢?

Activity启动时会调用到ActivityThread中的performLaunchActivity方法,并最终调用createBaseContextForActivity方法创建Context对象:

[java]viewplaincopy

137ContextImplappContext=newContextImpl();

138appContext.init(r.packageInfo,r.token,this);

139appContext.setOuterContext(activity);

Service启动时会调用到ActivityThread中的handleCreateService方法,并最终创建Context对象:

[java]viewplaincopy

140ContextImplcontext=newContextImpl();

141context.init(packageInfo,null,this);

142Applicationap

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 外语学习 > 英语学习

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1