日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > Android >内容正文

Android

Android O: View的绘制流程(一): 创建和加载

發布時間:2025/3/15 Android 44 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Android O: View的绘制流程(一): 创建和加载 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

從這篇博客開始,我們會用幾篇文章,?
基于Android O的代碼,分析一下View的繪制流程。

在分析具體的繪制流程前,我們先來了解一下XML中定義的View,?
如何被創建和加載。


一、setContentView?
在分析具體的代碼前,我們先看看Android的視圖結構:?

如上圖所示,每個Activity都與一個Window(具體來說是PhoneWindow)相關聯,后者用于承載實際的用戶界面。?
Window是一個抽象的類,它表示屏幕上用于繪制各種UI元素及響應用戶輸入事件的一個矩形區域。

DecorView是一個應用窗口的根容器,它本質上是一個FrameLayout。?
DecorView僅包含一個子View,它是一個垂直的LinearLayout。?
該LinearLayout包含兩個子元素,一個是TitleView(ActionBar的容器),?
另一個是ContentView(窗口內容的容器)。

ContentView是一個FrameLayout。?
我們在Activity中調用setContentView時,就是在設置它的子View。

現在我們就從Activity的setContentView開始分析,?
看看XML中的View如何被一步步創建和加載。

public void setContentView(@LayoutRes int layoutResID) {//設置Activity的contentViewgetWindow().setContentView(layoutResID);//設置TitleView, 即ActionBarinitWindowDecorActionBar();}............./*** 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();//確保可以繪制ActionBarif (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());}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

從代碼來看,Activity的setContentView函數,會先加載自己的ContentView,?
然后再利用initWindowDecorActionBar函數加載ActionBar。

我們主要跟進一下getWindow().setContentView函數,?
即PhoneWindow中的setContentView函數。

對應的代碼如下:

public void setContentView(int layoutResID) {if (mContentParent == null) {// mContentParent為ContentView的父容器, 即DecorView// 若為空, 則調用installDecor()生成installDecor();} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {// FEATURE_CONTENT_TRANSITIONS的用途可參考其注釋// 主要是支持動畫切換Activity的content view// mContentParent不為null, 則移除DecorView的所有子ViewmContentParent.removeAllViews();}if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,getContext());//轉移到新的content viewtransitionTo(newScene);} else {//默認在此處填充布局, 將我們在xml中定義的contentView加載到mContentParent中mLayoutInflater.inflate(layoutResID, mContentParent);}.............final Callback cb = getCallback();if (cb != null && !isDestroyed()) {// 回調onContentChanged(), 通知Activity窗口內容發生了改變cb.onContentChanged();}......... }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

從上面的代碼可以看出,在setContentView函數中,?
主要是通過LayoutInflater的inflate函數,來解析XML文件并填充ContentView。

二、inflate?
我們一起來跟進一下對應的源碼:

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {return inflate(resource, root, root != null);}public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {final Resources res = getContext().getResources();............//parser中已經包含了XML信息final XmlResourceParser parser = res.getLayout(resource);try {return inflate(parser, root, attachToRoot);} finally {parser.close();}}public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {synchronized (mConstructorArgs) {...........//初始化一些參數final Context inflaterContext = mContext;final AttributeSet attrs = Xml.asAttributeSet(parser);Context lastContext = (Context) mConstructorArgs[0];mConstructorArgs[0] = inflaterContext;View result = root;try {// Look for the root node.int type;// 循環查找START_TAGwhile ((type = parser.next()) != XmlPullParser.START_TAG &&type != XmlPullParser.END_DOCUMENT) {// Empty}//結束前,還未找到START_TAG, 則拋出異常if (type != XmlPullParser.START_TAG) {throw new InflateException(parser.getPositionDescription()+ ": No start tag found!");}final String name = parser.getName();...............//單獨處理xml中的merge標簽if (TAG_MERGE.equals(name)) {//使用merge時,root不能為null且attachToRoot必須為trueif (root == null || !attachToRoot) {throw new InflateException("<merge /> can be used only with a valid "+ "ViewGroup root and attachToRoot=true");}//遞歸填充布局rInflate(parser, root, inflaterContext, attrs, false);} else {// Temp is the root view that was found in the xml// 創建出xml中的根Viewfinal View temp = createViewFromTag(root, name, inflaterContext, attrs);ViewGroup.LayoutParams params = null;if (root != null) {.............// Create layout params that match root, if supplied// 獲取父容器的布局參數params = root.generateLayoutParams(attrs);if (!attachToRoot) {// Set the layout params for temp if we are not// attaching. (If we are, we use addView, below)// 若attachToRoot參數為false, // 則僅將父容器的布局參數設置給根View// 否則,會將根View添加到父容器temp.setLayoutParams(params);}}............// Inflate all children under temp against its context.// 遞歸加載根View的子ViewrInflateChildren(parser, temp, attrs, true);...........// We are supposed to attach all the views we found (int temp)// to root. Do that now.if (root != null && attachToRoot) {//根View被父容器“包裹”root.addView(temp, params);}// Decide whether to return the root that was passed in or the// top view found in xml.// 確定返回值if (root == null || !attachToRoot) {result = temp;}}} catch(....) {.......} finally {// Don't retain static reference on context.// 用類的成員來保存context, 避免內存泄露mConstructorArgs[0] = lastContext;mConstructorArgs[1] = null;..........}return result;} }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107

從上面的代碼可以看出,當XML以merge標簽開始時,?
將直接調用rInflate函數來進行處理。

否則,正常情況下,會先創建出XML的根View,?
然后利用rInflateChildren加載根View的子View。?
最終,根據條件選擇是否將根View attach到父容器中。

三、rInflate?
我們先來看看rInflate對應的代碼:

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;//解析整個xml(指當前的START_TAG~END_TAG這段區域)while (((type = parser.next()) != XmlPullParser.END_TAG ||parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {//必須從START_TAG開始解析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");}//處理<include>標記, 其中最終也利用rInflate、 rInflateChildren加載ViewparseInclude(parser, context, parent, attrs);} else if (TAG_MERGE.equals(name)) {throw new InflateException("<merge /> must be the root element");} else {//一般情況//根據標記,創建出Viewfinal View view = createViewFromTag(parent, name, context, attrs);final ViewGroup viewGroup = (ViewGroup) parent;final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);//遞歸加載子View, 此時view作為新的根ViewrInflateChildren(parser, view, attrs, true);//加載完畢后,填充到父容器viewGroup.addView(view, params);}//根據標志進行回調if (pendingRequestFocus) {parent.restoreDefaultFocus();}if (finishInflate) {parent.onFinishInflate();}} }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54

從上面代碼可以看出,rInflate函數負責解析當前區域的XML,?
然后創建出這段區域的根View,并利用rInflateChildren加載子View,?
最終將根View填充到父容器內。

我們來看看rInflateChildren的代碼:

//容易看出,實際上還是調用了rInflate函數//即將傳入的View作為新的根View進行加載final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,boolean finishInflate) throws XmlPullParserException, IOException {rInflate(parser, parent, parent.getContext(), attrs, finishInflate);}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

至此,Activity加載界面的流程分析完畢。

四、總結?
?
從代碼的角度來看,Activity加載界面的流程是比較好理解的。?
整體的思想實際上可以簡化為上圖,其中最重要的函數是rInflate。?
通過該函數,系統會以先序遍歷的方式,創建XML對應的View。

簡單來講,創建View的方式類似于:?
假設一段XML范圍內,定義了根View及1~n個子View,?
那么創建出根View后,必須先加載View 1及其所有子View,?
才會開始加載View 2。

版權聲明:轉載請注明:http://blog.csdn.net/gaugamela/article

總結

以上是生活随笔為你收集整理的Android O: View的绘制流程(一): 创建和加载的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。