通過(guò)本文你可能會(huì)了解以下幾個(gè)方面
- 1.Activity視圖從準(zhǔn)備到繪制顯示的基本流程
- 2.視圖繪制和Activity生命周期的關(guān)系
- 3.子線程不能更新UI的原因和原理
- 4.invalidate和postInvalidate機(jī)制
- 5.ViewRootImpl和View的綁定流程
上篇文章中我們分析了一個(gè)Activity的視圖結(jié)構(gòu),但是直到setContentView執(zhí)行完畢,我們也僅僅是得到了一個(gè)布局,若不做任何處理,他是顯示出來(lái)的,今天我們就來(lái)介紹一個(gè)視圖的加載。首先說(shuō)一下setContentView之后視圖是在哪加載的。
這還要從Activity的創(chuàng)建流程開(kāi)始看,這里不多敘述,如果看我的的關(guān)于四大組件中Context的分析話應(yīng)該清楚,在初始化Activity的地方,也就是ActivityThread中的performLaunchActivity方法中,從這個(gè)方法向前看,也就是調(diào)用的地方,在handleLaunchActivity方法中:
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
....
Activity a = performLaunchActivity(r, customIntent);
if (a != null) {
....
handleResumeActivity(r.token, false, r.isForward,
!r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);
....
}
Activity初始化之后,就調(diào)用了handleResumeActivity方法:
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
....
r = performResumeActivity(token, clearHide, reason);
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
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) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
wm.addView(decor, l);
} else {
a.onWindowAttributesChanged(l);
}
}
}
......
if (r.activity.mVisibleFromClient) {
r.activity.makeVisible();
}
}
....
}
首先執(zhí)行了performResumeActivity,在這里面回調(diào)了onResume方法,也就是一個(gè)activity創(chuàng)建完成前的最后一個(gè)生命周期回調(diào)。下面先獲得了activity的DecorView,也就是一個(gè)完整的視圖,之后先把他的可見(jiàn)性設(shè)為隱藏。然后調(diào)用了wm.addView(decor, l);wm就是ViewManager 。如果你了解或者看過(guò)我的Toast源碼分析,應(yīng)該知道Toast也是通過(guò)這種方式顯示出來(lái)的。所以到這一步才能把一個(gè)activity的視圖顯示出來(lái),隨后調(diào)用了activity的makeVisible再把DecorView設(shè)為可見(jiàn):
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);
}
這里的mDecor在Activity自身沒(méi)有初始化的地方,只在handleResumeActivity方法中通過(guò)a.mDecor = decor;初始化。
這里才是把一個(gè)activity顯示出來(lái)。下面我么重點(diǎn)看一下wm.addView的實(shí)現(xiàn)。首先看一下wm 也就是ViewManager 來(lái)歷,來(lái)自于getWindowManager:
public WindowManager getWindowManager() {
return mWindowManager;
}
只是簡(jiǎn)單返回一下,再看他初始化的地方,在attach方法中通過(guò)mWindowManager = mWindow.getWindowManager();初始化。
final void attach(...) {
...
mWindow = new PhoneWindow(this, window, activityConfigCallback);
....
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
...
mWindowManager = mWindow.getWindowManager();
...
}
mWindow是一個(gè)PhoneWindow對(duì)象,所以去看看他的getWindowManager方法,發(fā)現(xiàn)并沒(méi)有實(shí)現(xiàn),所以用的是父類Window中的:
public WindowManager getWindowManager() {
return mWindowManager;
}
又是簡(jiǎn)單取值,所以還要看初始化的地方:
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
boolean hardwareAccelerated) {
mAppToken = appToken;
mAppName = appName;
mHardwareAccelerated = hardwareAccelerated
|| SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false);
if (wm == null) {
wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
}
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
這個(gè)setWindowManager其實(shí)是在activity中的attch方法中調(diào)用的,先傳入了一個(gè)WindowManager ,也就是獲取的系統(tǒng)服務(wù),然后調(diào)用了其內(nèi)部方法createLocalWindowManager,在setWindowManager中的類型轉(zhuǎn)換告訴了我們實(shí)際調(diào)用的是WindowManagerImpl:
android\frameworks\base\core\java\android\view\WindowManagerImpl.java
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
return new WindowManagerImpl(mContext, parentWindow);
}
發(fā)現(xiàn)實(shí)際上new了一個(gè)WindowManagerImpl對(duì)象。WindowManagerImpl實(shí)現(xiàn)了WindowManager接口。到這里基本上是跟蹤清楚了,上文中調(diào)用的addView實(shí)際上調(diào)用的WindowManagerImpl的addView方法:
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
android.util.SeempLog.record_vg_layout(383,params);
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
發(fā)現(xiàn)又調(diào)用了mGlobal的addview。mGlobal是WindowManagerGlobal 的單例,看他的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);
try {
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}
這個(gè)方法主要是給要添加的view設(shè)置LayoutParams,然后將View,ViewRootImpl和LayoutParams分別存在三個(gè)集合中,然后調(diào)用ViewRootImpl的setView方法:
android\frameworks\base\core\java\android\view\ViewRootImpl.java
這個(gè)方法比較長(zhǎng),但是重要的地方是調(diào)用了 requestLayout():
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
這里先檢查了線程,所謂子線程不能跟新UI的原因就在這,這個(gè)問(wèn)題在文末再具體講。緊接著調(diào)用了scheduleTraversals方法
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
重點(diǎn)在mTraversalRunnable:
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
這里執(zhí)行了performTraversals,這個(gè)方法是ViewRootImpl的核心。ViewRootImpl是具體更新View的管理類,所有關(guān)于View的更新操作都是在這里執(zhí)行的,performTraversals就是view繪制的起始,這個(gè)方法很長(zhǎng)也和很復(fù)雜,需要詳細(xì)了解的可以去看完整源碼。這里僅簡(jiǎn)單介紹一下整個(gè)流程:
- 1.預(yù)測(cè)量:對(duì)整個(gè)控件第一次策略,各個(gè)控件會(huì)上報(bào)各自期望的尺寸
- 2.窗口布局:根據(jù)預(yù)測(cè)量的結(jié)果對(duì)窗口進(jìn)行重新布局
- 3.最終測(cè)量:根據(jù)布局結(jié)果,進(jìn)行最后的測(cè)量,并回調(diào)view的measure
- 4.布局階段:根據(jù)最終測(cè)量結(jié)果進(jìn)行布局,確定控件位置,回調(diào)view的layout
- 5.繪制階段:最后回調(diào)onDraw將一個(gè)view畫(huà)出來(lái)
在performTraversals方法中,會(huì)先后執(zhí)行下面幾個(gè)方法:
private void performTraversals() {
......
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
......
if (triggerGlobalLayoutListener) {
mAttachInfo.mRecomputeGlobalAttributes = false;
mAttachInfo.mTreeObserver.dispatchOnGlobalLayout();
}
.....
performDraw();
}
......
}
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
if (mView == null) {
return;
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
在這里面調(diào)用了mView的measure方法,mView在setView中初始化,就是wm.addView中傳進(jìn)來(lái)的DecorView,但是DecorView并沒(méi)有measure的實(shí)現(xiàn),調(diào)用的是父類中的,這個(gè)方法中調(diào)用了onMeasure方法,在DecorView中又會(huì)調(diào)用父類也就是FrameLayout的onMeasure,在這里會(huì)遍歷所有子view,并調(diào)用其的measure,就這樣測(cè)量了整個(gè)視圖的view。
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
mLayoutRequested = false;
mScrollMayChange = true;
mInLayout = true;
final View host = mView;
if (host == null) {
return;
}
if (DEBUG_ORIENTATION || DEBUG_LAYOUT) {
Log.v(mTag, "Laying out " + host + " to (" +
host.getMeasuredWidth() + ", " + host.getMeasuredHeight() + ")");
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout");
try {
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
....
}
同樣調(diào)用了DecorView的layout,也是父類的layout,然后調(diào)用onLayout,在DecorView的onLayout中,先調(diào)用了FrameLayout的onLayout,F(xiàn)rameLayout中調(diào)用了layoutChildren來(lái)對(duì)每個(gè)子view進(jìn)行布局,也是調(diào)用子view的layout方法。
private void performDraw() {
...
draw(fullRedrawNeeded);
...
}
private void draw(boolean fullRedrawNeeded) {
Surface surface = mSurface;
....
drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)
....
}
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty) {
// Draw with software renderer.
final Canvas canvas;
try {
....
canvas = mSurface.lockCanvas(dirty);
....
mView.draw(canvas);
....
}
performDraw最后調(diào)用到了drawSoftware,先獲取了canvas,然后執(zhí)行了DecorView的draw,具體流程和上面兩點(diǎn)類似。
到這里就把DecorView繪制出來(lái)了,setView中在執(zhí)行完requestLayout之后,又調(diào)用了addToDisplay從而把整個(gè)視圖顯示出來(lái)。。
最后總結(jié)一下,一個(gè)Activity的視圖界面,在setContentView時(shí)被準(zhǔn)備出來(lái),但是還沒(méi)有繪制顯示(界面的測(cè)量布局繪制都沒(méi)有執(zhí)行),一直到生命周期中onResume中也沒(méi)有執(zhí)行,而是在ActivityThread的handleResumeActivity中,回調(diào)完onResume后,在wm.addView時(shí)才做了測(cè)量布局繪制,所以我們?cè)趏nCreate或者onResume都拿不到控件的某些信息,如getWidth。若要解決這個(gè)問(wèn)題,可以添加一個(gè)回調(diào),如view.getViewTreeObserver().addOnGlobalLayoutListener。這個(gè)回調(diào)在performTraversals中調(diào)起,他的位置在performMeasure及performLayout之后,在performDraw之前(見(jiàn)上文代碼片段),所以能獲得正常的數(shù)據(jù)。
文末再來(lái)說(shuō)一下文中遺留的問(wèn)題,就是子線程為什么不能更新UI。
首先我們?nèi)绻谧泳€程中更新UI,直觀的就會(huì)有這樣錯(cuò)誤:

其實(shí)到這里需要糾正一點(diǎn),出現(xiàn)這樣錯(cuò)誤并不能說(shuō)子線程不能更新UI,或者UI界面只能在主線程內(nèi)更新,好像主線程比子線程神圣很多似的。正如錯(cuò)誤信息中所說(shuō)的哪那樣,只能在視圖創(chuàng)建的那個(gè)線程中更新UI。
首先先看一下這個(gè)錯(cuò)誤的來(lái)歷,在ViewRootImpl的checkThread方法中,也就是requestLayout中執(zhí)行的第一個(gè)方法:
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
我們說(shuō)他檢測(cè)了線程,就是檢測(cè)當(dāng)前線程是不是創(chuàng)建該視圖的線程。mThread 在構(gòu)造中被初始化,賦值為構(gòu)造函數(shù)執(zhí)行時(shí)的那個(gè)線程。為什么更新UI時(shí)會(huì)報(bào)錯(cuò)呢?一般更新一個(gè)view時(shí)會(huì)調(diào)用invalidate方法,我們看一下流程:
public void invalidate() {
invalidate(true);
}
public void invalidate(boolean invalidateCache) {
invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
}
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
boolean fullInvalidate) {
...
final AttachInfo ai = mAttachInfo;
final ViewParent p = mParent;
if (p != null && ai != null && l < r && t < b) {
final Rect damage = ai.mTmpInvalRect;
damage.set(l, t, r, b);
p.invalidateChild(this, damage);
}
.........
}
}
ViewParent 是一個(gè)接口,ViewRootImpl就實(shí)現(xiàn)了這個(gè)接口,最后會(huì)調(diào)用到ViewRootImpl的invalidateChild方法:
@Override
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
checkThread();
....
}
可見(jiàn)這個(gè)方法第一步就在檢查線程。至于為什么要原始線程呢,還是和線程安全相關(guān)的.
為了打消大家的疑慮,我來(lái)給大家做一個(gè)關(guān)于在子線程創(chuàng)建視圖,而不能在主線程更新的例子:
public void click(View view) {
System.out.println(bt.getWidth());
if (f){
f = false;
new Thread(){
@Override
public void run() {
super.run();
Looper.prepare();
level = new Level(MainActivity.this);
WindowManager mWindowManager = (WindowManager) getApplicationContext()
.getSystemService(Context.WINDOW_SERVICE);
View mView = level;
final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
params.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
int flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
params.flags = flags;
params.format = PixelFormat.TRANSLUCENT;
params.width = WindowManager.LayoutParams.MATCH_PARENT;
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
params.gravity = Gravity.TOP;
mWindowManager.addView(mView, params);
level.setXY(10,10);
Looper.loop();
}
}.start();
}else{
level.setXY(20,20);
}
}
public void setXY(int x,int y){
rx = x;
ry = y;
invalidate();
}
就是創(chuàng)建一個(gè)懸浮窗(目前能想到的在子線程創(chuàng)建UI的只有這個(gè),activity界面都是在主線程創(chuàng)建的,我們干涉不了),第一次執(zhí)行click,啟動(dòng)一個(gè)子線程創(chuàng)建一個(gè)懸浮窗,第二字執(zhí)行click是調(diào)用level.setXY(20,20);,這個(gè)肯定是在主線程,但是報(bào)錯(cuò)了,還是一樣的錯(cuò)誤,就是只能在原始線程更新。同樣在子線程中,我也調(diào)用了setXY,是沒(méi)有問(wèn)題的。
這個(gè)例子大家可以自己嘗試驗(yàn)證,level類是我項(xiàng)目中一個(gè)自定義view,大家嘗試時(shí)隨意一個(gè)view都可以。
同樣大家肯定都做過(guò)在子線程彈出Toast,也進(jìn)一步驗(yàn)證了子線程也是可以執(zhí)行UI操作的,只不過(guò)要一定條件。另外如果一定要在子線程更新,可以調(diào)用postInvalidate,可以看一下他的實(shí)現(xiàn)還有和Invalidate的區(qū)別。
public void postInvalidate() {
postInvalidateDelayed(0);
}
public void postInvalidateDelayed(long delayMilliseconds) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
attachInfo.mViewRootImpl.dispatchInvalidateDelayed(this, delayMilliseconds);
}
}
這里最后調(diào)用了attachInfo.mViewRootImpl.dispatchInvalidateDelayed。attachInfo是View內(nèi)部類AttachInfo的一個(gè)對(duì)象,通過(guò)他將view和ViewRootImpl進(jìn)行了綁定,關(guān)于它的綁定我們下面再說(shuō),先看dispatchInvalidateDelayed:
public void dispatchInvalidateDelayed(View view, long delayMilliseconds) {
Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view);
mHandler.sendMessageDelayed(msg, delayMilliseconds);
}
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_INVALIDATE:
((View) msg.obj).invalidate();
break;
.....
}
看到了吧,同Handler機(jī)制,進(jìn)行了更新,mHandler隨著ViewRootImpl創(chuàng)建,所以是一個(gè)線程,間接在一個(gè)線程調(diào)用view的invalidate,所以沒(méi)問(wèn)題。
最后說(shuō)一下ViewRootImpl和View的綁定??梢?jiàn)View中有很多方法都直接用到了mViewRootImpl,那么在哪里綁定的呢?還是看AttachInfo這個(gè)View的內(nèi)部類,他在ViewRootImpl的構(gòu)造中初始化:
public ViewRootImpl(Context context, Display display) {
....
mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,
context);
...
}
這里AttachInfo持有了ViewRootImpl對(duì)象,隨后在setView中設(shè)置了根viewmAttachInfo.mRootView = view;,也就是DecorView。到這里viewmAttachInfo持有了ViewRootImpl和根view。然后在performTraversals方法中,調(diào)用了
host.dispatchAttachedToWindow(mAttachInfo, 0);
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
mAttachInfo = info;
....
}
就這樣DecorView獲得了AttachInfo 對(duì)象,這樣就可以獲得ViewRootImpl
public ViewRootImpl getViewRootImpl() {
if (mAttachInfo != null) {
return mAttachInfo.mViewRootImpl;
}
return null;
}
但具體某個(gè)view是如何和ViewRootImpl綁定的呢?不要忘了DecorView的所有孩子都是addView進(jìn)去的,看一下addview的實(shí)現(xiàn):
public void addView(View child) {
addView(child, -1);
}
public void addView(View child, int index) {
...
addView(child, index, params);
}
public void addView(View child, int index, LayoutParams params) {
...
addViewInner(child, index, params, false);
}
private void addViewInner(View child, int index, LayoutParams params,
boolean preventRequestLayout) {
...
AttachInfo ai = mAttachInfo;
if (ai != null && (mGroupFlags & FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW) == 0) {
boolean lastKeepOn = ai.mKeepScreenOn;
ai.mKeepScreenOn = false;
child.dispatchAttachedToWindow(mAttachInfo, (mViewFlags&VISIBILITY_MASK));
...
}
}
看到了吧,子view把父view的mAttachInfo綁定到自己身上的。