1. 程式人生 > >淺談Android之Activity Decor View建立流程介紹

淺談Android之Activity Decor View建立流程介紹

6 Activity DecorView建立流程介紹

上頭已經完整的介紹了Activity的啟動流程,Activity是如何繫結Window,Window的décor view是如何通過ViewRootImpl與WMS建立關聯的,也就是說,整個框架已經有了,唯一缺的就是Activity如何初始化建立DecorView了。

接下去通過相對不是那麼複雜的LinearLayout,來完整的介紹Décor View以及其內部

ContentView的詳細建立過程

6.1 DecorView的初始化和佈局介紹

首先定義一個Layout檔案activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

    android:id="@+id/activity_main"

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    android:orientation="vertical">

    <TextView

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:text="Hello World --- One" />

    <TextView

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:text="Hello World --- Two" />

</LinearLayout>

這個Layout佈局很簡單,LinearLayout內建兩個TextView,接著在Activity.onCreate的時候將其作為ContentView設定到Activity:

protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

}

接著看setContentView:

//Activity.java

public void setContentView(int layoutResID) {

    getWindow().setContentView(layoutResID);

    initWindowDecorActionBar();

}

最終會把content view設定到Activity關聯Window那,接著看PhoneWindow.setContentView:

    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();

        }

    }

這裡ContentParent肯定為null,接著呼叫installDecor:

  private void installDecor() {

        if (mDecor == null) {

            mDecor = generateDecor();

            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);

            mDecor.setIsRootNamespace(true);

            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {

                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);

            }

        }

        if (mContentParent == null) {

            mContentParent = generateLayout(mDecor);

            ……

}

    }

mDecor View在初始狀態下肯定為null,所以這裡肯定會呼叫generateDecor建立DecorView:

protected DecorView generateDecor() {

return new DecorView(getContext(), -1);

}

DecorView派生自FrameLayout:

private final class DecorView extends FrameLayout

由於mContentParent在初始狀態下也為空,所以接著呼叫generateLayout建立Content Root

View:

protected ViewGroup generateLayout(DecorView decor) {

        ……

        TypedArray a = getWindowStyle();

        ……

        mDecor.startChanging();

        View in = mLayoutInflater.inflate(layoutResource, null);

        decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));

        mContentRoot = (ViewGroup) in;

        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);

        if (contentParent == null) {

            throw new RuntimeException("Window couldn't find content container view");

        }

        ……

mDecor.finishChanging();

        return contentParent;

    }

先根據layoutResource對應的layout檔案建立View作為mContentRoot並新增到DecorView,接著從mContentRoot查詢名稱為ID_ANDROID_CONTENT的child view作為content Parent並最終儲存到mContentParent.

installDecor完成後,接著我們回到setContentView, 接著執行的程式碼為:

mLayoutInflater.inflate(layoutResID, mContentParent);

根據layoutResID指定的layout檔案建立View並新增到mContentParent中

setContentView結束後,DecorView建立完成,接下去我們看下DécorView完整的佈局結構圖:


通過圖可以很清晰的看出,DecorView是top level view,Content Root是其子檢視,Content Root用於顯示內容,主要包含:

1)  Action bar view

2)  Content Parent view

我們在Activity.onCreate中呼叫setContentView設定的自定義View是新增到Content Parent

View中的

6.2 inflate view介紹

在開發過程中,我們可以通過編寫layout檔案,然後在程式碼中使用inflate並傳入該layout檔案實現對View的建立,比如上文中ContentView的建立:

mLayoutInflater.inflate(layoutResID, mContentParent);

上面程式碼通過inflate建立layoutResID對應的View並新增到mContentParent中,接下去詳細介紹下inflate的實現

為了便於後續分析,先簡單介紹下在app工程編譯時,layout檔案是被怎樣處理的,從程式碼上看,Android應該是支援兩種處理方式的

比如原先的Layout內部描述是這樣的:

<LinearLayout >

    <TextView />

    <TextView />

</LinearLayout>

第一種處理方式:

將layout內部所有View的tag從類名改成view,原先的tag在處理完後,會被儲存到名稱為class的屬性欄位中,編譯處理後,會變成:

<view

class=”LinearLayout”>

    <view class=”TextView” />

    <view class=”TextView” />

</view>

Tag統一改為view,然後新增class屬性用於描述view的class path,如果是自定義檢視,這裡需要給出全路徑,如果是系統定義檢視,則給出相對路徑即可,inflate在解析的時候,會自動補全android.view.字首

第二種處理方式:

編譯處理後,會變成:

<android.view.LinearLayout”>

    <android.view.TextView />

    <android.view.TextView />

</view>

也就是說,在TAG name中把class path補全了

最新的系統版本應該都用的第二種方式,所以後續我們就基於第二種方式來分析

最後,android打包工具會將layout資料重新格式化並儲存到新的資料結構中,然後儲存到layout同名檔案並最終打包到apk中。

接下去通過程式碼,一步一步分析inflate的整個過程

先看下LayoutInflater的初始化程式碼:

mLayoutInflater = LayoutInflater.from(context);

接著看LayoutInflater.from:

public static LayoutInflater from(Context context) {

LayoutInflater LayoutInflater =

                (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

        ……

        return LayoutInflater;

}

其實就是對context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)呼叫做下封裝,ContextImpl裡頭最終呼叫PolicyManager.makeNewLayoutInflater->

Policy. makeNewLayoutInflater建立LayoutInflater:

//Policy.java

public LayoutInflater makeNewLayoutInflater(Context context) {

        return new PhoneLayoutInflater(context);

}

LayoutInflater建立結束,接著看對其inflate函式的呼叫

PhoneLayoutInflate派生自LayoutInflater,不過它沒重新實現啥函式,基本上還是使用LayoutInflate的實現,inflate函式也是:

//LayoutInflater.java

   public View inflate(int resource, ViewGroup root, boolean attachToRoot) {

        final Resources res = getContext().getResources();

        final XmlResourceParser parser = res.getLayout(resource);

        try {

            return inflate(parser, root, attachToRoot);

        } finally {

            parser.close();

        }

    }

由於root不為空,所以attachToRoot為true

先呼叫res.getLayout獲取resource對應layout檔案的配置資料並儲存到parser中,接著呼叫inflate對應的過載函式:

public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {

        synchronized (mConstructorArgs) {

            ……

            final AttributeSet attrs = Xml.asAttributeSet(parser);

            Context lastContext = (Context)mConstructorArgs[0];

            mConstructorArgs[0] = mContext;

            View result = root;

            try {

                // Look for the root node.

                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();

                ……

                if (TAG_MERGE.equals(name)) {

                    ……

                } else {

                    // Temp is the root view that was found in the xml

                    final View temp = createViewFromTag(root, name, attrs, false);

                    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)

                            temp.setLayoutParams(params);

                        }

                    }

                    ……

                    // Inflate all children under temp

                    rInflate(parser, temp, attrs, true, true);

                    ……

                    // We are supposed to attach all the views we found (int temp)

                    // to root. Do that now.

                    if (root != null && attachToRoot) {

                        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 (XmlPullParserException e) {

                ……

            } catch (IOException e) {

                ……

            } finally {

                // Don't retain static reference on context.

                mConstructorArgs[0] = lastContext;

                mConstructorArgs[1] = null;

            }

            return result;

        }

}

首先通過parser拿到關聯的AttributeSet,接著通過while迴圈,直到解析到型別為

XmlPullParser.START_TAG為止,由於我們的TAG型別肯定不是TAG_MERGE,所以接著走到else部分程式碼,這裡先呼叫createViewFromTag建立tag對應的view, 也就是Layout檔案中的Top Parent View,接著呼叫rInflate遞迴建立其全部childviews

先看createViewFromTag的實現:

//LayoutInflater.java 

View createViewFromTag(View parent, String name, AttributeSet attrs, boolean inheritContext) {

       if (name.equals("view")) {

name = attrs.getAttributeValue(null, "class");

}

        Context viewContext;

        if (parent != null && inheritContext) {

            viewContext = parent.getContext();

        } else {

            viewContext = mContext;

        }

        // Apply a theme wrapper, if requested.

        final TypedArray ta = viewContext.obtainStyledAttributes(attrs, ATTRS_THEME);

        final int themeResId = ta.getResourceId(0, 0);

        if (themeResId != 0) {

            viewContext = new ContextThemeWrapper(viewContext, themeResId);

        }

        ta.recycle();

        if (name.equals(TAG_1995)) {

            // Let's party like it's 1995!

            return new BlinkLayout(viewContext, attrs);

        }

        if (DEBUG) System.out.println("******** Creating view: " + name);

        try {

            View view;

            if (mFactory2 != null) {

                view = mFactory2.onCreateView(parent, name, viewContext, attrs);

            } else if (mFactory != null) {

                view = mFactory.onCreateView(name, viewContext, attrs);

            } else {

                view = null;

            }

            if (view == null && mPrivateFactory != null) {

                view = mPrivateFactory.onCreateView(parent, name, viewContext, attrs);

            }

            if (view == null) {

                final Object lastContext = mConstructorArgs[0];

                mConstructorArgs[0] = viewContext;

                try {

                    if (-1 == name.indexOf('.')) {

                        view =onCreateView(parent, name, attrs);

                    } else {

                        view = createView(name, null, attrs);

                    }

                } finally {

                    mConstructorArgs[0] = lastContext;

                }

            }

            return view;

        } catch (InflateException e) {

            ……

        } catch (ClassNotFoundException e) {

           ……

        } catch (Exception e) {

           ……

        }

    }

由於這裡name本身就對應類的class path,所以equals(“view”)肯定為false,在預設情況下,

mFactory2,mFactory以及mPrivateFactory都是未設定的,類名肯定是包含”.”的,接著呼叫

createView(name, null, attrs):

public final View createView(String name, String prefix, AttributeSet attrs)

            throws ClassNotFoundException, InflateException {

        Constructor<? extends View> constructor = sConstructorMap.get(name);

        Class<? extends View> clazz = null;

        try {

            if (constructor == null) {

                // Class not found in the cache, see if it's real, and try to add it

                clazz = mContext.getClassLoader().loadClass(

prefix != null ? (prefix + name) : name).asSubclass(View.class);

                if (mFilter != null && clazz != null) {

                    boolean allowed = mFilter.onLoadClass(clazz);

                    if (!allowed) {

                        failNotAllowed(name, prefix, attrs);

                    }

                }

                constructor = clazz.getConstructor(mConstructorSignature);

                sConstructorMap.put(name, constructor);

            } else {

                ……

            }

            Object[] args = mConstructorArgs;

            args[1] = attrs;

            constructor.setAccessible(true);

            final View view = constructor.newInstance(args);

            if (view instanceof ViewStub) {

                // Use the same context when inflating ViewStub later.

                final ViewStub viewStub = (ViewStub) view;

                viewStub.setLayoutInflater(cloneInContext((Context) args[0]));

            }

            return view;

        }

        ……

}

sConstructorMap是快取map,如果已經load過的class會被快取在其中,後續就可直接通過name從map中拿到對應的class資料,由於咱們是第一次建立,這裡constructor肯定為空,接著呼叫loadclass載入name對應的class,然後儲存到sConstructorMap中

最後呼叫view對應的建構函式並傳入attrs,就這樣,view被建立成功了,由於在構造的時候傳入了AttributeSet物件,所以,View必須在構造時通過attrs讀取並儲存對應的屬性資料以完成View的初始化。

createViewFromTag介紹完後,在inflate內部,接著rinflate函式被呼叫用以遞迴建立當前元素的所有child views:

void rInflate(XmlPullParser parser, View parent, final AttributeSet attrs,

            boolean finishInflate, boolean inheritContext) throws XmlPullParserException,

            IOException {

       final int depth = parser.getDepth();

        int type;

        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)) {

                ……

            } else if (TAG_TAG.equals(name)) {

                ……

            } else if (TAG_INCLUDE.equals(name)) {

                ……

            } else if (TAG_MERGE.equals(name)) {

                ……

            } else {

                final View view = createViewFromTag(parent, name, attrs, inheritContext);

                final ViewGroup viewGroup = (ViewGroup) parent;

                final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);

                rInflate(parser, view, attrs, true, true);

                viewGroup.addView(view, params);

            }

        }

        if (finishInflate) parent.onFinishInflate();

    }

看while迴圈的條件,只有兩種情況while迴圈才會退出或者不進入:

1)      Xml標籤型別為XmlPullParser.END_TAG並且parser.getDepth() <=depth

2)      Xml標籤型別為XmlPullParser.END_DOCUMENT

再加上迴圈內部:

if (type != XmlPullParser.START_TAG) {

continue;

}

程式碼的存在,也就是說,迴圈只會處理XML START_TAG標籤型別

整個邏輯總結如下:

1)  根據當前元素的START_TAG標籤來觸發呼叫createViewFromTag建立對應view,接著將這個View作為parent view引數在rinflate被呼叫時傳入,這時通過rinflate的while函式的判斷條件,就會存在兩種情況

2)  第一種是,這個元素沒有子元素了,那parser.next獲取的必定是當前元素的END_TAG,並且由於深度相同,即parser.getDepth() == depth,while迴圈不會進入,表明元素已經建立完成,接著呼叫if(finishInflate) parent.onFinishInflate(),然後結束本次rinflate呼叫

3)  第二種是,這個元素存在子元素,rinflate的while迴圈會遍歷其下一層的所有子元素,每個子元素的處理流程回到(1)步驟,子元素通過rinflate遞迴呼叫結束後,再將建立的子元素view通過viewGroup.addView(view,params)新增到當前元素對應的viewgroup中,

最後parser.next碰到當前元素的END_TAG標籤,並且parser.getDepth() == depth,當前元素全部子元素建立結束,接著呼叫if (finishInflate) parent.onFinishInflate(),然後結束本次rinflate呼叫

感覺遞迴在處理這種內部層層巢狀的資料結構解析,真的非常好用

通過上頭還可以看出,View.onFinishInflate這個回撥,是在View被構造結束並且完成對其child view的建立新增後才被呼叫的,也就是說,咱們在這個回撥函式裡,能夠讀取View的預設引數和child views,僅此而已.

相關推薦

AndroidActivity Decor View建立流程介紹

6 Activity DecorView建立流程介紹 上頭已經完整的介紹了Activity的啟動流程,Activity是如何繫結Window,Window的décor view是如何通過ViewRootImpl與WMS建立關聯的,也就是說,整個框架已經有了,唯一缺的就是Ac

AndroidActivity觸控事件傳輸機制介紹

8 Activity觸控事件傳輸機制介紹 當我們觸控式螢幕幕的時候,程式會收到對應的觸控事件,這個事件是在app端去讀取的嗎?肯定不是,如果app能讀取,那會亂套的,所以app不會有這個許可權,系統按鍵的讀取以及分發都是通過WindowManagerService來完成

AndroidActivity 視窗顯示流程介紹(二)

7.3 Activity Décorview佈局(layout) Measure確定Décor View以及child views的大小,layout則是確定child view在其parent view中的顯示區域,只有layout結束,view的left,right,t

AndroidActivity 視窗顯示流程介紹(一)

7 Activity 視窗顯示流程介紹 Activity 視窗顯示,其實就是Décor View繪製並顯示的過程,但是在繪製之前,Décor View需要先做下面兩件事情: 1)  確定Décor View的大小 2)  對Décor View進行佈局操作,也就是確定Déc

AndroidSurfaceFlinger相關介紹(三)

3.3 Surface Java層相關封裝 主要介紹三個類,對應如下: Java C++ SurfaceSession.java SurfaceComposeClient 對應JNI檔案為: android_view_surfacesession.cpp

AndroidApp視窗檢視管理

5 App視窗檢視管理 WindowManagerGlobal負責管理App所有要新增到WMS的視窗,介面即為上頭的addView 首先,對於App本地視窗來說,其最核心的資料無非就兩個,一個是Window Parameters,另一個就是視窗的DécorView,一個負

android與javathis與activity.this

由於的java是草草的的過了一遍那樣學的 所以對很多概念已經是不太清楚了,這兩天開始看android 然後就發現很多東西有點看著懵逼 ,我是打算通過安卓去鞏固的java的 然後我看到了在活動中使用toast這裡 首先在此之前我的已經手動的寫了一個button的,那麼我希望說單擊butt

AndroidAndroid中的MVP

個人開發的微信小程式,目前功能是書籍推薦,後續會完善一些新功能,希望大家多多支援! 前言 為什麼使用MVP,網上有很多說法,最主要就是減輕了Activity的責任,相比於MVC中的Activity承擔的責任太多,因此有必要講講MVP。 MVP入門 在MVC框

android的MVP設計模式記憶體洩露問題

我上次寫了淺談mvp,經過一段時間的思考,發現我忽略了一個問題 記憶體洩露問題。 因為Presenter中持有View介面物件,這個介面物件實際為MainActivity.this,Modle中也同時擁有Presenter物件例項,當MainActivi

android中非同步載入"取消非同步載入"二

首先,我得解釋一下為什麼我的標題取消非同步載入打引號,這是因為可能最後實現效果並不是你自己想象中的那樣。大家看取消非同步載入,這不是很簡單嗎?AsyncTask中不是有一個cancel方法嗎?直接呼叫該方法不就行了嗎?但是事實上是這樣的嗎?如果真是這樣,我相信我就沒有以寫這

android中圖片處理圖形變換特效Matrix(四)

今天,我們就來談下android中圖片的變形的特效,在上講部落格中我們談到android中圖片中的色彩特效來實現的。改變它的顏色主要通過ColorMatrix類來實現。 現在今天所講的圖片變形的特效主要就是通過Matrix類來實現,我們通過上篇部落格知道,改變色彩特效,主要

Android中的非同步載入ListView中圖片的快取及優化三

     隔了很久沒寫部落格,現在必須快速脈動回來。今天我還是接著上一個多執行緒中的非同步載入系列中的最後一個使用非同步載入實現ListView中的圖片快取及其優化。具體來說這次是一個綜合Demo.但是個人覺得裡面還算有點價值的就是裡面的圖片的快取的實現。因為老實說它確實能

android中圖片處理色彩特效處理ColorMatrix(三)

在android開發中對圖片處理很是頻繁,其中對圖片的顏色處理就是很常見的一種。我們經常看到一些類似美圖秀秀,美顏相機的app,為什麼那麼黑的人拍出來是確實那麼地白呢?長的那麼那個(醜)的人,用美顏相機拍出來的看起來也有那麼回事(拍出來就感覺挺漂亮)。就像網上有個段子,有錢

android中的ListView解決ScrollView和ListView巢狀衝突(實際上一切都是浮雲,閒的蛋疼)(一)

     相信大家都已經可以熟練使用ListView和GridView,大神們估計都在使用RecyclerView了。如果還在使用ListView,你肯定有這樣的一個深刻的感受,那就是在做一個APP的時候使用ListView和GridView很頻繁,並且經常會遇到一個頁面中

Android進階

熱文導讀 | 點選標題閱讀作者:斜槓Allen地址:http://www.apkbus.com/

androidActivity建立與關閉

Activity的啟動和關閉 1.啟動activity activity的啟動分為兩種,一種為入口activity,另一種為其他activity 在AndroidManifests進行配置,入口activity的啟動只要在要啟動的activity里加入intent,例如下面程式碼將MainActivity作為

soaRESTful

let net ado soap 業務 淺談 網絡資源 ado.net 面向服務 今晚打算花點時間整理一下面向服務的架構oap。1傳統中小型項目架構一般是這樣的:(java)html+servlet+jdbc.和(.net)html+handler+ado.net都是在一臺

AndroidActivity之間傳遞對象

this .get tin 方式 art desc 內容 ext pri 在非常多時候,我們須要在Activity之間傳遞對象,比方當你點擊了某列表的item,須要傳遞給下一個Activity該對象,那我們須要該怎麽做呢? Android支持兩種傳遞對象的方式。一種

Android中的組播(多播)

-1 ip協議 strong 多個 接受 端口 ui線程 nbsp 數據 組播使用UDP對一定範圍內的地址發送相同的一組Packet,即一次可以向多個接受者發出信息,其與單播的主要區別是地址的形式。IP協議分配了一定範圍的地址空間給多播(多播只能使用這個範圍內

MYSQL日誌文件系統

mysql日誌文件系統 同大多數關系型數據庫一樣,日誌文件是MySQL數據庫的重要組成部分。MySQL有幾種不同的日誌文件,通常包括錯誤日誌文件,二進制日誌,通用日誌,慢查詢日誌,等等。這些日誌可以幫助我們定位mysqld內部發生的事件,數據庫性能故障,記錄數據的變更歷史,用戶恢復數據庫等等 MySQL