转载请注明文章出处LooperJing!
您可能听说过View ,ViewManager,Window,PhoneWindow,WindowManager,WindowManagerService,可是你知道这几个类是什么关系,干嘛用的。概括的来说,View是放在Window中的,Window是一个抽象类,它的具体实现是PhoneWindow,PhoneWindow还有个内部类DecorView,WindowManager是一个interface,继承自ViewManager,它是外界访问Window的入口,,提供了add/remove/updata的方法操作View,WindowManager与WindowManagerSerice是个跨进程的过程,WindowManagerService的职责是对系统中的所有窗口进行管理。如果您不太清楚,建议往下看,否则就不要看了。
1、Window的类型
Android系统的Window有很多种,大体上来说,Framework定义了三种窗口类型;
系统Window
常见的系统Window有哪些呢?比如在手机电量低的时候,会有一个提示电量低的Window,我们输入文字的时候,会弹出输入法Window,还有搜索条Window,来电显示Window,Toast对应的Window,可以总结出来,系统Window是独立与我们的应用程序的,对于应用程序而言,我们理论上是无法创建系统Window,因为没有权限,这个权限只有系统进程有。应用程序Window
所谓应用窗口指的就是该窗口对应一个Activity,因此,要创建应用窗口就必须在Activity中完成了。本节后面会分析Activity对应的Window的创建过程。子Window
所谓的子Window,是说这个Window必须要有一个父窗体,比如PopWindow,Dialog因为有自己的WindowToken,属于应用程序Window,这个比较特殊。
这就是Framework定义了三种窗口类型,这三种类型定义在WindowManager的内部类LayoutParams中,WindowManager讲这三种类型 进行了细化,把每一种类型都用一个int常量来表示,这些常量代表窗口所在的层,WindowManagerService在进行窗口叠加的时候,会按照常量的大小分配不同的层,常量值越大,代表位置越靠上面,所以我们可以猜想一下,应用程序Window的层值常量要小于子Window的层值常量,子Window的层值常量要小于系统Window的层值常量。Window的层级关系如下所示。
实际上应用程序的Window的层级范围是1 99,子Window的层级范围是10001999,系统Window的层级范围是2000~2999,这些值对应着WindowManager.LayoutParams的type参数,如果我们想窗口处在上面,那么只要采用层级比较大的type就行了。OK,到此我们对Window有了一个初步的认识。
2、怎么去描述一个Window
上面说了Window分为三种,用Window的type区分,在搞清楚Window的创建之前,我们需要知道怎么去描述一个Window,我们就把Window当做一个实体类,给我的感觉,它必须要下面几个字段。
width:描述窗口的宽度
height:描述窗口的高度
type:这是哪一种类型的Window
实际上WindowManager.LayoutParams对Window有很详细的定义。
public interface WindowManager extends ViewManager {
...........
public static class LayoutParams extends ViewGroup.LayoutParams
implements Parcelable {
//窗口的起点坐标
public int x;
public int y;
//以下定义都是描述窗口的类型
public int type;
//第一个应用窗口
public static final int FIRST_APPLICATION_WINDOW = 1;
//所有程序窗口的base窗口,其他应用程序窗口都显示在它上面
public static final int TYPE_BASE_APPLICATION = 1;
//所有Activity的窗口
public static final int TYPE_APPLICATION = 2;
//目标应用窗口未启动之前的那个窗口
public static final int TYPE_APPLICATION_STARTING = 3;
//最后一个应用窗口
public static final int LAST_APPLICATION_WINDOW = 99;
//第一个子窗口
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 LAST_SUB_WINDOW = 1999;
//系统窗口,非应用程序创建
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;
//电话窗口,它用于电话交互(特别是呼入),置于所有应用程序之上,状态栏之下
public static final int TYPE_PHONE = FIRST_SYSTEM_WINDOW+2;
//系统警告提示窗口,出现在应用程序窗口之上
public static final int TYPE_SYSTEM_ALERT = FIRST_SYSTEM_WINDOW+3;
//锁屏窗口
public static final int TYPE_KEYGUARD = FIRST_SYSTEM_WINDOW+4;
//信息窗口,用于显示Toast
public static final int TYPE_TOAST = FIRST_SYSTEM_WINDOW+5;
//系统顶层窗口,显示在其他一切内容之上,此窗口不能获得输入焦点,否则影响锁屏
public static final int TYPE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW+6;
//电话优先,当锁屏时显示,此窗口不能获得输入焦点,否则影响锁屏
public static final int TYPE_PRIORITY_PHONE = FIRST_SYSTEM_WINDOW+7;
//系统对话框窗口
public static final int TYPE_SYSTEM_DIALOG = FIRST_SYSTEM_WINDOW+8;
//锁屏时显示的对话框
public static final int TYPE_KEYGUARD_DIALOG = FIRST_SYSTEM_WINDOW+9;
//系统内部错误提示,显示在任何窗口之上
public static final int TYPE_SYSTEM_ERROR = FIRST_SYSTEM_WINDOW+10;
//内部输入法窗口,显示于普通UI之上,应用程序可重新布局以免被此窗口覆盖
public static final int TYPE_INPUT_METHOD = FIRST_SYSTEM_WINDOW+11;
//内部输入法对话框,显示于当前输入法窗口之上
public static final int TYPE_INPUT_METHOD_DIALOG= FIRST_SYSTEM_WINDOW+12;
//墙纸窗口
public static final int TYPE_WALLPAPER = FIRST_SYSTEM_WINDOW+13;
//状态栏的滑动面板
public static final int TYPE_STATUS_BAR_PANEL = FIRST_SYSTEM_WINDOW+14;
//安全系统覆盖窗口,这些窗户必须不带输入焦点,否则会干扰键盘
public static final int TYPE_SECURE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW+15;
//最后一个系统窗口
public static final int LAST_SYSTEM_WINDOW = 2999;
........
//窗口特征标记
public int flags;
//当该window对用户可见的时候,允许锁屏
public static final int FLAG_ALLOW_LOCK_WHILE_SCREEN_ON = 0x00000001;
//窗口后面的所有内容都变暗
public static final int FLAG_DIM_BEHIND = 0x00000002;
//Flag:窗口后面的所有内容都变模糊
public static final int FLAG_BLUR_BEHIND = 0x00000004;
//窗口不能获得焦点
public static final int FLAG_NOT_FOCUSABLE = 0x00000008;
//窗口不接受触摸屏事件
public static final int FLAG_NOT_TOUCHABLE = 0x00000010;
//即使在该window在可获得焦点情况下,允许该窗口之外的点击事件传递到当前窗口后面的的窗口去
public static final int FLAG_NOT_TOUCH_MODAL = 0x00000020;
//当手机处于睡眠状态时,如果屏幕被按下,那么该window将第一个收到触摸事件
public static final int FLAG_TOUCHABLE_WHEN_WAKING = 0x00000040;
//当该window对用户可见时,屏幕出于常亮状态
public static final int FLAG_KEEP_SCREEN_ON = 0x00000080;
//:让window占满整个手机屏幕,不留任何边界
public static final int FLAG_LAYOUT_IN_SCREEN = 0x00000100;
//允许窗口超出整个手机屏幕
public static final int FLAG_LAYOUT_NO_LIMITS = 0x00000200;
//window全屏显示
public static final int FLAG_FULLSCREEN = 0x00000400;
//恢复window非全屏显示
public static final int FLAG_FORCE_NOT_FULLSCREEN = 0x00000800;
//开启窗口抖动
public static final int FLAG_DITHER = 0x00001000;
//安全内容窗口,该窗口显示时不允许截屏
public static final int FLAG_SECURE = 0x00002000;
//锁屏时显示该窗口
public static final int FLAG_SHOW_WHEN_LOCKED = 0x00080000;
//系统的墙纸显示在该窗口之后
public static final int FLAG_SHOW_WALLPAPER = 0x00100000;
//当window被显示的时候,系统将把它当做一个用户活动事件,以点亮手机屏幕
public static final int FLAG_TURN_SCREEN_ON = 0x00200000;
//该窗口显示,消失键盘
public static final int FLAG_DISMISS_KEYGUARD = 0x00400000;
//当该window在可以接受触摸屏情况下,让因在该window之外,而发送到后面的window的触摸屏可以支持split touch
public static final int FLAG_SPLIT_TOUCH = 0x00800000;
//对该window进行硬件加速,该flag必须在Activity或Dialog的Content View之前进行设置
public static final int FLAG_HARDWARE_ACCELERATED = 0x01000000;
//让window占满整个手机屏幕,不留任何边界
public static final int FLAG_LAYOUT_IN_OVERSCAN = 0x02000000;
//透明状态栏
public static final int FLAG_TRANSLUCENT_STATUS = 0x04000000;
//透明导航栏
public static final int FLAG_TRANSLUCENT_NAVIGATION = 0x08000000;
..........
//软输入法模式
public int softInputMode;
//用于描述软键盘显示规则的bite的mask
public static final int SOFT_INPUT_MASK_STATE = 0x0f;
//没有软键盘显示的约定规则
public static final int SOFT_INPUT_STATE_UNSPECIFIED = 0;
//可见性状态softInputMode,请不要改变软输入区域的状态
public static final int SOFT_INPUT_STATE_UNCHANGED = 1;
//用户导航(navigate)到你的窗口时隐藏软键盘
public static final int SOFT_INPUT_STATE_HIDDEN = 2;
//总是隐藏软键盘
public static final int SOFT_INPUT_STATE_ALWAYS_HIDDEN = 3;
//用户导航(navigate)到你的窗口时显示软键盘
public static final int SOFT_INPUT_STATE_VISIBLE = 4;
//总是显示软键盘
public static final int SOFT_INPUT_STATE_ALWAYS_VISIBLE = 5;
//显示软键盘时用于表示window调整方式的bite的mask
public static final int SOFT_INPUT_MASK_ADJUST = 0xf0;
//不指定显示软件盘时,window的调整方式
public static final int SOFT_INPUT_ADJUST_UNSPECIFIED = 0x00;
//当显示软键盘时,调整window内的控件大小以便显示软键盘
public static final int SOFT_INPUT_ADJUST_RESIZE = 0x10;
//当显示软键盘时,调整window的空白区域来显示软键盘,即使调整空白区域,软键盘还是有可能遮挡一些有内容区域,这时用户就只有退出软键盘才能看到这些被遮挡区域并进行
public static final int SOFT_INPUT_ADJUST_PAN = 0x20;
//当显示软键盘时,不调整window的布局
public static final int SOFT_INPUT_ADJUST_NOTHING = 0x30;
//用户导航(navigate)到了你的window
public static final int SOFT_INPUT_IS_FORWARD_NAVIGATION = 0x100;
//窗口的对齐方式
public int gravity;
//期望的位图格式,默认为不透明,参考android.graphics.PixelFormat
public int format;
//窗口所使用的动画设置,它必须是一个系统资源而不是应用程序资源,因为窗口管理器不能访问应用程序
public int windowAnimations;
//整个窗口的半透明值,1.0表示不透明,0.0表示全透明
public float alpha = 1.0f;
//当FLAG_DIM_BEHIND设置后生效,该变量指示后面的窗口变暗的程度,1.0表示完全不透明,0.0表示没有变暗
public float dimAmount = 1.0f;
public static final float BRIGHTNESS_OVERRIDE_NONE = -1.0f;
public static final float BRIGHTNESS_OVERRIDE_OFF = 0.0f;
public static final float BRIGHTNESS_OVERRIDE_FULL = 1.0f;
public float screenBrightness = BRIGHTNESS_OVERRIDE_NONE;
//用来覆盖用户设置的屏幕亮度,表示应用用户设置的屏幕亮度,从0到1调整亮度从暗到最亮发生变化
public float buttonBrightness = BRIGHTNESS_OVERRIDE_NONE;
public static final int ROTATION_ANIMATION_ROTATE = 0;
public static final int ROTATION_ANIMATION_CROSSFADE = 1;
public static final int ROTATION_ANIMATION_JUMPCUT = 2;
//屏幕旋转动画
public int rotationAnimation = ROTATION_ANIMATION_ROTATE;
//窗口的标示符
public IBinder token = null;
//此窗口所在应用的包名
public String packageName = null;
//窗口屏幕方向
public int screenOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
//控制status bar是否可见,两种赋值 View#STATUS_BAR_VISIBLE;View#STATUS_BAR_HIDDEN
public int systemUiVisibility;
......
}
}
提取几个重要的参数
- width:描述窗口的宽度,该变量是父类ViewGroup.LayoutParams的成员变量。
- height:描述窗口的高度,该变量同样是父类ViewGroup.LayoutParams的成员变量。
- x:描述窗口的起点X轴的坐标。
- y:描述窗口起点Y轴的坐标。
- type:窗口的类型,分为三个大类型:应用窗口,子窗口,系统窗口。
- flag:窗口特征标记,比如是否全屏,是否隐藏标题栏等。
- gravity:窗口的对齐方式,居中还是置顶或者置底等等。
Window是一个是一个抽象的概念,千万不要认为我们所看到的就是Window,我们平时所看到的是视图,每一个Window都对应着一个View,View和Window通过ViewRootImpl来建立联系。有了View,Window的存在意义在哪里呢,因为View不能单独存在,它必须依附着Window,所以有视图的地方就有Window,比如Activity,一个Dialog,一个PopWindow,一个菜单,一个Toast等等。
3、Window的创建过程与显示过程
通过上面我们知道视图和Window的关系,那么有一个问题,是先有视图,还是先有Window。这个答案只有在源码中找了。应用程序的入口类是ActivityThread,在ActivityThread中有performLaunchActivity来启动Activity,这个performLaunchActivity方法内部会创建一个Activity。
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
...
Activity activity = null;
try {
//通过反射机制创建一个Activity
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
activity = mInstrumentation.newActivity( cl, component.getClassName(), r.intent);
StrictMode.incrementExpectedActivityCount(activity.getClass());
r.intent.setExtrasClassLoader(cl);
r.intent.prepareToEnterProcess();
if (r.state != null) {
r.state.setClassLoader(cl);
}
} catch (Exception e) {
...
}
try {
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
if (activity != null) {
Context appContext = createBaseContextForActivity(r, activity);
CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
Configuration config = new Configuration(mCompatConfiguration);
//这个里面创建了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);
}
activity.mCalled = false;
if (r.isPersistable()) {
//调用Activity的onCreate方法
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
mInstrumentation.callActivityOnCreate(activity, r.state);
}
mActivities.put(r.token, r);
...
} catch (Exception e) {
...
}
return activity;
}
如果activity不为null,就会调用attach,在attach方法中通过PolicyManager创建了Window对象,并且给Window设置了回调接口。
mWindow = PolicyManager.makeNewWindow(this);
mWindow.setCallback(this);/设置回调函数,使得Activity可以处理一些事件
PolicyManager的实现类是Policy
public Window makeNewWindow(Context context) {
return new PhoneWindow(context);
}
这样Window就创建出来了,所以先有Window,后有视图,视图依赖Window存在,再说一说视图(Activity)为Window设置的回调接口。
/**
* API from a Window back to its caller. This allows the client to
* intercept key dispatching, panels and menus, etc.
*/
public interface Callback {
public boolean dispatchKeyEvent(KeyEvent event);
public boolean dispatchKeyShortcutEvent(KeyEvent event);
public boolean dispatchTouchEvent(MotionEvent event);
public boolean dispatchTrackballEvent(MotionEvent event);
public boolean dispatchGenericMotionEvent(MotionEvent event);
public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event);
public View onCreatePanelView(int featureId);
public boolean onCreatePanelMenu(int featureId, Menu menu);
public boolean onPreparePanel(int featureId, View view, Menu menu);
public boolean onMenuOpened(int featureId, Menu menu);
public boolean onMenuItemSelected(int featureId, MenuItem item);
public void onWindowAttributesChanged(WindowManager.LayoutParams attrs);
public void onContentChanged();
public void onWindowFocusChanged(boolean hasFocus);
public void onAttachedToWindow();
public void onDetachedFromWindow();
public void onPanelClosed(int featureId, Menu menu);
public boolean onSearchRequested();
public ActionMode onWindowStartingActionMode(ActionMode.Callback callback);
public void onActionModeStarted(ActionMode mode);
public void onActionModeFinished(ActionMode mode);
}
Activity实现了这个回调接口,当Window的状态发生变化的时候,就会回调Activity中实现的这些接口,有些回调接口我们还是熟悉的,dispatchTouchEvent,onAttachedToWindow,onDetachedFromWindow等。
下面分析view是如何附属到window上的,通过上面可以看到,在attach之后就要执行callActivityOnCreate,在onCreate中我们会调用setContentView方法。
public void setContentView(int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
getWindow获取了Window对象,Window的具体实现类是PhoneWindow,所以要看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) {
//第一步,构建DecroView
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 {
//第二步,将View添加到mContentParent中
mLayoutInflater.inflate(layoutResID, mContentParent);
}
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
//第三步,回调Activity的onContentChanged方法,通知视图发生了改变
cb.onContentChanged();
}
}
这里涉及到一个mContentParent变量,他是一个DecorView的一部分,DecorView是PhoneWindow的一个内部类,我先介绍一下关于DecorView的知识。
DecorView是Activity的顶级VIew,DecorView继承自FrameLayout,在DecorView中有上下两个部分,上面是标题栏,下面是内容栏,我们通过PhoneWindow的setContentView所设置的布局文件是加到内容栏(mContentParent)里面的,View层的事件都是先经过DecorView在传递给我们的View的。
OK在回到setContentView的源码分析,我们可以得到Activity的Window创建需要三步。
- 1、 如果没有DecorView,在installDecor中创建DecorView。
- 2、将View添加到decorview中的mContentParent中。
- 3、回调Activity的onContentChanged接口。
先看看第一步,installDecor的源码
...
if (mDecor == null) {
mDecor = generateDecor();
...
}
...
installDecor中调用了generateDecor,继续看
protected DecorView generateDecor() {
return new DecorView(getContext(), -1);
}
直接给new一个DecorView,有了DecorView之后,就可以加载具体的布局文件到DecorView中了,具体的布局文件和系统和主题有关系。
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
}
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);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
在看第二步,将View添加到decorview中的mContentParent中。
mLayoutInflater.inflate(layoutResID, mContentParent);
直接将Activity视图加到DecorView的mContentParent中,最后一步,回调Activity的onContentChanged接口。在Activity中寻找onContentChanged方法,它是个空实现,我们可以在子Activity中处理。
public void onContentChanged() {}
到此DecorView被创建完毕,我们一开始从Thread中的handleLaunchActivity方法开始分析,首先加载Activity的字节码文件,利用反射的方式创建一个Activity对象,调用Activity对象的attach方法,在attach方法中,创建系统需要的Window并为设置回调,这个回调定义在Window之中,由Activity实现,当Window的状态发生变化的时候,就会回调Activity实现的这些回调方法。调用attach方法之后,Window被创建完成,这时候需要关联我们的视图,在handleLaunchActivity中的attach执行之后就要执行handleLaunchActivity中的callActivityOnCreate,在onCreate中我们会调用setContentView方法。通过setContentView,创建了Activity的顶级View---DecorView,DecorView的内容栏(mContentParent)用来显示我们的布局。这个是我们上面分析得到了一个大致流程,走到这里,这只是添加的过程,还要有一个显示的过程,显示的过程就要调用handleLaunchActivity中的handleResumeActivity方法了。最后会调用makeVisible方法。
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);
}
这里面首先拿到WindowManager对象,用tWindowManager 的父接口ViewManager接收,ViewManager可以
最后调用 mDecor.setVisibility(View.VISIBLE)设置mDecor可见。到此,我们终于明白一个Activity是怎么显示在我们的面前了。
参考链接:
http://blog.csdn.net/feiduclear_up/article/details/49201357