Android 开发艺术探索读书笔记 8 -- 理解 Windo
hello,夏天 (图片来源于网络)本篇文章主要介绍以下几个知识点:
- Window 和 WindowManager
- Window 的内部机制
- Window 的创建过程
Window 表示一个窗口的概念,是一个抽象类,其具体实现是 PhoeWindow。
WindowManager 是外界访问 Window 的入口,创建一个 Window 需要通过 WindowManager。
Android 中的所有视图都是通过 Window 来呈现的(如 Activity、Dialog、Toast 等),Window 是 View 的直接管理者。
8.1 Window 和 WindowManager
下面代码将一个 Button 添加到屏幕坐标为(100,300)的位置上,演示了通过 WindowManager 添加 Window 的过程:
mFloatingButton = new Button(this);
mFloatingButton.setText("button");
mLayoutParams = new WindowManager.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, 0, 0, PixelFormat.TRANSPARENT);
// Flags 参数表 Window 的属性,常用的有如下:
// 1. FLAG_NOT_FOCUSABLE 表示 Window 不需要获取焦点,也不需要接收各种输入事件,此标记会同时启用 FLAG_NOT_TOUCH_MODAL,最终事件会直接传递给下层具有焦点的 Window
// 2. FLAG_NOT_TOUCH_MODAL 此模式下,系统会将当前 Window 区域外的单击事件传递给底层的 Window,当前 Window 区域内的单击事件则自己处理。若不开启此标记则其他 Window 将无法收到单击事件。
// 3. FLAG_SHOW_WHEN_LOCKED 开启此模式可以让 Window 显示在锁屏的界面上
mLayoutParams.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL ;
mLayoutParams.gravity = Gravity.LEFT;
mLayoutParams.x = 100;
mLayoutParams.y = 300;
mWindowManager.addView(mFloatingButton, mLayoutParams);
WindowManager 继承了 ViewManager, 所提供的功能常用的有以下3个方法:
public interface ViewManager{
// 添加 View
public void addView(View view, ViewGroup.LayoutParams params);
// 更新 View
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
// 删除 View
public void removeView(View view);
}
根据上面的方法,实现拖动 Window 的效果只需根据手指的位置来设定 LayoutParams 中的 x 和 y 的值即可:
- 给 View 设置
onTouchListener
; - 在
onTouch
方法中不断更新 View 的位置。
代码如下:
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(mFloatingButton, mLayoutParams);
break;
default:
break;
}
return false;
}
8.2 Window 的内部机制
Window 是一个抽象的概念,每一个 Window 都对应着一个 View 和一个 ViewRootImpl
,Window 和 View 通过 ViewRootImpl
来建立联系,即 Window 是以 View 的形式存在。
下面从添加、删除、更新来分析 Window 的内部机制。
8.2.1 Window 的添加过程
Window 的添加需通过接口 WindowManager 的 addView
来实现,而其真正的实现类是 WindowManagerImpl
:
public final class WindowManagerImpl implements WindowManager {
// 将所有的操作全部委托给 WindowManagerGlobal 来实现
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
private final Context mContext;
. . .
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
@Override
public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
mGlobal.updateViewLayout(view, params);
}
@Override
public void removeView(View view) {
mGlobal.removeView(view, false);
}
. . .
}
可以看出 WindowManagerImpl
并没直接实现 Window 的三大操作,而是全交给了 WindowManagerGlobal
来处理:
public final class WindowManagerGlobal {
// 存储所有 Window 所对应的 View
private final ArrayList<View> mViews = new ArrayList<View>();
// 存储所有 Window 所对应的 ViewRootImpl
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
// 存储所有 Window 所对应的布局参数
private final ArrayList<WindowManager.LayoutParams> mParams = new ArrayList<WindowManager.LayoutParams>();
// 存储那些正在被删除的 View 对象
private final ArraySet<View> mDyingViews = new ArraySet<View>();
. . .
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
// 1. 检查参数是否合法,如果是子 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);
}
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
. . .
// 2. 创建 ViewRootImpl 并将 View 添加到列表中
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 {
// 3. 通过 ViewRootImpl 来更新界面并完成 Window 的添加过程
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
. . .
}
}
}
上面第3步由 ViewRootImpl
的 setView
方法来完成:
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
. . .
// 1. requestLayout 来完成异步刷新请求
requestLayout();
. . .
// 2. 通过 WindowSession 最终来完成 Window 的添加过程
try {
mOrigWindowType = mWindowAttributes.type;
mAttachInfo.mRecomputeGlobalAttributes = true;
collectViewAttributes();
// mWindowSession 的类型是 IWindowSession,是一个 Binder 对象,
// 真正的实现类是 Session,也就是 Window 的添加过程是一次 IPC 调用
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);
} catch (RemoteException e) {
. . .
}
. . .
}
}
}
其中 setView
内部的 requestLayout
方法如下:
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
// scheduleTraversals 是 View 的绘制入口
scheduleTraversals();
}
}
上面 setView
内部的 mWindowSession
,即 Session
,内部会通过 WindowManagerService
来实现 Window 的添加,具体怎么添加这里就不分析了。
以上就是 Window 的添加流程。
8.2.2 Window 的删除过程
Window 的删除过程和添加过程一样是通过WindowManagerGlobal
来实现的, 其removeView
如下:
public final class WindowManagerGlobal {
. . .
public void removeView(View view, boolean immediate) {
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
synchronized (mLock) {
// 1. 通过 findViewLocked 来查找待删除的 View 的索引(查找过程就是建立的数组遍历)
int index = findViewLocked(view, true);
View curView = mRoots.get(index).getView();
// 2. 调用 removeViewLocked 来做进一步的删除
removeViewLocked(index, immediate);
if (curView == view) {
return;
}
throw new IllegalStateException("...");
}
}
}
上面的 removeViewLocked
如下:
private void removeViewLocked(int index, boolean immediate) {
// removeViewLocked 是通过 ViewRootImpl 来完成删除操作的
ViewRootImpl root = mRoots.get(index);
View view = root.getView();
if (view != null) {
InputMethodManager imm = InputMethodManager.getInstance();
if (imm != null) {
imm.windowDismissed(mViews.get(index).getWindowToken());
}
}
// 具体的删除操作由 ViewRootImpl 的 die 方法完成
boolean deferred = root.die(immediate);
if (view != null) {
view.assignParent(null);
if (deferred) {
mDyingViews.add(view);
}
}
}
上面 ViewRootImpl
的 die
方法如下:
/**
* @param immediate True, do now if not in traversal. False, put on queue and do later.
* @return True, request has been queued. False, request has been completed.
*/
boolean die(boolean immediate) {
// Make sure we do execute immediately if we are in the middle of a traversal or the damage
// done by dispatchDetachedFromWindow will cause havoc on return.
if (immediate && !mIsInTraversal) {
// 若是同步删除,则不发消息直接调用 doDie 方法
doDie();
return false;
}
if (!mIsDrawing) {
destroyHardwareRenderer();
} else {
Log.e(mTag, "... ");
}
// 若是异步删除,则发送 MSG_DIE 消息,ViewRootImpl 中的 Handler 会处理此消息并调用 doDie 方法
mHandler.sendEmptyMessage(MSG_DIE);
return true;
}
上面 doDie
方法在内部会调用方法 dispatchDetachedFromWindow
来实现真正的删除 View 逻辑。
方法 dispatchDetachedFromWindow
主要做四件事:
(1)垃圾回收相关的工作
(2)通过 Session 的 remove
方法删除 Window
(3)在内部调用 View 的 onDetachedFromWindow()
以及 onDetachedFromWindowInternal()
,做资源回收工作。
(4)调用 WindowManagerGlobal
的 doRemoveView
方法刷新数据。
8.2.3 Window 的更新过程
Window 的更新过程还是看 WindowManagerGlobal
中的 updateViewLayout
方法,如下:
public final class WindowManagerGlobal {
. . .
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;
// 1. 更新 View 的 LayoutParams
view.setLayoutParams(wparams);
synchronized (mLock) {
int index = findViewLocked(view, true);
ViewRootImpl root = mRoots.get(index);
mParams.remove(index);
mParams.add(index, wparams);
// 2. 更新 ViewRootImpl 中的 LayoutParams
// ViewRootImpl 会对 View 重新布局、更新 Window 的视图
root.setLayoutParams(wparams, false);
}
}
}
8.3 Window 的创建过程
上面分析可知,View 是 Android 中的视图的呈现方式,但 View 不能单独存在,必须附着在 Window 上面,因此有视图的地方就有 Window。
下面分析一些视图元素中的 Window 的创建过程。
8.3.1 Activity 的 Window 创建过程
要分析 Activity 的 Window 创建过程就必须了解 Activity 的启动过程。
Activity 的启动过程很复杂,最终会由 ActivityThread
中的 performLaunchActivity()
来完成整个启动过程,代码如下:
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
. . .
Activity activity = null;
try {
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
// 通过类加载器创建 Activity 的实例对象
activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
. . .
} catch (Exception e) { . . . }
try {
. . .
if (activity != null) {
Context appContext = createBaseContextForActivity(r, activity);
CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
Configuration config = new Configuration(mCompatConfiguration);
if (DEBUG_CONFIGURATION) Slog.v(TAG, ". . . ");
// 调用其 attach 方法为其关联运行过程中所依赖的一系列上下文环境变量
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);
. . .
}
} catch (Exception e) { . . . }
return activity;
}
在 Activity 的 attach
方法里,系统会创建 Activity 所属的 Window 对象并为其设置回调接口:
// 创建 Window 对象
mWindow = PolicyManager.makeNewWindow(this);
// 实现 Window 的 Callback 接口
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
. . .
上面 Activity 的 Window 是通过 PolicyManager
的一个工厂方法来创建的,其 makeNewWindow
如下:
public Window makeNewWindow(Context context){
// Window 的具体实现是 PhoneWindow
return new PhoneWindow(context);
}
到这里 Window 已经创建完了,接下来分析 Activity 的视图是如何附属在 Window 上。
由于 Activity 的视图由 setContentView
方法提供,只需看其实现即可:
public void setContentView(int layoutResID){
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
从上面代码可知 Activity 将具体实现交给 Window 处理,而 Window 的具体实现是 PhoneWindow
,PhoneWindow
的 setContentView
方法大致遵循如下步骤:
1. 若无 DecorView
,则创建它
DecorView 是一个 FrameLayout,是 Activity 中的顶级 View,其创建过程由 installDecor
方法来完成,此方法内部通过 generateDecor
方法来直接创建 DecorView
:
protected DecorView generateDecor(){
return new DecorView(getContext(), -1);
}
2. 将 View 添加到 DecorView
的 mContentParent
中
步骤1已创建和初始化 DecorView
,接下来直接将 Activity 的视图添加到 DecorView
的 mContentParent
中即可:
mLayoutInflater.inflate(layoutResID, mContentParent);
3. 回调 Activity 的 onContentChanged
方法通知 Activity 视图已发生改变
由于 Activity 实现了 Window 的 Callback
接口,其布局文件已被添加到 DecorView
的 mContentParent
中,需要通知 Activity 做相应的处理:
final Callback cb = getCallback();
if(cb != null && !isDestroyed()){
cb.onContentChanged();
}
经过上面3个步骤,DecorView
已被创建和初始完毕,Activity 的布局文件也添加到了 DecorView
的 mContentParent
中。
接下来在 ActivityThread
的 handleResumeActivity
方法中会先调用 Activity 的 onResume
方法,再调用 Activity 的 makeVisible()
方法:
void makeVisible(){
if(!mWindowAdded){
ViewManager wm = getWindowManager();
vm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = ture;
}
mDecor.setVisibility(View.VISIBLE);
}
在上面 makeVisible
方法中,DecorView
真正地完成了添加和显示这两个过程,Activity 的视图才能被用户看到。
以上便是 Activity 中的 Window 的创建过程。
8.3.2 Dialog 的 Window 创建过程
Dialog 的 Window 的创建过程和 Activity 类似,有如下几个步骤:
1. 创建 Window
Dialog 中的 Window 的创建同样是通过 PolicyManager
的 makeNewWindow
方法来完成的,过程和 Activity 类似:
Dialog(Context context, int theme, boolean createContextThemeWrapper){
. . .
mWindowManger = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
Window w = PolicyManage.makeNewWindow(mContext);
mWindow = w;
w.setCallback(this);
w.setOnWindowDismissedCallback(this);
. . .
}
2. 初始化 DecorView
并将 Dialog 的视图添加到 DecorView
中
这个过程也和 Acitivity 的类似,通过 Window 去添加指定的布局文件:
public void setContentView(int layoutResID){
mWindow.setContentView(layoutResID);
}
3. 将 DecorView
添加到 Window 中并显示
在 Dialog 的 show
方法中,会通过 WindowManager
将 DecorView
添加到 Window 中:
mWindowManager.addView(mDecor, 1);
mShowing = true;
以上便是 Dialog 的 Window 创建过程,和 Activity 的类似。
注:普通的 Dialog 必须采用 Activity 的 Context,若采用 Application 的 Context 会报错。
8.3.3 Toast 的 Window 创建过程
Toast 和 Dialog 不同,稍复杂。Toast 也是基于 Window 来实现的,由于具有定时取消功能,系统采用了 Handler。
具体的过程这里不再介绍了,有兴趣的可去看看书。本篇文章主要是对 Window 有一个更加清晰的认识,理解 Window 和 View 的依赖关系。
本篇文章就介绍到这。