Window
表示窗口的概念,他是一个抽象类,他的真正实现类是PhoneWindow
,WindowManager
用来对Window
进行管理,是外接访问Window
的入口,Window
操作的具体实现是在WindowManagerService
中,WindowMager
和WindowManagerService
交互是IPC
的过程
Android中所有的视图都是附加在Window上
上呈现的,不管Activity,Dialog,Toast
,他们的视图都是附加在Window
上的,因此Window
实际上是View
的直接管理者
下面我们来详细的了解Window
我们先来了解一下如何使用WindwoMagaer
来添加一个Window
Button button = new Button(this);
button.setText("Window");
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, 0, 0, PixelFormat.TRANSPARENT);
layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION;
layoutParams.flags= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
layoutParams.gravity= Gravity.LEFT|Gravity.TOP;
layoutParams.x=100;
layoutParams.y=300;
WindowManager windowManager = getWindowManager();
windowManager.addView(button,layoutParams);
这段代码可以添加一个Window
,位置在(100,300)处,这里面有俩个参数比较重要分别是,type
和flag
,下面分别介绍一下这俩个参数
type
参数表示Window
的类型,Window
有三种类型,分别是Application Window
(应用窗口),Sub Window
(子窗口)和System Window
(系统窗口),每个大类型又包含多个小类型,他们都定义在WindowMager
的静态内部类LayoutParams
中,下面对这三种类型进行讲解
Application Window(应用窗口)
Activity就是典型的应用窗口,应用窗口包含的类型如下:
public static final int FIRST_APPLICATION_WINDOW = 1;
//窗口的基础值,其他窗口要大于这个值
public static final int TYPE_BASE_APPLICATION = 1;
// 普通应用程序的窗口
public static final int TYPE_APPLICATION = 2;
public static final int TYPE_APPLICATION_STARTING = 3;
public static final int TYPE_DRAWN_APPLICATION = 4;
public static final int LAST_APPLICATION_WINDOW = 99;
应用窗口就包括了以上几中类型,其中最上方是起始值,最下方是结束值,也就是说应用窗口的Type值的范围是1-99
,这个数值的大小涉及窗口的层级
Sub Window(子窗口)
子窗口不能够独立存在,要依附在其他窗口上才行,PopupWindow
就属于子窗口,子窗口的定义类型如下:
//子窗口的初始值
public static final int FIRST_SUB_WINDOW = 1000;
public static final int TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW;
public static final int TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW + 1;
public static final int TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW + 2;
public static final int TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW + 3;
public static final int TYPE_APPLICATION_MEDIA_OVERLAY = FIRST_SUB_WINDOW + 4;
public static final int TYPE_APPLICATION_ABOVE_SUB_PANEL = FIRST_SUB_WINDOW + 5;
//子窗口的结束值
public static final int LAST_SUB_WINDOW = 1999;
可以看出子窗口的type值范围是1000-1999
System Window (系统窗口)
Toast,输入法窗口,系统音量条窗口,系统错误窗口,都属于系统窗口,系统窗口的类型定义如下:
public static final int FIRST_SYSTEM_WINDOW = 2000;
public static final int TYPE_STATUS_BAR = FIRST_SYSTEM_WINDOW;
public static final int TYPE_SEARCH_BAR = FIRST_SYSTEM_WINDOW+1;
@Deprecated
public static final int TYPE_PHONE = FIRST_SYSTEM_WINDOW+2;
@Deprecated
public static final int TYPE_SYSTEM_ALERT = FIRST_SYSTEM_WINDOW+3;
public static final int TYPE_KEYGUARD = FIRST_SYSTEM_WINDOW+4;
@Deprecated
public static final int TYPE_TOAST = FIRST_SYSTEM_WINDOW+5;
@Deprecated
public static final int TYPE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW+6;
@Deprecated
public static final int TYPE_PRIORITY_PHONE = FIRST_SYSTEM_WINDOW+7;
...
public static final int LAST_SYSTEM_WINDOW = 2999;
系统窗口接近40个,这里只列出一小部分,系统窗口的Type值在2000-2999之间
窗口的显示次序
上面介绍的Type值越大,就意味着靠用户越近,很显然系统的窗口是最大的,他在应用窗口和子窗口的上方
Flag
就是窗口的标志,用于控制Window
的显示,同样被定义在WindowManager
的内部类LayoutParams
中,一共有20多个,这里列出一些常用的
type | 描述 |
---|---|
FLAG_ALLOW_LOCK_WHILE_SCREEN_ON | 只要窗口可见,就允许在开启状态的屏幕上锁屏 |
FLAG_NOT_FOCUSABLE | 窗口不能获取输入焦点,设置该标志的同时,FLAG_NOT_TOUCH_MODAL也会被设置 |
FLAG_NOT_TOUCH_MODAL | 将该窗口区域外的触摸事件,传递给其他Window,而自己只会处理窗口区域内的触摸事件 |
FLAG_NOT_TOUCHABLE | 窗口不接受任何触摸事件 |
FLAG_KEEP_SCREEN_ON | 只要窗口可见,就一直保持屏幕长亮 |
FLAG_LAYOUT_NO_LIMITS | 允许窗口超出屏幕外 |
FLAG_FULLSCREEN | 隐藏所有的屏幕装饰窗口,比如游戏视频等全屏显示 |
FLAG_SHOW_WHEN_LOCKED | 窗口可以在锁屏窗口之上显示 |
FLAG_IGNORE_CHEEK_PRESSES | 当用户脸贴近屏幕时(比如打电话时),不会响应此事件 |
FLAG_TURN_SCREEN_ON | 窗口显示时将屏幕点亮 |
设置Window的Flag除了上方的方式外还可以采用下面的方式
//第一种
Window window = getWindow();
window.addFlags();
//第二种
Window window = getWindow();
window.setFlags();
我们在写登陆界面的时候,默认弹出的软键盘窗口可能会覆盖输入框下面的按钮,为了让软键盘按照期望的方式显示,,WindowMagaer的静态内部类LayoutParams中定义了软键盘的相关模式,我们介绍一下常用的
SoftInputMode | 描述 |
---|---|
SOFT_INPUT_STATE_UNSPECIFIED | 没有指定状态,系统会选择一个合适的状态或依赖于主题的设置 |
SOFT_INPUT_STATE_UNCHANGED | 不会改变软键盘的状态 |
SOFT_INPUT_STATE_HIDDEN | 当用户进入该窗口时,软键盘默认隐藏 |
SOFT_INPUT_STATE_ALWAYS_HIDDEN | 当窗口获取焦点时,软键盘总是隐藏 |
SOFT_INPUT_ADJUST_RESIZE | 当软键盘弹出时,窗口会调整大小 |
SOFT_INPUT_ADJUST_PAN | 当软键盘弹出时,窗口不需要调整大小,要确认输入焦点是否可见 |
软键盘模式可以在AndroidManifest中设置
<activity android:name=".CameraActivity"
android:launchMode="singleTask"
android:windowSoftInputMode="adjustPan">
</activity>
也可以代码设置
getWindow().setSoftInputMode();
WindowMagaer
所提供的功能很简单,只有常用的三个方法即,添加View,更新View,删除View
,这个三个方法定义在ViewManager
中,而WindowManager
继承自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);
}
Window
是一个抽象概念,每一个Window
都对应一个View
和一个ViewRootImpl
,Window
和View
通过ViewRootImpl
来建立联系,因此Window
不是实际存在的,他是以View
的形式存在的,在实际是一个中,不能直接访问Window
,只有通过WindowManager
才能访问
Window
的添加是通过WindowManager
的addView
方法实现的,我们WindowManager##addView
方法作为入口来分析,WindowMagager
是一个接口,真正的实现在WindowManagerImpl
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
我们发现他其实把事情交给了WindowManagerGlobal
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
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
//注释3
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
// do this last because it fires off messages to start doing things
try {
//注释4
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}
我们首先了解几个重要的变量
//储存所有Window对应的View
private final ArrayList<View> mViews = new ArrayList<View>();
//储存所有Window对应的ViewRoot
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
//布局参数列表
private final ArrayList<WindowManager.LayoutParams> mParams =
new ArrayList<WindowManager.LayoutParams>();
ViewRootImpl有很多的职责
我们继续看一下ViewRootImpl
的setView
方法
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
···
// Schedule the first layout -before- adding to the window
// manager, to make sure we do the relayout before receiving
// any other events from the system.
requestLayout();
···
try {
mOrigWindowType = mWindowAttributes.type;
mAttachInfo.mRecomputeGlobalAttributes = true;
collectViewAttributes();
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);
} catch (RemoteException e) {
}
}
这个方法首先会调用requestLayout
方法来完成一部刷新请求
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
scheduleTraversals
实际是View绘制的入口
然后调用mWindowSession.addToDisplay
方法,mWindowSession
是一个IWindowSession
类型的,是一个Binder
对象,用于进程间通信,也就是说addToDisplay
方法其实是运行在WMS
所在的进程system_server
进程
@Override
public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets,
Rect outOutsets, InputChannel outInputChannel) {
return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
outContentInsets, outStableInsets, outOutsets, outInputChannel);
}
addToDisplay
方法内部调用了WMS
的addWindow
方法并将自身也就是Session
传入了进去,每个应用程序都会有一个Session
,WMS
会用ArrayList
来保存起来,这样所有的工作都交给了WMS
来做
WMS
会为这个添加的窗口分配Surface
,并确定窗口的显示次序,负责显示界面的是画布Surface
,而不是窗口本身,WMS
会把Surface
交给SurfaceFlinger
处理,SurfaceFlinger
会把这些Surface
混合并绘制到屏幕上
Window
的更新过程和添加过程是类似的,需要调用WindowManager
的updateViewLayout
方法,然后会继续进入WindowManagerGlobal
的updateViewLayout
方法,我们直接从这个方法进行分析
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.setLayoutParams(wparams);
synchronized (mLock) {
//注释2
int index = findViewLocked(view, true);
//注释3
ViewRootImpl root = mRoots.get(index);
//注释4
mParams.remove(index);
//注释5
mParams.add(index, wparams);
//注释6
root.setLayoutParams(wparams, false);
}
}
ViewRoot
的setLayoutParams
方法,将更新的参数设置到ViewRootImpl
中,setLayoutParams方法最终会调用ViewRootImpl
的scheduleTraversals
方法,我们看下这个方法
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
//注释1
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
我们看下注释1,mChoreographer
翻译为编舞者
,用于接受系统的VSync
信号,在下一个帧渲染时控制一些操作,mChoreographer
的postCallback
方法用于添加回调,这个添加的回调,将在下一帧渲染时执行,这个添加的回调指的是TraversalRunnable
类型的mTraversalRunnable
,如下:
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
这个方法内部调用了doTraversal
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
这个方法又调用了performTraversals
,这个方法中更新了Window
的视图,并且完成了View的绘制流程,measure,layout,draw
,这样就完成了View的更新
这个需要了解App的启动过程,这个我就不再重复说了,不了解的可以看我之前的文章Android App启动过程,他最后会调用performLaunchActivity
方法来完成整个启动过程,这个方法内部会通过类加载器创建Activity的实例对象,并调用了attach
方法,为其关联运行中所依赖的一系列变量
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
...
Activity activity = null;
try {
java.lang.ClassLoader cl = appContext.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
...
} catch (Exception e) {
...
}
try {
// 返回之前创建过的 application 对象
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
...
if (activity != null) {
...
// attach 到 window 上
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);
...
if (r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
mInstrumentation.callActivityOnCreate(activity, r.state);
}
...
}
} catch (Exception e) {
...
}
return activity;
}
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor,
Window window, ActivityConfigCallback activityConfigCallback) {
attachBaseContext(context);
mFragments.attachHost(null /*parent*/);
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
mWindow.setSoftInputMode(info.softInputMode);
}
if (info.uiOptions != 0) {
mWindow.setUiOptions(info.uiOptions);
}
mUiThread = Thread.currentThread();
...
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
if (mParent != null) {
mWindow.setContainer(mParent.getWindow());
}
mWindowManager = mWindow.getWindowManager();
mCurrentConfig = config;
mWindow.setColorMode(info.colorMode);
setAutofillCompatibilityEnabled(application.isAutofillCompatibilityEnabled());
enableAutofillCompatibilityIfNeeded();
}
这个方法,会创建Activity
所属的Window对
象并为其设置回调接口,到这里Window
已经创建完成了,下面我们分析一下Activity的视图是怎么依附到Window
上的,由于Activity的视图是从setContentView
方法提供,我们从setContentView
方法开始分析
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
我们点进去发现他其实调用了PhoneWindow的setContentView方法,我们看下这个方法
public void setContentView(int layoutResID) {
// Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
// decor, when theme attributes and the like are crystalized. Do not check the feature
// before this happens.
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
这个方法主要做了以下几件事
DecorView
不存在则创建DecorView
,DecorView
是Activity
中的顶级View
,一般来说他包括标题栏
和内容栏
,这个会随着主题的改变而改变,反正内容栏一定存在,并且他有固定的id
为android.R.id.content
, 创建DecorView
由installDecor
方法完成,内部会通过generateDecor
方法创建,这个时候DecorView
还是一个空白的Framlayout
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
//创建DecroView
mDecor = generateDecor(-1);
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
//向DecorView添加内容
mContentParent = generateLayout(mDecor);
}
...
}
protected DecorView generateDecor(int featureId) {
...
return new DecorView(context, featureId, this, getAttributes());
}
DecorView
的结构,通过generateLayout
方法加载具体的布局文件到DecorView中,并为内容栏变量赋值 ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
setContentView
的View
添加到内容栏中, mLayoutInflater.inflate(layoutResID, mContentParent);
这时Activity
的布局文件就已经添加到了DecorView
的内容栏中onContentChanged
方法,通知Activity
视图已经改变通过上方的步骤,现在DecorView
已经创建并初始化完成,Activity
的布局也添加到DecorView
的内容栏中,但是这个时候DecorView
还没有被WindowManager
添加到Window
中
在ActivityThread
的handleResumeActivity
会调用Activity
的onResume
方法,并且会调用ViewManager
的addView
方法把DecorView
添加到Window
中
@Override
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
String reason) {
//调用Activity的onResume方法
final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
...
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
//获取WindowMagaer
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
//把DecorView添加到Window
wm.addView(decor, l);
}
...
}
到这里Activity的Window创建过程分析完毕
参考:《Android开发艺术探索》《Android进阶解密》