1. 程式人生 > >【凱子哥帶你學Framework】Activity介面顯示全解析

【凱子哥帶你學Framework】Activity介面顯示全解析

前幾天凱子哥寫的Framework層的解析文章《Activity啟動過程全解析》,反響還不錯,這說明“寫讓大家都能看懂的Framework解析文章”的思想是基本正確的。

我個人覺得,深入分析的文章必不可少,但是對於更多的Android開發者——即只想做應用層開發,不想了解底層實現細節——來說,“整體上把握,重要環節深入“是更好的學習方式。因為這樣既可以有完整的知識體系,又不會在浩瀚的原始碼世界裡迷失興趣和方向。

所以呢,今天凱子哥又帶來一篇文章,接著上一篇的結尾,重點介紹Activity開啟之後,Android系統對介面的一些操作及相關知識。

本期關鍵字

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

下面貼上這個圖,你就可以看明白了(轉自 工匠若水)

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

我們通過setContentView()設定的介面,為什麼在onResume()之後才對使用者可見呢?

有開發經驗的朋友應該知道,我們的介面元素在onResume()之後才對使用者是可見的,這是為啥呢?

那我們就追蹤一下,onResume()是什麼時候呼叫的,然後看看做了什麼操作就Ok了。

話說,前文說到,我們想要開啟一個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()的時候進行了關聯。

插一張類圖(轉自工匠若水

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