Activity界面显示全解析
最后更新于:2022-04-01 09:53:32
> 前几天凯子哥写的Framework层的解析文章[《Activity启动过程全解析》](http://blog.csdn.net/zhaokaiqiang1992/article/details/49428287),反响还不错,这说明“写让大家都能看懂的Framework解析文章”的思想是基本正确的。
> 我个人觉得,深入分析的文章必不可少,但是对于更多的Android开发者——即只想做应用层开发,不想了解底层实现细节——来说,“整体上把握,重要环节深入“是更好的学习方式。因为这样既可以有完整的知识体系,又不会在浩瀚的源码世界里迷失兴趣和方向。
> 所以呢,今天凯子哥又带来一篇文章,接着上一篇的结尾,重点介绍Activity开启之后,Android系统对界面的一些操作及相关知识。
- [本期关键字](http://blog.csdn.net/zhaokaiqiang1992/article/details/49681321#本期关键字)
- [学习目标](http://blog.csdn.net/zhaokaiqiang1992/article/details/49681321#学习目标)
- [写作方式](http://blog.csdn.net/zhaokaiqiang1992/article/details/49681321#写作方式)
- [进入正题](http://blog.csdn.net/zhaokaiqiang1992/article/details/49681321#进入正题)
- [onCreate中的setContentView到底做了什么为什么不能在setContentView之后设置某些Window属性标志](http://blog.csdn.net/zhaokaiqiang1992/article/details/49681321#oncreate中的setcontentview到底做了什么为什么不能在setcontentview之后设置某些window属性标志)
- [Activity中的findViewById本质上是在做什么](http://blog.csdn.net/zhaokaiqiang1992/article/details/49681321#activity中的findviewbyid本质上是在做什么)
- [Window和PhoneWindow是什么关系WindowManager是做什么的](http://blog.csdn.net/zhaokaiqiang1992/article/details/49681321#window和phonewindow是什么关系windowmanager是做什么的)
- [Activity中Window类型的成员变量mWindow是什么时候初始化的](http://blog.csdn.net/zhaokaiqiang1992/article/details/49681321#activity中window类型的成员变量mwindow是什么时候初始化的)
- [PhoneWindowsetContentView到底发生了什么](http://blog.csdn.net/zhaokaiqiang1992/article/details/49681321#phonewindowsetcontentview到底发生了什么)
- [如何验证上一个问题](http://blog.csdn.net/zhaokaiqiang1992/article/details/49681321#如何验证上一个问题)
- [我们通过setContentView设置的界面为什么在onResume之后才对用户可见呢](http://blog.csdn.net/zhaokaiqiang1992/article/details/49681321#我们通过setcontentview设置的界面为什么在onresume之后才对用户可见呢)
- [ViewManagerWindowManagerWindowManagerImplWindowManagerGlobal到底都是些什么玩意](http://blog.csdn.net/zhaokaiqiang1992/article/details/49681321#viewmanagerwindowmanagerwindowmanagerimplwindowmanagerglobal到底都是些什么玩意)
- [ViewRootImpl是什么有什么作用ViewRootImpl如何与WMS通信](http://blog.csdn.net/zhaokaiqiang1992/article/details/49681321#viewrootimpl是什么有什么作用viewrootimpl如何与wms通信)
- [从什么时候开始绘制整个Activity的View树的](http://blog.csdn.net/zhaokaiqiang1992/article/details/49681321#从什么时候开始绘制整个activity的view树的)
- [Window的类型有几种分别在什么情况下会使用到哪一种](http://blog.csdn.net/zhaokaiqiang1992/article/details/49681321#window的类型有几种分别在什么情况下会使用到哪一种)
- [为什么使用PopWindow的时候不设置背景就不能触发事件](http://blog.csdn.net/zhaokaiqiang1992/article/details/49681321#为什么使用popwindow的时候不设置背景就不能触发事件)
- [在Activity中使用Dialog的时候为什么有时候会报错Unable to add window token is not valid is your activity running](http://blog.csdn.net/zhaokaiqiang1992/article/details/49681321#在activity中使用dialog的时候为什么有时候会报错unable-to-add-window-token-is-not-valid-is-your-activity-running)
- [为什么Toast需要由系统统一控制在子线程中为什么不能显示Toast](http://blog.csdn.net/zhaokaiqiang1992/article/details/49681321#为什么toast需要由系统统一控制在子线程中为什么不能显示toast)
- [结语](http://blog.csdn.net/zhaokaiqiang1992/article/details/49681321#结语)
- [参考文章](http://blog.csdn.net/zhaokaiqiang1992/article/details/49681321#参考文章)
## 本期关键字
- Window
- PhoneWindow
- WindowManager
- WindowManagerImpl
- WindowManagerGlobal
- RootViewImpl
- DecorView
- Dialog
- PopWindow
- Toast
## 学习目标
- 了解Android中Activity界面显示的流程,涉及到的关键类,以及关键流程
- 解决在开发中经常遇到的问题,并在源码的角度弄清楚其原因
- 了解Framework层与Window相关的一些概念和细节
## 写作方式
老样子,咱们还是和上次一样,采用一问一答的方式进行学习,毕竟“带着问题学习”才是比较高效的学习方式。
## 进入正题
话说,在上次的文章中,我们解析到了从手机开机第一个zygote进程开启,到App的第一个Activity的onCreate()结束,那么我们这里就接着上次留下的茬,从第一个Activity的onCreate()开始说起。
### onCreate()中的setContentView()到底做了什么?为什么不能在setContentView()之后设置某些Window属性标志?
一个最简单的onCreate()如下:
~~~
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
~~~
通过上面几行简单的代码,我们的App就可以显示在activity_main.xml文件中设计的界面了,那么这一切到底是怎么做到的呢?
我们跟踪一下源码,然后就在Activity的源码中找到了3个setContentView()的重载函数:
~~~
public void setContentView(int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
public void setContentView(View view) {
getWindow().setContentView(view);
initWindowDecorActionBar();
}
public void setContentView(View view, ViewGroup.LayoutParams params) {
getWindow().setContentView(view, params);
initWindowDecorActionBar();
}
~~~
我们上面用到的就是第一个方法。虽然setContentView()的重载函数有3种,但是我们可以发现,内部做的事情都是基本一样的。首先是调用
getWindow()获取到一个对象,然后调用了这个对象的相关方法。咱们先来看一下,getWindow()到底获取到了什么对象。
~~~
private Window mWindow;
public Window getWindow() {
return mWindow;
}
~~~
喔,原来是一个Window对象,你现在可能不知道Window到底是个什么玩意,但是没关系,你只要能猜到它肯定和咱们的界面现实有关系就得了,毕
竟叫“Window”么,Windows系统的桌面不是叫“Windows”桌面么,差不多的东西,反正是用来显示界面的就得了。
那么initWindowDecorActionBar()函数是做什么的呢?
写了这么多程序,看名字也应该能猜出八九不离十了,init是初始化,Window是窗口,Decor是装饰,ActionBar就更不用说了,所以这个方法应该就是”初始化装饰在窗口上的ActionBar”,来,咱们看一下代码实现:
~~~
/**
* Creates a new ActionBar, locates the inflated ActionBarView,
* initializes the ActionBar with the view, and sets mActionBar.
*/
private void initWindowDecorActionBar() {
Window window = getWindow();
// Initializing the window decor can change window feature flags.
// Make sure that we have the correct set before performing the test below.
window.getDecorView();
if (isChild() || !window.hasFeature(Window.FEATURE_ACTION_BAR) || mActionBar != null) {
return;
}
mActionBar = new WindowDecorActionBar(this);
mActionBar.setDefaultDisplayHomeAsUpEnabled(mEnableDefaultActionBarUp);
mWindow.setDefaultIcon(mActivityInfo.getIconResource());
mWindow.setDefaultLogo(mActivityInfo.getLogoResource());
}
~~~
哟,没想到这里第一行代码就又调用了getWindow(),接着往下调用了window.getDecorView(),从注释中我们知道,在调用这个方法之后,Window
的特征标志就被初始化了,还记得如何让Activity全屏吗?
~~~
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FILL_PARENT, WindowManager.LayoutParams.FILL_PARENT);
setContentView(R.layout.activity_main);
}
~~~
而且这两行代码必须在setContentView()之前调用,知道为啥了吧?因为在这里就把Window的相关特征标志给初始化了,在setContentView()之后调
用就不起作用了!
如果你还不确定的话,我们可以再看下window.getDecorView()的部分注释
~~~
/**
* Note that calling this function for the first time "locks in"
* various window characteristics as described in
* {@link #setContentView(View, android.view.ViewGroup.LayoutParams)}
*/
public abstract View getDecorView();
~~~
“注意,这个方法第一次调用的时候,会锁定在setContentView()中描述的各种Window特征”
所以说,这也同样解释了为什么在setContentView()之后设置Window的一些特征标志,会不起作用。如果以后遇到类似问题,可以往这方面想一下。
### Activity中的findViewById()本质上是在做什么?
在上一个问题里面,咱们提到了一个很重要的类——Window,下面先简单看一下这个类的几个方法:
~~~
public abstract class Window {
public abstract void setContentView(int layoutResID);
public abstract void setContentView(View view);
public abstract void setContentView(View view, ViewGroup.LayoutParams params);
public View findViewById(int id) {
return getDecorView().findViewById(id);
}
}
~~~
哇塞,有个好眼熟的方法,findViewById()!
是的,你每次在Activity中用的这个方法,其实间接调用了Window类里面的方法!
~~~
public View findViewById(int id) {
return getWindow().findViewById(id);
}
~~~
不过,findViewById()的最终实现是在View及其子类里面的,所以getDecorView()获取到的肯定是一个View对象或者是View的子类对象:
~~~
public abstract View getDecorView();
~~~
Activity、Window中的findViewById()最终调用的,其实是View的findViewById()。
~~~
public class View implements Drawable.Callback, KeyEvent.Callback,
AccessibilityEventSource {
public final View findViewById(int id) {
if (id < 0) {
return null;
}
return findViewTraversal(id);
}
protected View findViewTraversal(int id) {
if (id == mID) {
return this;
}
return null;
}
}
~~~
但是,很显然,最终调用的肯定不是View类里面的findViewTraversal(),因为这个方法只会返回自身。
而且,findViewById()是final修饰的,不可被重写,所以说,肯定是调用的被子类重写的findViewTraversal(),再联想到,我们的界面上有很多的View,那么既能作为View的容器,又是View的子类的类是什么呢?很显然,是ViewGroup!
~~~
public abstract class ViewGroup extends View implements ViewParent, ViewManager {
@Override
protected View findViewTraversal(int id) {
if (id == mID) {
return this;
}
final View[] where = mChildren;
final int len = mChildrenCount;
for (int i = 0; i < len; i++) {
View v = where[i];
if ((v.mPrivateFlags & PFLAG_IS_ROOT_NAMESPACE) == 0) {
v = v.findViewById(id);
if (v != null) {
return v;
}
}
}
return null;
}
}
~~~
所以说,在onCreate()中调用findViewById()对控件进行绑定的操作,实质上是通过在某个View中查找子View实现的,这里你先记住,这个View叫做
DecorView,而且它位于用户窗口的最下面一层。
### Window和PhoneWindow是什么关系?WindowManager是做什么的?
话说,咱们前面介绍Window的时候,只是简单的介绍了下findViewById(),还没有详细的介绍下这个类,下面咱们一起学习一下。
前面提到过,Window是一个抽象类,抽象类肯定是不能实例化的,所以咱们需要使用的是它的实现类,Window的实现类有哪些呢?咱们从Dash中看下Window类的文档
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da7cb9c48.png "")
Window只有一个实现类,就是PhoneWindow!所以说这里扯出了PhoneWindow这个类。
而且文档还说,这个类的一个实例,也就是PhoneWindow,应该被添加到Window Manager中,作为顶层的View,所以,这里又扯出了一个WindowManager的概念。
除此之外,还说这个类提供了标准的UI策略,比如背景,标题区域,和默认的按键处理等等,所以说,咱们还知道了Window和PhoneWindow这两个类的作用!
所以说,看文档多重要呀!
OK,现在咱们已经知道了Window和唯一的实现类PhoneWindow,以及他们的作用。而且我们还知道了WindowManager,虽然不知道干嘛的,但是从名字也可以猜出是管理Window的,而且还会把Window添加到里面去,在下面的模块中,我会详细的介绍WindowManager这个类。
### Activity中,Window类型的成员变量mWindow是什么时候初始化的?
在每个Activity中都有一个Window类型的对象mWindow,那么是什么时候初始化的呢?
是在attach()的时候。
还记得attach()是什么时候调用的吗?是在ActivityThread.performLaunchActivity()的时候:
~~~
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
Activity activity = null;
try {
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
} catch (Exception e) {
...ignore some code...
}
try {
...ignore some code...
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.voiceInteractor);
...ignore some code...
} catch (Exception e) { }
return activity;
}
~~~
在attach()里面做了些什么呢?
~~~
public class Activity extends ContextThemeWrapper
implements LayoutInflater.Factory2,
Window.Callback, KeyEvent.Callback,
OnCreateContextMenuListener, ComponentCallbacks2,
Window.OnWindowDismissedCallback {
private Window mWindow;
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, IVoiceInteractor voiceInteractor) {
...ignore some code...
//就是在这里实例化了Window对象
mWindow = PolicyManager.makeNewWindow(this);
//设置各种回调
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
//这就是传说中的UI线程,也就是ActivityThread所在的,开启了消息循环机制的线程,所以在Actiivty所在线程中使用Handler不需要使用Loop开启消息循环。
mUiThread = Thread.currentThread();
...ignore some code...
//终于见到了前面提到的WindowManager,可以看到,WindowManager属于一种系统服务
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();
}
}
~~~
attach()是Activity实例化之后,调用的第一个函数,在这个时候,就实例化了Window。那么这个PolicyManager是什么玩意?
~~~
mWindow = PolicyManager.makeNewWindow(this);
~~~
来来来,咱们一起RTFSC(Read The Fucking Source Code)!
~~~
public final class PolicyManager {
private static final String POLICY_IMPL_CLASS_NAME =
"com.android.internal.policy.impl.Policy";
private static final IPolicy sPolicy;
static {
// Pull in the actual implementation of the policy at run-time
try {
Class policyClass = Class.forName(POLICY_IMPL_CLASS_NAME);
sPolicy = (IPolicy)policyClass.newInstance();
} catch (ClassNotFoundException ex) {
throw new RuntimeException(
POLICY_IMPL_CLASS_NAME + " could not be loaded", ex);
} catch (InstantiationException ex) {
throw new RuntimeException(
POLICY_IMPL_CLASS_NAME + " could not be instantiated", ex);
} catch (IllegalAccessException ex) {
throw new RuntimeException(
POLICY_IMPL_CLASS_NAME + " could not be instantiated", ex);
}
}
private PolicyManager() {}
public static Window makeNewWindow(Context context) {
return sPolicy.makeNewWindow(context);
}
}
~~~
“Policy”是“策略”的意思,所以就是一个策略管理器,采用了策略设计模式。而sPolicy是一个IPolicy类型,IPolicy实际上是一个接口
~~~
public interface IPolicy {}
~~~
所以说,sPolicy的实际类型是在静态代码块里面,利用反射进行实例化的Policy类型。静态代码块中的代码在类文件加载进类加载器之后就会执行,
sPolicy就实现了实例化。
那我们看下在Policy里面实际上是做了什么
~~~
public class Policy implements IPolicy {
//看见PhoneWindow眼熟么?还有DecorView,眼熟么?这就是前面所说的那个位于最下面的View,findViewById()就是在它里面找的
private static final String[] preload_classes = {
"com.android.internal.policy.impl.PhoneLayoutInflater",
"com.android.internal.policy.impl.PhoneWindow",
"com.android.internal.policy.impl.PhoneWindow$1",
"com.android.internal.policy.impl.PhoneWindow$DialogMenuCallback",
"com.android.internal.policy.impl.PhoneWindow$DecorView",
"com.android.internal.policy.impl.PhoneWindow$PanelFeatureState",
"com.android.internal.policy.impl.PhoneWindow$PanelFeatureState$SavedState",
};
//由于性能方面的原因,在当前Policy类加载的时候,会预加载一些特定的类
static {
for (String s : preload_classes) {
try {
Class.forName(s);
} catch (ClassNotFoundException ex) {
Log.e(TAG, "Could not preload class for phone policy: " + s);
}
}
}
//终于找到PhoneWindow了,我没骗你吧,前面咱们所说的Window终于可以换成PhoneWindow了~
public Window makeNewWindow(Context context) {
return new PhoneWindow(context);
}
}
~~~
PhoneWindow.setContentView()到底发生了什么?
上面说了这么多,实际上只是追踪到了PhoneWindow.setContentView(),下面看一下到底在这里执行了什么:
~~~
@Override
public void setContentView(int layoutResID) {
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);
}
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
@Override
public void setContentView(View view) {
setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
view.setLayoutParams(params);
final Scene newScene = new Scene(mContentParent, view);
transitionTo(newScene);
} else {
mContentParent.addView(view, params);
}
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
~~~
当我们第一次调用serContentView()的时候,mContentParent是没有进行过初始化的,所以会调用installDecor()。
为什么能确定mContentParent是没有初始化的呢?因为mContentParent就是在installDecor()里面赋值的
~~~
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor();
...
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
}
}
~~~
在generateDecor()做了什么?返回了一个DecorView对象。
~~~
protected DecorView generateDecor() {
return new DecorView(getContext(), -1);
}
~~~
还记得前面推断出的,DecorView是一个ViewGroup的结论吗?看下面,DecorView继承自FrameLayout,所以咱们的推论是完全正确的。而且
DecorView是PhoneWindow的私有内部类,这两个类关系紧密!
~~~
public class PhoneWindow extends Window implements MenuBuilder.Callback {
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {}
}
~~~
咱们再看一下在对mContentParent赋值的generateLayout(mDecor)做了什么
~~~
protected ViewGroup generateLayout(DecorView decor) {
...判断并设置了一堆的标志位...
//这个是我们的界面将要采用的基础布局xml文件的id
int layoutResource;
//根据标志位,给layoutResource赋值
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
layoutResource = R.layout.screen_swipe_dismiss;
}
...我们设置不同的主题以及样式,会采用不同的布局文件...
else {
//我们在下面代码验证的时候,就会用到这个布局,记住它哦
layoutResource = R.layout.screen_simple;
}
//要开始更改mDecor啦~
mDecor.startChanging();
//将xml文件解析成View对象,至于LayoutInflater是如何将xml解析成View的,咱们后面再说
View in = mLayoutInflater.inflate(layoutResource, null);
//decor和mDecor实际上是同一个对象,一个是形参,一个是成员变量
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
mContentRoot = (ViewGroup) in;
//这里的常量ID_ANDROID_CONTENT就是 public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
//而且,由于是直接执行的findViewById(),所以本质上还是调用的mDecor.findViewById()。而在上面的decor.addView()执行之前,decor里面是空白的,所以我们可以断定,layoutResource所指向的xml布局文件内部,一定存在一个叫做“content”的ViewGroup
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
......
mDecor.finishChanging();
//最后把id为content的一个ViewGroup返回了
return contentParent;
}
~~~
当上的代码执行之后,mDecor和mContentParent就初始化了,往下就会执行下面的代码,利用LayoutInflater把咱们传进来的layoutResID转化成
View对象,然后添加到id为content的mContentParent中
~~~
mLayoutInflater.inflate(layoutResID, mContentParent);
~~~
所以到目前为止,咱们已经知道了以下几个事实,咱们总结一下:
- DecorView是PhoneWindow的内部类,继承自FrameLayout,是最底层的界面
- PhoneWindow是Window的唯一子类,他们的作用就是提供标准UI,标题,背景和按键操作
- 在DecorView中会根据用户选择的不同标志,选择不同的xml文件,并且这些布局会被添加到DecorView中
- 在DecorView中,一定存在一个叫做“content”的ViewGroup,而且我们在xml文件中声明的布局文件,会被添加进去
既然是事实,那么怎么才能验证一下呢?
### 如何验证上一个问题
首先,说明一下运行条件:
~~~
//主题
name="AppTheme" parent="@android:style/Theme.Holo.Light.NoActionBar"
//编译版本
android {
compileSdkVersion 19
buildToolsVersion '19.1.0'
defaultConfig {
applicationId "com.socks.uitestapp"
minSdkVersion 15
targetSdkVersion 19
versionCode 1
versionName "1.0"
}
}
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
compile 'com.android.support:appcompat-v7:19.1.0'
}
//Activity代码
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
//activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="Hello World!"
android:textSize="20sp" />
~~~
OK,咱们的软件已经准备好了,采用的是最简单的布局,界面效果如下:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da7cd05de.png "")
下面用Hierarchy看一下树状结构:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da7ce9b7a.png "")
第一层,就是上面的DecorView,里面有一个线性布局,上面的是ViewStub,下面就是id为content的ViewGroup,是一个FrameLayout。而我们通过setContentView()设置的布局,就是TextView了。
能不能在源码里面找到源文件呢?当然可以,这个布局就是screen_simple.xml
frameworks/base/core/res/res/layout/screen_simple.xml
~~~
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:orientation="vertical">
<ViewStub android:id="@+id/action_mode_bar_stub"
android:inflatedId="@+id/action_mode_bar"
android:layout="@layout/action_mode_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="?attr/actionBarTheme" />
<FrameLayout
android:id="@android:id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:foregroundInsidePadding="false"
android:foregroundGravity="fill_horizontal|top"
android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>
~~~
所以,即使你不调用setContentView(),在一个空Activity上面,也是有布局的。而且肯定有一个DecorView,一个id为content的FrameLayout。
你可以采用下面的方式获取到DecorView,但是你不能获取到一个DecorView实例,只能获取到ViewGroup。
下面贴上这个图,你就可以看明白了(转自 [工匠若水](http://blog.csdn.net/yanbober))
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da7959677.jpg "")
~~~
ViewGroup view = (ViewGroup) getWindow().getDecorView();
~~~
我们通过setContentView()设置的界面,为什么在onResume()之后才对用户可见呢?
有开发经验的朋友应该知道,我们的界面元素在onResume()之后才对用户是可见的,这是为啥呢?
那我们就追踪一下,onResume()是什么时候调用的,然后看看做了什么操作就Ok了。
这一下,我们又要从ActivityThread开始说起了,不熟悉的快去看前一篇文章《Activity启动过程全解析》]([http://blog.csdn.net/zhaokaiqiang1992/article/details/49428287](http://blog.csdn.net/zhaokaiqiang1992/article/details/49428287))。
话说,前文说到,我们想要开启一个Activity的时候,ActivityThread的handleLaunchActivity()会在Handler中被调用
~~~
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
//就是在这里调用了Activity.attach()呀,接着调用了Activity.onCreate()和Activity.onStart()生命周期,但是由于只是初始化了mDecor,添加了布局文件,还没有把
//mDecor添加到负责UI显示的PhoneWindow中,所以这时候对用户来说,是不可见的
Activity a = performLaunchActivity(r, customIntent);
......
if (a != null) {
//这里面执行了Activity.onResume()
handleResumeActivity(r.token, false, r.isForward,
!r.activity.mFinished && !r.startsNotResumed);
if (!r.activity.mFinished && r.startsNotResumed) {
try {
r.activity.mCalled = false;
//执行Activity.onPause()
mInstrumentation.callActivityOnPause(r.activity);
}
}
}
}
~~~
所以说,ActivityThread.handleLaunchActivity执行完之后,Activity的生命周期已经执行了4个(onCreate、onStart()、onResume、onPause())。
下面咱们重点看下handleResumeActivity()做了什么
~~~
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume) {
//这个时候,Activity.onResume()已经调用了,但是现在界面还是不可见的
ActivityClientRecord r = performResumeActivity(token, clearHide);
if (r != null) {
final Activity a = r.activity;
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
//decor对用户不可见
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
//这里记住这个WindowManager.LayoutParams的type为TYPE_BASE_APPLICATION,后面介绍Window的时候会见到
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
if (a.mVisibleFromClient) {
a.mWindowAdded = true;
//终于被添加进WindowManager了,但是这个时候,还是不可见的
wm.addView(decor, l);
}
if (!r.activity.mFinished && willBeVisible
&& r.activity.mDecor != null && !r.hideForNow) {
//在这里,执行了重要的操作!
if (r.activity.mVisibleFromClient) {
r.activity.makeVisible();
}
}
}
~~~
从上面的分析中我们知道,其实在onResume()执行之后,界面还是不可见的,当我们执行了Activity.makeVisible()方法之后,界面才对我们是可见的
~~~
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);
~~~
OK,其实讲到了这里,关于Activity中的界面显示应该算是告一段落了,我们知道了Activity的生命周期方法的调用时机,还知道了一个最简单的Activity
的界面的构成,并了解了Window、PhoneWindow、DecorView、WindowManager的存在。
但是我还是感觉不过瘾,因为上面只是在流程上大体上过了一遍,对于Window、WindowManager的深入了解还不够,所以下面就开始讲解Window、WindowManager等相关类的稍微高级点的知识。
前面看累了的朋友,可以上个厕所,泡个咖啡,休息下继续往下看。
### ViewManager、WindowManager、WindowManagerImpl、WindowManagerGlobal到底都是些什么玩意?
WindowManager其实是一个接口,和Window一样,起作用的是它的实现类
~~~
public interface WindowManager extends ViewManager {
//对这个异常熟悉么?当你往已经销毁的Activity中添加Dialog的时候,就会抛这个异常
public static class BadTokenException extends RuntimeException {
public BadTokenException() {
}
public BadTokenException(String name) {
super(name);
}
}
//其实WindowManager里面80%的代码是用来描述这个内部静态类的
public static class LayoutParams extends ViewGroup.LayoutParams
implements Parcelable {
}
}
~~~
WindowManager继承自ViewManager这个接口,从注释和方法我们可以知道,这个就是用来描述可以对Activity中的子View进行添加和移除能力的接口。
~~~
/** Interface to let you add and remove child views to an Activity. To get an instance
* of this class, call {@link android.content.Context#getSystemService(java.lang.String) Context.getSystemService()}.
*/
public interface ViewManager
{
public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
}
~~~
那么我们在使用WindowManager的时候,到底是在使用哪个类呢?
是WindowManagerImpl。
~~~
public final class WindowManagerImpl implements WindowManager {}
~~~
怎么知道的呢?那我们还要从Activity.attach()说起
话说,在attach()里面完成了mWindowManager的初始化
~~~
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, IVoiceInteractor voiceInteractor) {
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
mWindowManager = mWindow.getWindowManager();
}
~~~
那我们只好看下(WindowManager)context.getSystemService(Context.WINDOW_SERVICE)是什么玩意了。
这里要说明的是,context是一个ContextImpl对象,这里先记住就好,以后再细说。
~~~
class ContextImpl extends Context {
//静态代码块,完成各种系统服务的注册
static {
......
registerService(WINDOW_SERVICE, new ServiceFetcher() {
Display mDefaultDisplay;
public Object getService(ContextImpl ctx) {
Display display = ctx.mDisplay;
if (display == null) {
if (mDefaultDisplay == null) {
DisplayManager dm = (DisplayManager)ctx.getOuterContext().
getSystemService(Context.DISPLAY_SERVICE);
mDefaultDisplay = dm.getDisplay(Display.DEFAULT_DISPLAY);
}
display = mDefaultDisplay;
}
//没骗你吧
return new WindowManagerImpl(display);
}});
......
}
@Override
public Object getSystemService(String name) {
ServiceFetcher fetcher = SYSTEM_SERVICE_MAP.get(name);
return fetcher == null ? null : fetcher.getService(this);
}
}
~~~
要注意的是,这里返回的WindowManagerImpl对象,最终并不是和我们的Window关联的,而且这个方法是有可能返回null的,所以在
Window.setWindowManager()的时候,进行了处理
~~~
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
boolean hardwareAccelerated) {
mAppToken = appToken;
mAppName = appName;
mHardwareAccelerated = hardwareAccelerated
|| SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false);
//重试一遍
if (wm == null) {
wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
}
//设置parentWindow,创建真正关联的WindowManagerImpl对象
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
public final class WindowManagerImpl implements WindowManager {
//最终调用的这个构造
private WindowManagerImpl(Display display, Window parentWindow) {
mDisplay = display;
mParentWindow = parentWindow;
}
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
return new WindowManagerImpl(mDisplay, parentWindow);
}
}
~~~
所以说,每一个Activity都有一个PhoneWindow成员变量,并且也都有一个WindowManagerImpl,而且,PhoneWindow和WindowManagerImpl
在Activity.attach()的时候进行了关联。
插一张类图(转自[工匠若水](http://blog.csdn.net/yanbober))
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da79baa0a.jpg "")
知道了这些,那下面的操作就可以直接看WindowManagerImpl了。
其实WindowManagerImpl这个类也没有什么看头,为啥这么说呢?因为他其实是代理模式中的代理。是谁的代理呢?是WindowManagerGlobal。
~~~
public final class WindowManagerImpl implements WindowManager {
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
private final Display mDisplay;
private final Window mParentWindow;
@Override
public void addView(View view, ViewGroup.LayoutParams params) {
mGlobal.addView(view, params, mDisplay, mParentWindow);
}
@Override
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
mGlobal.updateViewLayout(view, params);
}
@Override
public void removeView(View view) {
mGlobal.removeView(view, false);
}
@Override
public void removeViewImmediate(View view) {
mGlobal.removeView(view, true);
}
}
~~~
从上面的代码中可以看出来,WindowManagerImpl里面对ViewManager接口内方法的实现,都是通过代理WindowManagerGlobal的方法实现的,
所以重点转移到了WindowManagerGlobal这个类。
还记得前面我们的DecorView被添加到了WindowManager吗?
~~~
wm.addView(decor, l);
~~~
其实最终调用的是WindowManagerGlobal.addView();
~~~
public final class WindowManagerGlobal {
private static IWindowManager sWindowManagerService;
private static IWindowSession sWindowSession;
private final ArrayList<View> mViews = new ArrayList<View>();
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
private final ArrayList<WindowManager.LayoutParams> mParams =
new ArrayList<WindowManager.LayoutParams>();
//WindowManagerGlobal是单例模式
private static WindowManagerGlobal sDefaultWindowManager;
public static WindowManagerGlobal getInstance() {
synchronized (WindowManagerGlobal.class) {
if (sDefaultWindowManager == null) {
sDefaultWindowManager = new WindowManagerGlobal();
}
return sDefaultWindowManager;
}
}
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
......
synchronized (mLock) {
ViewRootImpl root;
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
}
......
try {
//注意下这个方法,因为下面介绍ViewRootImpl的时候会用到
root.setView(view, wparams, panelParentView);
}catch (RuntimeException e) {
}
}
}
~~~
我们看到,WindowManagerGlobal是单例模式,所以在一个App里面只会有一个WindowManagerGlobal实例。在WindowManagerGlobal里面维
护了三个集合,分别存放添加进来的View(实际上就是DecorView),布局参数params,和刚刚实例化的ViewRootImpl对象,
WindowManagerGlobal到底干嘛的呢?
其实,WindowManagerGlobal是和WindowManagerService(即WMS)通信的。
还记得在上一篇文章中我们介绍ActivityThread和AMS之间的IBinder通信的吗?是的,这里也是IBinder通信。
~~~
public static IWindowSession getWindowSession() {
synchronized (WindowManagerGlobal.class) {
if (sWindowSession == null) {
try {
InputMethodManager imm = InputMethodManager.getInstance();
IWindowManager windowManager = getWindowManagerService();
sWindowSession = windowManager.openSession(
......
} catch (RemoteException e) {
Log.e(TAG, "Failed to open window session", e);
}
}
return sWindowSession;
}
}
public static IWindowManager getWindowManagerService() {
synchronized (WindowManagerGlobal.class) {
if (sWindowManagerService == null) {
//ServiceManager是用来管理系统服务的,比如AMS、WMS等,这里就获取到了WMS的客户端代理对象
sWindowManagerService = IWindowManager.Stub.asInterface(
ServiceManager.getService("window"));
}
return sWindowManagerService;
}
}
~~~
首先通过上面的方法获取到IBinder对象,然后转化成了WMS在本地的代理对象IWindowManager,然后通过openSession()初始化了sWindowSession
对象。这个对象是干什么的呢?
“Session“是会话的意思,这个类就是为了实现与WMS的会话的,谁和WMS的对话呢?WindowManagerGlobal类内部并没有用这个类呀!
是ViewRootImpl与WMS的对话。
### ViewRootImpl是什么?有什么作用?ViewRootImpl如何与WMS通信
你还记得么?在前面将WindowManagerGlobal.addView()的时候,实例化了一个ViewRootImpl,然后添加到了一个集合里面,咱们先看下ViewRootImpl的构造函数吧
~~~
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks {
public ViewRootImpl(Context context, Display display) {
mContext = context;
//获取WindowSession
mWindowSession = WindowManagerGlobal.getWindowSession();
mDisplay = display;
......
mWindow = new W(this);
//默认不可见
mViewVisibility = View.GONE;
//这个数值就是屏幕宽度的dp总数
mDensity = context.getResources().getDisplayMetrics().densityDpi;
mChoreographer = Choreographer.getInstance();
mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
}
}
~~~
在这个构造方法里面,主要是完成了各种参数的初始化,并且最关键的,获取到了前面介绍的WindowSession,那么你可能好奇了,这个ViewRootImpl
到底有什么作用呢?
ViewRootImpl负责管理视图树和与WMS交互,与WMS交互是通过WindowSession。而且ViewRootImpl也负责UI界面的布局与渲染,负责把一些事件分发至Activity,以便Activity可以截获事件。大多数情况下,它管理Activity顶层视图DecorView,它相当于MVC模型中的Controller。
WindowSession是ViewRootImpl获取之后,主动和WMS通信的,但是我们在前面的文章知道,客户端和服务器需要互相持有对方的代理引用,才能实现双向通信,那么WMS是怎么得到ViewRootImpl的通信代理的呢?
是在ViewRootImpl.setView()的时候。
还记得不?在上面介绍WindowManagerGlobal.addView()的时候,我还重点说了下,在这个方法的try代码块中,调用了ViewRootImpl.setView(),下面咱们看下这个方法干嘛了:
~~~
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
int res;
requestLayout();
try {
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mInputChannel);
}catch (RemoteException e) {
throw new RuntimeException("Adding window failed", e);
} finally {
}
}
}
}
~~~
为了突出重点,我简化了很多代码,从上面可以看出来,是mWindowSession.addToDisplay()这个方法把mWindow传递给我WMS,WMS就持有了
当前ViewRootlmpl的代理,就可以调用W对象让ViewRootlmpl做一些事情了。
这样,双方都有了对方的接口,WMS中的Session注册到WindowManagerGlobal的成员WindowSession中,ViewRootImpl::W注册到WindowState中的成员mClient中。前者是为了App改变View结构时请求WMS为其更新布局。后者代表了App端的一个添加到WMS中的View,每一个像这样通过WindowManager接口中addView()添加的窗口都有一个对应的ViewRootImpl,也有一个相应的ViewRootImpl::W。它可以理解为是ViewRootImpl中暴露给WMS的接口,这样WMS可以通过这个接口和App端通信。
另外源码中很多地方采用了这种将接口隐藏为内部类的方式,这样可以实现六大设计原则之一——接口最小原则。
### 从什么时候开始绘制整个Activity的View树的?
注意前面代码中的requestLayout();因为这个方法执行之后,我们的ViewRootImpl才开始绘制整个View树!
~~~
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
~~~
~~~
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
//暂停UI线程消息队列对同步消息的处理
mTraversalBarrier = mHandler.getLooper().postSyncBarrier();
//向Choreographer注册一个类型为CALLBACK_TRAVERSAL的回调,用于处理UI绘制
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
}
}
~~~
“Choreographer就是一个消息处理器,根据vsync 信号 来计算frame“
解释起来比较麻烦,我们暂时不展开讨论,你只要知道,当回调被触发之后,mTraversalRunnable对象的run()就会被调用
~~~
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
~~~
doTraversal()中最关键的,就是调用了performTraversals(),然后就开始mesure,layout,draw了,这里面的具体逻辑本篇文章不讲,因为重点是
Activity的界面显示流程,这一块属于View的,找时间单独拿出来说
~~~
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "performTraversals");
try {
performTraversals();
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
~~~
来回倒腾了这么多,终于看见界面了,让我哭会 T^T
### Window的类型有几种?分别在什么情况下会使用到哪一种?
Window的类型是根据WindowManager.LayoutParams的type属性相关的,根据类型可以分为三类:
- 取值在FIRST_APPLICATION_WINDOW与LAST_APPLICATION_WINDOW之间(1-99),是常用的顶层应用程序窗口,须将token设置成Activity的token,比如前面开启Window的时候设置的类型即为TYPE_APPLICATION
- 在FIRST_SUB_WINDOW和LAST_SUB_WINDOW(1000-1999)之间,与顶层窗口相关联,需将token设置成它所附着宿主窗口的token,比如PopupWindow就是TYPE_APPLICATION_PANEL
- 取值在FIRST_SYSTEM_WINDOW和LAST_SYSTEM_WINDOW(2000-2999)之间,不能用于应用程序,使用时需要有特殊权限,它是特定的系统功能才能使用,比如Toast就是TYPE_TOAST=2005,所以不需要特殊权限
下面是所有的Type说明
~~~
//WindowType:开始应用程序窗口
public static final int FIRST_APPLICATION_WINDOW = 1;
//WindowType:所有程序窗口的base窗口,其他应用程序窗口都显示在它上面
public static final int TYPE_BASE_APPLICATION = 1;
//WindowType:普通应用程序窗口,token必须设置为Activity的token来指定窗口属于谁
public static final int TYPE_APPLICATION = 2;
//WindowType:应用程序启动时所显示的窗口,应用自己不要使用这种类型,它被系统用来显示一些信息,直到应用程序可以开启自己的窗口为止
public static final int TYPE_APPLICATION_STARTING = 3;
//WindowType:结束应用程序窗口
public static final int LAST_APPLICATION_WINDOW = 99;
//WindowType:SubWindows子窗口,子窗口的Z序和坐标空间都依赖于他们的宿主窗口
public static final int FIRST_SUB_WINDOW = 1000;
//WindowType: 面板窗口,显示于宿主窗口的上层
public static final int TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW;
//WindowType:媒体窗口(例如视频),显示于宿主窗口下层
public static final int TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW+1;
//WindowType:应用程序窗口的子面板,显示于所有面板窗口的上层
public static final int TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW+2;
//WindowType:对话框,类似于面板窗口,绘制类似于顶层窗口,而不是宿主的子窗口
public static final int TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW+3;
//WindowType:媒体信息,显示在媒体层和程序窗口之间,需要实现半透明效果
public static final int TYPE_APPLICATION_MEDIA_OVERLAY = FIRST_SUB_WINDOW+4;
//WindowType:子窗口结束
public static final int LAST_SUB_WINDOW = 1999;
//WindowType:系统窗口,非应用程序创建
public static final int FIRST_SYSTEM_WINDOW = 2000;
//WindowType:状态栏,只能有一个状态栏,位于屏幕顶端,其他窗口都位于它下方
public static final int TYPE_STATUS_BAR = FIRST_SYSTEM_WINDOW;
//WindowType:搜索栏,只能有一个搜索栏,位于屏幕上方
public static final int TYPE_SEARCH_BAR = FIRST_SYSTEM_WINDOW+1;
//WindowType:电话窗口,它用于电话交互(特别是呼入),置于所有应用程序之上,状态栏之下
public static final int TYPE_PHONE = FIRST_SYSTEM_WINDOW+2;
//WindowType:系统提示,出现在应用程序窗口之上
public static final int TYPE_SYSTEM_ALERT = FIRST_SYSTEM_WINDOW+3;
//WindowType:锁屏窗口
public static final int TYPE_KEYGUARD = FIRST_SYSTEM_WINDOW+4;
//WindowType:信息窗口,用于显示Toast
public static final int TYPE_TOAST = FIRST_SYSTEM_WINDOW+5;
//WindowType:系统顶层窗口,显示在其他一切内容之上,此窗口不能获得输入焦点,否则影响锁屏
public static final int TYPE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW+6;
//WindowType:电话优先,当锁屏时显示,此窗口不能获得输入焦点,否则影响锁屏
public static final int TYPE_PRIORITY_PHONE = FIRST_SYSTEM_WINDOW+7;
//WindowType:系统对话框
public static final int TYPE_SYSTEM_DIALOG = FIRST_SYSTEM_WINDOW+8;
//WindowType:锁屏时显示的对话框
public static final int TYPE_KEYGUARD_DIALOG = FIRST_SYSTEM_WINDOW+9;
//WindowType:系统内部错误提示,显示于所有内容之上
public static final int TYPE_SYSTEM_ERROR = FIRST_SYSTEM_WINDOW+10;
//WindowType:内部输入法窗口,显示于普通UI之上,应用程序可重新布局以免被此窗口覆盖
public static final int TYPE_INPUT_METHOD = FIRST_SYSTEM_WINDOW+11;
//WindowType:内部输入法对话框,显示于当前输入法窗口之上
public static final int TYPE_INPUT_METHOD_DIALOG= FIRST_SYSTEM_WINDOW+12;
//WindowType:墙纸窗口
public static final int TYPE_WALLPAPER = FIRST_SYSTEM_WINDOW+13;
//WindowType:状态栏的滑动面板
public static final int TYPE_STATUS_BAR_PANEL = FIRST_SYSTEM_WINDOW+14;
//WindowType:安全系统覆盖窗口,这些窗户必须不带输入焦点,否则会干扰键盘
public static final int TYPE_SECURE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW+15;
//WindowType:拖放伪窗口,只有一个阻力层(最多),它被放置在所有其他窗口上面
public static final int TYPE_DRAG = FIRST_SYSTEM_WINDOW+16;
//WindowType:状态栏下拉面板
public static final int TYPE_STATUS_BAR_SUB_PANEL = FIRST_SYSTEM_WINDOW+17;
//WindowType:鼠标指针
public static final int TYPE_POINTER = FIRST_SYSTEM_WINDOW+18;
//WindowType:导航栏(有别于状态栏时)
public static final int TYPE_NAVIGATION_BAR = FIRST_SYSTEM_WINDOW+19;
//WindowType:音量级别的覆盖对话框,显示当用户更改系统音量大小
public static final int TYPE_VOLUME_OVERLAY = FIRST_SYSTEM_WINDOW+20;
//WindowType:起机进度框,在一切之上
public static final int TYPE_BOOT_PROGRESS = FIRST_SYSTEM_WINDOW+21;
//WindowType:假窗,消费导航栏隐藏时触摸事件
public static final int TYPE_HIDDEN_NAV_CONSUMER = FIRST_SYSTEM_WINDOW+22;
//WindowType:梦想(屏保)窗口,略高于键盘
public static final int TYPE_DREAM = FIRST_SYSTEM_WINDOW+23;
//WindowType:导航栏面板(不同于状态栏的导航栏)
public static final int TYPE_NAVIGATION_BAR_PANEL = FIRST_SYSTEM_WINDOW+24;
//WindowType:universe背后真正的窗户
public static final int TYPE_UNIVERSE_BACKGROUND = FIRST_SYSTEM_WINDOW+25;
//WindowType:显示窗口覆盖,用于模拟辅助显示设备
public static final int TYPE_DISPLAY_OVERLAY = FIRST_SYSTEM_WINDOW+26;
//WindowType:放大窗口覆盖,用于突出显示的放大部分可访问性放大时启用
public static final int TYPE_MAGNIFICATION_OVERLAY = FIRST_SYSTEM_WINDOW+27;
//WindowType:......
public static final int TYPE_KEYGUARD_SCRIM = FIRST_SYSTEM_WINDOW+29;
public static final int TYPE_PRIVATE_PRESENTATION = FIRST_SYSTEM_WINDOW+30;
public static final int TYPE_VOICE_INTERACTION = FIRST_SYSTEM_WINDOW+31;
public static final int TYPE_ACCESSIBILITY_OVERLAY = FIRST_SYSTEM_WINDOW+32;
//WindowType:系统窗口结束
public static final int LAST_SYSTEM_WINDOW = 2999;
~~~
为什么使用PopWindow的时候,不设置背景就不能触发事件?
我们在使用PopupWindow的时候,会发现如果不给PopupWindow设置背景,那么就不能触发点击返回事件,有人认为这个是BUG,其实并不是的。
我们以下面的方法为例,其实所有的显示方法都有下面的流程:
~~~
public void showAtLocation(IBinder token, int gravity, int x, int y) {
if (isShowing() || mContentView == null) {
return;
}
mIsShowing = true;
mIsDropdown = false;
WindowManager.LayoutParams p = createPopupLayout(token);
p.windowAnimations = computeAnimationResource();
//在这里会根据不同的设置,配置不同的LayoutParams属性
preparePopup(p);
if (gravity == Gravity.NO_GRAVITY) {
gravity = Gravity.TOP | Gravity.START;
}
p.gravity = gravity;
p.x = x;
p.y = y;
if (mHeightMode < 0) p.height = mLastHeight = mHeightMode;
if (mWidthMode < 0) p.width = mLastWidth = mWidthMode;
invokePopup(p);
}
~~~
我们重点看下preparePopup()
~~~
private void preparePopup(WindowManager.LayoutParams p) {
//根据背景的设置情况进行不同的配置
if (mBackground != null) {
final ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams();
int height = ViewGroup.LayoutParams.MATCH_PARENT;
//如果设置了背景,就用一个PopupViewContainer对象来包裹之前的mContentView,并设置背景后
PopupViewContainer popupViewContainer = new PopupViewContainer(mContext);
PopupViewContainer.LayoutParams listParams = new PopupViewContainer.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, height
);
popupViewContainer.setBackground(mBackground);
popupViewContainer.addView(mContentView, listParams);
mPopupView = popupViewContainer;
} else {
mPopupView = mContentView;
}
}
~~~
为啥包了一层PopupViewContainer,就可以处理按钮点击事件了?因为PopupWindow没有相关事件回调,也没有重写按键和触摸方法,所以接收不
到对应的信号
~~~
public class PopupWindow {}
~~~
而PopupViewContainer则可以,因为它重写了相关方法
~~~
private class PopupViewContainer extends FrameLayout {
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
if (getKeyDispatcherState() == null) {
return super.dispatchKeyEvent(event);
}
if (event.getAction() == KeyEvent.ACTION_DOWN
&& event.getRepeatCount() == 0) {
KeyEvent.DispatcherState state = getKeyDispatcherState();
if (state != null) {
state.startTracking(event, this);
}
return true;
} else if (event.getAction() == KeyEvent.ACTION_UP) {
//back键消失
KeyEvent.DispatcherState state = getKeyDispatcherState();
if (state != null && state.isTracking(event) && !event.isCanceled()) {
dismiss();
return true;
}
}
return super.dispatchKeyEvent(event);
} else {
return super.dispatchKeyEvent(event);
}
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mTouchInterceptor != null && mTouchInterceptor.onTouch(this, ev)) {
return true;
}
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
final int x = (int) event.getX();
final int y = (int) event.getY();
//触摸在外面就消失
if ((event.getAction() == MotionEvent.ACTION_DOWN)
&& ((x < 0) || (x >= getWidth()) || (y < 0) || (y >= getHeight()))) {
dismiss();
return true;
} else if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
dismiss();
return true;
} else {
return super.onTouchEvent(event);
}
}
}
~~~
在Activity中使用Dialog的时候,为什么有时候会报错“Unable to add window – token is not valid; is your activity running?”?
这种情况一般发生在什么时候?一般发生在Activity进入后台,Dialog没有主动Dismiss掉,然后从后台再次进入App的时候。
为什么会这样呢?
还记得前面说过吧,子窗口类型的Window,比如Dialog,想要显示的话,比如保证appToken与Activity保持一致,而当Activity销毁,再次回来的时候,Dialog试图重新创建,调用ViewRootImp的setView()的时候就会出问题,所以记得在Activity不可见的时候,主动Dismiss掉Dialog。
~~~
if (res < WindowManagerGlobal.ADD_OKAY) {
switch (res) {
case WindowManagerGlobal.ADD_BAD_APP_TOKEN:
case WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN:
throw new WindowManager.BadTokenException(
"Unable to add window -- token " + attrs.token
+ " is not valid; is your activity running?");
case WindowManagerGlobal.ADD_NOT_APP_TOKEN:
throw new WindowManager.BadTokenException(
"Unable to add window -- token " + attrs.token
+ " is not for an application");
case WindowManagerGlobal.ADD_APP_EXITING:
throw new WindowManager.BadTokenException(
"Unable to add window -- app for token " + attrs.token
+ " is exiting");
case WindowManagerGlobal.ADD_DUPLICATE_ADD:
throw new WindowManager.BadTokenException(
"Unable to add window -- window " + mWindow
+ " has already been added");
case WindowManagerGlobal.ADD_STARTING_NOT_NEEDED:
// Silently ignore -- we would have just removed it
// right away, anyway.
return;
case WindowManagerGlobal.ADD_MULTIPLE_SINGLETON:
throw new WindowManager.BadTokenException(
"Unable to add window " + mWindow +
" -- another window of this type already exists");
case WindowManagerGlobal.ADD_PERMISSION_DENIED:
throw new WindowManager.BadTokenException(
"Unable to add window " + mWindow +
" -- permission denied for this window type");
case WindowManagerGlobal.ADD_INVALID_DISPLAY:
throw new WindowManager.InvalidDisplayException(
"Unable to add window " + mWindow +
" -- the specified display can not be found");
}
throw new RuntimeException(
"Unable to add window -- unknown error code " + res);
}
}
~~~
为什么Toast需要由系统统一控制,在子线程中为什么不能显示Toast?
首先Toast也属于窗口系统,但是并不是属于App的,是由系统同一控制的。
关于这一块不想说太多,具体实现机制请参考后面的文章。
为了看下面的内容,你需要知道以下几件事情:
1. Toast的显示是由系统Toast服务控制的,与系统之间的通信方式是Binder
1. 整个Toast系统会维持最多50个Toast的队列,依次显示
1. 负责现实工作的是Toast的内部类TN,它负责最终的显示与隐藏操作
1. 负责给系统Toast服务发送内容的是INotificationManager的实现类,它负责在Toast.show()里面把TN对象传递给系统消息服务,service.enqueueToast(pkg, tn, mDuration);这样Toast服务就持有客户端的代理,可以通过TN来控制每个Toast的显示与隐藏。
再来张图(转自[工匠若水](http://blog.csdn.net/yanbober))
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da7abdb0e.jpg "")
ok,现在假如你知道上面这些啦,那么我们下面就看为什么在子线程使用Toast.show()会提示
~~~
"No Looper; Looper.prepare() wasn't called on this thread."
~~~
原因很简单,因为TN在操作Toast的时候,是通过Handler做的
~~~
@Override
public void show() {
if (localLOGV) Log.v(TAG, "SHOW: " + this);
mHandler.post(mShow);
}
@Override
public void hide() {
if (localLOGV) Log.v(TAG, "HIDE: " + this);
mHandler.post(mHide);
}
~~~
所以说,TN初始化的线程必须为主线程,在子线程中使用Handler,由于没有消息队列,就会造成这个问题。
## 结语
上面写了这么多,你可能看了前面忘了后面,下面,凯子哥给你总结一下,这篇文章到底讲了什么东西:
- 每个Activity,都至少有一个Window,这个Window实际类型为PhoneWindow,当Activity中有子窗口,比如Dialog的时候,就会出现多个Window。Activity的Window是我们控制的,状态栏和导航栏的Window由系统控制。
- 在DecorView的里面,一定有一个id为content的FraneLayout的布局容器,咱们自己定义的xml布局都放在这里面。
- Activity的Window里面有一个DecorView,它使继承自FrameLayout的一个自定义控件,作为整个View层的容器,及View树的根节点。
- Window是虚拟的概念,DecorView才是看得见,摸得着的东西,Activity.setContentView()实际调用的是PhoneWindow.setContentView(),在这里面实现了DecorView的初始化和id为content的FraneLayout的布局容器的初始化,并且会根据主题等配置,选择不同的xml文件。而且在Activity.setContentView()之后,Window的一些特征位将被锁定。
- Activity.findViewById()实际上调用的是DecorView的findviewById(),这个方法在View中定义,但是是final的,实际起作用的是在ViewGroup中被重写的findViewTraversal()方法。
- Activity的mWindow成员变量是在attach()的时候被初始化的,attach()是Activity被通过反射手段实例化之后调用的第一个方法,在这之后生命周期方法才会依次调用
- 在onResume()刚执行之后,界面还是不可见的,只有执行完Activity.makeVisible(),DecorView才对用户可见
- ViewManager这个接口里面就三个接口,添加、移除和更新,实现这个接口的有WindowManager和ViewGroup,但是他们两个面向的对象是不一样的,WindowManager实现的是对Window的操作,而ViewGroup则是对View的增、删、更新操作。
- WindowManagerImpl是WindowManager的实现类,但是他就是一个代理类,代理的是WindowManagerGlobal,WindowManagerGlobal一个App里面就有一个,因为它是单例的,它里面管理了App中所有打开的DecorView,ContentView和PhoneWindow的布局参数WindowManager.LayoutParams,而且WindowManagerGlobal这个类是和WMS通信用的,是通过IWindowSession对象完成这个工作的,而IWindowSession一个App只有一个,但是每个ViewRootImpl都持有对IWindowSession的引用,所以ViewRootImpl可以和WMS喊话,但是WMS怎么和ViewRootImpl喊话呢?是通过ViewRootImpl::W这个内部类实现的,而且源码中很多地方采用了这种将接口隐藏为内部类的方式,这样可以实现六大设计原则之一——接口最小原则,这样ViewRootImpl和WMS就互相持有对方的代理,就可以互相交流了
- ViewRootImpl这个类每个Activity都有一个,它负责和WMS通信,同时相应WMS的指挥,还负责View界面的测量、布局和绘制工作,所以当你调用View.invalidate()和View.requestLayout()的时候,都会把事件传递到ViewRootImpl,然后ViewRootImpl计算出需要重绘的区域,告诉WMS,WMS再通知其他服务完成绘制和动画等效果,当然,这是后话,咱们以后再说。
- Window分为三种,子窗口,应用窗口和系统窗口,子窗口必须依附于一个上下文,就是Activity,因为它需要Activity的appToken,子窗口和Activity的WindowManager是一个的,都是根据appToken获取的,描述一个Window属于哪种类型,是根据LayoutParam.type决定的,不同类型有不同的取值范围,系统类的的Window需要特殊权限,当然Toast比较特殊,不需要权限
- PopupWindow使用的时候,如果想触发按键和触摸事件,需要添加一个背景,代码中会根据是否设置背景进行不同的逻辑判断
- Dialog在Activity不可见的时候,要主动dismiss掉,否则会因为appToken为空crash
- Toast属于系统窗口,由系统服务NotificationManagerService统一调度,NotificationManagerService中维持着一个集合ArrayList,最多存放50个Toast,但是NotificationManagerService只负责管理Toast,具体的现实工作由Toast::TN来实现
最后来一张Android的窗口管理框架(转自[ariesjzj](http://blog.csdn.net/jinzhuojun))
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da7d2d3a7.jpg "")
OK,关于Activity的界面显示就说到这里吧,本篇文章大部分的内容来自于阅读下面参考文章之后的总结和思考,想了解更详细的可以研究下。
下次再见,拜拜~
## 参考文章
- [Android应用Activity、Dialog、PopWindow、Toast窗口添加机制及源码分析](http://blog.csdn.net/yanbober/article/details/46361191)
- [Android应用setContentView与LayoutInflater加载解析机制源码分析](http://blog.csdn.net/yanbober/article/details/45970721)
- [Android 4.4(KitKat)窗口管理子系统 - 体系框架](http://blog.csdn.net/jinzhuojun/article/details/37737439)
- [Android 之 Window、WindowManager 与窗口管理](http://blog.csdn.net/xieqibao/article/details/6567814)
- [图解Android - Android GUI 系统 (1) - 概论](http://www.cnblogs.com/samchen2009/p/3364327.html)
尊重原创,转载请注明:From 凯子哥([](http://blog.csdn.net/zhaokaiqiang1992)[http://blog.csdn.net/zhaokaiqiang1992](http://blog.csdn.net/zhaokaiqiang1992)) 侵权必究!
关注我的微博,可以获得更多精彩内容
[![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da7b5de49.png)](http://weibo.com/u/1783932377?s=6uyXnP)