> 前幾天凱子哥寫的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類的文檔

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,咱們的軟件已經準備好了,采用的是最簡單的布局,界面效果如下:

下面用Hierarchy看一下樹狀結構:

第一層,就是上面的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))

~~~
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))

知道了這些,那下面的操作就可以直接看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))

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

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)) 侵權必究!
關注我的微博,可以獲得更多精彩內容
[](http://weibo.com/u/1783932377?s=6uyXnP)
- 前言
- Android觸摸屏事件派發機制詳解與源碼分析一(View篇)
- Android觸摸屏事件派發機制詳解與源碼分析二(ViewGroup篇)
- Android觸摸屏事件派發機制詳解與源碼分析三(Activity篇)
- Android應用setContentView與LayoutInflater加載解析機制源碼分析
- Android應用Context詳解及源碼解析
- Android異步消息處理機制詳解及源碼分析
- Android應用Activity、Dialog、PopWindow、Toast窗口添加機制及源碼分析
- Android ListView工作原理完全解析,帶你從源碼的角度徹底理解
- Activity啟動過程全解析
- Android應用AsyncTask處理機制詳解及源碼分析
- 說說 PendingIntent 的內部機制
- Android Activity.startActivity流程簡介
- Activity界面顯示全解析
- 框架層理解Activity生命周期(APP啟動過程)
- APK安裝過程及原理詳解
- Android構建過程簡述
- Android應用層View繪制流程與源碼分析