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)
';