1. 程式人生 > >【Android 原始碼解析】淺談DecorView與ViewRootImpl

【Android 原始碼解析】淺談DecorView與ViewRootImpl

一、前言

對於Android開發者而言,View無疑是開發中經常接觸的,包括它的事件分發機制、測量、佈局、繪製流程等。如果要自定義一個View,那麼應該對以上流程有所瞭解、研究。在深入接觸View的測量、佈局、繪製這三個流程之前,我們從Activity入手,看看從Activity建立後到View的繪製之前,所要經歷的步驟。

二、從setContentView說起

一般地,我們在Activity中,會在onCreate()方法中寫下這樣一句:

setContentView(R.layout.activity_main);

顯然,這是為Activity設定一個我們定義好的activity_main.xml佈局,我們跟蹤一下原始碼,看看這個方法是怎樣做的,

Activity#setContentView

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

Activity#getWindow

public Window getWindow() {
        return mWindow;
    }

從上面看出,setContentView呼叫了mWindow的setContentView()方法,那麼這個mWindow又是什麼?

追蹤一下原始碼,發現mWindow是Window型別的,但它是一個抽象類,setContentView也是一個抽象方法,我們我們要找Window類的實現類才行。我們在Actvity中查詢一下mWindow在哪裡被賦值了,可以發現在Activity#attach方法中有如下實現:

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, String referrer, IVoiceInteractor voiceInteractor,
            Window window) {
        attachBaseContext(context);

        //...

        mWindow = new PhoneWindow(this, window);
        
        //..
    }

我們只看關鍵部分,PhoneWindow是Window的實現類,那麼我們在PhoneWindow類中找到setContentView方法,看看它是如何實現的,PhoneWindow#setContentView

@Override
    public void setContentView(int layoutResID) {
        //先判斷mContentParent是否存在,如果為null,則installDecor()
        if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            //...
        } else {
            //把setContentView裡設定的佈局,新增到mContentParent上
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        //...
    }

那麼這個mContentParent是什麼?我們來看下其註釋:

// This is the view in which the window contents are placed. It is either
    // mDecor itself, or a child of mDecor where the contents go.
    ViewGroup mContentParent;

它是DecorView或者DecorView的子元素。

這裡先梳理一下以上內容:Activity通過PhoneWindow的setContentView方法來設定佈局,而設定佈局之前,回先判斷mContentParent是否存在,而我們設定的佈局是mContentParent的子元素。

建立DecorView

接著上面提到的installDecor()方法,我們看看它的原始碼,PhoneWindow#install

private void installDecor() {
        mForceDecorInstall = false;
        //如果mDecor為空,就例項化mDecor
        if (mDecor == null) {
            mDecor = generateDecor(-1);
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        }
        //如果mContentParent為空,就例項化mContentParent
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);

            //...
            
            }
        }
    }

首先,如果mDecor是空,會例項化mDecor。

protected DecorView generateDecor(int featureId) {
        return new DecorView(context, featureId, this, getAttributes());
}

DecorView繼承於FrameLayout,由此可知它是一個ViewGroup。

其實,DecorView是整個ViewTree的最頂層View,它是一個FrameLayout佈局,代表了整個應用的介面。

而該DecorView只有一個子元素LinearLayout,這個LinearLayout有標題View和內容View兩個元素,而內容View則是上面提到的mContentParent。想知道為什麼是這樣,可通過檢視原始碼PhoneWindow#generateLayout():

protected ViewGroup generateLayout(DecorView decor) {

        // ...

        int layoutResource;
        int features = getLocalFeatures();

        //...

        //這個layout就是一個LinearLayout
        layoutResource = R.layout.screen_simple;

        //呼叫這個方法,是把這個LinearLayout新增到DecorView中
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);

        //這個ID_ANDROID_CONTENT就是com.android.internal.R.id.content
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        if (contentParent == null) {
            throw new RuntimeException("Window couldn't find content container view");
        }

        return contentParent;
    }

可以看到,generateLayout方法主要就是把一個LinearLayout新增到DecorView中,然後返回內容View。具體是由mDecor.onResourcesLoaded()操作。

void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
        //...

        mDecorCaptionView = createDecorCaptionView(inflater);
        final View root = inflater.inflate(layoutResource, null);
        if (mDecorCaptionView != null) {
            if (mDecorCaptionView.getParent() == null) {
                addView(mDecorCaptionView,
                        new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
            }
            mDecorCaptionView.addView(root,
                    new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
        } else {

            addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        }
        mContentRoot = (ViewGroup) root;
    }

這裡把R.layout.screen_simple的佈局原始碼也貼一下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    android:orientation="vertical"  
    android:fitsSystemWindows="true">  
    <!-- Popout bar for action modes -->  
    <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" />  

    <FrameLayout android:id="@android:id/content"  
        android:layout_width="match_parent"   
        android:layout_height="0dip"  
        android:layout_weight="1"  
        android:foregroundGravity="fill_horizontal|top"  
        android:foreground="?android:attr/windowContentOverlay" />  
</LinearLayout>

到目前為止,通過setContentView方法,建立了DecorView載入了我們提供的佈局(addView),但是,我們的View還是不可見的,因為我們僅僅載入了佈局,並沒有對View進行任何的測量、佈局、繪製工作。

在View進行測量流程之前,還要進行一個步驟,那就是把DecorView新增至Window,然後觸發

ViewRootImpl#performTraversals方法,在該方法內部會測量、佈局、繪製。

將DecorView新增至Window

我們已經知道怎麼把建立DecorView,現在我們要把這個DecorView新增到Window物件上。而要了解這個過程,我們要先了解一下Activity的建立過程:

首先,在ActivityThread#handleLaunchActivity中啟動Activity,在裡面會呼叫Activity#onCreate方法,從而完成上述DecorView的建立,當onCreate執行完畢,在handleLaunchActivity方法會繼續呼叫ActivityThread#handleResumeActivity方法,我們來看原始碼:

final void handleResumeActivity(IBinder token,
            boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
        // ...

        ActivityClientRecord r = mActivities.get(token);
        r = performResumeActivity(token, clearHide, reason);

        if (r != null) {
            final Activity a = r.activity;

            // ...
            if (r.window == null && !a.mFinished && willBeVisible) {
                //獲取Window物件
                r.window = r.activity.getWindow();
                //獲取DecorView物件
                View decor = r.window.getDecorView();
                decor.setVisibility(View.INVISIBLE);
                //獲取windowManager物件
                ViewManager wm = a.getWindowManager();
                WindowManager.LayoutParams l = r.window.getAttributes();
                a.mDecor = decor;
                l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
                l.softInputMode |= forwardBit;
                if (r.mPreserveWindow) {
                    a.mWindowAdded = true;
                    r.mPreserveWindow = false;
                    ViewRootImpl impl = decor.getViewRootImpl();
                    if (impl != null) {
                        impl.notifyChildRebuilt();
                    }
                }
                if (a.mVisibleFromClient && !a.mWindowAdded) {
                    a.mWindowAdded = true;
                    //呼叫windowManager的addView方法
                    wm.addView(decor, l);
                }

            }

        }
    }

在該方法內部,獲取Activity的window物件、DecorView物件,以及windowManagerImpl物件,然後呼叫

WindowMangerImpl#addView方法:

public final class WindowManagerImpl implements WindowManager {
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();

    // ...
    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    }
    
    // ...

}

其實是呼叫了WindowMangerGlobal#addView方法:

public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        //...

        ViewRootImpl root;
        View panelParentView = null;

        synchronized (mLock) {
            // ...

            root = new ViewRootImpl(view.getContext(), display);

            view.setLayoutParams(wparams);

            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);
        }

        try {
            root.setView(view, wparams, panelParentView);
        } catch (RuntimeException e) {
            synchronized (mLock) {
                final int index = findViewLocked(view, false);
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
            }
            throw e;
        }
    }

這裡呼叫ViewRootImpl#setView方法。在這個方法裡,

最後,歡迎一起交流學習,我的郵箱[email protected]

相關推薦

Android 原始碼解析DecorViewViewRootImpl

一、前言 對於Android開發者而言,View無疑是開發中經常接觸的,包括它的事件分發機制、測量、佈局、繪製流程等。如果要自定義一個View,那麼應該對以上流程有所瞭解、研究。在深入接觸View的測量、佈局、繪製這三個流程之前,我們從Activity入手,看看從Act

Android原始碼解析View.post()到底幹了啥

本篇文章已授權微信公眾號 guolin_blog (郭霖)獨家釋出 emmm,大夥都知道,子執行緒是不能進行 UI 操作的,或者很多場景下,一些操作需要延遲執行,這些都可以通過 Handler 來解決。但說實話,實在是太懶了,總感覺寫 Handler 太麻煩了,一不小心又很容

藍芽檔案傳輸之obex層之上的分析Android原始碼解析

 在上節中我們仔細分析了藍芽檔案傳輸過程中涉及到的UI介面,最終定格在藍芽裝置掃描的介面,我們只要選擇自己想要傳輸的藍芽裝置就可以進行藍芽檔案的傳輸了。那就是這樣一個簡單的裝置選擇的點選會引發哪些

JDK原始碼分析HashMap的原理

這篇文章給出了這樣的一道面試題: 在 HashMap 中存放的一系列鍵值對,其中鍵為某個我們自定義的型別。放入 HashMap 後,我們在外部把某一個 key 的屬性進行更改,然後我們再用這個 key 從 HashMap 裡取出元素,這時候 HashMap 會返回什麼? 文中已給出示例程式碼與答案, k

STL 原始碼剖析 STL 迭代器 traits 程式設計技法

![攝於清華五道口](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b9cd144f2eeb4d85856e265bfc630591~tplv-k3u1fbpfcp-zoom-1.image) 大家好,我是小賀。 > 點贊再看,養成習慣 &

大話設計模式——設計模式基礎

表示 無用功 隱式 art -s -m 個人 pri one   初學設計模式給我最大的感受是:人類真是偉大啊!單單是設計模式的基礎課程就讓我感受到了強烈的生活氣息。個人感覺《大話設計模式》這本書寫的真好。讓貌似非常晦澀難懂的設計模式變的生活化。趣味化。   以下淺談一

Unity遊戲開發Unity遊戲開發中的單元測試

可靠 屬於 sin 自定義類型 允許 ogr 兩個 階段 ast 一、單元測試的定義與作用   單元測試定義:單元測試在傳統軟件開發中是非常重要的工具,它是指對軟件中的最小可測試單元進行檢查和驗證,一般情況下就是對代碼中的一個函數去進行驗證,檢查它的正確性。一個單元測試是

ASP.NET 系列緩存技術在ASP.NET中的運用

進行 喜歡 之間 framework cnblogs 磁盤 onf lin bug 本篇文章雖不談架構,但是Cache又是架構中不可或缺的部分,因此,在講解Cache的同時,將會提及到部分架構知識,關於架構部分,讀者可以不用理解,或者直接跳過涉及架構部分的內容, 你只

廣州網站建設公司外貿國內網站設計的不同之處

  所謂的外貿是什麼?就是主要針對外國客戶所做的一個網站,並且這個網站還要符合外國客戶的體驗習慣以及需要獲得外國搜尋引擎的抓爬和收錄,在能在網際網路上立足。而國內的網站建設又是怎樣的呢?不同之處又在那裡?下面來聽聽廣東鋒火網站建設公司怎麼說吧!        網站內容   要想取得

自然語言處理語料庫

文章目錄 【自然語言處理】淺談語料庫 前言 一、淺談語料庫 1、語料和語料庫 2、語料庫語言學 3、 建議語料庫的意義 二、語料庫深入瞭解

演算法微解讀線段樹

淺談線段樹 (來自TRTTG大佬的供圖) 線段樹個人理解和運用時,認為這個是一個比較實用的優化演算法。 這個東西和區間樹有點相似,是一棵二叉搜尋樹,也就是查詢節點和節點所帶值的一種演算法。 使用線段樹可以快速的查詢某一個節點在若干條線段中出現的次數,時間複雜度為O(logN),這個時間複雜度非常的理想,但是空

演算法微解讀01分數規劃

淺談01分數規劃 所謂01分數規劃,看到這個名字,可能會想到01揹包,其實長得差不多。 這個演算法就是要求“價效比”最高的解。sum(v)/sum(w)最高的解。 定義 我們給定兩個陣列,a[i]表示選取i的收益,b[i]表示選取i的代價。如果選取i,定義x[i]=1否則x[i]=0。每個物品只有選和不選的

Android原始碼ubuntu上編譯I.MX6Q原始碼

參考文件: 一、Android刷機的元件含義 Android 啟動流程: 當你的Android手機啟動時首先會啟動RADIO,然後是SPL。 此時SPL 會根據你的按鍵,確定進入哪個模式( 例如Recovery,Fastboot等等), 如果沒有按其他

領卓教育" Hello,world!"

相信每個程式設計師小哥哥和程式媛小姐姐們對"Hello,world!"都不會陌生。接觸嵌入式也有幾個月時間了,今天我來說說我對這個最基礎的程式的看法。 Hello World 中文意思是『你好,世界』。因為《The C Programming Language

Android深入解析Manifest配置檔案解析(上)(英文版)

<action> 語法規則: <action android:name="string"/> 描述 : Adds an action to an intentfilter. An elementmust contain one or more

Netty原始碼解析NioEventLoop

上一篇部落格【Netty原始碼學習】EventLoopGroup中我們介紹了EventLoopGroup,實際說來EventLoopGroup是EventLoop的一個集合,EventLoop是一個單執行緒的執行緒池,其介面和類實現關係如下:接下來我們主要介紹實現類NioEv

設計模式之一對MVC設計模式的理解

    在APP開發中,我們經常提MVC,顧名思義,M:Model,模型層,或者叫資料層,V:View,檢視層.C:Control,控制器層,或者叫邏輯層.每次實現某項功能的時間,本著唯一責任制的原則

公眾號系列SAP項目管理的技能

能力 獲得 問題 支持 href 開發 nor 管理 出發 公眾號:SAP Technical 本文作者:matinal 原文出處:http://www.cnblogs.com/SAPmatinal/ 原文鏈接:【【公眾號系列】淺談SAP項目管理的技能

數學教學論文小學生數學學習興趣的培養

助教 主動 情況 讀寫 教育 趣味性 培養 參與 敢於 淺談小學生數學學習興趣的培養   作者:劉亞儒   摘要:古代教育家朱熹曾說:“教人未見其興趣,必不樂學。”由此可見,興趣的培養在教學過程中至關重要,有利於提高數學課堂的教學效率和學生的學習質量。因此,本文對如何培養學

Andorid原始碼解析View.post() 到底幹了啥

本篇文章已授權微信公眾號 guolin_blog (郭霖)獨家釋出 emmm,大夥都知道,子執行緒是不能進行 UI 操作的,或者很多場景下,一些操作需要延遲執行,這些都可以通過 Handler 來解決。但說實話,實在是太懶了,總感覺寫 Hand