Activity UI加載流程 LayoutInflater布局解析 源碼分析(一)

1. 分析入口為Activity onCreate調(diào)用的 setContentView(..)方法

    //Activity類
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }

2.Activity類中 setContentView(..)調(diào)用了 getWindow().setContentView(layoutResID), getWindow()獲取了一個Window對象并調(diào)用了Window對象的setContentView()方法

 //Activity類
 public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

Window: 頂級窗口外觀和行為策略的抽象基類哺窄。這個類的一個實例應該被用作添加到窗口管理器的頂層視圖。它提供了標準的UI策略叹侄,如背景,標題區(qū)域,默認密鑰處理等。

3.Window對象是一個抽象了他的唯一實現(xiàn)是PhoneWindow 其中setContentView(...)才是關鍵

  //PhoneWindow類
  public void setContentView(int layoutResID) {
        if (mContentParent == null) {//mContentParent  ViewGroup 視圖容器 判斷是否為空 實際是 DecorView 這是頂View
            installDecor();//初始化DecorView  設置Activity主題
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {//沒有動畫移除所有View
            mContentParent.removeAllViews();//移除視圖
        }
        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {//有過度動畫 執(zhí)行動畫
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {//初始化布局
            mLayoutInflater.inflate(layoutResID, mContentParent);//解析XML 添加View
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

3.1 初始化頂層容器DecorView 初始化后還要設置當前界面 style(樣式)添加初始化布局

  //PhoneWindow類
 private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {//DecorView為空直接創(chuàng)建
            mDecor = generateDecor(-1);
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        } else {
            mDecor.setWindow(this);//綁定當前PhoneWindow
        }
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);//設置style Activity 樣式 并為DecorView初始布局樣式
            .....
        }
}

3.2 generateLayout()Activity style實在這里解析配置

    protected ViewGroup generateLayout(DecorView decor) {
        TypedArray a = getWindowStyle();//獲取頁面當前樣式數(shù)據(jù) 方法如下:
        if (false) {
            System.out.println("From style:");
            String s = "Attrs:";
            for (int i = 0; i < R.styleable.Window.length; i++) {
                s = s + " " + Integer.toHexString(R.styleable.Window[i]) + "="
                        + a.getString(i);
            }
            System.out.println(s);
        }

        mIsFloating = a.getBoolean(R.styleable.Window_windowIsFloating, false);//判斷當前Window是否是浮窗
        int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR)
                & (~getForcedWindowFlags());
        if (mIsFloating) {
            setLayout(WRAP_CONTENT, WRAP_CONTENT);
            setFlags(0, flagsToUpdate);
        } else {
            setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);
        }

        //以下都是設置style 的狀態(tài)位
        if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {//是否顯示狀態(tài)欄
            requestFeature(FEATURE_NO_TITLE);
        } else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
            // Don't allow an action bar if there is no title.
            requestFeature(FEATURE_ACTION_BAR);
        }

        .....
        int layoutResource;//布局文件id
        int features = getLocalFeatures()

        //根據(jù)主題不一樣加載不同的布局文件
        if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
            layoutResource = R.layout.screen_swipe_dismiss;
            setCloseOnSwipeEnabled(true);
        } else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
            if (mIsFloating) {
                TypedValue res = new TypedValue();
                getContext().getTheme().resolveAttribute(
                        R.attr.dialogTitleIconsDecorLayout, res, true);
                layoutResource = res.resourceId;
            } else {
                layoutResource = R.layout.screen_title_icons;
            }

        ......
         mDecor.startChanging();
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);//加載主題布局

        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);//拿到加載程序員布局父容器
        ......

        return contentParent;//返回程序員布局容器


}

    //獲取Style解析樣式數(shù)據(jù)
   public final TypedArray getWindowStyle() {
        synchronized (this) {
            if (mWindowStyle == null) {
                mWindowStyle = mContext.obtainStyledAttributes(
                        com.android.internal.R.styleable.Window);
            }
            return mWindowStyle;
        }
    }

Activity布局.png

4.通過以上過程我們拿到了程序源定義布局文件 父容器 mContentParent (ViewGroup )現(xiàn)在 mLayoutInflater.inflate(layoutResID, mContentParent)接卸自定義布局文件 一直追蹤inflate()方法會到達LayoutInflater類中

LayoutInflater解析xml生成view

  • 先解析布局文件根節(jié)點
//LayoutInflater類
 public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
        final Resources res = getContext().getResources();
        final XmlResourceParser parser = res.getLayout(resource);//Xml解析器
        try {
            return inflate(parser, root, attachToRoot);//調(diào)用下面方法
        } finally {
            parser.close();
        }
    }
   //初始化布局文件根節(jié)點
  public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
            final Context inflaterContext = mContext;
            final AttributeSet attrs = Xml.asAttributeSet(parser);
            Context lastContext = (Context) mConstructorArgs[0];
            mConstructorArgs[0] = inflaterContext;
            View result = root;
            try {
                //解析Xml 尋找根節(jié)點
                int type;
                while ((type = parser.next()) != XmlPullParser.START_TAG &&
                        type != XmlPullParser.END_DOCUMENT) {
                    // Empty
                }
                if (type != XmlPullParser.START_TAG) {
                    throw new InflateException(parser.getPositionDescription()
                            + ": No start tag found!");
                }

                final String name = parser.getName();//獲取根節(jié)點name
               if (TAG_MERGE.equals(name)) {//如果節(jié)點名merge 
                          if (root == null || !attachToRoot) {//沒有根節(jié)點 或者 不加載到父布局中 拋出異常
                              throw new InflateException("<merge /> can be used only with a valid "
                                        + "ViewGroup root and attachToRoot=true");
                          }
                         rInflate(parser, root, inflaterContext, attrs, false);//根節(jié)點為merge解析
                   }else{//正常節(jié)點
                          final View temp = createViewFromTag(root, name, inflaterContext, attrs);//生成view
                          ViewGroup.LayoutParams params = null;
                          if (root != null) {
                                 params = root.generateLayoutParams(attrs);//調(diào)用父布局generateLayoutParams獲取LayoutParams
                                if (!attachToRoot) {//不加入父布局直接設置參數(shù)  Adapter因為復用 是由Adapter管理不需要直接加入父布局
                                          temp.setLayoutParams(params);//設置LayoutParams生成的View
                                      }
                                rInflateChildren(parser, temp, attrs, true);//解析子節(jié)點 這個方法最終調(diào)用 rInflate()抛杨;
                                if (root != null && attachToRoot) {//需要加入父布局 
                                           root.addView(temp, params);//將改節(jié)點加入父容器
                                     }
                                  .....
                            }
                    }
                  }

}
  • 根節(jié)點下的字節(jié)點
    void rInflate(XmlPullParser parser, View parent, Context context,
            AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {

        final int depth = parser.getDepth();
        int type;
        boolean pendingRequestFocus = false;

        while (((type = parser.next()) != XmlPullParser.END_TAG ||
                parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {

            if (type != XmlPullParser.START_TAG) {
                continue;
            }

            final String name = parser.getName();

            if (TAG_REQUEST_FOCUS.equals(name)) {
                pendingRequestFocus = true;
                consumeChildElements(parser);
            } else if (TAG_TAG.equals(name)) {
                parseViewTag(parser, parent, attrs);
            } else if (TAG_INCLUDE.equals(name)) {
                if (parser.getDepth() == 0) {
                    throw new InflateException("<include /> cannot be the root element");
                }
                parseInclude(parser, context, parent, attrs);
            } else if (TAG_MERGE.equals(name)) {
                throw new InflateException("<merge /> must be the root element");
            } else {
                final View view = createViewFromTag(parent, name, context, attrs);
                final ViewGroup viewGroup = (ViewGroup) parent;
                final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
                rInflateChildren(parser, view, attrs, true);
                viewGroup.addView(view, params);
            }
        }

        if (pendingRequestFocus) {
            parent.restoreDefaultFocus();
        }

        if (finishInflate) {
            parent.onFinishInflate();
        }
    }

總的邏輯圖

Activity UI加載.png

總結:

每一個Activity都有一個關聯(lián)的Window(PhoneWindow )對象,用來描述程序窗口

每一個窗口又包含一個DecorView對象荐类,DecorView對象用來描述窗口的視圖--XML布局

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末怖现,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子玉罐,更是在濱河造成了極大的恐慌屈嗤,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,270評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件吊输,死亡現(xiàn)場離奇詭異饶号,居然都是意外死亡,警方通過查閱死者的電腦和手機季蚂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評論 3 395
  • 文/潘曉璐 我一進店門茫船,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人扭屁,你說我怎么就攤上這事算谈。” “怎么了料滥?”我有些...
    開封第一講書人閱讀 165,630評論 0 356
  • 文/不壞的土叔 我叫張陵然眼,是天一觀的道長。 經(jīng)常有香客問我葵腹,道長高每,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,906評論 1 295
  • 正文 為了忘掉前任践宴,我火速辦了婚禮鲸匿,結果婚禮上,老公的妹妹穿的比我還像新娘阻肩。我一直安慰自己带欢,他們只是感情好,可當我...
    茶點故事閱讀 67,928評論 6 392
  • 文/花漫 我一把揭開白布磺浙。 她就那樣靜靜地躺著洪囤,像睡著了一般。 火紅的嫁衣襯著肌膚如雪撕氧。 梳的紋絲不亂的頭發(fā)上瘤缩,一...
    開封第一講書人閱讀 51,718評論 1 305
  • 那天,我揣著相機與錄音伦泥,去河邊找鬼剥啤。 笑死锦溪,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的府怯。 我是一名探鬼主播刻诊,決...
    沈念sama閱讀 40,442評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼牺丙!你這毒婦竟也來了则涯?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,345評論 0 276
  • 序言:老撾萬榮一對情侶失蹤冲簿,失蹤者是張志新(化名)和其女友劉穎粟判,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體峦剔,經(jīng)...
    沈念sama閱讀 45,802評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡档礁,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,984評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了吝沫。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片呻澜。...
    茶點故事閱讀 40,117評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖惨险,靈堂內(nèi)的尸體忽然破棺而出羹幸,到底是詐尸還是另有隱情,我是刑警寧澤平道,帶...
    沈念sama閱讀 35,810評論 5 346
  • 正文 年R本政府宣布睹欲,位于F島的核電站供炼,受9級特大地震影響一屋,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜袋哼,卻給世界環(huán)境...
    茶點故事閱讀 41,462評論 3 331
  • 文/蒙蒙 一冀墨、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧涛贯,春花似錦诽嘉、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至稀余,卻和暖如春悦冀,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背睛琳。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評論 1 272
  • 我被黑心中介騙來泰國打工盒蟆, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留踏烙,地道東北人。 一個月前我還...
    沈念sama閱讀 48,377評論 3 373
  • 正文 我出身青樓历等,卻偏偏與公主長得像讨惩,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子寒屯,可洞房花燭夜當晚...
    茶點故事閱讀 45,060評論 2 355

推薦閱讀更多精彩內(nèi)容