关键字:Window WindowManager Android
转载请注明链接:http://blog.csdn.net/feather_wch/article/details/79186967
- 以面试题形式总结Window、WindowManager所有知识点
- 总结Window和WindowManager的基本使用以及概念
- 分析Window的内部机制
- 分析Activity、Dialog、Toast的Window创建过程
版本:2018/2/13-1
1、Window是什么?
- 表示一个窗口的概念,是所有
View
的直接管理者,任何视图都通过Window
呈现(单击事件由Window->DecorView->View; Activity的setContentView
底层通过Window
完成)Window
是一个抽象类,具体实现是PhoneWindow
- 创建
Window
需要通过WindowManager
创建WindowManager
是外界访问Window
的入口Window
具体实现位于WindowManagerService
中WindowManager
和WindowManagerService
的交互是通过IPC
完成
2、如果通过WindowManager
添加Window
(代码实现)?
//1. 控件
Button button = new Button(this);
button.setText("Window Button");
//2. 布局参数
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT, 0, 0, PixelFormat.TRANSPARENT);
layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
layoutParams.gravity = Gravity.LEFT | Gravity.TOP;
layoutParams.x = 100;
layoutParams.y = 300;
// 必须要有type不然会异常: the specified window type 0 is not valid
layoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
//3. 获取WindowManager并添加控件到Window中
WindowManager windowManager = getWindowManager();
windowManager.addView(button, layoutParams);
- 注意一定要指定布局类型
layoutParams.type
- 需要动态申请
Draw over other apps
权限:http://blog.csdn.net/feather_wch/article/details/79185045
3、LayoutParams的flags
属性
Flags | 解释 |
---|---|
FLAG_NOT_FOCUSABLE | 表示Window 不需要焦点,会同时启用FLAG_NOT_TOUCH_MODAL , 最终事件会直接传递到下层具有焦点的Window |
FLAG_NOT_TOUCH_MODEL | 将当前Window 区域以外的单击事件传递给底层Window ,当前区域内的单击事件自己处理(如果不开启,其他Window 会无法收到单击事件) |
FLAG_SHOW_WHEN_LOCKED | 可以让Window 显示在锁屏的界面上 |
4、LayoutParams的type
属性
Window类型 | 含义 | Window层级 | Type参数 |
---|---|---|---|
应用Window | 对应着一个Activity | 1~99(视图最下层) | |
子Window | 不能单独存在,需要附属在特定的父Window 之中(如Dialog就是子Window) |
1000~1999 | |
系统Window | 需要声明权限才能创建的Window ,比如Toast 和系统状态栏 |
2000~2999(视图最上层) | TYPE_SYSTEM_OVERLAY / TYPE_SYSTEM_ERROR |
需要在AndroidManifest中声明权限:
SYSTEM_ALERT_WINDOW
5、WindowManager的三个主要功能:添加、更新、删除View
public interface ViewManager
{
public void addView(View view, ViewGroup.LayoutParams params); //添加View
public void updateViewLayout(View view, ViewGroup.LayoutParams params); //更新View
public void removeView(View view); //删除View
}
6、通过WindowManager实现拖动View的效果
- 给
View
设置onTouchListener
监听器- 在
onTouch
方法中根据当前坐标,来更新ViewupdateViewLayout
mButton.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
int rawX = (int) event.getRawX();
int rawY = (int) event.getRawY();
switch (event.getAction()){
case MotionEvent.ACTION_MOVE:
mLayoutParams.x = rawX;
mLayoutParams.y = rawY;
mWindowManager.updateViewLayout(mButton, mLayoutParams);
break;
}
return false;
}
});
7、Window概念解析
- Window和View通过
ViewRootImpl
建立联系Window
并不是实际存在的,而是以View
的形式存在WindowManager
的三个接口方法也是针对View
的- 实际使用中无法直接访问Window,必须通过
WindowManager
- View是视图的呈现方式,但是不能单独存在,必须依附在
Window
这个抽象的概念上
8、Window的添加过程addView
WindowManager
是一个接口,真正实现类是WindowManagerImpl
,并最终以代理模式交给WindowManagerGlobal
实现
//WindowManagerImpl中的三大方法都交给WindowManangerGlobal实现
public void addView(View view, ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.updateViewLayout(view, params);
}
public void removeView(View view) {
mGlobal.removeView(view, false);
}
WindowManagerGlobal
的setView
会通过ViewRootImpl
的setView
建立View
和Window
的联系
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {
//1. 检查参数是否合法
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");}
//2. 如果是子Window会调整布局参数
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
if (parentWindow != null) {
parentWindow.adjustLayoutParamsForSubWindow(wparams);
} else {
//3. 如果没有父Window则根据该app的硬件加速设置,设置给该View
...
}
synchronized (mLock) {
//...省略....
//4. 创建Window所对应的ViewRootImpl
ViewRootImpl root = new ViewRootImpl(view.getContext(), display);
//5. 将Window对应的ViewRootImpl、View、布局参数添加到列表中
view.setLayoutParams(wparams);
mViews.add(view);//将Window对应的View添加到列表中
mRoots.add(root);//将ViewRootImpl添加到列表中
mParams.add(wparams);//将Window对应的布局参数添加到列表中
//6. 通过ViewRootImpl完成View的绘制过程,以及Window的添加过程
try {
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
//...
}
}
}
- 通过ViewRootImpl的setView完成View的绘制过程,以及Window的添加过程
//ViewRootImpl.java
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
...
//1. 完成异步刷新请求-进行View绘制
requestLayout();
...
//2. WindowSession的类型是IWindowSession,Binder对象(IPC调用),真正实现类是Session
res = mWindowSession.addToDisplay(mWindow, ...);
...
}
//ViewRootImpl.java: 完成异步刷新请求(绘制View)
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
//1. 实际是View绘制的入口(测量、布局、重绘)
scheduleTraversals();
}
}
//Session.java
public int addToDisplay(IWindow window, ...) {
//通过`WindowManagerService`实现Window的添加
return mService.addWindow(this, window, ...);
}
9、Window的删除过程removeView
如同addView
一样最终由WindowManagerGlobal
实现:
//WindowManagerImpl.java
public void removeView(View view) {
mGlobal.removeView(view, false);
}
//WindowManagerGlobal.java
public void removeView(View view, boolean immediate) {
...
synchronized (mLock) {
//1. 查询待删除的View的索引
int index = findViewLocked(view, true);
View curView = mRoots.get(index).getView();
//2. 进行进一步删除
removeViewLocked(index, immediate);
...
}
}
//WindowManagerGlobal.java
private void removeViewLocked(int index, boolean immediate) {
ViewRootImpl root = mRoots.get(index);
View view = root.getView();
...
//1. ViewRoot调用die,发送删除请求后立即返回,此时View并没有完成删除
boolean deferred = root.die(immediate);
if (view != null) {
view.assignParent(null);
//2. 将View添加到等待删除的列表中
if (deferred) {
mDyingViews.add(view);
}
}
}
//ViewRootImpl.java
boolean die(boolean immediate) {
//1. 如果是同步删除,直接调用doDie
if (immediate && !mIsInTraversal) {
doDie();
return false;
}
//2. 如果是异步删除,发送消息。最终由Handler接收并且调用`doDie()`
mHandler.sendEmptyMessage(MSG_DIE);
return true;
}
//ViewRootImpl.java: 同步删除
void doDie() {
...
synchronized (this) {
//1. 删除`view`
dispatchDetachedFromWindow();
...
}
//2. 刷新数据,将Window对应的所有对象从列表中删除
WindowManagerGlobal.getInstance().doRemoveView(this);
}
//ViewRootImpl.java: 删除View
void dispatchDetachedFromWindow() {
/**=================================
* 1. 调用View的dispatchDetachedFromWindow
* -内部会调用onDetachedFromWindow和onDetachedFromWindowInternal
* -onDetachedFromWindow()在View删除时调用,可以进行:终止动画和线程等操作
*=================================*/
if (mView != null && mView.mAttachInfo != null) {
mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(false);
mView.dispatchDetachedFromWindow();
}
//2. 【IPC】调用`Session`的方法删除`Window`, 最终调用WMS的`removeWindow()`
mWindowSession.remove(mWindow);
......
}
WindowManager
中提供了两种删除接口:removeView
异步删除、removeViewImmediate
同步删除(不建议使用)
11、Window的更新过程updateViewLayout
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
...
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
//1. 给View设置新布局
view.setLayoutParams(wparams);
//2. 按照index从列表中删除旧布局,并添加新布局
synchronized (mLock) {
int index = findViewLocked(view, true);
ViewRootImpl root = mRoots.get(index);
mParams.remove(index);
mParams.add(index, wparams);
//3. 更新ViewRootImpl中的布局(会对View进行测量、布局、重绘,)
root.setLayoutParams(wparams, false);
}
}
- 和添加删除类似,最终调用
WindowManagerGlobal
的updateViewLayout
方法root.setLayoutParams
会对View进行重新布局——测量、布局、重绘root.setLayoutParams
还会通过WindowSession
更新Window
的视图——最终通过WindowManagerService
的relayoutWindow()
实现(IPC)
12、Activity的启动过程
启动最终由主线程(ActivityThread)的handleLaunchActivity()处理:
//ActivityThread.java : 处理Activity的创建
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
//1. 创建Activity前进行初始化
WindowManagerGlobal.initialize();
//2. 完成整个启动过程
Activity a = performLaunchActivity(r, customIntent);
if (a != null) {
//3. 内部调用onResume()
handleResumeActivity(r.token, false, r.isForward, ......);
......
}
......
}
//ActivityThread.java
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
//1. 创建Context
ContextImpl appContext = createBaseContextForActivity(r);
//2. 类加载器创建Activity
java.lang.ClassLoader cl = appContext.getClassLoader();
activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
//3. 创建Application
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
//4. window
Window window = null;
if (r.mPendingRemoveWindow != null && r.mPreserveWindow) {
window = r.mPendingRemoveWindow;
...
}
//5. 给Activity关联Context、创建所属于的Window对象并设置回调接口
activity.attach(appContext, this, ...,window, ...);
//6. 调用onCreate()-最终完成整个启动过程
if (r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
mInstrumentation.callActivityOnCreate(activity, r.state);
}
return activity;
}
//Activity.java
final void attach(Context context, ..., Window window, ...) {
//1. 创建PhoneWindow
mWindow = new PhoneWindow(this, window, activityConfigCallback);
/**=======================================================
* 2. `Activity`实现了`window`的`callback接口`
* -外界状态改变时会回调Activity的方法:
* -dispatchTouchEvent(处理屏幕点击事件)
* -onAttachedToWindow(window被关联到WindowManager时调用)
* -...
*=========================================================*/
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
...
//3. 建立Window和WindowManager的联系
mWindow.setWindowManager((WindowManager)context.getSystemService(Context.WINDOW_SERVICE), ...);
if (mParent != null) {
mWindow.setContainer(mParent.getWindow());
}
mWindowManager = mWindow.getWindowManager();
mCurrentConfig = config;
...
}
//Window.java内部接口:
public interface Callback {
//1. 事件分发机制源头
public boolean dispatchTouchEvent(MotionEvent event);
public boolean dispatchKeyEvent(KeyEvent event);
public void onAttachedToWindow();
public void onDetachedFromWindow();
......
}
13、* 老版Window
对象的创建是通过PolicyManager
的makeNewWindow
方法实现:
PolicyManager
是一个策略类- Activity的
Window
就是通过PolicyManager
的一个工厂方法创建PolicyManager
实现的工厂方法全部在策略接口IPolicy
中声明PolicyManager
的实现类是Policy
类
//Window的实现类就是`PhoneWindow`
public Window makeNewWindow(Context context){
return new PhoneWindow(context);
}
14、Activity的视图附加到Window的流程分析(setContentView)
//Activity.java
public void setContentView(View view) {
//通过PhoneWindow的setContentView
getWindow().setContentView(view);
initWindowDecorActionBar();
}
//PhoneWindow.java
public void setContentView(int layoutResID) {
//1. 创建DecorView
if (mContentParent == null) {
installDecor();
}
//2. 将Activity视图添加到mContentParent中(android.R.id.content)
mLayoutInflater.inflate(layoutResID, mContentParent);
/**========================================
* 3. 回调Activity的onContentChanged()方法
* -用于通知Activity视图发生改变
* -onContentChanged()是空实现可以自定义处理
*========================================*/
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
...
}
//PhoneWindow.java
private void installDecor() {
mForceDecorInstall = false;
//1. DecorView不存在, 则创建
if (mDecor == null) {
mDecor = generateDecor(-1);
...
} else {
//2. DecorView存在,则与PhoneWindow关联
mDecor.setWindow(this);
}
//3. 加载具体布局文件,到DecorView中
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
...
}
}
/**=============================================================
* `DecorView`的真正显示过程
* -通过onCreate()仅仅是将`Activity`的布局添加到`DecorView`
* -`DecorView`添加到`Window`中后才能真正显示出来
* -`DecorView`是在主线程的handleResumeActivity()中添加到Window并显示
* //ActivityThread.java
*=============================================================*/
final void handleResumeActivity(IBinder token, ...) {
//1. onResume()
r = performResumeActivity(token, clearHide, reason);
//2. 完成`DecorView`的添加和显示两个过程
r.activity.makeVisible();
...
}
//Activity.java
void makeVisible() {
//1. 将`DecorView`添加到`Window`中(通过WindowManager)
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
//2. 将DecorView显示出来
mDecor.setVisibility(View.VISIBLE);
}
15、Dialog的Window创建过程详解
Dialog的使用:
AlertDialog alertDialog = new AlertDialog.Builder(this).setTitle("AlerDialog!").create();
alertDialog.show();
Dialog的流程源码:
//Dialog.java: 显示Dialog
public void show() {
if (mShowing) {
if (mDecor != null) {
//1. 如果对应DecorView已经存在,直接显示
mDecor.setVisibility(View.VISIBLE);
}
return;
}
//2. 获取DecorView
mDecor = mWindow.getDecorView();
//3. 通过WM的addView将Dialog进行显示
WindowManager.LayoutParams l = mWindow.getAttributes();
mWindowManager.addView(mDecor, l);
...
}
16、Dialog的销毁流程分析
//Dialog.java
public void dismiss() {
//1. 线程安全,最终都在主线程移除该Dialog
if (Looper.myLooper() == mHandler.getLooper()) {
dismissDialog();
} else {
mHandler.post(mDismissAction);
}
}
//Dialog.java
void dismissDialog() {
//1. 移除Window
mWindowManager.removeViewImmediate(mDecor);
...
}
//WindowManagerImpl.java
public void removeViewImmediate(View view) {
mGlobal.removeView(view, true);
}
17、 Dialog的注意点
Dialog
必须采用Activity
的Context
,因为需要有应用token
(Application
的Context
没有应用token)- 也可以将
Dialog
的Window
通过type
设置为系统Window就不再需要token。
alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
完整代码
//权限判断
if (Build.VERSION.SDK_INT >= 23) {
if (!Settings.canDrawOverlays(this)) {
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivityForResult(intent, 1);
} else {
//TODO 做你需要的事情
alertDialog = new AlertDialog.Builder(getApplicationContext()).setTitle("AlerDialog!").create();
alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
alertDialog.show();
}
}
//AndroidManifest.xml-没有系统权限会导致报错
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
18、Toast的内部机制介绍
Toast
也是基于Window
来实现的Toast
具有定时取消功能,系统采用Handler
实现Toast
内部有两类IPC过程:1-Toast访问NotificationManagerService;2-NotificationManagerService回调Toast
的TN
接口
19、Toast的show()方法原理分析
/**
* Toast.java中显示和隐藏都是IPC过程
*/
public void show() {
if (mNextView == null) {
throw new RuntimeException("setView must have been called");
}
//1. 获取NotificaitonMnagerService
INotificationManager service = getService();
String pkg = mContext.getOpPackageName();
//2. TN是Binder类,NotificationManagerService处理Toast显示时,会跨进程回调TN中的方法
//3. TN运行在Binder线程池中,需要通过Hanlder将其切换到(发送Toast请求的)线程中
//4. 使用Hanlder意味着Toast无法在没有Looper的线程中弹出
TN tn = mTN;
tn.mNextView = mNextView;
try {
//5. NotificationManagerService进行toast
service.enqueueToast(pkg, //当前应用包名
tn,
mDuration);
} catch (RemoteException e) {
// Empty
}
}
//NotificationManagerService
public void enqueueToast(String pkg, ITransientNotification callback, int duration)
{
...参数合法性判断...
...省略...
synchronized (mToastQueue) {
.......
try {
//1. 将Toast请求封装为`ToastRecord`
ToastRecord record;
int index = indexOfToastLocked(pkg, callback);
//2. 如果ToastRecord已经在队列中(mToastQueue),则直接更新
if (index >= 0) {
record = mToastQueue.get(index);
record.update(duration);
} else {
/**=================================================================
*3. 限制非系统应用,队列最多能存50个(MAX_PACKAGE_NOTIFICATIONS)。
* 防止DOS(Denail of Service)攻击:
* 如果一个应用大量连续弹出Toast,会导致其他应用没有机会弹出Toast
*===============================================================*/
if (!isSystemToast) {
int count = 0;
final int N = mToastQueue.size();
for (int i=0; ifinal ToastRecord r = mToastQueue.get(i);
if (r.pkg.equals(pkg)) { //同一个包
count++;
if (count >= MAX_PACKAGE_NOTIFICATIONS) {
Slog.e(TAG, "Package has already posted " + count
+ " toasts. Not showing more. Package=" + pkg);
return;
}
}
}
}
//4. 将Toast存放到队列中
record = new ToastRecord(callingPid, pkg, callback, duration);
mToastQueue.add(record);
......
}
/**===========================================================
* 5. 用于显示当前Toast
* Toast显示由ToastRecord的callback(Toast的TN对象的远程Binder)完成
* 通过CallBack调用了TN中的方法,该方法运行在Toast请求方的Binder线程池中
*===========================================================*/
//5. 用于显示当前Toast。
if (index == 0) {
showNextToastLocked(); //Toast显示由ToastRecord的callback(Toast的TN对象的远程Binder)完成
}
} finally {
Binder.restoreCallingIdentity(callingId);
}
}
}
//NotificationManagerService.java
void showNextToastLocked() {
ToastRecord record = mToastQueue.get(0);
while (record != null) {
...省略...
try {
//1. 跨进程调用Toast中TN内部的show()方法进行展示
record.callback.show();
/**===========================================================
* 2. 会发送一个延时消息(取决于Toast的时长)。
* 1-Message最终会由NotificationMaService中的WorkerHandler处理
* 2-调用handleTimeout->cancelToastLocked->`record.callback.hide();
* 3-最终调用TN中的hide方法并且将ToastRecord从队列中移除
*===========================================================*/
scheduleTimeoutLocked(record);
return;
} catch (RemoteException e) {
...省略...
}
}
}
20、Toast的cancel()方法原理分析
- 内部调用
NotificationManagerService
的cancelToast方法()- cancelToast方法()->cancelToastLocked()->record.callback.hide();
- record.callback.hide()通过IPC调用TN中的hide方法
- 最后将ToastRecord移除队列
//Toast.java
public void cancel() {
mTN.hide();
try {
getService().cancelToast(mContext.getPackageName(), mTN);
} catch (RemoteException e) {
// Empty
}
}
21、Toast的TN(Binder)内部机制
//Toast.java TN
private static class TN extends ITransientNotification.Stub {
...省略...
WindowManager mWM;
TN() { ...省略... }
//1. 开启了mShow的Runnable
public void show() {
mHandler.post(mShow);
}
//2. 开启了mHide的Runnable
public void hide() {
if (localLOGV) Log.v(TAG, "HIDE: " + this);
mHandler.post(mHide);
}
//3. 调用handleShow()进行显示
final Runnable mShow = new Runnable() {
public void run() {
handleShow();
}
};
//4. 调用handleHide()进行显示
final Runnable mHide = new Runnable() {
public void run() {
handleHide();
mNextView = null;
}
};
//5. 真正完成显示功能
public void handleShow() {
......
//7. 将Toast视图添加到Window中
mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
......
mWM.addView(mView, mParams);
......
}
//6. 真正完成隐藏功能
public void handleHide() {
if (mView != null) {
//8. 将Toast视图从Window中移除
if (mView.getParent() != null) {
mWM.removeView(mView);
}
mView = null;
}
}
private void trySendAccessibilityEvent() {
......
}
}
22、WindowManager的addView工作流程:
23、Window的removeView()流程分析:
24、Activity从创建到显示的工作流程:
- 建立Window和WindowManager联系:
mWindow.setWindowManager((WindowManager)context.getSystemService(Context.WINDOW_SERVICE), ...);