Android中WindowManager与WMS的解析
最近在改bug的时候发现在windowManager.addView的时候会发生莫名其妙的崩溃,那个崩溃真的是让你心态爆炸,潜心研究了两天window相关的东西,虽然不是很深奥的东西,本人也只是弄清楚了window的添加逻辑,在此分享给大家:
一、悬浮窗的概念
在android中,无论我们的app界面,还是系统桌面,再或者是手机下方的几个虚拟按键和最上方的状态栏,又或者是一个吐司。。。我们所看到的所有界面,都是由一个个悬浮窗口组成的。
但是这些窗口有不同的级别:
- 系统的是老大,是最高级别,你没见过你下载的什么app把你的下拉菜单盖住了吧-。=
- 其次是每一个应用,都有自己的一个应用级别窗口。
- 在应用之内能创建好多的界面,所以还有一种是应用内的窗口。
基于上述三种,android把悬浮窗划分成三个级别,并通过静态int型变量来表示:
/** *Startofsystem-specificwindowtypes.Thesearenotnormally *createdbyapplications. **/ publicstaticfinalintFIRST_SYSTEM_WINDOW=2000; /** *Endoftypesofsystemwindows. **/ publicstaticfinalintLAST_SYSTEM_WINDOW=2999;
2000~2999:在系统级别的悬浮窗范围内,一般我们要想创建是需要申请权限。
publicstaticfinalintFIRST_SUB_WINDOW=1000; /** *Endoftypesofsub-windows. **/ publicstaticfinalintLAST_SUB_WINDOW=1999;
1000~1999:子窗口级别的悬浮窗,他如果想要创建必须在一个父窗口下。
publicstaticfinalintTYPE_BASE_APPLICATION=1; publicstaticfinalintLAST_APPLICATION_WINDOW=99;
1~99:应用程序级别的悬浮窗,作为每个应用程序的基窗口。
在每段的范围内都有众多个窗口类型,这个具体就不说了,因为太多了根本说不完。。
但是说了这么半天,悬浮窗到底是个啥东西,可能这个名词听得很多,但是仔细想想android中用到的哪个控件还是哪个类叫悬浮窗?没有吧,那么View总该知道吧(不知道别说你是做android的)
其实说白了悬浮窗就是一个被包裹的view。因为除了一个view他还有很多的属性:长宽深度,类型,证书等等东西,只是属性很多而且属性之间的依赖关系有一些复杂而已。简单的来说可以这么理解。
二、WindowManager介绍
上面简单介绍了悬浮窗的概念,而WindowManager是对悬浮窗进行操作的一个媒介。
WindowManager是一个接口,他是继承了ViewManager接口中的三个方法:
publicinterfaceViewManager { publicvoidaddView(Viewview,ViewGroup.LayoutParamsparams); publicvoidupdateViewLayout(Viewview,ViewGroup.LayoutParamsparams); publicvoidremoveView(Viewview); }
windowManage暴露给我们的只是这个三个方法,真的是简单粗暴,但是很实用。
这三个方法看名字就知道含义了,增删改嘛,就不多说啦。
而在上面提到的对于悬浮窗的三种分类,也是WindowManager的内部类:WindowManager.LayoutParams,关于LayoutParams是什么在这里就不多说了。这不是我们的重点。
我们平时想要添加一个悬浮窗,就会使用第一个方法:
WindowManagerwindowManager=getWindowManager(); windowManager.addView(.....);
我们在getWindowManager获取的类,实际上是WindowManager的是WindowManager的实现类:WindowManagerImpl。接下来我们走一下添加悬浮窗的流程。
三、悬浮窗添加流程
入口肯定是从自己的addView中,上面说到了WindowManager的实现类是WindowManagerImpl,来看一下:
@Override publicvoidaddView(@NonNullViewview,@NonNullViewGroup.LayoutParamsparams){ applyDefaultToken(params); mGlobal.addView(view,params,mContext.getDisplay(),mParentWindow); }
这里有两步:第一步是给layoutparams设置一个默认的令牌(就是token这个属性,至于这个干什么的等会再说)
privatevoidapplyDefaultToken(@NonNullViewGroup.LayoutParamsparams){ //设置条件:有默认令牌,而且不是子窗口级别的悬浮窗 if(mDefaultToken!=null&&mParentWindow==null){ if(!(paramsinstanceofWindowManager.LayoutParams)){ thrownewIllegalArgumentException("ParamsmustbeWindowManager.LayoutParams"); } //如果没有令牌就设置默认令牌 finalWindowManager.LayoutParamswparams=(WindowManager.LayoutParams)params; if(wparams.token==null){ wparams.token=mDefaultToken; } } }
然后调用了mGlobal的addView:
publicvoidaddView(Viewview,ViewGroup.LayoutParamsparams, Displaydisplay,WindowparentWindow){ /**进行一系列判空操作。。。**/ if(parentWindow!=null){ parentWindow.adjustLayoutParamsForSubWindow(wparams); }else{ //Ifthere'snoparent,thenhardwareaccelerationforthisviewis //setfromtheapplication'shardwareaccelerationsetting. finalContextcontext=view.getContext(); if(context!=null &&(context.getApplicationInfo().flags &ApplicationInfo.FLAG_HARDWARE_ACCELERATED)!=0){ wparams.flags|=WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; } } ViewRootImplroot; root=newViewRootImpl(view.getContext(),display); view.setLayoutParams(wparams); mViews.add(view); mRoots.add(root); mParams.add(wparams); //dothislastbecauseitfiresoffmessagestostartdoingthings try{ root.setView(view,wparams,panelParentView); }catch(RuntimeExceptione){ //BadTokenExceptionorInvalidDisplayException,cleanup. if(index>=0){ removeViewLocked(index,true); } throwe; } } }
看到WindowManagerGLobal中有三个属性:mViews、mRoots、mParams,可以大胆猜测这个类中保存了我们进程中的所有视图以及相关属性。在这里主要关注一下ViewRootImpl的这个实例对象root,接下来的会走进root的setView中。
ViewRootImpl的setView方法内容有点多,我这里就截取关键的两部分:
1.
intres;/**=WindowManagerImpl.ADD_OKAY;**/ try{ mOrigWindowType=mWindowAttributes.type; mAttachInfo.mRecomputeGlobalAttributes=true; collectViewAttributes(); res=mWindowSession.addToDisplay(mWindow,mSeq,mWindowAttributes, getHostVisibility(),mDisplay.getDisplayId(),mWinFrame, mAttachInfo.mContentInsets,mAttachInfo.mStableInsets, mAttachInfo.mOutsets,mAttachInfo.mDisplayCutout,mInputChannel);
创建了一个名为res的int类型变量,他要获取到的是悬浮窗添加的结果:成功或者失败。
2.
if(res第二部分是res返回失败的所有情况,在添加成功的时候res为OKAY,而非OKAY的情况就是上述情况。
接下来来看一下添加悬浮窗的操作,就是1中mWindowSession.addToDisplay。mWindowSession类型如下:
finalIWindowSessionmWindowSession;在这里其实用到了aidl跨进程通信,最终执行该方法的类是Session:
@Override publicintaddToDisplay(IWindowwindow,intseq,WindowManager.LayoutParamsattrs, intviewVisibility,intdisplayId,RectoutFrame,RectoutContentInsets, RectoutStableInsets,RectoutOutsets, DisplayCutout.ParcelableWrapperoutDisplayCutout,InputChanneloutInputChannel){ returnmService.addWindow(this,window,seq,attrs,viewVisibility,displayId,outFrame, outContentInsets,outStableInsets,outOutsets,outDisplayCutout,outInputChannel); }这个mService就是一个关键了系统类——WindowMamagerService(WMS)。到了这里我们简单过一下思路:在addView之后,通过WindowManagerGlobal进行一些相关配置,传入ViewRootImpl,再通过aidl方式发送给WMS系统服务。
可能有小伙伴会疑惑。好端端的为什么要用aidl实现?最开始本人也有这个疑惑,但是后来想了想所有的窗口无论系统窗口还是第三方app,窗口都是要通过一个类去进行添加允许判断,这里使用aidl是在合适不过的了。我们接着看一下WMS的addWindow方法:
这个addWindow方法又是一段超长的代码,所以也就不全粘,说一下他的简单流程吧,主要是分为三步:权限判断、条件筛选、添加窗口
WMS的addWindow方法:
intres=mPolicy.checkAddPermission(attrs,appOp); if(res!=WindowManagerGlobal.ADD_OKAY){ returnres; }首先进行一个权限判断,
finalWindowManagerPolicymPolicy;WindowManagerPolicy的实现类是PhoneWindowManagerPolicy,看一下他的实现:
又是小一百行的代码,我们拆开来看:
//排除不属于三种类型悬浮窗范围内的type //很明显的三段排除。 if(!((type>=FIRST_APPLICATION_WINDOW&&type<=LAST_APPLICATION_WINDOW) ||(type>=FIRST_SUB_WINDOW&&type<=LAST_SUB_WINDOW) ||(type>=FIRST_SYSTEM_WINDOW&&type<=LAST_SYSTEM_WINDOW))){ returnWindowManagerGlobal.ADD_INVALID_TYPE; } //不是系统级别的悬浮窗直接满足条件 if(typeLAST_SYSTEM_WINDOW){ returnADD_OKAY; } //以下几种不是系统警告类型的系统弹窗,会满足条件,除此之外的使用默认判断的方式 if(!isSystemAlertWindowType(type)){ switch(type){ caseTYPE_TOAST: outAppOp[0]=OP_TOAST_WINDOW; returnADD_OKAY; caseTYPE_DREAM: caseTYPE_INPUT_METHOD: caseTYPE_WALLPAPER: caseTYPE_PRESENTATION: caseTYPE_PRIVATE_PRESENTATION: caseTYPE_VOICE_INTERACTION: caseTYPE_ACCESSIBILITY_OVERLAY: caseTYPE_QS_DIALOG: //Thewindowmanagerwillcheckthese. returnADD_OKAY; } returnmContext.checkCallingOrSelfPermission(INTERNAL_SYSTEM_WINDOW) ==PERMISSION_GRANTED?ADD_OKAY:ADD_PERMISSION_DENIED; } 后面的几段代码会频繁出现最后的这段代码:mContext.checkCallingOrSelfPermission,具体实现的类是ContextFixture:
@Override publicintcheckCallingOrSelfPermission(Stringpermission){ if(mPermissionTable.contains(permission) ||mPermissionTable.contains(PERMISSION_ENABLE_ALL)){ logd("checkCallingOrSelfPermission:"+permission+"returnGRANTED"); returnPackageManager.PERMISSION_GRANTED; }else{ logd("checkCallingOrSelfPermission:"+permission+"returnDENIED"); returnPackageManager.PERMISSION_DENIED; } }这里会使用默认权限判断的方式,要么允许对应权限,要么就是拥有全部权限,否则就会返回DENIED。
这个说完接着回到checkPermission方法。
//对于系统进程直接满足允许 finalintcallingUid=Binder.getCallingUid(); if(UserHandle.getAppId(callingUid)==Process.SYSTEM_UID){ returnADD_OKAY; }说实话下面这一段代码我看的不是很明白,只是看到了这里对8.0之后做了版本限制,直接使用默认检查方式。
ApplicationInfoappInfo; try{ appInfo=mContext.getPackageManager().getApplicationInfoAsUser( attrs.packageName, 0/*flags*/, UserHandle.getUserId(callingUid)); }catch(PackageManager.NameNotFoundExceptione){ appInfo=null; } if(appInfo==null||(type!=TYPE_APPLICATION_OVERLAY&&appInfo.targetSdkVersion>=O)){ return(mContext.checkCallingOrSelfPermission(INTERNAL_SYSTEM_WINDOW) ==PERMISSION_GRANTED)?ADD_OKAY:ADD_PERMISSION_DENIED; }这段是要从PackageManager中获取ApplicationInfo,如果获取失败会抛出NameNotFound异常。所以下面的判断是在异常的时候使用默认权限处理方式。
最后还以一步检查操作,关系不大就不看了。到这里checkPermission方法就结束了。
权限检查的步骤已经结束,接着就是根据上述获取到的结果进行条件筛选。
if(res!=WindowManagerGlobal.ADD_OKAY){ returnres; }首先在权限检查的步骤获取权限失败,那么会直接返回,不会执行条件筛选的步骤。而真正的条件筛选步骤代码也是很多,我这里直接粘过来然后说了。
//111111111111111 if(!mDisplayReady){ thrownewIllegalStateException("Displayhasnotbeeninitialialized"); } finalDisplayContentdisplayContent=getDisplayContentOrCreate(displayId); if(displayContent==null){ Slog.w(TAG_WM,"Attemptedtoaddwindowtoadisplaythatdoesnotexist:" +displayId+".Aborting."); returnWindowManagerGlobal.ADD_INVALID_DISPLAY; } if(!displayContent.hasAccess(session.mUid) &&!mDisplayManagerInternal.isUidPresentOnDisplay(session.mUid,displayId)){ Slog.w(TAG_WM,"Attemptedtoaddwindowtoadisplayforwhichtheapplication" +"doesnothaveaccess:"+displayId+".Aborting."); returnWindowManagerGlobal.ADD_INVALID_DISPLAY; } if(mWindowMap.containsKey(client.asBinder())){ Slog.w(TAG_WM,"Window"+client+"isalreadyadded"); returnWindowManagerGlobal.ADD_DUPLICATE_ADD; } //22222222222222 if(type>=FIRST_SUB_WINDOW&&type<=LAST_SUB_WINDOW){ parentWindow=windowForClientLocked(null,attrs.token,false); if(parentWindow==null){ Slog.w(TAG_WM,"Attemptedtoaddwindowwithtokenthatisnotawindow:" +attrs.token+".Aborting."); returnWindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN; } if(parentWindow.mAttrs.type>=FIRST_SUB_WINDOW &&parentWindow.mAttrs.type<=LAST_SUB_WINDOW){ Slog.w(TAG_WM,"Attemptedtoaddwindowwithtokenthatisasub-window:" +attrs.token+".Aborting."); returnWindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN; } } //333333333333333 if(type==TYPE_PRIVATE_PRESENTATION&&!displayContent.isPrivate()){ Slog.w(TAG_WM,"Attemptedtoaddprivatepresentationwindowtoanon-privatedisplay.Aborting."); returnWindowManagerGlobal.ADD_PERMISSION_DENIED; } //444444444444444 AppWindowTokenatoken=null; finalbooleanhasParent=parentWindow!=null; //Useexistingparentwindowtokenforchildwindowssincetheygointhesametoken //asthereparentwindowsowecanapplythesamepolicyonthem. WindowTokentoken=displayContent.getWindowToken( hasParent?parentWindow.mAttrs.token:attrs.token); //Ifthisisachildwindow,wewanttoapplythesametypecheckingrulesasthe //parentwindowtype. finalintrootType=hasParent?parentWindow.mAttrs.type:type; booleanaddToastWindowRequiresToken=false; if(token==null){ if(rootType>=FIRST_APPLICATION_WINDOW&&rootType<=LAST_APPLICATION_WINDOW){ Slog.w(TAG_WM,"Attemptedtoaddapplicationwindowwithunknowntoken" +attrs.token+".Aborting."); returnWindowManagerGlobal.ADD_BAD_APP_TOKEN; } if(rootType==TYPE_INPUT_METHOD){ Slog.w(TAG_WM,"Attemptedtoaddinputmethodwindowwithunknowntoken" +attrs.token+".Aborting."); returnWindowManagerGlobal.ADD_BAD_APP_TOKEN; } if(rootType==TYPE_VOICE_INTERACTION){ Slog.w(TAG_WM,"Attemptedtoaddvoiceinteractionwindowwithunknowntoken" +attrs.token+".Aborting."); returnWindowManagerGlobal.ADD_BAD_APP_TOKEN; } if(rootType==TYPE_WALLPAPER){ Slog.w(TAG_WM,"Attemptedtoaddwallpaperwindowwithunknowntoken" +attrs.token+".Aborting."); returnWindowManagerGlobal.ADD_BAD_APP_TOKEN; } if(rootType==TYPE_DREAM){ Slog.w(TAG_WM,"AttemptedtoaddDreamwindowwithunknowntoken" +attrs.token+".Aborting."); returnWindowManagerGlobal.ADD_BAD_APP_TOKEN; } if(rootType==TYPE_QS_DIALOG){ Slog.w(TAG_WM,"AttemptedtoaddQSdialogwindowwithunknowntoken" +attrs.token+".Aborting."); returnWindowManagerGlobal.ADD_BAD_APP_TOKEN; } if(rootType==TYPE_ACCESSIBILITY_OVERLAY){ Slog.w(TAG_WM,"AttemptedtoaddAccessibilityoverlaywindowwithunknowntoken" +attrs.token+".Aborting."); returnWindowManagerGlobal.ADD_BAD_APP_TOKEN; } if(type==TYPE_TOAST){ //AppstargetingSDKaboveNMR1cannotarbitraryaddtoastwindows. if(doesAddToastWindowRequireToken(attrs.packageName,callingUid, parentWindow)){ Slog.w(TAG_WM,"Attemptedtoaddatoastwindowwithunknowntoken" +attrs.token+".Aborting."); returnWindowManagerGlobal.ADD_BAD_APP_TOKEN; } } finalIBinderbinder=attrs.token!=null?attrs.token:client.asBinder(); finalbooleanisRoundedCornerOverlay= (attrs.privateFlags&PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY)!=0; token=newWindowToken(this,binder,type,false,displayContent, session.mCanAddInternalSystemWindow,isRoundedCornerOverlay); }elseif(rootType>=FIRST_APPLICATION_WINDOW&&rootType<=LAST_APPLICATION_WINDOW){ atoken=token.asAppWindowToken(); if(atoken==null){ Slog.w(TAG_WM,"Attemptedtoaddwindowwithnon-applicationtoken" +token+".Aborting."); returnWindowManagerGlobal.ADD_NOT_APP_TOKEN; }elseif(atoken.removed){ Slog.w(TAG_WM,"Attemptedtoaddwindowwithexitingapplicationtoken" +token+".Aborting."); returnWindowManagerGlobal.ADD_APP_EXITING; }elseif(type==TYPE_APPLICATION_STARTING&&atoken.startingWindow!=null){ Slog.w(TAG_WM,"Attemptedtoaddstartingwindowtotokenwithalreadyexisting" +"startingwindow"); returnWindowManagerGlobal.ADD_DUPLICATE_ADD; } }elseif(rootType==TYPE_INPUT_METHOD){ if(token.windowType!=TYPE_INPUT_METHOD){ Slog.w(TAG_WM,"Attemptedtoaddinputmethodwindowwithbadtoken" +attrs.token+".Aborting."); returnWindowManagerGlobal.ADD_BAD_APP_TOKEN; } }elseif(rootType==TYPE_VOICE_INTERACTION){ if(token.windowType!=TYPE_VOICE_INTERACTION){ Slog.w(TAG_WM,"Attemptedtoaddvoiceinteractionwindowwithbadtoken" +attrs.token+".Aborting."); returnWindowManagerGlobal.ADD_BAD_APP_TOKEN; } }elseif(rootType==TYPE_WALLPAPER){ if(token.windowType!=TYPE_WALLPAPER){ Slog.w(TAG_WM,"Attemptedtoaddwallpaperwindowwithbadtoken" +attrs.token+".Aborting."); returnWindowManagerGlobal.ADD_BAD_APP_TOKEN; } }elseif(rootType==TYPE_DREAM){ if(token.windowType!=TYPE_DREAM){ Slog.w(TAG_WM,"AttemptedtoaddDreamwindowwithbadtoken" +attrs.token+".Aborting."); returnWindowManagerGlobal.ADD_BAD_APP_TOKEN; } }elseif(rootType==TYPE_ACCESSIBILITY_OVERLAY){ if(token.windowType!=TYPE_ACCESSIBILITY_OVERLAY){ Slog.w(TAG_WM,"AttemptedtoaddAccessibilityoverlaywindowwithbadtoken" +attrs.token+".Aborting."); returnWindowManagerGlobal.ADD_BAD_APP_TOKEN; } }elseif(type==TYPE_TOAST){ //AppstargetingSDKaboveNMR1cannotarbitraryaddtoastwindows. addToastWindowRequiresToken=doesAddToastWindowRequireToken(attrs.packageName, callingUid,parentWindow); if(addToastWindowRequiresToken&&token.windowType!=TYPE_TOAST){ Slog.w(TAG_WM,"Attemptedtoaddatoastwindowwithbadtoken" +attrs.token+".Aborting."); returnWindowManagerGlobal.ADD_BAD_APP_TOKEN; } }elseif(type==TYPE_QS_DIALOG){ if(token.windowType!=TYPE_QS_DIALOG){ Slog.w(TAG_WM,"AttemptedtoaddQSdialogwindowwithbadtoken" +attrs.token+".Aborting."); returnWindowManagerGlobal.ADD_BAD_APP_TOKEN; } }elseif(token.asAppWindowToken()!=null){ Slog.w(TAG_WM,"Non-nullappWindowTokenforsystemwindowofrootType="+rootType); //Itisnotvalidtouseanapptokenwithothersystemtypes;wewill //insteadmakeanewtokenforit(asifnullhadbeenpassedinforthetoken). attrs.token=null; token=newWindowToken(this,client.asBinder(),type,false,displayContent, session.mCanAddInternalSystemWindow); } //5555555555555 finalWindowStatewin=newWindowState(this,session,client,token,parentWindow, appOp[0],seq,attrs,viewVisibility,session.mUid, session.mCanAddInternalSystemWindow); if(win.mDeathRecipient==null){ //Clienthasapparentlydied,sothereisnoreasonto //continue. Slog.w(TAG_WM,"Addingwindowclient"+client.asBinder() +"thatisdead,aborting."); returnWindowManagerGlobal.ADD_APP_EXITING; } if(win.getDisplayContent()==null){ Slog.w(TAG_WM,"AddingwindowtoDisplaythathasbeenremoved."); returnWindowManagerGlobal.ADD_INVALID_DISPLAY; } finalbooleanhasStatusBarServicePermission= mContext.checkCallingOrSelfPermission(permission.STATUS_BAR_SERVICE) ==PackageManager.PERMISSION_GRANTED; mPolicy.adjustWindowParamsLw(win,win.mAttrs,hasStatusBarServicePermission); win.setShowToOwnerOnlyLocked(mPolicy.checkShowToOwnerOnly(attrs)); res=mPolicy.prepareAddWindowLw(win,attrs); if(res!=WindowManagerGlobal.ADD_OKAY){ returnres; } finalbooleanopenInputChannels=(outInputChannel!=null &&(attrs.inputFeatures&INPUT_FEATURE_NO_INPUT_CHANNEL)==0); if(openInputChannels){ win.openInputChannel(outInputChannel); } //666666666666666 if(type==TYPE_TOAST){ if(!getDefaultDisplayContentLocked().canAddToastWindowForUid(callingUid)){ Slog.w(TAG_WM,"AddingmorethanonetoastwindowforUIDatatime."); returnWindowManagerGlobal.ADD_DUPLICATE_ADD; } if(addToastWindowRequiresToken ||(attrs.flags&LayoutParams.FLAG_NOT_FOCUSABLE)==0 ||mCurrentFocus==null ||mCurrentFocus.mOwnerUid!=callingUid){ mH.sendMessageDelayed( mH.obtainMessage(H.WINDOW_HIDE_TIMEOUT,win), win.mAttrs.hideTimeoutMilliseconds); } }这里讲筛选部分大体分成这么几个步骤:
- 系统以及初始化的一些判断:就像最开始的四个判断。
- 子窗口类型时候的对父窗口的相关筛选(父是否为空,以及父亲的类型判断)
- 一种特殊的私有类型条件筛选,该类型属于系统类型
- 涉及证书(token)的窗口类型条件筛选。
- 状态栏权限条件筛选
- 吐司类型的条件筛选
在代码中对应的步骤有明确的标注,而具体的代码大多只是一些判断,所以在感觉没有细说的必要了。
在条件筛选完成之后,剩下的类型都是符合添加的类型,从现在开始就开始对不同的type进行不同的添加。经过多到加工后,将OKAY返回。
如果能从添加窗口的步骤返回,就说明一定是OKAY的。那么我们可以一步步跳回层层调用的代码,最终在ViewRootImpl中,对没有添加成功的抛出异常。
if(res对于OKAY的,在ViewRootImpl中会做一些其他的操作,反正我是没看懂-。=、
四、小结
到这里WMS的添加悬浮窗口的流程差不多就过了一遍了。可能有些地方说的不是很细,大家下来可以关注一下个别几个点。整个过程有这么几个需要强调的地方。
- 函数循环嵌套,共同消费返回值。
- 异常循环嵌套
- 个别地方对M和O以上的系统进行了限制
如果在添加悬浮窗的时候使用了不同的type,可能会发生异常:本人拿了一个8.0的手机,分别对窗口type设置为OVERLAY和ERROR。因为ERROR类型是被弃用的,我发现使用ERROR会抛出异常,而OVERLAY不会。同样的拿了一个6.0的手机添加ERROR类型就没有异常抛出,肯定是上述的问题导致的,但是具体在哪一块我还没有找到,因为整个流程的出口太多了-。=。
此外在WindowManagerGlobal.addView方法中,有一个地方:
if(parentWindow!=null){ parentWindow.adjustLayoutParamsForSubWindow(wparams); }else{这个方法是对于有子窗口类型的证书处理,网上查了一下该方法在四点几、六点几和8.0是不同的,也就是说对证书的处理方式变化了,这里本人还没有细看,有兴趣的盆友可以研究一下然后评论交流一番。
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对毛票票的支持。如果你想了解更多相关内容请查看下面相关链接