Android—Window、WindowManage、屏幕绘制及刷新
Activity窗口層級:?
所以在onCreate方法體中setContentView方法都是設置DecorView的ContentView。?
Window、PhoneWindow、DecorView的關系:
public abstract class Window {...@Nullablepublic View findViewById(@IdRes int id) {return getDecorView().findViewById(id);}public abstract void setContentView(@LayoutRes int layoutResID);... }Window是一個抽象基類,它提供了一系列窗口的方法,比如設置背景,標題等等,而它的唯一實現類則是PhoneWindow
public class PhoneWindow extends Window implements MenuBuilder.Callback {private final static String TAG = "PhoneWindow";...// This is the top-level view of the window, containing the window decor.private DecorView mDecor;// 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.private ViewGroup mContentParent;private ViewGroup mContentRoot;... }在PhoneWindow里面,出現了成員變量DecorView的而這里,DecorView則是PhoneWindow里面的一個內部類,它是繼承FrameLayout,所以DecorView是一個FrameLayout窗口。
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {/* package */int mDefaultOpacity = PixelFormat.OPAQUE;/** The feature ID of the panel, or -1 if this is the application's DecorView */private final int mFeatureId;private final Rect mDrawingBounds = new Rect();private final Rect mBackgroundPadding = new Rect();private final Rect mFramePadding = new Rect();private final Rect mFrameOffsets = new Rect();....}ViewManager和它的實現類:
public interface ViewManager {public void addView(View view, ViewGroup.LayoutParams params);public void updateViewLayout(View view, ViewGroup.LayoutParams params);public void removeView(View view); }ViewManager接口定義了add、update、remove方法操作View。
實現類:
ViewGroup
public abstract class ViewGroup extends View implements ViewParent, ViewManager {private static final String TAG = "ViewGroup";...public void addView(View child, LayoutParams params) {addView(child, -1, params);}/** @param child the child view to add* @param index the position at which to add the child or -1 to add last* @param params the layout parameters to set on the child*/public void addView(View child, int index, LayoutParams params) {// addViewInner() will call child.requestLayout() when setting the new LayoutParams// therefore, we call requestLayout() on ourselves before, so that the child's request// will be blocked at our levelrequestLayout();invalidate(true);addViewInner(child, index, params, false);}...ViewGroup里面實現了ViewManager接口,View通過ViewGroup的addView方法添加到ViewGroup中,而ViewGroup層層嵌套到最頂級都會顯在一個窗口Window中。
WindowManager
/* The interface that apps use to talk to the window manager. Use Context.getSystemService(Context.WINDOW_SERVICE) to get one of these. */ public interface WindowManager extends ViewManager {public static class BadTokenException extends RuntimeException{...}public static class InvalidDisplayException extends RuntimeException{...}public Display getDefaultDisplay();public void removeViewImmediate(View view);public static class LayoutParams extends ViewGroup.LayoutParamsimplements Parcelable可以看到WindowManager是一個接口,而且它繼承與ViewManager。WindowManager字面理解就是窗口管理器,每一個窗口管理器都與一個的窗口顯示綁定。獲取實例可以通過Context.getSystemService(Context.WINDOW_SERVICE)獲取。既然繼承了ViewManager,那么它也就可以進行添加刪除View的操作了,不過它的操作放在它的實現類WindowManagerImpl里面。
public final class WindowManagerImpl implements WindowManager {private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();private final Display mDisplay;private final Window mParentWindow;@Overridepublic void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {applyDefaultToken(params);mGlobal.addView(view, params, mDisplay, mParentWindow);}...@Overridepublic void removeView(View view) {mGlobal.removeView(view, false);} }WindowManagerImpl的方法實現又是調用了WindowManagerGlobal的方法。所以WindowManager最后方法實現是在WindowManagerGlobal里面。
一個WindowManager管理多個Window,那么DecorView與WM的綁定是在什么時候?
activity的啟動主要是ActivityThread.attach()方法,attach方法最后調用handleBindApplication(),通過mInstrumentation運行onCreate方法,之后調用onStart,onRestoreInstanceState。
private void handleBindApplication(AppBindData data) {//創建appContext final ContextImpl appContext = ContextImpl.createAppContext(this, data.info);try {// If the app is being launched for full backup or restore, bring it up in// a restricted environment with the base application class.//通過反射創建Applicationapp = data.info.makeApplication(data.restrictedBackupMode, null);mInitialApplication = app;try {//調用Application的onCreate方法mInstrumentation.callApplicationOnCreate(app);} catch (Exception e) {}} }跳過Activity的onCreate,onStart方法以及onRestoreInstanceState恢復數據方法,直接到handleResumeActivity方法就是onResume。
final void handleResumeActivity(IBinder token,boolean clearHide, boolean isForward, boolean reallyResume) {//調用activity.onResume,把activity數據記錄更新到ActivityClientRecordActivityClientRecord r = performResumeActivity(token, clearHide);.............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 (a.mVisibleFromClient) {a.mWindowAdded = true;//decorView添加進windowmanager,把當前的DecorView與WindowManager綁定一起wm.addView(decor, l);}.....}所以DecorView與WindowManager在onResume之后綁定的,這也解釋了在onCreate、onStart、onResume中不能獲取View寬高的原因。ViewRootImpl 還未創建,就不存在渲染操作,也就不存在View的測量步驟了。
addView其實是上面WindowManagerGlobal的方法。
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開始繪制viewroot.setView(view, wparams, panelParentView);...}ViewRootImpl,它代表了View樹的根,每個View 的刷新,繪制,點擊事件的分發其實都是由 ViewRootImpl 作為發起者的,由 ViewRootImpl 控制這些操作從 DecorView 開始遍歷 View 樹去分發處理。
我們看下前面調用到了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.//繪制viewrequestLayout(); ...//將DecorView與ViewRootImpl綁定。view.assignParent(this);}@Overridepublic void requestLayout() {if (!mHandlingLayoutInLayoutRequest) {checkThread();mLayoutRequested = true;//重點scheduleTraversals();}}void assignParent(ViewParent parent) {if (mParent == null) {mParent = parent;} else if (parent == null) {mParent = null;} else {throw new RuntimeException("view " + this + " being added, but"+ " it already has a parent");}}view.assignParent(this)將ViewRootImpl設為decorView的parent。
requestLayout里調用了scheduleTraversals()方法。這個方法是渲染屏幕的關鍵方法,由它來發起一次繪制View樹的任務請求,invalidate()和postInvalidate()里也調用了這個方法。?
ViewRootImpl 是實現了 ViewParent 接口的,所以assignParent方法就將 DecorView 和 ViewRootImpl 綁定起來了。每個Activity 的根布局都是 DecorView,而 DecorView 的 parent 又是 ViewRootImpl。
上面是ViewRootImpl的requestLayout方法,下面看下View的requestLayout方法。
public void requestLayout() {if (mMeasureCache != null) mMeasureCache.clear();if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {// Only trigger request-during-layout logic if this is the view requesting it,// not the views in its parent hierarchyViewRootImpl viewRoot = getViewRootImpl();if (viewRoot != null && viewRoot.isInLayout()) {if (!viewRoot.requestLayoutDuringLayout(this)) {return;}}mAttachInfo.mViewRequestingLayout = this;}//為當前view設置標記位 PFLAG_FORCE_LAYOUTmPrivateFlags |= PFLAG_FORCE_LAYOUT;mPrivateFlags |= PFLAG_INVALIDATED;if (mParent != null && !mParent.isLayoutRequested()) {//向父容器請求布局mParent.requestLayout();}if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {mAttachInfo.mViewRequestingLayout = null;} }?標記位的作用就是標記了當前的View是需要進行重新布局的,接著調用mParent.requestLayout方法,即調用父容器的requestLayout方法,而父容器又會調用它的父容器的requestLayout方法,即requestLayout事件層層向上傳遞,因為剛剛把decorView的父親設為ViewRootImpl,所以事件直到ViewRootImpl,也即是說子View的requestLayout事件,最終會被ViewRootImpl接收并得到處理。
流程圖:
屏幕刷新:16.6ms
Android系統中每隔16.6ms會發送一次VSYNC信號有可能會觸發UI的渲染。
一個典型的顯示系統中,一般包括CPU、GPU、display三個部分。
- CPU一般負責計算數據,把計算好數據交給GPU。
- GPU會對圖形數據進行渲染,渲染好后放到buffer里存起來。
- Display(屏幕或者顯示器)負責把buffer里的數據呈現到屏幕上。
在屏幕刷新中,Android系統引入了雙緩沖機制。GPU只向Back Buffer中寫入繪制數據,Dispaly只讀取Frame Buffer的數據。GPU會定期交換Back Buffer和Frame Buffer。交換的頻率也是16.6ms,這就與屏幕的刷新頻率保持了同步。
雙緩沖機制作用:避免GPU寫數據時,Display在讀取數據從而造成的屏幕閃爍。
上面我們已經看過了requestLayout方法,下面通過invalidate方法,分析刷新View的過程。
//View.class @UiThread public class View implements Drawable.Callback, KeyEvent.Callback,AccessibilityEventSource {... public void invalidate() {invalidate(true);}//invalidateCache為true表示全部重繪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);}...}} }invalidateInternal方法中通過調用View的父布局invalidateChild方法來請求重繪。View的父布局可能是ViewGroup或者是ViewRootImpl類。
public abstract class ViewGroup extends View implements ViewParent, ViewManager {@Overridepublic final void invalidateChild(View child, final Rect dirty) {ViewParent parent = this;final AttachInfo attachInfo = mAttachInfo;if (attachInfo != null) {...//這是一個從當前的布局View向上不斷遍歷當前布局View的父布局,最后遍歷到ViewRootImpl的循環do {View view = null;if (parent instanceof View) {view = (View) parent;}if (drawAnimation) {if (view != null) {view.mPrivateFlags |= PFLAG_DRAW_ANIMATION;} else if (parent instanceof ViewRootImpl) {((ViewRootImpl) parent).mIsAnimating = true;}}... //這里調用的是父布局的invalidateChildInParent方法parent = parent.invalidateChildInParent(location, dirty);} while (parent != null);}} }public final class ViewRootImpl implements ViewParent,View.AttachInfo.Callbacks, ThreadedRenderer.HardwareDrawCallbacks {@Overridepublic void invalidateChild(View child, Rect dirty) {invalidateChildInParent(null, dirty);} }所以如果View的父布局是ViewGroup,會進入一個do while循環,最終兩個都還是會到ViewRootImpl的invalidateChildInParent方法中。
public final class ViewRootImpl implements ViewParent,View.AttachInfo.Callbacks, ThreadedRenderer.HardwareDrawCallbacks {@Overridepublic ViewParent invalidateChildInParent(int[] location, Rect dirty) {checkThread();....invalidateRectOnScreen(dirty);return null;} private void invalidateRectOnScreen(Rect dirty) {final Rect localDirty = mDirty;...if (!mWillDrawSoon && (intersected || mIsAnimating)) {//調用scheduleTraversals方法進行繪制scheduleTraversals();}} }可以看到同requestLayout方法一樣,都是調用到了ViewRootImpl中的scheduleTraversals方法。
//ViewRootImpl.class void scheduleTraversals() {if (!mTraversalScheduled) {....//關鍵在這里!!!mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);if (!mUnbufferedInputDispatch) {scheduleConsumeBatchedInput();}....} }Choreoprapher類的作用是編排輸入事件、動畫事件和繪制事件的執行,它的postCallback方法的作用就是將要執行的事件放入內部的一個隊列中,最后會執行傳入的Runnable,這里也就是mTraversalRunnable。所以我們來看看mTraversalRunnable
//ViewRootImpl.class final class TraversalRunnable implements Runnable {@Overridepublic void run() {doTraversal();} } final TraversalRunnable mTraversalRunnable = new TraversalRunnable();void doTraversal() {if (mTraversalScheduled) {mTraversalScheduled = false;mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);....//找到我們的performTraversals方法來,這里就是開始繪制View的地方啦!performTraversals();....}}小結:當我們調用View的invalidate或requestLayout方法后,View會去不斷向上調用父布局的繪制方法并在這個過程中計算需要重繪的區域,最終調用過程會走到ViewRootImpl中,調用的是ViewRootImpl的performTraversals執行重繪操作。
performTraversals方法里面就是執行onMeasure,onLayout,onDraw三部曲了。不過有時可能只需要執行?performDraw()?繪制流程,有時可能只執行performMeasure() 測量和 performLayout()布局流程。
performTraversals() 是在 doTraversal() 中被調用的,而 doTraversal() 又被封裝到一個 Runnable 里,那么關鍵就是這個 Runnable 什么時候被執行了??
postCallback()最后會調用到一個native方法nativeScheduleVsync(mReceiverPtr),這個方法其實相當于向系統訂閱一個接受一個Vsync信號。android系統每過16.6ms會發送一個Vsync信號,由下面這個類來接收Vsync信號。
private final class FrameDisplayEventReceiver extends DisplayEventReceiverimplements Runnable {...@Overridepublic void onVsync(long timestampNanos, int builtInDisplayId, int frame) {...mTimestampNanos = timestampNanos;mFrame = frame;Message msg = Message.obtain(mHandler, this);msg.setAsynchronous(true);mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);}@Overridepublic void run() {mHavePendingVsync = false;doFrame(mTimestampNanos, mFrame);}}上面這個類中的onVsync()函數就是來回調Vsync信號,這個回調方法里面將這個類自己的run()方法傳給mHandler來進行處理,同時將這個消息設為異步消息。
void doFrame(long frameTimeNanos, int frame) {final long startNanos;synchronized (mLock) {...try {...// Choreographer.CALLBACK_TRAVERSAL這個參數和 mChoreographer.postCallback()里面傳入的一致doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);...}...}void doCallbacks(int callbackType, long frameTimeNanos) {CallbackRecord callbacks;synchronized (mLock) {...// 取出之前放入mCallbackQueues的mTraversalRunnablecallbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(now / TimeUtils.NANOS_PER_MS);...// 回調mTraversalRunnable的run函數for (CallbackRecord c = callbacks; c != null; c = c.next) {c.run(frameTimeNanos);}...}}所以當Vsync信號來時,調用doFrame方法運行performTraversals方法。
總結:
通過WindowManager將DecorView傳給ViewRootImpl的setView方法,在setView方法中的performTraversals()方法中分別對View進行measure, layout, draw,通過ViewGroup的類分別對子View依次進行測量,擺放和繪制。
requestlayout和invalidate的區別?
-
調用View的requestLayout會不斷回調直到ViewRootImpl調用它的requestLayout的會觸發onMeasure重新測量,并調用布局onLayout重新布局,不一定會調用onDraw,除非顯示不一樣了,調用順序是requestLayout ,然后invalidate。
-
view的invalidate會遞歸調用父view的invalidateChildInParent,直到ViewRootImpl的invalidateChildInParent,然后觸發peformTraversals,會導致當前view被重繪,不會導致onMeasure和onLayout被調用,只有OnDraw會被調用
-
postInvalidate是在非UI線程中調用,invalidate則是在UI線程中調用。
畫面造成丟幀大體上有兩類原因:
View刷新時機:
代碼里調用了某個 View 發起的刷新請求,這個重繪工作并不會馬上就開始,而是需要等到下一個屏幕刷新信號來的時候才開始。
而只有當界面有刷新的需要時,我們 app 才會在下一個屏幕刷新信號來時,遍歷繪制 View 樹來重新計算屏幕數據。如果界面沒有刷新的需要,一直保持不變時,我們 app 就不會去接收每隔 16.6ms 的屏幕刷新信號事件了,但底層仍然會以這個固定頻率來切換每一幀的畫面,只是后面這些幀的畫面都是相同的而已。
?
總結
以上是生活随笔為你收集整理的Android—Window、WindowManage、屏幕绘制及刷新的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: MPEG-4 AVC/H.264 信息
- 下一篇: 研究显示每天工作超8小时得心脏病概率增加