(八)理解Window和WindowManager
Android的所有的视图(Activity、Dialog、Toast等的View)都是通过Window来呈现的。
Window实际是View的直接管理者。
WindowManager.png
8.1 Window和WindowManager
如何使用一个WindowManager添加一个Window:
WindowManager.addView(View v , WindowManager.LayoutParams lp) 。示例源码如下:首先添加权限:
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
public class MainActivity extends Activity implements View.OnTouchListener {
private Button mCreateWindowButton;
private Button mFloatingButton;
private WindowManager.LayoutParams mLayoutParams;
private WindowManager mWindowManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
}
private void initView() {
mCreateWindowButton = (Button) findViewById(R.id.button1);
mWindowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
}
public void onButtonClick(View v) {
if (v == mCreateWindowButton) {
mFloatingButton = new Button(this);
mFloatingButton.setText("click me");
mLayoutParams = new WindowManager.LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, 0, 0,
PixelFormat.TRANSPARENT);
mLayoutParams.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL
| LayoutParams.FLAG_NOT_FOCUSABLE
| LayoutParams.FLAG_SHOW_WHEN_LOCKED;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
mLayoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
}else {
mLayoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
}
mLayoutParams.gravity = Gravity.LEFT | Gravity.TOP;
mLayoutParams.x = 100;
mLayoutParams.y = 300;
mFloatingButton.setOnTouchListener(this);
mWindowManager.addView(mFloatingButton, mLayoutParams);
}
}
@Override
public boolean onTouch(View v, MotionEvent event) {
int rawX = (int) event.getRawX();
int rawY = (int) event.getRawY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
break;
}
case MotionEvent.ACTION_MOVE: {
int x = (int) event.getX();
int y = (int) event.getY();
mLayoutParams.x = rawX;
mLayoutParams.y = rawY;
mWindowManager.updateViewLayout(mFloatingButton, mLayoutParams);
break;
}
case MotionEvent.ACTION_UP: {
break;
}
default:
break;
}
return false;
}
@Override
protected void onDestroy() {
try {
mWindowManager.removeView(mFloatingButton);
} catch (IllegalArgumentException e) {
e.printStackTrace();
}
super.onDestroy();
}
}
效果图:
Window.jpg
8.2 Window的内部机制
Window是一个抽象的概念,每一个Window都对应着一个View和一个ViewRootImpl,Window和View通过ViewRootImpl建立联系。
WindowManager对Window主要有三大操作:添加、更新和删除。这三个方法主要是定义在ViewManager接口中:
public interface ViewManager {
public void addView(View view, ViewGroup.LayoutParams params);//添加过程
public void updateViewLayout(View view, ViewGroup.LayoutParams params);//更新过程
public void removeView(View view);//删除过程
}
WindowManager也是一个接口,继承了ViewManager接口:
public interface WindowManager extends ViewManager {}
WindowManagerImpl 实现了WindowManager:源码如下:核心还是添加、更新和删除3个方法。
public final class WindowManagerImpl implements WindowManager{
@Override
public void addView(View view, ViewGroup.LayoutParams params){
mGlobal.addView(view, params, mDisplay, mParentWindow);
}
@Override
public void updateViewLayout(View view, ViewGroup.LayoutParams params){
mGlobal.updateViewLayout(view, params);
}
@Override
public void removeView(View view){
mGlobal.removeView(view, false);
}
}
WindowManagerImpl并没有直接实现Window的三大操作,而是交给了WindowManagerGlobal。WindowManagerGlobal以单例模式向外提供自己的实例:
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
8.2.1 Window的添加过程
1、检查参数是否合法,如果是子Window那么还需要调整一些布局参数
2、创建ViewRootImpl并将View添加到列表中
3、通过ViewRootImpl来更新界面并完成Window的添加过程
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.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}
8.2.2 Window的删除过程
public void removeView(View view, boolean immediate) {
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
synchronized (mLock) {
int index = findViewLocked(view, true);
View curView = mRoots.get(index).getView();
removeViewLocked(index, immediate);
if (curView == view) {
return;
}
throw new IllegalStateException("Calling with view " + view
+ " but the ViewAncestor is attached to " + curView);
}
}
8.2.3 Window的更新过程
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
view.setLayoutParams(wparams);
synchronized (mLock) {
int index = findViewLocked(view, true);
ViewRootImpl root = mRoots.get(index);
mParams.remove(index);
mParams.add(index, wparams);
root.setLayoutParams(wparams, false);
}
}
8.3 Window的创建过程
View是Android中得视图得呈现方式,但是View不能单独存在,它必须附着Window这个抽象得概念上马,因此有视图得地方就有Window。
8.3.1 Activity的Window的创建过程
步骤:
1、如果没有DecorView,那么就创建它;
2、将View添加到DecorView得mContentParent中;
3、回调Activity得onContentChanged方法通知Activity视图已经发生改变。
8.3.2 Dialog的Window的创建过程
步骤:
1、创建Window;
2、初始化DecorView并将Dialog得视图添加到DecorView中,
3、将DecorView添加到Window中显示。
8.3.3 Toast的Window的创建过程
Toast的Window的创建过程与Dialog的Window的创建过程类似,但是由于Toast具有定时取消的功能,所以系统采用了Handler。