关于WindowManager在Android(安卓)N和Android(安卓)N以下表现差异的分析总结
1. 问题描述
通过WindowManager往窗口里添加浮动按钮,在Android7.0时该按钮可以全局保留,直至进程被杀掉。而Android7.0以下(以Android4.4为例)浮动按钮随Activity的onStop()方法被覆盖。
以下为浮动按钮的实现代码:
WindowManager mWm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);WindowManager.LayoutParams params = new WindowManager.LayoutParams();params.type= LayoutParams.TYPE_PHONE; // 系统提示类型,重要params.format=1;params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; params.flags = params.flags | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;params.flags = params.flags | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS; params.width = dipToPixel(60);params.height = dipToPixel(60);params.x = dipToPixel(140);params.y = dipToPixel(230);View view = LayoutInflater.from(mContext).inflate(R.layout.float_view, null);mWm.addView(view, params);
FloatView floatView = new FloatView(this); //传入Activity Context
2.解决方案
考虑到可能由于传入的Context为Activity导致的问题,进行修改后发现浮动按钮在Android4.4下正常运行。
FloatView floatView = new FloatView(getApplicationContext());
以下则以两方面分析差异原因:
* 使用Activity表现不同的原因。
* 使用Application表现相同的原因。
3.原因分析
##### * 使用Activity表现不同的原因。
首先,可以了解到Activity对getSystemService方法进行了重写:
@Override public Object getSystemService(@ServiceName @NonNull String name) { if (getBaseContext() == null) { throw new IllegalStateException( "System services not available to Activities before onCreate()"); } if (WINDOW_SERVICE.equals(name)) { return mWindowManager; } else if (SEARCH_SERVICE.equals(name)) { ensureSearchManager(); return mSearchManager; } return super.getSystemService(name); }
WindowManager
在Activity初始化的时候就已经被创建。mWindowManager
变量在ActivityThread的handleLaunchedActivity
的activity.attach()
方法中赋值。最终该变量指向了WindowManagerImpl
对象。
public WindowManagerImpl createLocalWindowManager(Window parentWindow) { return new WindowManagerImpl(mContext, parentWindow); }
此处的parentWindow
为当前Activity的Window
。
之后使用WindowManager
的addView
方法添加按钮,最终会调用WindowManagerGlobal
的addView
方法。其中该方法有如下语句:
if (parentWindow != null) { parentWindow.adjustLayoutParamsForSubWindow(wparams); }
从该该方法开始,表现出现差异:
//Android 7.0 void adjustLayoutParamsForSubWindow(WindowManager.LayoutParams wp) { CharSequence curTitle = wp.getTitle(); if (wp.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW && wp.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) { if (wp.token == null) { View decor = peekDecorView(); if (decor != null) { wp.token = decor.getWindowToken(); } } //... } else if (wp.type >= WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW && wp.type <= WindowManager.LayoutParams.LAST_SYSTEM_WINDOW) { if (curTitle == null || curTitle.length() == 0) { final StringBuilder title = new StringBuilder(32); title.append("Sys").append(wp.type); if (mAppName != null) { title.append(":").append(mAppName); } wp.setTitle(title); } } else { if (wp.token == null) { wp.token = mContainer == null ? mAppToken : mContainer.mAppToken; } if ((curTitle == null || curTitle.length() == 0) && mAppName != null) { wp.setTitle(mAppName); } } //... }
//Android 4.4 void adjustLayoutParamsForSubWindow(WindowManager.LayoutParams wp) { CharSequence curTitle = wp.getTitle(); if (wp.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW && wp.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) { if (wp.token == null) { View decor = peekDecorView(); if (decor != null) { wp.token = decor.getWindowToken(); } } //... } else { if (wp.token == null) { wp.token = mContainer == null ? mAppToken : mContainer.mAppToken; } if ((curTitle == null || curTitle.length() == 0) && mAppName != null) { wp.setTitle(mAppName); } } //... }
代码大概的逻辑是判断该LayoutParams
的type
是属于应用窗口级别,还是系统窗口级别。从而为LayoutParams
添加对应的token
。
此处token
可以用来绑定Activity
和WindowManager
。当视图发生变更需要重新遍历或者调用onStop覆盖掉当前Activity时,就可以通过该token找到对应的ViewRootImpl
,从而进行View树的相关操作。
在Android7.0时,系统窗口没有设置token,4.4时,token设置为默认的mAppToken
,即Activity启动时由AMS
创建并传递过来的IBinder
对象。
在Activity进入后台后,会调用Activity中的performStop方法,方法中执行以下语句:
WindowManagerGlobal.getInstance().setStoppedState(mToken, true);
从而调用了setStoppedState方法:
public void setStoppedState(IBinder token, boolean stopped) { synchronized (mLock) { int count = mViews.size(); for (int i = 0; i < count; i++) { //token是Activity的mToken 不为空 if (token == null || mParams.get(i).token == token) { ViewRootImpl root = mRoots.get(i); root.setWindowStopped(stopped); } } } }
当LayoutParams的token
与Activity的token
匹配时,则取出ViewRootImpl
调用setWindowStopped
方法,从而执行onStop过程的后续操作。
Android7.0 token为null,不匹配,无法进入判断,则不进行对浮动按钮的后续操作,所以会常驻留窗口。
Android4.4 token为Activity持有的mToken
,匹配,会执行后续操作。
##### * 使用Application表现相同的原因。
Application没有对getSystemService
进行重写,全部实现交由ContextImpl
实现。
@Override public Object getSystemService(String name) { return SystemServiceRegistry.getSystemService(this, name); }
上述从SystemServiceRegistry
取出相应服务。SystemServiceRegistry
在APP创建时会初始化各种服务。其中包括WindowManager
。
registerService(Context.WINDOW_SERVICE, WindowManager.class, new CachedServiceFetcher() { @Override public WindowManager createService(ContextImpl ctx) { return new WindowManagerImpl(ctx);}});
通过Application的getSystemService
获得的WindowManager
仍然是WindowManagerImpl
对象。Application和Activity创建的区别也在此处。
//Application调用public WindowManagerImpl(Context context) { this(context, null); }//Activity调用private WindowManagerImpl(Context context, Window parentWindow) { mContext = context; mParentWindow = parentWindow;}
Application获得的WindowManagerImpl
的mParentWindow
属性为空。这样会在addView
时跳过token的赋值。
if (parentWindow != null) { parentWindow.adjustLayoutParamsForSubWindow(wparams); }
从而在setStopState
方法判断mParams.get(i).token == token
中不论是7.0还是4.4中token都为null,不匹配,无法进入后续的stop操作,浮动按钮就常驻窗口。
4. 个人理解
Android系统为每个Activity初始化时添加一个本地的WindowManager
引用。由于Activity涉及频繁的View操作,对用WindowManager
的使用频率较高,提前初始化对于效率会是一种提升。
更多相关文章
- Android(安卓)学习路线总结
- 网络请求框架AsyncHttpclient的简单使用
- Android(安卓)UI控件之RadioGroup、RadioButton
- Android(安卓)MediaPlayer类详解
- 关于Android如何禁止屏幕旋转刷新界面
- android studio更改module名字
- 获取Google Maps API 指纹证书时获取的是SHA1型的,而认证时需要的
- Android四大组件之 BroadcastReceiver
- android中BroadCastReceiver使用(广播的接受和发送)