<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                > 前幾天凱子哥寫的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://box.kancloud.cn/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://box.kancloud.cn/2016-03-16_56e8da7cd05de.png "") 下面用Hierarchy看一下樹狀結構: ![](https://box.kancloud.cn/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://box.kancloud.cn/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://box.kancloud.cn/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://box.kancloud.cn/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://box.kancloud.cn/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://box.kancloud.cn/2016-03-16_56e8da7b5de49.png)](http://weibo.com/u/1783932377?s=6uyXnP)
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看