从问题单处理了解Toast系统窗口显示、Activity窗口创建、WindowManagerService对系统窗口组织排布(一)
2016-09-28 12:19
459 查看
今天又收到测试同事的一个问题单,在手机上使用多任务键的截屏功能,然后将截屏图片删除掉,在通知栏中点击当前截屏中通知的分享功能,随便选择一种分享方式,因为截屏图片已经被删除,所以此时弹出toast提示分享文件不存在,但是奇怪的是,toast被当前的分享方式对话框盖住了,只有取消掉分享方式的对话框,才能看到文件不存在的toast提示。
因为之前有看过老罗的相关博客,其中有介绍到WindowManagerService对系统窗口Zorder的排在组织,所以一拿到此题目,目标应该是非常明确的,肯定是分享方式的窗口的Zorder顺序大于toast的值了,所以排在toast窗口之上了。OK,目标明确了,那就从问题的出发点入手进行分析。
首先很明确,当前的分享方式的窗口对应的是系统中ChooserActivity,我们可以看一下ChooserActivity在Manifest当中的注册定义,从定义中很明确的可以知道,如果我们想调用分享,那么也可以通知<action android:name="android.intent.action.CHOOSER" />这个action来调用,这个action同时也定义的Intent类中,大家不需要自己写。
那么进一步明确,我们现在要确定的就是Toast的层值和ChooserActivity的层值,各位卓友还需要明确一点,就是当前窗口的层值是什么?它是怎么来的?这些问题可以参看老罗的博客:Android窗口管理服务WindowManagerService对窗口的组织方式分析、Android窗口管理服务WindowManagerService计算窗口Z轴位置的过程分析相关文章,里边有非常详细的介绍,这里我们就大致走一下这个流程。
首先,每一个应用程序添加一个窗口在起点是从ActivityThread类的handleResumeActivity方法中开始的,此方法的代码如下:
其中performResumeActivity调用最终就会调用到我们当前Activity的onResume方法,willBeVisible变量的意思很明显,就是我们即将添加的Activity窗口是否将要被显示,窗口添加的实际开始就是wm.addView(decor, l)这句引起的。decor对象就是我们在自己Activity的onCreate方法当中调用setContentView构建好的当前Activity的View对象,如果想要了解此过程,可参考Android应用程序窗口(Activity)的视图对象(View)的创建过程分析。l对象就是根据当前的窗口属性构建好的一个WindowManager.LayoutParams实体,wm是Activity类的getWindowManager()方法返回的mWindowManager对象,mWindowManager对象是在Activity实例化时attach()方法中进行赋值,addView方法的定义是在ViewManager当中的,ViewManager中WindowManager的父类,此方法的实现是在WindowManagerImpl类当中,实现非常简单,就是调用mGlobal.addView(view,
params, mDisplay, mParentWindow),mGlobal是一个WindowManagerGlobal实体,WindowManagerGlobal的addView方法的代码如下,此代码在我的博客中也经常用到,也是应用程序添加窗口的必经之路,大家必须要熟悉:
此方法的调用我们就不说了,大家如果不了解,可以自行百度。这里呢调用到了ViewRootImpl的setView方法,向WinowManagerService添加我们Activity窗口的工作也是在这里执行的,详细方法我就不引用了,我们直接看调用的地方:res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(), mAttachInfo.mContentInsets,
mAttachInfo.mStableInsets, mAttachInfo.mOutsets, mInputChannel)。
mWindowSession是一个IWindowSession对象,它其实就是WindowManagerService当中调用openSession方法,直接new构造的一个Session对象,而在应用程序当中我们拿到的是一个binder代理,实现过程这里就追究了,大家请自行了解。那么它的addToDisplay自然也就是调用了Session类的addToDisplay方法,其中实现非常简单,就是转而调用mService.addWindow(),mService就是WindowManagerService对象了,是在Session类的构造函数中传进来的。那么我们就进入WindowManagerService的addWindow方法看一下究竟。方法代码如下:
这个方法会作的工作有几点:1、检查当前应用的添加窗口的权限;2、根据我们传进来的窗口布局参数的type类型,判断要添加的窗口类型,同时构建一个WindowToken对象;3、构建WindowState对象,此对象非常重要,它是我们当前Activity在WindowManagerService窗口管理中的代表;4、将ViewRootImpl传进来的一对InputChannel进行实例化并进行注册,因为ViewRootImpl中构建的InputChannel只是空壳,在native层没有对应的描述符,所以需要实例化;5、将构建好的WindowState对象添加到WindowManagerService的类变量mWindowMap中,它是管理所有WindowState对象的;6、调用assignLayersLocked对所有窗口进行重新排布。到这里,我们的Activity的窗口就添加到系统当中了。
好了,回到主题,添加窗口的过程我们大概走了一下,那么窗口层值是什么呢?
在WindowState类中有一个mBaseLayer和一个mSubLayer变量,mBaseLayer表示当前窗口的基本层值,mSubLayer表示当前窗口在子窗口中的层值,关于这两个变量的实际意义,大家如果还不明白,请自行百度。我们来看一下这两个变量的值是怎么定的呢?这两个变量的值都是在WindowState对象的构造方法中确定的,意思也就是说,当系统调用WindowManagerService的addWindow方法为我们的Activity添加窗口的时候,此时目标窗口的层值就可以确定了,层值也会在构造WindowState对象时确定好,而且不会再改变。mSubLayer比较简单,它只在当前窗口有子窗口时才有意义,这种情况下就调用mPolicy.subWindowTypeToLayerLw(a.type)对它进行赋值,否则就赋值为0;mBaseLayer有当前窗口是否有子窗口的情况下都是调用mPolicy.windowTypeToLayerLw()方法进行赋值的,不过两个调用传入的参数不一样。mPolicy是一个WindowManagerPolicy对象,它是在WindowState构造函数中赋值的,也就是WindowManagerService的mPolicy对象,实际是一个PhoneWindowManager,我们来看一下它的windowTypeToLayerLw方法:
很明显可以看到两个方法的区别,参数为WindowManager.LayoutParams的方法先把type进行了一下换算,然后将换算后的值作为参数,调用了参数为int的方法。从这里我们也可以看出来android系统中的窗口类型,TYPE_WALLPAPER表示壁纸窗口,TYPE_KEYGUARD_WALLPAPER表示锁屏的壁纸,TYPE_SYSTEM_DIALOG表示系统弹窗,比如ANR、异常终止等,TYPE_TOAST表示Toast窗口。大家可以看到在方法开始,直接对比type值如果大于等于FIRST_APPLICATION_WINDOW并且小于等于LAST_APPLICATION_WINDOW,则直接返回2,这里也就是我们最常用的应用程序的窗口Application。FIRST_APPLICATION_WINDOW和LAST_APPLICATION_WINDOW的值定义在WindowManager.LayoutParams中,值分别为1和99。那么我们的目标就非常明确了,应该就是创建ChooserActivity时的mBaseLayer的值大于Toast窗口的mBaseLayer值,才导致Toast窗口被覆盖了,好,有了推断的结论,来验证一下,在源码中加上相应的日志,执行mm,然后push替换,果然,Toast窗口层值为81000,而ChooserActivity的层值为171000,远远大于Toast的窗口层值,那么我们现在要作的就是要找到为什么ChooserActivity的窗口层值这么大了!
从这里往上分析,日志中还可以看到ChooserActivity对象的type值为2014,这个值是怎么来的呢?type值是取的attrs.type,attrs是在ViewRootImpl中调用addToDisplay方法时传进来的,而addToDisplay中的参数又是其类变量mWindowAttributes,它是在setView为调用mWindowAttributes.copyFrom(attrs)从方法参数中拷贝过来的一份,再往上,方法参数attrs是在WindowManagerGlobal的addView方法中传进来的,addView方法整个调用过程只是使用此参数,并未对其值进行修改,那么再往前,此处的attrs就是WindowManagerImpl中传进来的,而WindowManagerImpl中的参数又是在ActivityThread的wm.addView(decor,
l)调用时传入的,也就是我们前面所说的 l,好了,l 就是这个调用的根源了,我们从代码可以看出,如果进入if分支,则 l 的type属性值则被赋值为WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL,它的定义在WindowManager.LayoutParams当中,值正好是2014:public static final int TYPE_STATUS_BAR_PANEL = FIRST_SYSTEM_WINDOW+14,和我们的日志完全对应,也证明我们的分析是正确的。那么结论就出来了,就是因为多任务键截屏后,点击分享时,启动ChoosetActivity的intent不为空并且FLAG_ACTIVITY_ON_TOP标志不等于0,才导致ChoosetActivity的启动布局参数被放大,最终导致其覆盖在Toast系统窗口之上。
好了,这一篇博客就写到这里了,我们要分析的还有Toast的显示过程,因博客篇幅过长,所以Toast显示过程见下一篇博客,静请期待,谢谢大家。
因为之前有看过老罗的相关博客,其中有介绍到WindowManagerService对系统窗口Zorder的排在组织,所以一拿到此题目,目标应该是非常明确的,肯定是分享方式的窗口的Zorder顺序大于toast的值了,所以排在toast窗口之上了。OK,目标明确了,那就从问题的出发点入手进行分析。
首先很明确,当前的分享方式的窗口对应的是系统中ChooserActivity,我们可以看一下ChooserActivity在Manifest当中的注册定义,从定义中很明确的可以知道,如果我们想调用分享,那么也可以通知<action android:name="android.intent.action.CHOOSER" />这个action来调用,这个action同时也定义的Intent类中,大家不需要自己写。
那么进一步明确,我们现在要确定的就是Toast的层值和ChooserActivity的层值,各位卓友还需要明确一点,就是当前窗口的层值是什么?它是怎么来的?这些问题可以参看老罗的博客:Android窗口管理服务WindowManagerService对窗口的组织方式分析、Android窗口管理服务WindowManagerService计算窗口Z轴位置的过程分析相关文章,里边有非常详细的介绍,这里我们就大致走一下这个流程。
首先,每一个应用程序添加一个窗口在起点是从ActivityThread类的handleResumeActivity方法中开始的,此方法的代码如下:
final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, int reasonFlags/*LEUI-10257*/, boolean reallyResume) { // If we are getting ready to gc after going to the background, well // we are back active so skip it. unscheduleGcIdler(); mSomeActivitiesChanged = true; // TODO Push resumeArgs into the activity for consideration ActivityClientRecord r = performResumeActivity(token, clearHide); if (r != null) { final Activity a = r.activity; //[+LEUI-10257] r.reasonFlags = reasonFlags; //[-LEUI-10257] if (localLOGV) Slog.v( TAG, "Resume " + r + " started activity: " + a.mStartedActivity + ", hideForNow: " + r.hideForNow + ", finished: " + a.mFinished); final int forwardBit = isForward ? WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION : 0; // If the window hasn't yet been added to the window manager, // and this guy didn't finish itself or start another activity, // then go ahead and add the window. boolean willBeVisible = !a.mStartedActivity; if (!willBeVisible) { try { willBeVisible = ActivityManagerNative.getDefault().willActivityBeVisible( a.getActivityToken()); } catch (RemoteException e) { } } //[+LEUI-10257] checkReasonFlags(r); //[-LEUI-10257] if (r.window == null && !a.mFinished && willBeVisible) { r.window = r.activity.getWindow(); View decor = r.window.getDecorView(); decor.setVisibility(View.INVISIBLE); ViewManager wm = a.getWindowManager(); WindowManager.LayoutParams l = r.window.getAttributes(); a.mDecor = decor; if((r.intent != null) && (r.intent.getFlags() & Intent.FLAG_ACTIVITY_ON_TOP) != 0) { l.type = WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL; } else { l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION; } l.softInputMode |= forwardBit; if (a.mVisibleFromClient) { a.mWindowAdded = true; wm.addView(decor, l); }/*[LEUI-15516]*/else{ //See {@link Activity#setVisible} Slog.i(LEUI_TAG, "Resume "+r+" is NOT going to show a UI(addView)"); } // If the window has already been added, but during resume // we started another activity, then don't yet make the // window visible. } else if (!willBeVisible) { if (localLOGV) Slog.v( TAG, "Launch " + r + " mStartedActivity set"); r.hideForNow = true; } // Get rid of anything left hanging around. cleanUpPendingRemoveWindows(r); // The window is now visible if it has been added, we are not // simply finishing, and we are not starting another activity. if (!r.activity.mFinished && willBeVisible && r.activity.mDecor != null && !r.hideForNow) { if (r.newConfig != null) { r.tmpConfig.setTo(r.newConfig); if (r.overrideConfig != null) { r.tmpConfig.updateFrom(r.overrideConfig); } if (DEBUG_CONFIGURATION) Slog.v(TAG, "Resuming activity " + r.activityInfo.name + " with newConfig " + r.tmpConfig); performConfigurationChanged(r.activity, r.tmpConfig); freeTextLayoutCachesIfNeeded(r.activity.mCurrentConfig.diff(r.tmpConfig)); r.newConfig = null; } if (localLOGV) Slog.v(TAG, "Resuming " + r + " with isForward=" + isForward); WindowManager.LayoutParams l = r.window.getAttributes(); if ((l.softInputMode & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != forwardBit) { l.softInputMode = (l.softInputMode & (~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION)) | forwardBit; if (r.activity.mVisibleFromClient) { ViewManager wm = a.getWindowManager(); View decor = r.window.getDecorView(); wm.updateViewLayout(decor, l); } } r.activity.mVisibleFromServer = true; mNumVisibleActivities++; if (r.activity.mVisibleFromClient) { r.activity.makeVisible(); }/*[LEUI-15516]*/else{ //See {@link Activity#setVisible} Slog.i(LEUI_TAG, "Resume "+r+" is NOT going to show a UI (makeVisible)"); } } if (!r.onlyLocalRequest) { r.nextIdle = mNewActivities; mNewActivities = r; if (localLOGV) Slog.v( TAG, "Scheduling idle handler for " + r); Looper.myQueue().addIdleHandler(new Idler()); } r.onlyLocalRequest = false; // Tell the activity manager we have resumed. if (reallyResume) { try { ActivityManagerNative.getDefault().activityResumed(token); } catch (RemoteException ex) { } } } else { // If an exception was thrown when trying to resume, then // just end this activity. try { ActivityManagerNative.getDefault() .finishActivity(token, Activity.RESULT_CANCELED, null, false); } catch (RemoteException ex) { } } }
其中performResumeActivity调用最终就会调用到我们当前Activity的onResume方法,willBeVisible变量的意思很明显,就是我们即将添加的Activity窗口是否将要被显示,窗口添加的实际开始就是wm.addView(decor, l)这句引起的。decor对象就是我们在自己Activity的onCreate方法当中调用setContentView构建好的当前Activity的View对象,如果想要了解此过程,可参考Android应用程序窗口(Activity)的视图对象(View)的创建过程分析。l对象就是根据当前的窗口属性构建好的一个WindowManager.LayoutParams实体,wm是Activity类的getWindowManager()方法返回的mWindowManager对象,mWindowManager对象是在Activity实例化时attach()方法中进行赋值,addView方法的定义是在ViewManager当中的,ViewManager中WindowManager的父类,此方法的实现是在WindowManagerImpl类当中,实现非常简单,就是调用mGlobal.addView(view,
params, mDisplay, mParentWindow),mGlobal是一个WindowManagerGlobal实体,WindowManagerGlobal的addView方法的代码如下,此代码在我的博客中也经常用到,也是应用程序添加窗口的必经之路,大家必须要熟悉:
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) { if (view == null) { throw new IllegalArgumentException("view must not be null"); } if (display == null) { throw new IllegalArgumentException("display must not be null"); } if (!(params instanceof WindowManager.LayoutParams)) { throw new IllegalArgumentException("Params must be WindowManager.LayoutParams"); } final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params; if (parentWindow != null) { parentWindow.adjustLayoutParamsForSubWindow(wparams); } else { // If there's no parent, then hardware acceleration for this view is // set from the application's hardware acceleration setting. final Context context = view.getContext(); if (context != null && (context.getApplicationInfo().flags & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) { wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; } } ViewRootImpl root; View panelParentView = null; synchronized (mLock) { // Start watching for system property changes. if (mSystemPropertyUpdater == null) { mSystemPropertyUpdater = new Runnable() { @Override public void run() { synchronized (mLock) { for (int i = mRoots.size() - 1; i >= 0; --i) { mRoots.get(i).loadSystemProperties(); } } } }; SystemProperties.addChangeCallback(mSystemPropertyUpdater); } int index = findViewLocked(view, false); if (index >= 0) { if (mDyingViews.contains(view)) { // Don't wait for MSG_DIE to make it's way through root's queue. mRoots.get(index).doDie(); } else { throw new IllegalStateException("View " + view + " has already been added to the window manager."); } // The previous removeView() had not completed executing. Now it has. } // If this is a panel window, then find the window it is being // attached to for future reference. if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW && wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) { final int count = mViews.size(); for (int i = 0; i < count; i++) { if (mRoots.get(i).mWindow.asBinder() == wparams.token) { panelParentView = mViews.get(i); } } } root = new ViewRootImpl(view.getContext(), display); view.setLayoutParams(wparams); mViews.add(view); mRoots.add(root); mParams.add(wparams); } // do this last because it fires off messages to start doing things try { root.setView(view, wparams, panelParentView); } catch (RuntimeException e) { // BadTokenException or InvalidDisplayException, clean up. synchronized (mLock) { final int index = findViewLocked(view, false); if (index >= 0) { removeViewLocked(index, true); } } throw e; } }
此方法的调用我们就不说了,大家如果不了解,可以自行百度。这里呢调用到了ViewRootImpl的setView方法,向WinowManagerService添加我们Activity窗口的工作也是在这里执行的,详细方法我就不引用了,我们直接看调用的地方:res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(), mAttachInfo.mContentInsets,
mAttachInfo.mStableInsets, mAttachInfo.mOutsets, mInputChannel)。
mWindowSession是一个IWindowSession对象,它其实就是WindowManagerService当中调用openSession方法,直接new构造的一个Session对象,而在应用程序当中我们拿到的是一个binder代理,实现过程这里就追究了,大家请自行了解。那么它的addToDisplay自然也就是调用了Session类的addToDisplay方法,其中实现非常简单,就是转而调用mService.addWindow(),mService就是WindowManagerService对象了,是在Session类的构造函数中传进来的。那么我们就进入WindowManagerService的addWindow方法看一下究竟。方法代码如下:
public int addWindow(Session session, IWindow client, int seq, WindowManager.LayoutParams attrs, int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets, Rect outOutsets, InputChannel outInputChannel) { int[] appOp = new int[1]; int res = mPolicy.checkAddPermission(attrs, appOp); if (res != WindowManagerGlobal.ADD_OKAY) { return res; } boolean reportNewConfig = false; WindowState attachedWindow = null; long origId; final int type = attrs.type; synchronized(mWindowMap) { if (!mDisplayReady) { throw new IllegalStateException("Display has not been initialialized"); } final DisplayContent displayContent = getDisplayContentLocked(displayId); if (displayContent == null) { Slog.w(TAG, "Attempted to add window to a display that does not exist: " + displayId + ". Aborting."); return WindowManagerGlobal.ADD_INVALID_DISPLAY; } if (!displayContent.hasAccess(session.mUid)) { Slog.w(TAG, "Attempted to add window to a display for which the application " + "does not have access: " + displayId + ". Aborting."); return WindowManagerGlobal.ADD_INVALID_DISPLAY; } if (mWindowMap.containsKey(client.asBinder())) { Slog.w(TAG, "Window " + client + " is already added"); return WindowManagerGlobal.ADD_DUPLICATE_ADD; } if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) { attachedWindow = windowForClientLocked(null, attrs.token, false); if (attachedWindow == null) { Slog.w(TAG, "Attempted to add window with token that is not a window: " + attrs.token + ". Aborting."); return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN; } if (attachedWindow.mAttrs.type >= FIRST_SUB_WINDOW && attachedWindow.mAttrs.type <= LAST_SUB_WINDOW) { Slog.w(TAG, "Attempted to add window with token that is a sub-window: " + attrs.token + ". Aborting."); return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN; } } if (type == TYPE_PRIVATE_PRESENTATION && !displayContent.isPrivate()) { Slog.w(TAG, "Attempted to add private presentation window to a non-private display. Aborting."); return WindowManagerGlobal.ADD_PERMISSION_DENIED; } boolean addToken = false; WindowToken token = mTokenMap.get(attrs.token); if (token == null) { if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) { Slog.w(TAG, "Attempted to add application window with unknown token " + attrs.token + ". Aborting."); return WindowManagerGlobal.ADD_BAD_APP_TOKEN; } if (type == TYPE_INPUT_METHOD) { Slog.w(TAG, "Attempted to add input method window with unknown token " + attrs.token + ". Aborting."); return WindowManagerGlobal.ADD_BAD_APP_TOKEN; } if (type == TYPE_VOICE_INTERACTION) { Slog.w(TAG, "Attempted to add voice interaction window with unknown token " + attrs.token + ". Aborting."); return WindowManagerGlobal.ADD_BAD_APP_TOKEN; } if (type == TYPE_WALLPAPER) { Slog.w(TAG, "Attempted to add wallpaper window with unknown token " + attrs.token + ". Aborting."); return WindowManagerGlobal.ADD_BAD_APP_TOKEN; } if (type == TYPE_KEYGUARD_WALLPAPER) { Slog.w(TAG, "Attempted to add keyguard wallpaper window with unknown token " + attrs.token + ". Aborting."); return WindowManagerGlobal.ADD_BAD_APP_TOKEN; } if (type == TYPE_DREAM) { Slog.w(TAG, "Attempted to add Dream window with unknown token " + attrs.token + ". Aborting."); return WindowManagerGlobal.ADD_BAD_APP_TOKEN; } if (type == TYPE_ACCESSIBILITY_OVERLAY) { Slog.w(TAG, "Attempted to add Accessibility overlay window with unknown token " + attrs.token + ". Aborting."); return WindowManagerGlobal.ADD_BAD_APP_TOKEN; } token = new WindowToken(this, attrs.token, -1, false); addToken = true; } else if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) { AppWindowToken atoken = token.appWindowToken; if (atoken == null) { Slog.w(TAG, "Attempted to add window with non-application token " + token + ". Aborting."); return WindowManagerGlobal.ADD_NOT_APP_TOKEN; } else if (atoken.removed) { Slog.w(TAG, "Attempted to add window with exiting application token " + token + ". Aborting."); return WindowManagerGlobal.ADD_APP_EXITING; } if (type == TYPE_APPLICATION_STARTING && atoken.firstWindowDrawn) { // No need for this guy! if (localLOGV) Slog.v( TAG, "**** NO NEED TO START: " + attrs.getTitle()); return WindowManagerGlobal.ADD_STARTING_NOT_NEEDED; } } else if (type == TYPE_INPUT_METHOD) { if (token.windowType != TYPE_INPUT_METHOD) { Slog.w(TAG, "Attempted to add input method window with bad token " + attrs.token + ". Aborting."); return WindowManagerGlobal.ADD_BAD_APP_TOKEN; } } else if (type == TYPE_VOICE_INTERACTION) { if (token.windowType != TYPE_VOICE_INTERACTION) { Slog.w(TAG, "Attempted to add voice interaction window with bad token " + attrs.token + ". Aborting."); return WindowManagerGlobal.ADD_BAD_APP_TOKEN; } } else if (type == TYPE_WALLPAPER) { if (token.windowType != TYPE_WALLPAPER) { Slog.w(TAG, "Attempted to add wallpaper window with bad token " + attrs.token + ". Aborting."); return WindowManagerGlobal.ADD_BAD_APP_TOKEN; } } else if (type == TYPE_KEYGUARD_WALLPAPER) { if (token.windowType != TYPE_KEYGUARD_WALLPAPER) { Slog.w(TAG, "Attempted to add keyguard wallpaper window with bad token " + attrs.token + ". Aborting."); return WindowManagerGlobal.ADD_BAD_APP_TOKEN; } } else if (type == TYPE_DREAM) { if (token.windowType != TYPE_DREAM) { Slog.w(TAG, "Attempted to add Dream window with bad token " + attrs.token + ". Aborting."); return WindowManagerGlobal.ADD_BAD_APP_TOKEN; } } else if (type == TYPE_ACCESSIBILITY_OVERLAY) { if (token.windowType != TYPE_ACCESSIBILITY_OVERLAY) { Slog.w(TAG, "Attempted to add Accessibility overlay window with bad token " + attrs.token + ". Aborting."); return WindowManagerGlobal.ADD_BAD_APP_TOKEN; } } else if (token.appWindowToken != null) { Slog.w(TAG, "Non-null appWindowToken for system window of type=" + type); // It is not valid to use an app token with other system types; we will // instead make a new token for it (as if null had been passed in for the token). attrs.token = null; token = new WindowToken(this, null, -1, false); addToken = true; } WindowState win = new WindowState(this, session, client, token, attachedWindow, appOp[0], seq, attrs, viewVisibility, displayContent); if (win.mDeathRecipient == null) { // Client has apparently died, so there is no reason to // continue. Slog.w(TAG, "Adding window client " + client.asBinder() + " that is dead, aborting."); return WindowManagerGlobal.ADD_APP_EXITING; } if (win.getDisplayContent() == null) { Slog.w(TAG, "Adding window to Display that has been removed."); return WindowManagerGlobal.ADD_INVALID_DISPLAY; } mPolicy.adjustWindowParamsLw(win.mAttrs); win.setShowToOwnerOnlyLocked(mPolicy.checkShowToOwnerOnly(attrs)); res = mPolicy.prepareAddWindowLw(win, attrs); if (res != WindowManagerGlobal.ADD_OKAY) { return res; } if (outInputChannel != null && (attrs.inputFeatures & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) { String name = win.makeInputChannelName(); InputChannel[] inputChannels = InputChannel.openInputChannelPair(name); win.setInputChannel(inputChannels[0]); inputChannels[1].transferTo(outInputChannel); mInputManager.registerInputChannel(win.mInputChannel, win.mInputWindowHandle); } // From now on, no exceptions or errors allowed! res = WindowManagerGlobal.ADD_OKAY; origId = Binder.clearCallingIdentity(); if (addToken) { mTokenMap.put(attrs.token, token); } win.attach(); mWindowMap.put(client.asBinder(), win); if (win.mAppOp != AppOpsManager.OP_NONE) { int startOpResult = mAppOps.startOpNoThrow(win.mAppOp, win.getOwningUid(), win.getOwningPackage()); if ((startOpResult != AppOpsManager.MODE_ALLOWED) && (startOpResult != AppOpsManager.MODE_DEFAULT)) { win.setAppOpVisibilityLw(false); } } if (type == TYPE_APPLICATION_STARTING && token.appWindowToken != null) { token.appWindowToken.startingWindow = win; if (DEBUG_EUI_STARTING_WINDOW || DEBUG_STARTING_WINDOW) Slog.v (TAG, "addWindow: " + token.appWindowToken + " startingWindow=" + win); } boolean imMayMove = true; if (type == TYPE_INPUT_METHOD) { win.mGivenInsetsPending = true; mInputMethodWindow = win; addInputMethodWindowToListLocked(win); imMayMove = false; } else if (type == TYPE_INPUT_METHOD_DIALOG) { mInputMethodDialogs.add(win); addWindowToListInOrderLocked(win, true); moveInputMethodDialogsLocked(findDesiredInputMethodWindowIndexLocked(true)); imMayMove = false; } else { addWindowToListInOrderLocked(win, true); if (type == TYPE_WALLPAPER || type == TYPE_KEYGUARD_WALLPAPER) { getMatchedWallpaperOffsetParams(type).mLastWallpaperTimeoutTime = 0; displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER; } else if (requestWallpaper(win) != 0) { displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER; } else if (mWallpaperTarget != null && mWallpaperTarget.mLayer >= win.mBaseLayer) { // If there is currently a wallpaper being shown, and // the base layer of the new window is below the current // layer of the target window, then adjust the wallpaper. // This is to avoid a new window being placed between the // wallpaper and its target. displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER; } } final WindowStateAnimator winAnimator = win.mWinAnimator; winAnimator.mEnterAnimationPending = true; winAnimator.mEnteringAnimation = true; if (displayContent.isDefaultDisplay) { mPolicy.getInsetHintLw(win.mAttrs, mRotation, outContentInsets, outStableInsets, outOutsets); } else { outContentInsets.setEmpty(); outStableInsets.setEmpty(); } if (mInTouchMode) { res |= WindowManagerGlobal.ADD_FLAG_IN_TOUCH_MODE; } if (win.mAppToken == null || !win.mAppToken.clientHidden) { res |= WindowManagerGlobal.ADD_FLAG_APP_VISIBLE; } mInputMonitor.setUpdateInputWindowsNeededLw(); //[+LEUI-15516] //win.canReceiveKeys return FALSE here when win.mAppToken != null, //because viewVisibility == View.INVISIBLE that set by ActivityThread.handleResumeActivity //[-LEUI-15516] boolean focusChanged = false; if (win.canReceiveKeys()) { focusChanged = updateFocusedWindowLocked(UPDATE_FOCUS_WILL_ASSIGN_LAYERS, false /*updateInputWindows*/); if (focusChanged) { imMayMove = false; } } if (imMayMove) { moveInputMethodWindowsIfNeededLocked(false); } assignLayersLocked(displayContent.getWindowList()); // Don't do layout here, the window must call // relayout to be displayed, so we'll do it there. if (focusChanged) { mInputMonitor.setInputFocusLw(mCurrentFocus, false /*updateInputWindows*/); } mInputMonitor.updateInputWindowsLw(false /*force*/); if (localLOGV || DEBUG_ADD_REMOVE) Slog.v(TAG, "addWindow: New client " + client.asBinder() + ": window=" + win + " Callers=" + Debug.getCallers(5)); else{//[+LEUI-15784] Slog.i(TAG,"addWindow: window="+win); } if (win.isVisibleOrAdding() && updateOrientationFromAppTokensLocked(false)) { reportNewConfig = true; } } if (reportNewConfig) { sendNewConfiguration(); } Binder.restoreCallingIdentity(origId); return res; }
这个方法会作的工作有几点:1、检查当前应用的添加窗口的权限;2、根据我们传进来的窗口布局参数的type类型,判断要添加的窗口类型,同时构建一个WindowToken对象;3、构建WindowState对象,此对象非常重要,它是我们当前Activity在WindowManagerService窗口管理中的代表;4、将ViewRootImpl传进来的一对InputChannel进行实例化并进行注册,因为ViewRootImpl中构建的InputChannel只是空壳,在native层没有对应的描述符,所以需要实例化;5、将构建好的WindowState对象添加到WindowManagerService的类变量mWindowMap中,它是管理所有WindowState对象的;6、调用assignLayersLocked对所有窗口进行重新排布。到这里,我们的Activity的窗口就添加到系统当中了。
好了,回到主题,添加窗口的过程我们大概走了一下,那么窗口层值是什么呢?
在WindowState类中有一个mBaseLayer和一个mSubLayer变量,mBaseLayer表示当前窗口的基本层值,mSubLayer表示当前窗口在子窗口中的层值,关于这两个变量的实际意义,大家如果还不明白,请自行百度。我们来看一下这两个变量的值是怎么定的呢?这两个变量的值都是在WindowState对象的构造方法中确定的,意思也就是说,当系统调用WindowManagerService的addWindow方法为我们的Activity添加窗口的时候,此时目标窗口的层值就可以确定了,层值也会在构造WindowState对象时确定好,而且不会再改变。mSubLayer比较简单,它只在当前窗口有子窗口时才有意义,这种情况下就调用mPolicy.subWindowTypeToLayerLw(a.type)对它进行赋值,否则就赋值为0;mBaseLayer有当前窗口是否有子窗口的情况下都是调用mPolicy.windowTypeToLayerLw()方法进行赋值的,不过两个调用传入的参数不一样。mPolicy是一个WindowManagerPolicy对象,它是在WindowState构造函数中赋值的,也就是WindowManagerService的mPolicy对象,实际是一个PhoneWindowManager,我们来看一下它的windowTypeToLayerLw方法:
@Override public int windowTypeToLayerLw(int type) { if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) { return 2; } switch (type) { case TYPE_PRIVATE_PRESENTATION: return 2; case TYPE_WALLPAPER: case TYPE_KEYGUARD_WALLPAPER: // wallpaper is at the bottom, though the window manager may move it. return 2; case TYPE_PHONE: return 3; case TYPE_SEARCH_BAR: case TYPE_VOICE_INTERACTION_STARTING: return 4; case TYPE_VOICE_INTERACTION: // voice interaction layer is almost immediately above apps. return 5; case TYPE_INPUT_CONSUMER: return 6; case TYPE_SYSTEM_DIALOG: return 7; case TYPE_TOAST: // toasts and the plugged-in battery thing return 8; case TYPE_PRIORITY_PHONE: // SIM errors and unlock. Not sure if this really should be in a high layer. return 9; case TYPE_DREAM: // used for Dreams (screensavers with TYPE_DREAM windows) return 10; case TYPE_SYSTEM_ALERT: // like the ANR / app crashed dialogs return 11; case TYPE_INPUT_METHOD: // on-screen keyboards and other such input method user interfaces go here. return 12; case TYPE_INPUT_METHOD_DIALOG: // on-screen keyboards and other such input method user interfaces go here. return 13; case TYPE_KEYGUARD_SCRIM: // the safety window that shows behind keyguard while keyguard is starting return 14; case TYPE_STATUS_BAR_SUB_PANEL: return 15; case TYPE_STATUS_BAR: return 16; case TYPE_CONTROL_CENTER_PANEL: return 17; case TYPE_STATUS_BAR_PANEL: return 17; case TYPE_KEYGUARD_DIALOG: return 18; case TYPE_VOLUME_OVERLAY: // the on-screen volume indicator and controller shown when the user // changes the device volume return 19; case TYPE_SYSTEM_OVERLAY: // the on-screen volume indicator and controller shown when the user // changes the device volume return 20; case TYPE_NAVIGATION_BAR: // the navigation bar, if available, shows atop most things return 21; case TYPE_NAVIGATION_BAR_PANEL: // some panels (e.g. search) need to show on top of the navigation bar return 22; case TYPE_SYSTEM_ERROR: // system-level error dialogs return 23; case TYPE_MAGNIFICATION_OVERLAY: // used to highlight the magnified portion of a display return 24; case TYPE_DISPLAY_OVERLAY: // used to simulate secondary display devices return 25; case TYPE_DRAG: // the drag layer: input for drag-and-drop is associated with this window, // which sits above all other focusable windows return 26; case TYPE_ACCESSIBILITY_OVERLAY: // overlay put by accessibility services to intercept user interaction return 27; case TYPE_SECURE_SYSTEM_OVERLAY: return 28; case TYPE_BOOT_PROGRESS: return 29; case TYPE_POINTER: // the (mouse) pointer layer return 30; } Log.e(TAG, "Unknown window type: " + type); return 2; } /** * @hide */ @Override public int windowTypeToLayerLw(WindowManager.LayoutParams attr) { int type = attr.type; if (type == WindowManager.LayoutParams.TYPE_TOAST && (attr.leuiFlags & WindowManager.LayoutParams.LEUI_FLAG_TOAST_SHOW_WHEN_LOCKED) != 0) type = WindowManager.LayoutParams.TYPE_KEYGUARD_SCRIM; return windowTypeToLayerLw(type); }
很明显可以看到两个方法的区别,参数为WindowManager.LayoutParams的方法先把type进行了一下换算,然后将换算后的值作为参数,调用了参数为int的方法。从这里我们也可以看出来android系统中的窗口类型,TYPE_WALLPAPER表示壁纸窗口,TYPE_KEYGUARD_WALLPAPER表示锁屏的壁纸,TYPE_SYSTEM_DIALOG表示系统弹窗,比如ANR、异常终止等,TYPE_TOAST表示Toast窗口。大家可以看到在方法开始,直接对比type值如果大于等于FIRST_APPLICATION_WINDOW并且小于等于LAST_APPLICATION_WINDOW,则直接返回2,这里也就是我们最常用的应用程序的窗口Application。FIRST_APPLICATION_WINDOW和LAST_APPLICATION_WINDOW的值定义在WindowManager.LayoutParams中,值分别为1和99。那么我们的目标就非常明确了,应该就是创建ChooserActivity时的mBaseLayer的值大于Toast窗口的mBaseLayer值,才导致Toast窗口被覆盖了,好,有了推断的结论,来验证一下,在源码中加上相应的日志,执行mm,然后push替换,果然,Toast窗口层值为81000,而ChooserActivity的层值为171000,远远大于Toast的窗口层值,那么我们现在要作的就是要找到为什么ChooserActivity的窗口层值这么大了!
从这里往上分析,日志中还可以看到ChooserActivity对象的type值为2014,这个值是怎么来的呢?type值是取的attrs.type,attrs是在ViewRootImpl中调用addToDisplay方法时传进来的,而addToDisplay中的参数又是其类变量mWindowAttributes,它是在setView为调用mWindowAttributes.copyFrom(attrs)从方法参数中拷贝过来的一份,再往上,方法参数attrs是在WindowManagerGlobal的addView方法中传进来的,addView方法整个调用过程只是使用此参数,并未对其值进行修改,那么再往前,此处的attrs就是WindowManagerImpl中传进来的,而WindowManagerImpl中的参数又是在ActivityThread的wm.addView(decor,
l)调用时传入的,也就是我们前面所说的 l,好了,l 就是这个调用的根源了,我们从代码可以看出,如果进入if分支,则 l 的type属性值则被赋值为WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL,它的定义在WindowManager.LayoutParams当中,值正好是2014:public static final int TYPE_STATUS_BAR_PANEL = FIRST_SYSTEM_WINDOW+14,和我们的日志完全对应,也证明我们的分析是正确的。那么结论就出来了,就是因为多任务键截屏后,点击分享时,启动ChoosetActivity的intent不为空并且FLAG_ACTIVITY_ON_TOP标志不等于0,才导致ChoosetActivity的启动布局参数被放大,最终导致其覆盖在Toast系统窗口之上。
好了,这一篇博客就写到这里了,我们要分析的还有Toast的显示过程,因博客篇幅过长,所以Toast显示过程见下一篇博客,静请期待,谢谢大家。
相关文章推荐
- 从问题单处理了解Toast系统窗口显示、Activity窗口创建、WindowManagerService对系统窗口组织排布(二)
- 从问题单处理了解ActivityManagerService
- Android窗口系统第三篇---WindowManagerService中窗口的组织方式
- Android窗口管理服务WindowManagerService显示Activity组件的启动窗口(Starting Window)的过程分析
- Android窗口管理服务WindowManagerService显示Activity组件的启动窗口(Starting Window)的过程分析
- Android窗口管理服务WindowManagerService显示Activity组件的启动窗口(Starting Window)的过程分析
- Android窗口管理服务WindowManagerService显示Activity组件的启动窗口(Starting Window)的过程分析
- Android窗口管理服务WindowManagerService显示Activity组件的启动窗口(Starting Window)的过程分析
- Android窗口管理服务WindowManagerService显示Activity组件的启动窗口(Starting Window)的过程分析
- Android Java Framwork显示Toast(无Activity和Service
- Android应用程序窗口(Activity)与WindowManagerService服务的连接过程分析
- Android窗口管理服务WindowManagerService切换Activity窗口(App Transition)的过程分析
- Android窗口管理服务WindowManagerService对窗口的组织方式分析
- Android窗口管理服务WindowManagerService显示窗口动画的原理分析
- Android窗口管理服务WindowManagerService切换Activity窗口(App Transition)的过程分析
- Android窗口管理服务WindowManagerService计算Activity窗口大小的过程分析
- Android窗口管理服务WindowManagerService显示窗口动画的原理分析
- Android窗口管理服务WindowManagerService计算Activity窗口大小的过程分析
- Android窗口管理服务WindowManagerService对窗口的组织方式分析
- Android窗口管理服务WindowManagerService显示窗口动画的原理分析