Android 4.2 由Context引发的思考

       最近在做类似于三星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的Contextr.token
Service的Contextnull
getApplicationContext获取的Contextnull

       因为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,一些有用的链接我放在下面:
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值