前言
在android當(dāng)中對于UI體系當(dāng)中往往我們會在繪制UI的時候碰到各種各樣的問題而不知道從何解決着裹, 也有時需要開發(fā)更改自定義組件時,需要做自己的調(diào)整瑰妄,或者是實現(xiàn)某個自定義特效時的思路不明確陷嘴,想要達到去玩轉(zhuǎn)UI的最為基礎(chǔ)的部分,就是去全面的深入了解UI的繪制流程.所以接下來帶大家去進行全面分析UI整體的繪制體系.
思路:android程序啟動--->Activity加載并完成生命周期--->setContentView--->圖形繪制
疑惑:
1.Android程序是如何啟動,Activity生命周期如何調(diào)用翰撑?
2.在Activity onCreate當(dāng)中我們的setContentView是如何將UI文件加載罩旋?
3.UI是如何繪制的啊央?
答案:
1.Android程序流程
眾所周知,我們的java程序想要開啟需要依賴于main方法涨醋,也就是我們的程序入口(主線程)進入瓜饥,但是在我們?nèi)粘i_發(fā)android程序的過程當(dāng)中我們并沒有發(fā)現(xiàn)main方法的存在,那么android當(dāng)中的是如何開始運行的浴骂?
熟悉的朋友們可能都知道在android當(dāng)中存在一個叫做ActivityThread的類乓土,這個類代表的是android當(dāng)中的主線程,而在這個類當(dāng)中我們看到了比較熟悉的main方法溯警,那么現(xiàn)在是否可以認為我們的android在打開app時是首先調(diào)用的是當(dāng)前這個類的main趣苏,也就是此處為我們的啟動點
在此處可以看到Activity調(diào)用了一個attach()方法
在這里我們可能首先要考慮的是getService拿出來的是什么?
進去之后梯轻,我們會發(fā)現(xiàn)
在這個當(dāng)中食磕,里面調(diào)用了的系統(tǒng)的ActivityManagerService這個服務(wù),并且給出了一個Binder接口
那么在這里喳挑,我們可以聯(lián)想到彬伦,在android當(dāng)中的binder通信機制,那么實際上我們的ActivityManager是有系統(tǒng)服務(wù)所調(diào)用管理伊诵,并且通過在binder接口當(dāng)中進行調(diào)用单绑,這也是為什么我們講Activity是跨進程訪問的原因
那么明白了這個時候能夠得到ActivityManager之后,我們接著回到attach當(dāng)中繼續(xù)看下去曹宴, 這個時候會發(fā)現(xiàn)搂橙,我們調(diào)用了一個attachApplication方法(見圖2)這個方法又是干嘛的?attachApplication在這里的作用其實實際上是ActivityThread通過attach獲取到笛坦,然后將applciationThread將其關(guān)聯(lián)区转,把activity相關(guān)信息存儲在applciationThread里面,apllicationThread的類為activity的各種狀態(tài)做了相對應(yīng)的準備工作
這個時候我們需要關(guān)注弯屈,ApplicationThread當(dāng)中做了什么蜗帜?
當(dāng)我們打開ApplicationThread中我們會看到一堆的schedle方法,這些方法的名稱其實就可以給我們表明资厉,代表的是在執(zhí)行Activity的某種狀態(tài)時調(diào)用的計劃執(zhí)行方法
這時我們會看到一個scheduleLaunchActivity方法,表示計劃加載時調(diào)用的
這里我門發(fā)現(xiàn)了一個很有意思的事情
這個上面我們會看到一個ActivityClientRecord對象蔬顾,這個對象其實實際上就是我們的Activity
而且似乎每一個方法還干了一件讓我們非常熟悉的一件事宴偿, 進行了一次sendMessage()將當(dāng)前創(chuàng)建的Activity發(fā)送了出去
當(dāng)走到這里我們會發(fā)現(xiàn)最終我們調(diào)用的是Handler的消息通信機制,也就是說诀豁,在這里我們可以總結(jié)一下窄刘,
當(dāng)Activity狀態(tài)改變時,都會有對應(yīng)的一個消息發(fā)送出去
而接收這里舷胜,我能發(fā)現(xiàn)通過發(fā)送時不同的狀態(tài)娩践,這邊調(diào)用了不同的handlerXXXActivity方法
在這里,我門貌似發(fā)現(xiàn)了Activity的生命周期的調(diào)用痕跡,那么其實到此為止翻伺,我門可以得出一個結(jié)論材泄,
Application運行的過程當(dāng)中,對于Activity的操作吨岭,狀態(tài)轉(zhuǎn)變拉宗,其實實際上是通過Handler消息機制來完成的,
Application當(dāng)中只管去發(fā)辣辫, 由消息機制負責(zé)調(diào)用旦事,因為在main方法當(dāng)中我門的Looper輪訓(xùn)器是一直在進行輪訓(xùn)的
而當(dāng)我們在加載Activity的時候,當(dāng)中調(diào)用了一個performLaunchActivity()方法急灭,在這個中間我發(fā)現(xiàn)了我們onCreate的調(diào)用痕跡
也就是說姐浮,到目前為止我們能夠明白,整個Application加載Activity的整套流程是怎么回事
那么接下來我們需要關(guān)注的是葬馋,在onCreate當(dāng)中我們所寫的setContentView到底干了什么
2.setContentView
在onCreate當(dāng)中我們往往會使用setContentView去進行設(shè)置我們自己的布局文件或者view单料,那么在這當(dāng)中他到底是怎么做的?通過觀察源碼点楼,這個時候通過一系列線索我找到了最終的位置PhoneWindow類
這個時候我們會看到他做了兩個事情扫尖,一個是installDecor,另一個是inflate掠廓,這兩個后一個不難猜出他是在進行布局文件的解析换怖, 前面的我們認為她是在初始化某個東西
進來之后發(fā)現(xiàn)他初始化了兩個東西,一個叫做mDecor,一個叫做mContentParent
我們看到了mDecor是一個DecorView
mContentParent是一個ViewGroup
透過注釋的翻譯蟀瞧,其實我們就能很明確知道這兩個是用來干嘛的
// This is the view in which the window contents are placed. It is either(這是窗口內(nèi)容放置的視圖)
// mDecor itself, or a child of mDecor where the contents go.(它要么是mDecor本身沉颂,要么是mDecor的子類的內(nèi)容。)
//This is the top-level view of the window, containing the window decor.(這是在窗口當(dāng)中的頂層View悦污,包含窗口的decor)
一個代表的是頂層view铸屉,一個用來裝他下面的視圖內(nèi)容
在接著往下看的時候,我門發(fā)現(xiàn)切端,generateLayout方法當(dāng)中彻坛,發(fā)現(xiàn)了在此處進行了大量的requestFeature的調(diào)用,也就是所踏枣,我們的requestFeature
然后在下面我門會發(fā)現(xiàn)在做了一件事情昌屉,
當(dāng)前這里竟然在加載布局文件,并且生成了一個view茵瀑, 但是好像貌似不是我門自己的
所以我們需要去探尋他到底加載了一個什么東東间驮?
這是我找到了一個比較有意思的組件,
在這個上面我看到了一句這樣的注釋
//This is an optimized layout for a screen, with the minimum set of features
enabled.
這是一個屏幕的優(yōu)化布局马昨,具有最小的特征集啟用竞帽。
通過注釋和一些資料分析扛施, 得到了一個比較坑的結(jié)果。
這是DecorView默認的一個渲染屹篓,然后我門自己的布局都是渲染到她的FrameLayout上的
那么在這里我門現(xiàn)在能夠明白疙渣,installDector其實實際上是在初始化兩個視圖容器,然后加載系統(tǒng)的R資源及特征抱虐,產(chǎn)生了一個基本布局
那么接著回到之前我門關(guān)注的另外一個方法mLayoutInflater.inflate(layoutResID, mContentParent);
這個方法就比較好理解了昌阿,
這這段注釋上面我門就可以得到一個信息
//Inflate a new view hierarchy from the specified xml resource.(從指定的視圖當(dāng)中獲取試圖的層次結(jié)構(gòu),意思就是恳邀,現(xiàn)在在加載自己的資源)
而具體流程就不貼代碼了給各位上一張圖
那么在這里我門就能夠明白懦冰,setContentView其實做了兩件比較核心的事情,就是加載環(huán)境配置谣沸,和自己的布局刷钢,那么接下來我門需要考慮的事情就是,他到底怎么畫到界面上的
3.UI是如何繪制的乳附?
通過前面兩個章節(jié)内地,我門了解到,程序?qū)τ赼ctivity生命周期的調(diào)用赋除,以及我們的視圖資源的由來阱缓。這是我門需要找到的是我門的繪制起點在哪?
在ActivityThread啟動時举农, 我發(fā)現(xiàn)在加載handleLaunchActivity方法調(diào)用performLaunchActivity方法之后又調(diào)用了一個handleResumeActivity在這里我發(fā)現(xiàn)了繪制流程的開始
通過前面的流程我門知道荆针,onCreate之行完成之后,所有資源交給WindowManager保管
在這里颁糟,將我們的VIew交給了WindowManager,此處調(diào)用了addView
進入addView之后我們發(fā)現(xiàn)了一段這樣的代碼航背,他將視圖,和參數(shù)還有我門的一個ViewRoot對象都用了容器去裝在了起來棱貌,那么在此處我門可以得出玖媚,是將所有的相關(guān)對象保存起來
mViews保存的是View對象,DecorView
mRoots保存和頂層View關(guān)聯(lián)的ViewRootImpl對象
mParams保存的是創(chuàng)建頂層View的layout參數(shù)婚脱。
而WindowManagerGlobal類也負責(zé)和WMS通信
而在此時今魔,有一句關(guān)鍵代碼root.setView,這里是將我們的參數(shù)起惕,和視圖同時交給了ViewRoot涡贱,那么這個時候我們來看下ViewRoot當(dāng)中的setView干了什么
終于在這里讓我發(fā)現(xiàn)了讓我明白的一步
在這里我門會看到view.assignParent的設(shè)置是this, 那么也就是說在view當(dāng)中parent其實實際上是ViewRoot
那么在setContentView當(dāng)中調(diào)用了一個setLayoutParams()是調(diào)用的ViewRoot的
而在ViewRoot當(dāng)中發(fā)現(xiàn)了setLayoutParams和preformLayout對requestLayout方法的調(diào)用
在requestLayout當(dāng)中發(fā)現(xiàn)了對scheduleTraversals方法的調(diào)用而scheduleTraversals當(dāng)中調(diào)用了doTraversal的訪問惹想,最終訪問到了performTraversals(),而在這個里面督函,我發(fā)現(xiàn)了整體的繪制流程的調(diào)用
當(dāng)前里面依次是用了
UI繪制先回去測量布局嘀粱,然后在進行布局的擺放激挪,當(dāng)所有的布局測量擺放完畢之后,進行繪制锋叨。
至此整體UI繪制過程我們就已經(jīng)非常清楚了垄分。
我門可以根據(jù)這種繪制的流程來操作自己的自定義組件。
作者:動腦學(xué)院Barry老師
原創(chuàng)博客娃磺,請注明轉(zhuǎn)載處....
鏈接:http://www.reibang.com/p/0f6b4bc86c7b
來源:簡書(昵稱BarryKerwin)