Android那兩個(gè)你碰不到但是很重要的類(lèi)之ViewRootImpl
前言
這兩個(gè)類(lèi)就是ActivityThread和ViewRootImpl,之所以說(shuō)碰不到是因?yàn)槲覀儫o(wú)法通過(guò)正常的方式引用這兩個(gè)類(lèi)或者其類(lèi)的對(duì)象,調(diào)用方法或者直接拿他的屬性。但他們其實(shí)又無(wú)處不在,應(yīng)用開(kāi)發(fā)中很多時(shí)候都和他們息息相關(guān),閱讀他們掌握其內(nèi)部實(shí)現(xiàn)對(duì)我們理解Android運(yùn)行機(jī)理有醍醐灌頂之療效,碼讀百變其義自見(jiàn),常讀常新。本文就嘗試從幾個(gè)我們經(jīng)常接觸的方面先談?wù)刅iewRootImpl。
1.ViewRootImpl哪來(lái)的?
首先是ViewRootImpl,位于android.view包下,從它所處的位置大概能猜到,跟View相關(guān)。其作用一句話總結(jié),就是連接Window和View的紐帶。
這個(gè)要從我們最熟悉的Activity開(kāi)始,我們知道Activity的設(shè)置布局View是通過(guò)setContentView() 方法這個(gè)方法里面也大有文章,我們簡(jiǎn)單的梳理下。
- Activity setcontentView()內(nèi)部調(diào)用了
getWindow().setContentView(layoutResID);也就是調(diào)用了Window的setContentView方法,Android里Window的唯一實(shí)現(xiàn)類(lèi)就是PhoneWindow,PhoneWindow setContentView,初始化DecorView和把我們?cè)O(shè)置的View作為其子類(lèi)。 - 目光轉(zhuǎn)移到
ActivityThread沒(méi)錯(cuò)是我們提及的另外一個(gè)主角,先關(guān)注他的handleResumeActivity()方法,里面關(guān)鍵的部門(mén)代碼,
public void handleResumeActivity(){
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
ViewManager wm = a.getWindowManager();
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
wm.addView(decor, l);
}WindowManager的實(shí)現(xiàn)類(lèi)WindowManageImpl的addView方法里調(diào)用了mGlobal.updateViewLayout(view, params);- 最后我們?cè)赪indowManagerGlobal的addView方法里找到了
public void addView(){
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
}小結(jié)
- 通過(guò)梳理這個(gè)過(guò)程我們知道,setContenview()其實(shí)只是在Window的下面掛了一個(gè)View鏈,View鏈的根就是ViewRootImpl。
- Window通過(guò)把View和Activity聯(lián)系在一起。
- View鏈的真正添加操作最終交給了WindowManagerGlobal執(zhí)行。
- 補(bǔ)充一點(diǎn):PopupWindow本質(zhì)就是在當(dāng)前Window下掛了一個(gè)View鏈,PopupWindow本身沒(méi)有Window,就如雷鋒塔沒(méi)有雷鋒一樣;Dialog是有自己的window關(guān)于這點(diǎn)可自行查閱源碼考證。
2 ViewRootImpl 一個(gè)View鏈渲染的中轉(zhuǎn)站
View的渲染是自定而上層層向下發(fā)起的,大致經(jīng)歷測(cè)量布局和繪制,View鏈的管理者就是ViewRootImpl。通過(guò)
scheduleTraversals()方法發(fā)起渲染動(dòng)作。交給Choreographer安排真正執(zhí)行的時(shí)間關(guān)于Choreographer不熟悉的可以參考我的其他文章。最終執(zhí)行performTraversals() 方法。
private void performTraversals(){
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
performLayout(lp, mWidth, mHeight);
performDraw();
}3 不能在子線程操作View?
ViewRoot的RequestLayout中有這樣一段代碼:
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}- 我們對(duì)View的操作,比如給TextView設(shè)置text,最終都會(huì)觸發(fā)ViewRootImpl的
requestLayout()方法,該方法有如上的一個(gè)check邏輯。這就是我們常說(shuō)的不能在子線程中更新View。 - 其實(shí)子線程中可以執(zhí)行View的操作,但是有個(gè)前提是:View還未掛載時(shí)。 View未掛載時(shí)時(shí)不會(huì)觸發(fā)requestLayout的,還只是一個(gè)普普通通的java對(duì)象。那掛載邏輯在哪?
4 View 掛載
- 在ViewRootImpl的
performTraversals()里有這個(gè)代碼
private void performTraversals(){
host.dispatchAttachedToWindow(mAttachInfo, 0);//此處的host為ViewGroup
}- ViewGroup的
dispatchAttachedToWindo()方法會(huì)把AttachInfo對(duì)象分配每一個(gè)View,最終實(shí)現(xiàn)我們所謂的掛載。
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
for (int i = 0; i < count; i++) {
final View child = children[i];
child.dispatchAttachedToWindow(info,
combineVisibility(visibility, child.getVisibility()));
}
- 實(shí)現(xiàn)掛載的View有任何風(fēng)吹草動(dòng)就會(huì)把事件傳遞到大bossViewRootImpl這里了。
通過(guò)addView添加進(jìn)的View也是會(huì)收到父View的mAttachInfo這里不展開(kāi)了。
5 View.post()的Runnable最終在哪執(zhí)行了?
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
getRunQueue().post(action);
return true;
}- 以上是View post()的代碼,可見(jiàn)如果已經(jīng)實(shí)現(xiàn)掛載的View,會(huì)直接把post進(jìn)來(lái)的消息交給Hanlder處理了給執(zhí)行,不然就post了HandlerActionQueue里。
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
..
if (mRunQueue != null) {
mRunQueue.executeActions(info.mHandler);//內(nèi)部也是調(diào)用handler.post()
mRunQueue = null;
}
..
}- 最終這些Runnable會(huì)在View掛載的時(shí)候執(zhí)行,也就是
dispatchAttachedToWindow()方法里執(zhí)行。
6 為什么View.post 可以獲取寬高
這個(gè)是是一個(gè)問(wèn)題延伸,在Activity中直接獲取寬高是獲取不到的,我們通常會(huì)使用view.post一個(gè)Runnable來(lái)獲取。原因就是Activity
onCreate時(shí)通過(guò)setContentView只是創(chuàng)建了View而未實(shí)現(xiàn)掛載,掛載是在onResume時(shí),未掛載的View其實(shí)沒(méi)有經(jīng)歷測(cè)量過(guò)程。。而通過(guò)post的方式,通過(guò)上一小節(jié)知道,未掛載的View上post之后,任務(wù)會(huì)在掛載之后,通過(guò)handler重新post,此時(shí)已經(jīng)ViewRootImpl已經(jīng)執(zhí)行了
performTraversals()完成了測(cè)量自然可以得到寬高。
7 還有一點(diǎn)值得注意
ViewRootImpl 不單單是渲染的中轉(zhuǎn)站,還是觸摸事件的中轉(zhuǎn)站。
硬件傳感器接收到觸摸事件經(jīng)過(guò)層層傳遞分發(fā)到應(yīng)用窗口的第一站就是ViewRootImpl。為什么這么說(shuō)?因?yàn)槲矣凶C據(jù)~。這是ViewRoot里的代碼
public void setView(){
..
mInputEventReceiver = new WindowInputEventReceiver(inputChannel,
Looper.myLooper());
}- WindowInputEventReceiver是ViewRootImpl的一個(gè)內(nèi)部類(lèi),其接收到input事件后,就會(huì)進(jìn)行事件分發(fā)。
- 這里給我們的啟發(fā)是,并不是所有的主線程任務(wù)執(zhí)行都是通過(guò)Handler機(jī)制, onTouch()事件是底層直接回調(diào)過(guò)來(lái)的,這就和我們之前卡頓監(jiān)控說(shuō)的方案里有一項(xiàng)就是對(duì)onTouchEvent的監(jiān)控。
總結(jié)
- ViewRoot的代碼有一萬(wàn)多行,本文分析的只是冰山一角,里面有大量細(xì)節(jié)直接研究。
- 通過(guò)ViewRootImpl相關(guān)幾個(gè)點(diǎn),簡(jiǎn)單的做了介紹分析希望對(duì)你有幫助。
以上就是Android那兩個(gè)你碰不到但是很重要的類(lèi)之ViewRootImpl的詳細(xì)內(nèi)容,更多關(guān)于Android ViewRootImpl的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android中使用Vectors(2)繪制優(yōu)美的路徑動(dòng)畫(huà)
這篇文章主要介紹了Android中使用Vectors(2)繪制優(yōu)美的路徑動(dòng)畫(huà)的相關(guān)資料,需要的朋友可以參考下2016-03-03
Android自定義View實(shí)現(xiàn)點(diǎn)贊控件
這篇文章主要介紹了Android自定義View實(shí)現(xiàn)點(diǎn)贊控件,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-11-11
Android實(shí)現(xiàn)列表時(shí)間軸
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)列表時(shí)間軸效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-05-05
Android編程開(kāi)發(fā)ScrollView中ViewPager無(wú)法正常滑動(dòng)問(wèn)題解決方法
這篇文章主要介紹了Android編程開(kāi)發(fā)ScrollView中ViewPager無(wú)法正?;瑒?dòng)問(wèn)題解決方法,以實(shí)例形式分析了ScrollView中滑動(dòng)失效的原因及解決方法,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-10-10
Android ADT和SDK Manager無(wú)法更新下載解決方案
這篇文章主要介紹了Android ADT和SDK Manager無(wú)法更新下載解決方案的相關(guān)資料,需要的朋友可以參考下2017-04-04
Android SD卡上文件操作及記錄日志操作實(shí)例分析
這篇文章主要介紹了Android SD卡上文件操作及記錄日志操作的方法,涉及Android針對(duì)SD卡與文件操作的相關(guān)技巧,需要的朋友可以參考下2016-01-01
實(shí)例講解Android中的View類(lèi)以及自定義View控件的方法
這篇文章主要介紹了Android中的View類(lèi)以及自定義View控件的方法,講解了如何繼承View類(lèi)并且展示了一個(gè)對(duì)View進(jìn)行重繪的例子,需要的朋友可以參考下2016-04-04
Kotlin協(xié)程Dispatchers原理示例詳解
這篇文章主要為大家介紹了Kotlin協(xié)程Dispatchers原理示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08

