Learn && Live
虚度年华浮萍于世,勤学善思至死不渝
前言
Hey,欢迎阅读Connor学Android系列,这个系列记录了我的Android原理知识学习、复盘过程,欢迎各位大佬阅读斧正!原创不易,转载请注明出处:http://t.csdn.cn/CjLtU,话不多说我们马上开始!
1.Window 和 WindowManager
Window
(1)整体上看,Window 是一个窗口的概念,是所有视图的载体,不管是 Activity、Dialog、Toast,它们的视图都是附属在 Window 上面的。例如在桌面显示一个悬浮窗,就需要用到 Window 来实现
(2)从源码来看,Window 是一个抽象类,它的唯一实现类是 PhoneWindow,每一个 Window 都对应着一个 View 和一个 ViewRootImpl,Window 和 View 通过 ViewRootImpl 来建立联系,因此实际上 Window 是以 View 的形式存在的
(3)Activity 中的 DecorVIew,Dialog 中的 View 都是在 PhoneWindow 中创建的,因此 Window 实际上是 View 的直接管理者,例如在 View 事件分发机制中,在 Activity 里面收到点击事件后,会首先通过 window 将事件传递到 DecorView,最后再分发到我们的 View 上。Activity 的 SetContentView 在底层也是通过 Window 来完成的。还有 findViewById 也是调用的 window
(4)Window 分三种类型
- 应用 Window:对应一个 Activity
- 子 Window:不能单独存在,需要附属在特定的父 Window 之中,如 Dialog
- 系统 Window:需要声明权限在能创建的 Window,如 Toast
.(5)Window 是分层的,层级大的会覆盖在层级小的 Window 上面,根据类型不同,层级范围不同
- 应用 Window:1-99
- 子 Window:1000-1999
- 系统 Window:2000-2999
WindowManager
(1)WindowManager 主要用于管理 Window,可以实现的操作包括:
- 创建一个 Window 并向其添加 View
- 更新 Window 中的 View
- 删除一个 Window,即删除其内的 View
(2)以添加 Window 为例,通过调用 Manager 的 addView() 方法实现,这个方法包含两个参数
-
flags:表示 Window 的显示属性
- FLAG_NOT_FOCUSABLE:表示 Window 不需要获取焦点,也不需要接收各种输入事件,此标记会同时启用 FLAG_NOT_TOUCH_MODEL,最终事件会直接传递给下层的具有焦点的 Window
- FLAG_NOT_TOUCH_MODEL:表示当前 Window 区域以外的单击事件传递给底层的 Window,当前 Window 区域以内的单击事件则自己处理,一般需要开启此标记,否则其他 Window 将无法收到单击事件
- FLAG_SHOW_WHEN_LOCKED:开启此模式可以让 Window 显示在锁屏的界面上
-
type:表示 Window 的类型,包含三种类型
- 应用类 Window:对应一个 Activity
- 子 Window:不能单独存在,需要附属在特定的父 Window 之中,如 Dialog
- 系统 Window:需要声明权限在能创建的 Window,如 Toast
val textView = TextView(this).apply {
text = "window"
textSize = 18f
setTextColor(Color.BLACK)
setBackgroundColor(Color.WHITE)
}
val parent = WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT, 0, 0, PixelFormat.TRANSPARENT
)
parent.type = WindowManager.LayoutParams.TYPE_APPLICATION
parent.flags =
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
parent.gravity = Gravity.END or Gravity.BOTTOM
parent.y = 500
parent.x = 100
windowManager.addView(textView, parent)
2.Window 的内部机制
2.1 Window 的添加过程
(1)Window 的添加主要完成两个工作:绘制 View、完成 Window 添加,这一过程通过 WindowManager 的 addView() 方法实现
(2)WindowManager 是一个接口,实现类是 WindowManagerImpl,因此实际调用的是 WindowManagerImpl 中的 addView() 方法
(3)WindowManagerImpl 中的 addView() 方法内由 WindowManagerGlobal 的 addView() 来真正实现 Window 的添加
@Override void addView(View view, ViewGroup.LayoutParams params) {
mGlobal.addView(view, params, mDisplay, mParentWindow);
}
WindowManagerGlobal 的 addView 方法主要分为如下三步
一、检查传入的参数是否合法,如果是子 Window 则还需要调整一些布局参数
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 {
//....
}
二、创建 ViewRootImpl 并将 View 添加到列表中
(1)WindowManagerGlobale 内部有如下几个列表:
- mViews:存储所有 Window 所对应的 View
- mRoots:存储所有 Window 所对应的 ViewRootImpl
- mParams:存储所有 Window 所对应的布局参数
- mDyingViews:存储被标记了即将被删除的 View 对象(Window)
(2)addView() 方法内会根据检查后的参数创建 ViewRootImpl 并为 View 设置布局参数,最后将内容加入到上述的对应的列表中
ViewRootImpl root;
View panelParentView = null;
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
三、通过 ViewRootImpl 来更新界面并完成 Window 的添加过程
(1)在 addView 方法中通过调用 ViewRootImpl 的 setView 方法来完成
(2)setView 方法内部会完成以下任务:
- 首先会通过 requestLayout 来完成异步刷新请求,其内的 scheduleTraversals 会进入 View 绘制的流程,从而完成页面的更新
- 接着调用 WindowSession 来完成 Window 的添加过程,WindowSession 是一个 Binder 对象,实现类是 Session
- Session 内部会通过 WindowManagerService 来实现 WIndow 的添加,所以可见 Window 的添加过程是一次 IPC 调用
// WindowManagerGlobal.addView
try {
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
// ViewRootImpl
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
requestLayout();
try {
mOrigWindowType = mWindowAttributes.type;
mAttachInfo.mRecomputeGlobalAttributes = true;
collectViewAttributes();
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel,
mTempInsets);
setFrame(mTmpFrame);
}
//.....
}
}
}
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
@Override
public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,int viewVisibility, int displayId, Rect outFrame, Rect outContentInsets,
Rect outStableInsets, Rect outOutsets, DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel, InsetsState outInsetsState) {
return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame,outContentInsets, outStableInsets, outOutsets, outDisplayCutout, outInputChannel,outInsetsState);
}
2.2 Window 的删除过程
(1)WindowManager 中提供了两种删除接口
- remoteView:异步删除,仅发送一个请求删除的消息后就立刻返回,不会等待完成删除操作
- remoteViewImmediate:同步删除,等待删除操作完成后再返回
(1)与添加过程类似,都是先通过 WindowManagerImpl 后,再进一步通过 WindowManagerGlobal 来实现的
@Override
public void removeView(View view) {
mGlobal.removeView(view, false);
}
(2)WindowManagerGlobal 的 removeView 中会完成以下工作
- 调用 findViewLocked 来查找待删除的 View 的索引
- 根据找到的索引调用 removeViewLocked 完成进一步的删除
@UnsupportedAppUsage
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);
}
}
(3)removeViewLokced 内部会调用 ViewRootImpl 的 die 方法完成异步删除操作,即只发送一个请求删除的消息后就会立刻返回了,这个时候 View 并没有完成删除操作,最后会将其作为待删除 View 添加到 mDyingViews 中
private void removeViewLocked(int index, boolean immediate) {
ViewRootImpl root = mRoots.get(index);
View view = root.getView();
if (view != null) {
InputMethodManager imm = view.getContext().getSystemService(InputMethodManager.class);
if (imm != null) {
imm.windowDismissed(mViews.get(index).getWindowToken());
}
}
boolean deferred = root.die(immediate);
if (view != null) {
view.assignParent(null);
if (deferred) {
mDyingViews.add(view);
}
}
}
(4)die 方法内部会根据传入的参数判断是异步删除还是同步删除
- 如果是异步删除,发送一个 MSG_DIE 消息,ViewRootImpl 中的 Handler 会处理此消息并调用 doDie 方法
- 如果是同步删除,则不发消息直接调用 doDie 方法
boolean die(boolean immediate) {
if (immediate && !mIsInTraversal) {
doDie();
return false;
}
if (!mIsDrawing) {
destroyHardwareRenderer();
} else {
Log.e(mTag, "Attempting to destroy the window while drawing!\n" +
" window=" + this + ", title=" + mWindowAttributes.getTitle());
}
mHandler.sendEmptyMessage(MSG_DIE);
return true;
}
(5)在 doDie 方法内会调用 dispatchDetachedFromWIndow 方法,其内部完成了真正删除 View 的逻辑,主要做四件事
- 垃圾回收相关的工作,比如清除数据和消息、移除回调
- 通过 Session 的 remove 方法删除 Window,与添加相同,是一个 IPC 过程,最终会调用 WMS 的 removeWindow 方法
- 调用 View 的 dispatchDetachedFromWindow 方法,其内部会调用 View 的 onDetachedFromWindow() 以及 onDetachedFromWindowInternal,当 View 从 Window 中移除时,会完成一些如终止动画、停止线程等资源回收工作
- 调用 WindowManagerGlobal 的 doRemoteView 方法刷新数据,包括 mRoots、mParams、mDyingViews,需要将当前 Window 所关联的这三类对象从列表中删除
2.3 Window 的更新过程
也是一样调用 WindowManagerGlobal 的 updateViewLayout 方法,主要做如下工作
(1)首先更新 View 的 LayoutParams
(2)接着调用 findViewLocked 方法获取更新 View 的索引
(3)根据 View 获取对应的 ViewRootImpl,更新其中的 LayoutParams 参数
(4)通过 setLayoutParams 更新参数从而进一步对 View 重新绘制
(5)最后通过 WMS 的 relayoutWindow 更新 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 中
view.setLayoutParams(wparams);
synchronized (mLock) {
//获取到 view 在列表中的索引
int index = findViewLocked(view, true);
//拿到 view 对应的 ViewRootImpl
ViewRootImpl root = mRoots.get(index);
//从参数列表中移除旧的参数
mParams.remove(index);
//将新的参数添加到指定的位置中
mParams.add(index, wparams);
//调用 ViewRootImpl.setLayoutPrams 对参数进行更新
root.setLayoutParams(wparams, false);
}
}
3.Window 的创建过程
3.1 Activity 的 Window 创建过程
前置知识
(1)首先明确 Activity、Window、DecorView 的关系:Activtiy 内包含一个 Window,Window 内部包含一个 DecorView,这个 DecorView 的 content 部分是 Activity 的视图,由 setContentView 方法设置
(2)Activity 的启动过程在最终会调用 AcitvityThread 中的 performLaunchActivity() 方法,这个方法内部会
-
通过类加载器创建 Activity 的实例对象
-
调用 attach 方法为其关联运行过程中所依赖的一系列上下文环境变量
-
此外,attach 方法中就会创建 Activity 所属的 Window 对象并为其设置回调接口,Window 对象的创建是通过 PolicyManager 的 makeNewWindow 方法实现的
-
由于 Activity 实现了 Window 的 Callback 接口,因此当 Window 接收到外界的状态变化时会调用 Activity 的方法,如 onAttachedToWindow、onDetachedFromWindow、dispatchTouchEvent 等
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
if (activity != null) {
appContext.setOuterContext(activity);
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor, window, r.configCallback,
r.assistToken);
//....
}
return activity;
}
(3)Activity 的 Window 创建只要完成两件事:Window 创建、将 Activity 的视图附属到 Window 上
Window 创建
(1)Activity 的 attach 方法中就会创建 Activity 所属的 Window 对象并为其设置回调接口
(2)Window 对象的创建是通过 PolicyManager 的 makeNewWindow 方法实现的
(3)makeNewWindow 方法内会根据 context ,new 并返回一个 PhoneWindow 对象
(4)由于 Activity 实现了 Window 的 Callback 接口,因此当 Window 接收到外界的状态变化时会调用 Activity 的方法,如 onAttachedToWindow、onDetachedFromWindow、dispatchTouchEvent 等
视图附属
(1)由 Activity 的 setContentView 方法,而具体的实现交给 Window 来完成
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
(2)setContentView 大致分为如下三步
一、如果没有 DecorView,则创建
(1)DecorView 的创建过程由 installDecor 方法完成,其内部会通过 generateDecor 方法直接 new 并返回一个 DecorView
protected DecorView generateDecor() {
return new DecorView(getContext(), -1);
}
(2)创建完成后继续初始化 DecorView 的结构,这个过程由 PhoneWindow 的 generateLayout 完成
- 通过 inflate 拿到 View
- 调用 DecorView 的 addView 将 View 添加进去
- 将当前 DecorView 设置为顶级 View
- 根据 R.id.content 获取 contentParent,方便后续将 View 设置为 ContentView
View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
mContentRoot = (ViewGroup) in;
ViewGroup contentParent = (ViewGroup) findViewById(ID_ANDROID_CONTENT);
二、将 View 添加到 DecorView 的 mContentParent
将 Activity 的视图添加到 DecorView 的 mContentParent 中
mLayoutInflater.inflate(layoutResID, mContentParent);
三、回调 Activity 的 onContentChanged 方法通知 Activity 视图发生改变
由于 Activity 实现了 Window 的 Callback 接口,当完成第二步时会回调 onContentChanged 方法通知 Activity 视图发生改变
final Callback cb = getCallback();
if(cb != null && !isDestroyed()) {
cb.onContentChanged();
}
3.2 Dialog 的 Window 创建过程
与 Activity 的类似
一、创建 PhoneWindow
同样由 PhoneWindow 的 makeNewWindow 方法直接创建一个对象完成
二、初始化 DecorView 并将 Dialog 的视图添加到 DecorView 中
通过 Window 的 setContentView 实现
三、将 DecorView 添加到 Window 中并显示
在 Dialog 的 show 方法中,会通过 WindowManager 将 DecorView 添加到 Window 中
mWindowManager.addView(mDecor, 1);
mShowing = true;
当 Dialog 被关闭时,通过 WindowManager 来删除 DecorView,注意这里是同步删除
mWindowManager.removeViewImmediate(mDecor);
3.3 Toast 的 Window 创建过程
(1)Toast 是系统 Window,其内部视图有两种指定方法:默认样式、通过 setView 指定自定义 View,两种方法都对应一个 View 对象
(2)Toast 提供 show、cancel 方法来显示、隐藏 Toast,其内部是一个 IPC 过程
(3)Toast 的显示和隐藏需要通过 NotificationManagerService,NMS 来实现,NMS 会回调 Toast 里的 TN 接口
(4)TN 是一个 Binder 类,在 Toast 和 NMS 进行 IPC 的过程中,当 NMS 处理 Toast 的显示或隐藏请求时会跨进程回调 TN 中的方法,此时由于 TN 运行在 Binder 线程池中,所以需要通过 Handler 切换到当前线程,即发送 Toast 请求的线程
(5)Toast 通过 WindowManager 将 view 直接添加到 Window 中,没有创建 PhoneWindow 和 DecorView,和 Activity、Dialog 不同
public void show() {
INotificationManager service = getService();
String pkg = mContext.getOpPackageName();
TN tn = mTN;
tn.mNextView = mNextView;
final int displayId = mContext.getDisplayId();
try {
if (Compatibility.isChangeEnabled(CHANGE_TEXT_TOASTS_IN_THE_SYSTEM)) {
if (mNextView != null) {
service.enqueueToast(pkg, mToken, tn, mDuration, displayId);
} else {
// ...
}
}
}
//....
}
public void cancel() {
mTN.hide();
try {
getService().cancelToast(mContext.getOpPackageName(), mToken);
} catch (RemoteException e) {
// Empty
}
//....
}
static private INotificationManager getService() {
if (sService != null) {
return sService;
}
sService = INotificationManager.Stub.asInterface(
ServiceManager.getService(Context.NOTIFICATION_SERVICE));
return sService;
}
Toast 显示和隐藏
(1)show 方法内会调用 enqueueToast 方法,这个方法需要三个参数
- 当前应用包名
- TN 远程回调对象
- Toast 的时长
INotificationManager service = getService();
String pkg = mContext.getOpPackageName();
TN tn = mTN;
tn.mNextView = mNextView;
final int displayId = mContext.getDisplayId();
try {
if (Compatibility.isChangeEnabled(CHANGE_TEXT_TOASTS_IN_THE_SYSTEM)) {
if (mNextView != null) {
service.enqueueToast(pkg, mToken, tn, mDuration, displayId);
} else {
// ...
}
}
}
(2)enqueueToast 方法会完成如下工作
- 首先将 Toast 请求封装成 ToastRecord,并加入 mToastQueue 队列中
- 这个队列最多能存放 50 个 Record
- 如果队列中有,则更新
- 添加完成后,NMS 会通过 showNextToastLocked 方法来显示当前的 Toast
public void enqueueToast(String pkg, IBinder token, ITransientNotification callback,
int duration, int displayId) {
enqueueToast(pkg, token, null, callback, duration, displayId, null);
}
private void enqueueToast(String pkg, IBinder token, @Nullable CharSequence text, @Nullable ITransientNotification callback, int duration, int displayId,
@Nullable ITransientNotificationCallback textCallback) {
synchronized (mToastQueue) {
int callingPid = Binder.getCallingPid();
final long callingId = Binder.clearCallingIdentity();
try {
ToastRecord record;
int index = indexOfToastLocked(pkg, token);
//如果队列中有,就更新它,而不是重新排在末尾
if (index >= 0) {
record = mToastQueue.get(index);
record.update(duration);
} else {
int count = 0;
final int N = mToastQueue.size();
for (int i = 0; i < N; i++) {
final ToastRecord r = mToastQueue.get(i);
//对于同一个应用,taost 不能超过 50 个
if (r.pkg.equals(pkg)) {
count++;
if (count >= MAX_PACKAGE_TOASTS) {
Slog.e(TAG, "Package has already queued " + count
+ " toasts. Not showing more. Package=" + pkg);
return;
}
}
}
//创建对应的 ToastRecord
record = getToastRecord(callingUid, callingPid, pkg,
isSystemToast, token,text, callback, duration, windowToken, displayId, textCallback);
mToastQueue.add(record);
index = mToastQueue.size() - 1;
keepProcessAliveForToastIfNeededLocked(callingPid);
}
// ==0 表示只有一个 toast了,直接显示,否则就是还有toast,真在进行显示
if (index == 0) {
showNextToastLocked(false);
}
} finally {
Binder.restoreCallingIdentity(callingId);
}
}
}
private ToastRecord getToastRecord(int uid, int pid, String packageName, boolean isSystemToast,
IBinder token, @Nullable CharSequence text, @Nullable ITransientNotification callback,
int duration, Binder windowToken, int displayId,
@Nullable ITransientNotificationCallback textCallback) {
if (callback == null) {
return new TextToastRecord(this, mStatusBar, uid, pid, packageName,
isSystemToast, token, text, duration, windowToken, displayId, textCallback);
} else {
return new CustomToastRecord(this, uid, pid, packageName,
isSystemToast, token, callback, duration, windowToken, displayId);
}
}
(3)showNextToastLocked 方法内,Toast 的显示是通过 ToastRecord 的 callback 完成的,这个 callback 实际上是 TN 的远程 Binder
(4)为了将执行环境切换到 Toast 请求所在线程,其内部使用 Handler,TN 会调用 handleShow 方法将 Toast 视图添加到 Window 中
mWM = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
mWM.addView(mView, mParams);
(5)显示以后,NMS 还会通过 scheduleTimeoutLocked 方法来发送一个延时消息,时长为之前的参数:LONG(3.5S) / SHORT(2S)
private void scheduleTimeoutLocked(ToastRecord r) {
mHandler.removeCallbacksAndMessage(r);
Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r);
long delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY;
mHandler.sendMessageDelayed(m, delay);
}
(6)延迟相应的时间后,NMS 会通过 cancelToastLocked 方法隐藏 Toast 并移除出 mToastQueue
(7)这里也是通过 ToastRecord 的 callback 完成,也是调用 TN 中的 handleHide 方法将 Toast 的视图从 Window 中移除
if(mView.getParent() != null) {
...
mWM.removeView(mView);
}