Android窗口機(jī)制系列
Android窗口機(jī)制(一)初識(shí)Android的窗口結(jié)構(gòu)
Android窗口機(jī)制(二)Window,PhoneWindow,DecorView,setContentView源碼理解
Android窗口機(jī)制(三)Window和WindowManager的創(chuàng)建與Activity
Android窗口機(jī)制(四)ViewRootImpl與View和WindowManager
Android窗口機(jī)制(五)最終章:WindowManager.LayoutParams和Token以及其他窗口Dialog,Toast
在前篇第(三)文章中,我們講到了在DecorView在handleResumeActivity方法中被綁定到了WindowManager,也就是調(diào)用了windowManager.addView(decorView)。而WindowManager的實(shí)現(xiàn)類是WindowManagerImpl,而它則是通過WindowManagerGlobal代理實(shí)現(xiàn)addView的,我們看下addView的方法
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
...
ViewRootImpl root;
View panelParentView = null;
...
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
//ViewRootImpl開始繪制view
root.setView(view, wparams, panelParentView);
...
}
可以看到在WindowManagerGlobal的addView中,最后是調(diào)用了ViewRootImpl的setView方法,那么這個(gè)ViewRootImpl到底是什么。
ViewRootImpl
看到ViewRootImpl想到可能會(huì)有ViewRoot類,但是看了源碼才知道,ViewRoot類在Android2.2之后就被ViewRootImpl替換了。我們看下說明
/* The top of a view hierarchy, implementing the needed protocol between View
* and the WindowManager. This is for the most part an internal implementation
* detail of {@link WindowManagerGlobal}.
*/
ViewRootImpl是一個(gè)視圖層次結(jié)構(gòu)的頂部,它實(shí)現(xiàn)了View與WindowManager之間所需要的協(xié)議,作為WindowManagerGlobal中大部分的內(nèi)部實(shí)現(xiàn)。這個(gè)好理解,在WindowManagerGlobal中實(shí)現(xiàn)方法中,都可以見到ViewRootImpl,也就說WindowManagerGlobal方法最后還是調(diào)用到了ViewRootImpl。addView,removeView,update調(diào)用順序
WindowManagerImpl -> WindowManagerGlobal -> ViewRootImpl
我們看下前面調(diào)用到了viewRootImpl的setView方法
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
...
// Schedule the first layout -before- adding to the window
// manager, to make sure we do the relayout before receiving
// any other events from the system.
requestLayout();
...
try {
...
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);
}
}
在setView方法中,
首先會(huì)調(diào)用到requestLayout(),表示添加Window之前先完成第一次layout布局過程,以確保在收到任何系統(tǒng)事件后面重新布局。requestLayout最終會(huì)調(diào)用performTraversals方法來完成View的繪制。
接著會(huì)通過WindowSession最終來完成Window的添加過程。在下面的代碼中mWindowSession類型是IWindowSession,它是一個(gè)Binder對(duì)象,真正的實(shí)現(xiàn)類是Session,也就是說這其實(shí)是一次IPC過程,遠(yuǎn)程調(diào)用了Session中的addToDisPlay方法。
@Override
public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets,
Rect outOutsets, InputChannel outInputChannel) {
return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
outContentInsets, outStableInsets, outOutsets, outInputChannel);
}
這里的mService就是WindowManagerService,也就是說Window的添加請(qǐng)求,最終是通過WindowManagerService來添加的。
View通過ViewRootImpl來繪制
前面說到,ViewRootImpl調(diào)用到requestLayout()來完成View的繪制操作,我們看下源碼
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
View繪制,先判斷當(dāng)前線程
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
如果不是當(dāng)前線程則拋出異常,這個(gè)異常是不是感覺很熟悉啊。沒錯(cuò),當(dāng)你在子線程更新UI沒使用handler的話就會(huì)拋出這個(gè)異常
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
拋出地方就是這里,一般在子線程操作UI都會(huì)調(diào)用到view.invalidate,而View的重繪會(huì)觸發(fā)ViewRootImpl的requestLayout,就會(huì)去判斷當(dāng)前線程。
接著看,判斷完線程后,接著調(diào)用scheduleTraversals()
void scheduleTraversals() {
if (!mTraversalScheduled) {
...
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
...
}
}
scheduleTraversals中會(huì)通過handler去異步調(diào)用mTraversalRunnable接口
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
接著
void doTraversal() {
...
performTraversals();
...
}
可以看到,最后真正調(diào)用繪制的是performTraversals()方法,這個(gè)方法很長(zhǎng)核心便是
private void performTraversals() {
......
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
......
performDraw();
}
......
}
而這個(gè)方法各自最終調(diào)用到的便是
......
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
....
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
......
mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
......
mView.draw(canvas);
會(huì)開始觸發(fā)測(cè)量繪制。
performTraversals方法會(huì)經(jīng)過measure、layout和draw三個(gè)過程才能將一個(gè)View繪制出來,所以View的繪制是ViewRootImpl完成的,另外當(dāng)手動(dòng)調(diào)用invalidate,postInvalidate,requestInvalidate也會(huì)最終調(diào)用performTraversals,來重新繪制View。
View與WindowManager聯(lián)系
那么View和WindowManager之間是怎么通過ViewRootImpl聯(lián)系的呢。
從第三篇文章中我們知道,WindowManager是繼承于ViewManager接口的,而ViewManager提供了添加View,刪除View,更新View的方法。就拿setContentView來說,當(dāng)Activity的onCreate調(diào)用到了setContentView后,view就會(huì)被繪制了嗎?肯定不是,setContentView只是把需要添加的View的結(jié)構(gòu)添加保存在DecorView中。此時(shí)的DecorView還并沒有被繪制(沒有觸發(fā)view.measure,layout,draw)。
DecorView真正的繪制顯示是在activity.handleResumeActivity方法中DecorView被添加到WindowManager時(shí)候,也就是調(diào)用到windowManager.addView(decorView)。而在windowManager.addView方法中調(diào)用到windowManagerGlobal.addView,開始創(chuàng)建初始化ViewRootImpl,再調(diào)用到viewRootImpl.setView,最后是調(diào)用到viewRootImpl的performTraversals來進(jìn)行view的繪制(measure,layout,draw),這個(gè)時(shí)候View才真正被繪制出來。
這也就是為什么我們?cè)趏nCreate方法中調(diào)用view.getMeasureHeight() = 0的原因,我們知道activity.handleResumeActivity最后調(diào)用到的是activity的onResume方法,但是按上面所說在onResume方法中調(diào)用就可以得到了嗎,答案肯定是否定的,因?yàn)閂iewRootImpl繪制View并非是同步的,而是異步(Handler)。
難道就沒有得監(jiān)聽了嗎?相信大家以前獲取使用的大多是
view.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
// TODO Auto-generated method stub
}
});
沒錯(cuò),的確是這個(gè),為什么呢,因?yàn)樵趘iewRootImpl的performTraversals的繪制最后,調(diào)用了
{
if (triggerGlobalLayoutListener) {
mAttachInfo.mRecomputeGlobalAttributes = false;
mAttachInfo.mTreeObserver.dispatchOnGlobalLayout();
}
...
performDraw();
}
dispatchOnGlobalLayout會(huì)觸發(fā)OnGlobalLayoutListener的onGlobalLayout()函數(shù)回調(diào)
但此時(shí)View并還沒有繪制顯示出來,只是先調(diào)用了measure和layout,但也可以得到它的寬高了。
另外,前面說到,ViewRootImpl在調(diào)用requestLayout準(zhǔn)備繪制View的時(shí)候會(huì)先判斷線程,這里我們前面分析了,但也只是分析了一點(diǎn)。
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
為什么這么說呢?
先看Activity下這段代碼
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv = (TextView) findViewById(R.id.tv);
new Thread(new Runnable() {
@Override
public void run() {
tv.setText("Hohohong Test");
}
}).start();
}
我是在onCreate里面的子線程去更新UI的,那么會(huì)報(bào)錯(cuò)嗎?測(cè)試后你就會(huì)知道不會(huì)報(bào)錯(cuò),如果你放置個(gè)Button點(diǎn)擊再去調(diào)用的話則會(huì)彈出報(bào)錯(cuò)。為什么會(huì)這樣?
答案就是跟ViewRootImpl的初始化有關(guān),因?yàn)樵趏nCreate的時(shí)候此時(shí)View還沒被繪制出來,ViewRootImpl還未創(chuàng)建出來,它的創(chuàng)建是在activity.handleResumeActivity的調(diào)用到windowManager.addView(decorView)時(shí)候,如前面說的ViewRootImpl才被創(chuàng)建起來
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
...
ViewRootImpl root;
...
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
//ViewRootImpl保存在一個(gè)集合List中
mRoots.add(root);
mParams.add(wparams);
//ViewRootImpl開始繪制view
root.setView(view, wparams, panelParentView);
...
}
此時(shí)創(chuàng)建完才會(huì)去判斷線程。是不是有種讓你豁然開朗的感覺!
View與ViewRootImpl的綁定
另外View和ViewRootImpl是怎么綁定在一起的呢?通過view.getViewRootImpl可以獲取到ViewRootImpl。
public ViewRootImpl getViewRootImpl() {
if (mAttachInfo != null) {
return mAttachInfo.mViewRootImpl;
}
return null;
}
而這個(gè)AttachInfo則是View里面一個(gè)靜態(tài)內(nèi)部類,它的構(gòu)造方法
AttachInfo(IWindowSession session, IWindow window, Display display,
ViewRootImpl viewRootImpl, Handler handler, Callbacks effectPlayer) {
mSession = session;
mWindow = window;
mWindowToken = window.asBinder();
mDisplay = display;
mViewRootImpl = viewRootImpl;
mHandler = handler;
mRootCallbacks = effectPlayer;
}
可以看到viewRootImpl在它的構(gòu)造方法里賦值了,那么這個(gè)方法肯定是在ViewRootImpl創(chuàng)建時(shí)創(chuàng)建的,而ViewRootImpl的創(chuàng)建是在調(diào)用WindowManagerGlobal.addView的時(shí)候
root = new ViewRootImpl(view.getContext(), display);
而構(gòu)造方法中
public ViewRootImpl(Context context, Display display) {
mContext = context;
mWindowSession = WindowManagerGlobal.getWindowSession();
...
mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this);
...
}
可以看到View與ViewRootImpl綁定一起了。
之后就可以通過view.getViewRootImpl獲取到,而在Window里面也可以獲取到ViewRootImpl,因?yàn)閃indow里面有DecorView(這里說的Window都是講它的實(shí)現(xiàn)類PhoneWindo),前三篇已經(jīng)介紹過了,通過DecorView來獲取到ViewRootImpl
private ViewRootImpl getViewRootImpl() {
if (mDecor != null) {
ViewRootImpl viewRootImpl = mDecor.getViewRootImpl();
if (viewRootImpl != null) {
return viewRootImpl;
}
}
throw new IllegalStateException("view not added");
}
另外,一個(gè)View會(huì)對(duì)應(yīng)一個(gè)ViewRootImpl嗎?我們做個(gè)測(cè)試,在一個(gè)布局中打印兩個(gè)不同控件的ViewRootImpl的內(nèi)存地址
Log.e(TAG, "getViewRootImpl: textView: " + tv.getViewRootImpl() );
Log.e(TAG, "getViewRootImpl: button: " + btn.getViewRootImpl() );
結(jié)果
可以看到,都是同一個(gè)對(duì)象,共用一個(gè)ViewRootImpl。
小結(jié)
- 之所以說ViewRoot是View和WindowManager的橋梁,是因?yàn)樵谡嬲倏乩L制View的是ViewRootImpl,View通過WindowManager來轉(zhuǎn)接調(diào)用ViewRootImpl
- 在ViewRootImpl未初始化創(chuàng)建的時(shí)候是可以進(jìn)行子線程更新UI的,而它創(chuàng)建是在activity.handleResumeActivity方法調(diào)用,即DecorView被添加到WindowManager的時(shí)候
- ViewRootImpl繪制View的時(shí)候會(huì)先檢查當(dāng)前線程是否是主線程,是才能繼續(xù)繪制下去
ViewRootImpl的功能可不只是繪制,它還有事件分發(fā)的功能,想要了解的深入的話可以看下
ViewRootImpl源碼分析事件分發(fā)
下篇文章將介紹Dialog,PopWindow,Toast這些窗口機(jī)制