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

前幾天凱子哥寫的Framework層的解析文章《Activity啓動過程全解析》,反響還不錯,這說明"寫讓你們都能看懂的Framework解析文章"的思路是基本正確的。
我我的以爲,深刻分析的文章必不可少,可是更多的Android開發者——即只想作應用層開發,不想了解底層實現細節——來講,"總體上把握,重要環節深刻"是更好的學習方式。所以這樣既能夠有完整的知識體系,又不會在好漢的源碼世界裏迷失興趣和方向。
因此呢,今天凱子哥又帶來一篇文章,接着上一篇的結尾,終點介紹Activity開啓後,Android系統對界面的一些操做及相關知識。java

本期關鍵字

  • Windowandroid

  • PhoneWindowweb

  • WindowManagerwindows

  • WindowManagerImpl設計模式

  • WindowManagerGlobalapp

  • RootViewImplide

  • DecorView函數

  • Dialogoop

  • 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類的文檔

 

enter description here

607813-4efd1d556bdffa48.png

Window只有一個實現類,就是PhoneWindow!因此說這裏扯出了PhoneWindow這個類。
並且文檔還說,這個類的一個實例,也就是PhoneWindow,應該被添加到WindowManager中,做爲頂層的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();
        }
    }

當咱們第一次調用setContentView()的時候,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文件中聲明的佈局文件,會被添加進去

既然是事實,那麼怎麼樣才能驗證一下呢?
我們下篇再說~

做者:凱子哥
連接:http://blog.csdn.net/zhaokaiqiang1992

相關文章
相關標籤/搜索