首先来看一张Android架构图,我们会发现WindowManager是属于Application Framework层的,即应用程序框架层,这一层的定义是:这一层是编写Google发布的核心应用时所使用的API框架,开发人员同样可以使用这些框架来开发自己的应用,这样便简化了程序开发的结构设计,但是必须要遵守其框架的开发原则。WindowManager就是其中一个系统提供的API框架。
WindowManager Service 是全局的,是唯一的。Window Manager 将用户的操作,翻译成为指令,发送给呈现在界面上的各个Window。Activity会将顶级的控件注册到 Window Manager 中,当用户真是触碰屏幕或键盘的时候,Window Manager就会通知到,而当控件有一些请求产生,也会经由ViewParent送回到Window Manager中。从而完成整个通信流程。
整个Android的窗口机制是基于WindowManager,这个接口可以添加view到屏幕,也可以从屏幕删除view。Android系统中的“窗口”类型虽然很多,但只有两大类是经常使用的:一是由系统进程管理的,称之为“系统窗口”;第二个就是由应用程序产生的,用于显示UI界面的“应用窗口”。
WindowManager 是一个负责统筹管理所有窗口的一个服务,从始到终一直在运作。之所以扯上WMS,因为它才是大Boss,所有的窗口变化都要通知到它。而WindowManager虽然与它没有之间的关系,但是对它负责,所有信息会经过一定的途径传回到WMS中。其实我们的Activity或者Diolog底层的实现也是通过WindowManager,这个 WindowManager是全局的,整个系统就是这个唯一的东东。它是显示View的最底层了。所以如果我们想显示一个界面,可以直接通过WindowManager来进行操作。
创建系统顶级窗口,实现悬浮窗口效果
先看如何用WindowManager创建一个窗口:
Button bb = new Button(getApplicationContext());
//在Activity和Service中都可以直接使用这个方法来获得WindowManager。其getSystemService返回的是一个WindowManagerImpl对象,这是一个存在于本地进程中的一个对象。而事实是 WindowManagerImpl 继承了WindowManager,而WindowManger继承了ViewManager。
WindowManager wmManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
//LayoutParams里面存放着的是窗口的属性,通过这个变量,可以为窗口赋予各式的属性。也可以改变它的属性值,来进行各种各样的操作,像悬浮窗口的拖动,拉伸等操作。
WindowManager.LayoutParams wmParams = new WindowManager.LayoutParams();
//类型: 应用程序窗口
wmParams.type = WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW ;
//不允许屏幕截图(即使使用手机助手的预览功能也不能看)
wmParams.flags = WindowManager.LayoutParams.FLAG_SECURE;
//设置坐标值,默认中心点是屏幕中央
wmParams.x=0;
wmParams.y=-200;
//宽度
wmParams.width = 400;
//高度
wmParams.height = 400;
//透明度,1.0表示不透明,0.0表示全透明
wmParams.alpha = 0.5f;
//创建View
wmManager.addView(bb, wmParams);
重点是addView(View view, ViewGroup.LayoutParams params);
第一个参数定义是窗口的界面,第二个参数是LayoutParams,定义窗口属性
LayoutParams属性介绍:
需要添加权限,注意Android6.0以上的系统对这个权限是动态控制的,如果不动态控制需要将应用的targetSdkVersion设置为6.0以下
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
WindowManager可以进行以下操作
(1)窗口添加
public void addView(View view, ViewGroup.LayoutParams params);
(2)窗口更新
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
(3)窗口删除
public void removeView(View view);
以上的三个方法都存在于ViewManager中。
其实系统的一些提示框也是WindowManager来实现的,比如说Toast,我们看一下它的源码:
public void handleShow(IBinder windowToken) {
if (localLOGV) Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView
+ " mNextView=" + mNextView);
if (mView != mNextView) {
// remove the old view if necessary
handleHide();
mView = mNextView;
Context context = mView.getContext().getApplicationContext();
String packageName = mView.getContext().getOpPackageName();
if (context == null) {
context = mView.getContext();
}
mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
// We can resolve the Gravity here by using the Locale for getting
// the layout direction
final Configuration config = mView.getContext().getResources().getConfiguration();
final int gravity = Gravity.getAbsoluteGravity(mGravity, config.getLayoutDirection());
mParams.gravity = gravity;
if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) {
mParams.horizontalWeight = 1.0f;
}
if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) {
mParams.verticalWeight = 1.0f;
}
mParams.x = mX;
mParams.y = mY;
mParams.verticalMargin = mVerticalMargin;
mParams.horizontalMargin = mHorizontalMargin;
mParams.packageName = packageName;
mParams.hideTimeoutMilliseconds = mDuration ==
Toast.LENGTH_LONG ? LONG_DURATION_TIMEOUT : SHORT_DURATION_TIMEOUT;
mParams.token = windowToken;
if (mView.getParent() != null) {
if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
mWM.removeView(mView);
}
if (localLOGV) Log.v(TAG, "ADD! " + mView + " in " + this);
mWM.addView(mView, mParams);
trySendAccessibilityEvent();
}
}
重点是 mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);和 mWM.addView(mView, mParams);这两句话,所以我们完全可以仿照源码写一个自己的Toast
Toast的属性
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
params.format = PixelFormat.TRANSLUCENT;
params.windowAnimations = com.android.internal.R.style.Animation_Toast;
params.type = WindowManager.LayoutParams.TYPE_TOAST;
params.setTitle("Toast");
params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
final Configuration config = mView.getContext().getResources().getConfiguration();
final int gravity = Gravity.getAbsoluteGravity(mGravity, config.getLayoutDirection());
mParams.gravity = gravity;
if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) {
mParams.horizontalWeight = 1.0f;
}
if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) {
mParams.verticalWeight = 1.0f;
}
mParams.x = mX;
mParams.y = mY;
mParams.verticalMargin = mVerticalMargin;
mParams.horizontalMargin = mHorizontalMargin;
mParams.packageName = packageName;
mParams.hideTimeoutMilliseconds = mDuration ==
Toast.LENGTH_LONG ? LONG_DURATION_TIMEOUT : SHORT_DURATION_TIMEOUT;
mParams.token = windowToken;
PopWindow的属性
private WindowManager.LayoutParams createPopupLayoutParams(IBinder token) {
final WindowManager.LayoutParams p = new WindowManager.LayoutParams();
// These gravity settings put the view at the top left corner of the
// screen. The view is then positioned to the appropriate location by
// setting the x and y offsets to match the anchor's bottom-left
// corner.
p.gravity = computeGravity();
p.flags = computeFlags(p.flags);
p.type = mWindowLayoutType;
p.token = token;
p.softInputMode = mSoftInputMode;
p.windowAnimations = computeAnimationResource();
if (mBackground != null) {
p.format = mBackground.getOpacity();
} else {
p.format = PixelFormat.TRANSLUCENT;
}
if (mHeightMode < 0) {
p.height = mLastHeight = mHeightMode;
} else {
p.height = mLastHeight = mHeight;
}
if (mWidthMode < 0) {
p.width = mLastWidth = mWidthMode;
} else {
p.width = mLastWidth = mWidth;
}
p.privateFlags = PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH
| PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME;
// Used for debugging.
p.setTitle("PopupWindow:" + Integer.toHexString(hashCode()));
return p;
}
参考文章:
decorView和window之间的层级及关系
Android DecorView浅析