Window
表示窗口的概念,他是一个抽象类,他的真正实现类是PhoneWindow
,WindowManager
用来对Window
进行管理,是外接访问Window
的入口,Window
操做的具体实现是在WindowManagerService
中,WindowMager
和WindowManagerService
交互是IPC
的过程java
Android中全部的视图都是附加在Window上
上呈现的,无论Activity,Dialog,Toast
,他们的视图都是附加在Window
上的,所以Window
其实是View
的直接管理者android
下面咱们来详细的了解Window
app
咱们先来了解一下如何使用WindwoMagaer
来添加一个Window
ide
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
,下面分别介绍一下这俩个参数oop
type
参数表示Window
的类型,Window
有三种类型,分别是Application Window
(应用窗口),Sub Window
(子窗口)和System Window
(系统窗口),每一个大类型又包含多个小类型,他们都定义在WindowMager
的静态内部类LayoutParams
中,下面对这三种类型进行讲解布局
Application Window(应用窗口)post
Activity就是典型的应用窗口,应用窗口包含的类型以下:ui
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
,这个数值的大小涉及窗口的层级this
Sub Window(子窗口)spa
子窗口不可以独立存在,要依附在其余窗口上才行,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进阶解密》