最近在做类似于三星S4的那种皮套(后面简称SmartCover),具有可操作的窗口,一方面用户可以保护手机屏幕,另一方面用户可以直接在SmartCover上接听电话,非常方便。在开发过程中发现一个问题,虽然最终解决但还是记录一下,好记性不如烂笔头啊。
转载请务必注明出处:http://blog.csdn.net/yihongyuelan
问题描述:
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属于system log,而Android默认打印的log也就是adb logcat直接输出的log是main log,所以需要加-b 指定buffer,同时我们需要过滤WindowManager这个TAG(WmS的Log TAG是WindowManager),所以在终端中输入:
adb logcat -b system -s "WindowManager"
然后按照复现步骤操作一遍并查看log。当在通话界面长按Power键之后会出现以下Log:
V/WindowManager( 536): Changing focus from Window{424ce3f0 u0 com.android.phone
/com.android.phone.InCallScreen} to Window{4232b120 u0 GlobalActions}
也就是说这时候的焦点从InCallScreen变到了GlobalActions上,GlobalActions就是关机对话框,这里没有问题。
但当我们关闭SmartCover时,WmS没有焦点变化的Log输出。
对比以没有关机对话框的Log:
V/WindowManager( 536): Changing focus from Window{424ce3f0 u0 com.android.phone
/com.android.phone.InCallScreen} to Window{4232b120 u0 com.android.phone/com.and
roid.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信息,如下:
先接通电话,后弹出关机对话框
V/WindowManager( 536): Adding window Window{4211a128 u0 GlobalActions} at 3 of
4
V/WindowManager( 536): Looking for focus: 4 = Window{4242da68 u0 StatusBar}, fl
ags=25165896, canReceive=false
V/WindowManager( 536): Looking for focus: 3 = Window{4211a128 u0 GlobalActions}
, flags=8519682, canReceive=true
我们看到先Add了一个Window(即GlobalActions关机对话框),然后寻找到焦点在GlobalActions上(canReceive=true),此时我们关闭SmartCover再次查看Log输出如下:
V/WindowManager( 536): Adding window Window{42b22850 u0 com.android.phone/com.a
ndroid.phone.InCallScreen} at 3 of 5
V/WindowManager( 536): Looking for focus: 5 = Window{4242da68 u0 StatusBar}, fl
ags=25165896, canReceive=false
V/WindowManager( 536): Looking for focus: 4 = Window{423e1f78 u0 GlobalActions}
, flags=8519682, canReceive=true
V/WindowManager( 536): Found focus @ 4 = Window{423e1f78 u0 GlobalActions}
可以看到关闭SmartCover后的确也弹出了显示界面,即这里的Add window(InCallScreen),只是在Looking for focus的时候还是找到的GlobalActions(关机对话框),即这里的4,而没有继续找到我们想要的3。如果不弹出关机对话框则显示的Log如下:
V/WindowManager( 536): Adding window Window{41f756d0 u0 com.android.phone/com.a
ndroid.phone.InCallScreen} at 3 of 4
V/WindowManager( 536): Looking for focus: 4 = Window{4242da68 u0 StatusBar}, fl
ags=25165896, canReceive=false
V/WindowManager( 536): Looking for focus: 3 = Window{41f756d0 u0 com.android.ph
one/com.android.phone.InCallScreen}, flags=23592960, canReceive=true
V/WindowManager( 536): Found focus @ 3 = Window{41f756d0 u0 com.android.phone/c
om.android.phone.InCallScreen}
V/WindowManager( 536): Changing focus from Window{424ce3f0 u0 com.android.phone
/com.android.phone.InCallScreen} to Window{41f756d0 u0 com.android.phone/com.and
roid.phone.InCallScreen}
从这里我们可以看到,如果没有关机GlobalActions(关机对话框)则焦点的获取是正常的,关机对话实际上也是一个Dialog,那么为什么它能获取到焦点呢?
Dialog显示流程图:
这里大致画一下Dialog显示流程,如图1:
图1
最终可以看到Dialog是在WmS中进行的显示添加,看一下其中几个重要的方法。
重要方法:
(1). addWindowToListInOrderLocked
该方法会根据appWindowToken的值不同,从而添加到不同的layer。
private void addWindowToListInOrderLocked(WindowState win, boolean addToToken) {
final IWindow client = win.mClient;
final WindowToken token = win.mToken;
final DisplayContent displayContent = win.mDisplayContent;
final WindowList windows = win.getWindowList();
final int N = windows.size();
final WindowState attached = win.mAttachedWindow;
int i;
WindowList tokenWindowList = getTokenWindowsOnDisplay(token, displayContent);
if (attached == null) {
int tokenWindowsPos = 0;
int windowListPos = tokenWindowList.size();
if (token.appWindowToken != null) {//如果appWindowToken不为null
int index = windowListPos - 1;
if (index >= 0) {
//... ...省略
} else {
//... ...省略
} else {
int newIdx = findIdxBasedOnAppTokens(win);
//there is a window above this one associated with the same
//apptoken note that the window could be a floating window
//that was created later or a window at the top of the list of
//windows associated with this token.
if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) {
Slog.v(TAG, "Adding window " + win + " at "
+ (newIdx + 1) + " of " + N);
}
windows.add(newIdx + 1, win);
if (newIdx < 0) {
// No window from token found on win's display.
tokenWindowsPos = 0;
} else {
tokenWindowsPos = indexOfWinInWindowList(
windows.get(newIdx), token.windows) + 1;
}
mWindowsChanged = true;
}
}
//... ...省略
} else {//如果appWindowToken为null
// Figure out where window should go, based on layer.
/// M:Ignore the wallpaper window when adding window @{
final int myLayer = win.mBaseLayer;
WindowState localWindow;
for (i=N-1; i>=0; i--) {
localWindow = windows.get(i);
if (localWindow.mBaseLayer <= myLayer
&& localWindow.mAttrs.type != TYPE_WALLPAPER) {
/// @}
break;
}
}
i++;
if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(
TAG, "Adding window " + win + " at "
+ i + " of " + N);
windows.add(i, win);
mWindowsChanged = true;
}
//... ...省略
}
}
在该方法中,会根据appWindowToken值是否为null采用不同的添加window策略,如果appWindowToken != null则最终会执行:
int newIdx = findIdxBasedOnAppTokens(win);
if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) {
Slog.v(TAG, "Adding window " + win + " at "
+ (newIdx + 1) + " of " + N);
}
windows.add(newIdx + 1, win);
文章前面的Log信息的确也是这样输出的“xxx at 3 of 4 ”“xxx at 3 of 5”,继续查看这里的findIdxBaseOnAppTokens方法:
private int findIdxBasedOnAppTokens(WindowState win) {
WindowList windows = win.getWindowList();
for(int j = windows.size() - 1; j >= 0; j--) {
WindowState wentry = windows.get(j);
if(wentry.mAppToken == win.mAppToken) {
return j;
}
}
return -1;
}
该方法中会去获取所有的WindowList,然后用一个for循环找到每一个Window的mAppToken,与当前需要添加的窗口的mAppToken进行比较。这简单的说明一下,Android添加Window的顺序有点类似于做奶油蛋糕,一层一层的添加,最顶层永远是当前具有焦点的窗口。通过该for循环,可以找到当前系统中是否具有和需要添加的Window相同mAppToken的窗口,如果有则返回对应的层级给它。有点绕,可以简单的这么说,当前系统的窗口我们可以用一个5层的奶油蛋糕来表示,顺序从底向上依次是:红->白->绿->黄->蓝,现在我想给这个蛋糕加一个绿色的巧克力上去(这里颜色相当于系统中的mAppToken),我们看到(for循环查找)当前已经有一层绿色了,那么我们就把这个绿色的巧克力放到(Add window)绿色这一层。
如果appWindowToken == null呢,那执行以下代码:
final int myLayer = win.mBaseLayer;
WindowState localWindow;
for (i=N-1; i>=0; i--) {
localWindow = windows.get(i);
if (localWindow.mBaseLayer <= myLayer
&& localWindow.mAttrs.type != TYPE_WALLPAPER) {
/// @}
break;
}
}
i++;
if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(
TAG, "Adding window " + win + " at "
+ i + " of " + N);
windows.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,系统也不会执行Focus change。
那么在什么情况下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对象:
ContextImpl appContext = new ContextImpl();
appContext.init(r.packageInfo, r.token, this);
appContext.setOuterContext(activity);
Service启动时会调用到ActivityThread中的handleCreateService方法,并最终创建Context对象:
ContextImpl context = new ContextImpl();
context.init(packageInfo, null, this);
Application app = packageInfo.makeApplication(false, mInstrumentation);
context.setOuterContext(service);
getAppliCationContext获取的实际上是Application的Context,该方法在ContextImpl中定义:
@Override
public Context getApplicationContext() {
return (mPackageInfo != null) ?
mPackageInfo.getApplication() : mMainThread.getApplication();
}
这里的mpackageInfo只有在ContextImpl初始化的时候(init方法)才会为null,因此这里返回的是mPackageInfo.getApplication()即:
Application getApplication() {
return mApplication;
}
该方法在/framework/base/core/java/android/app/LoadedApk.java中,其赋值在makeApplication方法中:
try {
java.lang.ClassLoader cl = getClassLoader();
ContextImpl appContext = new ContextImpl();
appContext.init(this, null, mActivityThread);
app = mActivityThread.mInstrumentation.newApplication(
cl, appClass, appContext);
appContext.setOuterContext(app);
} catch (Exception e) {
if (!mActivityThread.mInstrumentation.onException(app, e)) {
throw new RuntimeException(
"Unable to instantiate application " + appClass
+ ": " + e.toString(), e);
}
}
mActivityThread.mAllApplications.add(app);
mApplication = app;
通过简单的对比我们可以知道无论是Activity还是Service的Context生成都调用了ContextImpl的init方法即:
final void init(LoadedApk packageInfo, IBinder activityToken, ActivityThread mainThread) {
init(packageInfo, activityToken, mainThread, null, null, Process.myUserHandle());
}
final void init(LoadedApk packageInfo, IBinder activityToken, ActivityThread mainThread,
Resources container, String basePackageName, UserHandle user) {
mPackageInfo = packageInfo;
mBasePackageName = basePackageName != null ? basePackageName : packageInfo.mPackageName;
mResources = mPackageInfo.getResources(mainThread);
if (mResources != null && container != null
&& container.getCompatibilityInfo().applicationScale !=
mResources.getCompatibilityInfo().applicationScale) {
if (DEBUG) {
Log.d(TAG, "loaded context has different scaling. Using container's" +
" compatiblity info:" + container.getDisplayMetrics());
}
mResources = mainThread.getTopLevelResources(
mPackageInfo.getResDir(), Display.DEFAULT_DISPLAY,
null, container.getCompatibilityInfo());
}
mMainThread = mainThread;
mActivityToken = activityToken;
mContentResolver = new ApplicationContentResolver(this, mainThread, user);
mUser = user;
}
Context的token区别
Context类型 |
token类型 |
Activity的Context |
r.token |
Service的Context |
null |
getApplicationContext获取的Context |
null |
因为ContextImpl中的init方法第二个参数接收的是token,而三种方法获取到的token除Activity的token不为null以外,其余两者的token都为null。接下来看第二个重要方法
(2).updateFocusedWindowLocked
当我们将dialog添加到界面上显示之后,系统需要切换window的焦点,就是在该方法中完成的。
private boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) {
WindowState newFocus = computeFocusedWindowLocked();//获取新焦点
if (mCurrentFocus != newFocus) {//如果新焦点不等于原来的焦点,则焦点有改动,执行焦点切换
Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "wmUpdateFocus");
// This check makes sure that we don't already have the focus
// change message pending.
mH.removeMessages(H.REPORT_FOCUS_CHANGE);
mH.sendEmptyMessage(H.REPORT_FOCUS_CHANGE);
/// M: Enable more google log at WMS
Slog.v(TAG, "Changing focus from " + mCurrentFocus + " to " + newFocus);
final WindowState oldFocus = mCurrentFocus;
mCurrentFocus = newFocus;
mAnimator.setCurrentFocus(newFocus);
mLosingFocus.remove(newFocus);
int focusChanged = mPolicy.focusChangedLw(oldFocus, newFocus);
// TODO(multidisplay): Focused windows on default display only.
final DisplayContent displayContent = getDefaultDisplayContentLocked();
final WindowState imWindow = mInputMethodWindow;
if (newFocus != imWindow && oldFocus != imWindow) {
if (moveInputMethodWindowsIfNeededLocked(
mode != UPDATE_FOCUS_WILL_ASSIGN_LAYERS &&
mode != UPDATE_FOCUS_WILL_PLACE_SURFACES)) {
displayContent.layoutNeeded = true;
}
if (mode == UPDATE_FOCUS_PLACING_SURFACES) {
performLayoutLockedInner(displayContent, true /*initial*/, updateInputWindows);
focusChanged &= ~WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
} else if (mode == UPDATE_FOCUS_WILL_PLACE_SURFACES) {
// Client will do the layout, but we need to assign layers
// for handleNewWindowLocked() below.
assignLayersLocked(displayContent.getWindowList());
}
}
if ((focusChanged & WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT) != 0) {
// The change in focus caused us to need to do a layout. Okay.
displayContent.layoutNeeded = true;
if (mode == UPDATE_FOCUS_PLACING_SURFACES) {
performLayoutLockedInner(displayContent, true /*initial*/, updateInputWindows);
}
}
if (mode != UPDATE_FOCUS_WILL_ASSIGN_LAYERS) {
// If we defer assigning layers, then the caller is responsible for
// doing this part.
finishUpdateFocusedWindowAfterAssignLayersLocked(updateInputWindows);
}
Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
return true;
}
return false;
}
在该方法中,首先会去获取当前的焦点与先前的焦点进行比较,然后决定是否需要切换焦点。我们看到该方法中有一句很熟悉的log:
Slog.v(TAG, "Changing focus from " + mCurrentFocus + " to " + newFocus);
这正是我们前面打印出来的Log信息。也就是说,当前如果正在通话中,显示了关机对话框,焦点就由InCallScreen切换到了GlobalActions(关机对话框)上,这里也输出了该句Log,而我们此时再关闭SmartCover却没有执行焦点的切换,也就是mCurrenFocus == newFocus。
接下来的事情就简单了,我们只需要搞清楚newFocus为什么没有改变即可知道问题的原因了。继续查看
WindowState newFocus = computeFocusedWindowLocked();
即:
private WindowState computeFocusedWindowLocked() {
if (mAnimator.mUniverseBackground != null
&& mAnimator.mUniverseBackground.mWin.canReceiveKeys()) {
return mAnimator.mUniverseBackground.mWin;
}
final int displayCount = mDisplayContents.size();
for (int i = 0; i < displayCount; i++) {
final DisplayContent displayContent = mDisplayContents.valueAt(i);
WindowState win = findFocusedWindowLocked(displayContent);
if (win != null) {
return win;
}
}
return null;
}
newFocus实际上是WindowState,WindowState与客户端窗口一一对应,用于描述窗口状态。
继续查看findFocusedWindowLocked方法:
private WindowState findFocusedWindowLocked(DisplayContent displayContent) {
int nextAppIndex = mAppTokens.size()-1;
WindowToken nextApp = nextAppIndex >= 0 ? mAppTokens.get(nextAppIndex) : null;
final WindowList windows = displayContent.getWindowList();
for (int i = windows.size() - 1; i >= 0; i--) {
final WindowState win = windows.get(i);
if (localLOGV || DEBUG_FOCUS) Slog.v(
TAG, "Looking for focus: " + i
+ " = " + win
+ ", flags=" + win.mAttrs.flags
+ ", canReceive=" + win.canReceiveKeys());
//... ...省略
// Dispatch to this window if it is wants key events.
if (win.canReceiveKeys()) {
if (DEBUG_FOCUS) Slog.v(
TAG, "Found focus @ " + i + " = " + win);
return win;
}
}
return null;
}
这里我们可以看到之前在WmS中输出的Log,"canReceive"以及"Found focus @"等等。结合前面Log分析,我们可以知道,如果SmartCover弹出的Dialog要能够正常获取焦点,canReceive需要为true。该方法实际上就是遍历系统中所有的Window然后寻找匹配的Focus的Window,查找顺序是自顶向下,就好比前面的奶油蛋糕,从最上面那一层蓝色的开始查找。这里需要查看win.canReceiveKeys方法,该方法在framework/base/services/java/com/android/server/wm/WindowState.java中:
public final boolean canRe ceiveKeys() {
Slog.w(TAG, "**************************");
Slog.w(TAG, "which window="+this);
Slog.w(TAG, "Seven..(mViewVisibility == View.VISIBLE)="+(mViewVisibility == View.VISIBLE));
Slog.w(TAG, "Seven..((mAttrs.flags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) == 0)="+((mAttrs.flags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) == 0));
return isVisibleOrAdding()
&& (mViewVisibility == View.VISIBLE)
&& ((mAttrs.flags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) == 0);
}
这里我自己添加了一些Log输出,便于后续分析查看,继续查看isVisibleOrAdding方法:
boolean isVisibleOrAdding() {
final AppWindowToken atoken = mAppToken;
Slog.w(TAG, "Seven..mHasSurface="+mHasSurface);
Slog.w(TAG, "Seven..(!mRelayoutCalled && mViewVisibility == View.VISIBLE)="+(!mRelayoutCalled && mViewVisibility == View.VISIBLE));
Slog.w(TAG, "Seven..mPolicyVisibility="+mPolicyVisibility);
Slog.w(TAG, "Seven..!mAttachedHidden="+!mAttachedHidden);
Slog.w(TAG, "Seven..atoken ="+atoken);
Slog.w(TAG, "Seven..(atoken == null || !atoken.hiddenRequested)="+(atoken == null || !atoken.hiddenRequested));
Slog.w(TAG, "Seven..!mExiting="+!mExiting);
Slog.w(TAG, "Seven..!mDestroying="+!mDestroying);
return (mHasSurface || (!mRelayoutCalled && mViewVisibility == View.VISIBLE))
&& mPolicyVisibility && !mAttachedHidden
&& (atoken == null || !atoken.hiddenRequested)
&& !mExiting && !mDestroying;
}
最后,我们需要做的就是根据Log来分析原因了,看看到底是什么情况,才导致了SmartCover弹出的界面无法接收触摸事件,Log输出如下:
W/WindowState( 536): **************************
W/WindowState( 536): which window=Window{41efc2e0 u0 GlobalActions}
W/WindowState( 536): Seven..(mViewVisibility == View.VISIBLE)=true
W/WindowState( 536): Seven..((mAttrs.flags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) == 0)=true
W/WindowState( 536): Seven..mHasSurface=false
W/WindowState( 536): Seven..(!mRelayoutCalled && mViewVisibility == View.VISIBLE)=true
W/WindowState( 536): Seven..mPolicyVisibility=true
W/WindowState( 536): Seven..!mAttachedHidden=true
W/WindowState( 536): Seven..atoken =null
W/WindowState( 536): Seven..(atoken == null || !atoken.hiddenRequested)=true
W/WindowState( 536): Seven..!mExiting=true
W/WindowState( 536): Seven..!mDestroying=true
这是GlobalActions(关机对话框)弹出后的Log,经过分析可以知道canReceive = true。
W/WindowState( 536): **************************
W/WindowState( 536): which window=Window{424ce3f0 u0 com.android.phone/com.android.phone.InCallScreen}
W/WindowState( 536): Seven..(mViewVisibility == View.VISIBLE)=true
W/WindowState( 536): Seven..((mAttrs.flags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) == 0)=true
W/WindowState( 536): Seven..mHasSurface=true
W/WindowState( 536): Seven..(!mRelayoutCalled && mViewVisibility == View.VISIBLE)=false
W/WindowState( 536): Seven..mPolicyVisibility=true
W/WindowState( 536): Seven..!mAttachedHidden=true
W/WindowState( 536): Seven..atoken =AppWindowToken{424a11e8 token=Token{4250a330 ActivityRecord{4250a1f0 u0 com.android.phone/.InCallScreen}}}
W/WindowState( 536): Seven..(atoken == null || !atoken.hiddenRequested)=true
W/WindowState( 536): Seven..!mExiting=true
W/WindowState( 536): Seven..!mDestroying=true
这是SmartCover显示Dialog的Log,实际上canReceive 也是为true的,但是经过前面的分析我们也知道,Window的Focus查找顺序是自顶向下的,因为我们的Dialog添加的层级并不是最上面,而GlobalActions(关机对话框)则显示添加在最上面,因此已经找到具有canReceive = true的Window了,所以直接返回,不会管后面的Window是否canReceive为true。仔细想想google这样设计也是合理的,毕竟同一时间只能有一个窗口获取焦点,而且查找顺序是自顶向下,最上层的自然应该具有焦点,这样用户使用起来逻辑上才会觉得是正确的,即正在操作的窗口在最上面,之前操作的在下面。
解决方案:
既然知道问题所在了,解决方案自然就有了,将SmartCover显示Dialog的context从InCallScreen的context修改为getApplicationContext。因为前面分析也说过了,getApplicationContext的token为null,所以添加窗口时会将窗口添加到最上层。
总结
经过层层分析,终于知道了原因以及修改方案,最终的修改仅仅是改了一下context,可其中的分析各种曲折啊!还是那句话啊,不要小看任何不起眼的东西,说不定哪天你就会栽在那里!
知识点:
1. WmS负责Window的添加,添加顺序是自顶向下查找是否与之对应的token,如果有则将新window添加到那一层,如果没有则判断是否是baselayer(TYPE_WALLPAPER),如果不是baselayer则将新Window添加到最顶层。
2. WmS负责窗口Focus切换的管理。使用updateFocusedWindowLocked方法更新Focus。
3. 不同的context将导致token的不同,从而决定了新Window的显示层的不同。
注:
本文基于Android 4.2 (MTK Platform),对于其中token以及AmS,WmS流程及逻辑有兴趣的童鞋,推荐看看luoshengyang老师的blog,一些有用的链接我放在下面:
Android窗口管理剖析
Android应用程序窗口(Activity)与WindowManagerService服务的连接过程分析
android context 以及 getApplicationContext()
Android FrameWork——Touch事件派发过程详解