【轉載】【凱子哥帶你學Framework】Activity界面顯示全解析(下)

如何驗證上一個問題

首先,說明一下運行條件html

//主題
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,我們的軟件已經準備好了,採用的是最簡單的佈局,界面效果以下:java

 

enter description here

607813-9955ed18d1f64c3a.png

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

 

 

enter description here

607813-b094dee5d70bb9c4.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。
下面貼上這個圖,你就能夠看明白了(轉自 工匠若水)android

 

enter description here

607813-68e40a644c2c8784.png

 

ViewGroup view = (ViewGroup) getWindow().getDecorView();

咱們經過setContentView()設置的界面,爲何在onResume()以後纔對用戶可見呢?

有開發經驗的朋友應該知道,咱們的界面元素在onResume()以後纔對用戶是可見的,這是爲啥呢?
那咱們追蹤一下,onResume()是何時調用的,而後看看作了什麼操做就OK了。
這一下,咱們又要從ActivityThread開始提及了,不熟悉的快去看前一篇文章《Activity啓動過程全解析》(http://blog.csdn.net/zhaokaiqiang1992/article/details/49428287)。
話說,前文說到,咱們想要開啓一個Activity的時候,ActivityThread的handleLaunchActivity()會在Handler中被調用web

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等相關類的稍微高級點的只是。
前面看累了的朋友,能夠上個廁所,泡個咖啡,休息下繼續往下看。session

ViewManager、WindowManager、WindowManagerImpl、WindowManagerGlobal到底都是些什麼玩意?

WindowManager實際上是一個接口,和Window同樣,起做用的是它的實現類app

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。ide

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()的時候進行了關聯。
查一張類圖(轉自 工匠若水)

 

enter description here

607813-c97c66c7bf0edd98.png

 

知道了這些,那麼下面的操做就能夠直接看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模型中的Contriller。
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就持有了當前ViewRootImpl的代碼,就能夠調用W對象讓ViewRootImpl作一些事情了。
這樣,雙方有可對方的接口,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(),而後就開始measure、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;
            }
        }
    }

來回倒騰了這麼多,終於看見界面了,讓我哭會兒!!

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

  2. 整個Toast系統會維持最多50個Toast的隊列,依次顯示

  3. 負責現實工做的是Toast的內部類TN,它負責最終的顯示與隱藏操做

  4. 負責給系統Toast服務發送內容的是INotificationManager的實現類,它負責在Toast.show()裏面把TN對象傳遞給系統消息服務,service.enqueueToast(pkg, tn, mDuration);這樣Toast服務就持有客戶端的代理,能夠經過TN來控制每一個Toast的顯示與隱藏。

再來張圖(轉自 工匠若水)

 

enter description here

607813-8622411bfd58b91d.png

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)

 

enter description here

607813-5e7218a4453ea45e.png

OK,關於Activity的界面顯示就說到這裏吧,本篇文章大部分的內容來自於閱讀下面參考文章以後的總結和思考,想了解更詳細的能夠研究下。
下次再見,拜拜~

 

參考文章

http://blog.csdn.net/yanbober/article/details/46361191
http://blog.csdn.net/yanbober/article/details/45970721
http://blog.csdn.net/jinzhuojun/article/details/37737439
http://blog.csdn.net/xieqibao/article/details/6567814
http://www.cnblogs.com/samchen2009/p/3364327.html

做者:凱子哥
連接:http://www.jianshu.com/p/65295b2cb047

相關文章
相關標籤/搜索