源碼分析(setContentView()到底做了什么,布局繪制是在Activity的哪個周期)

很久之前的一邊文章介紹了 Android系統(tǒng)的啟動路程 以及APP的啟動流程 ,這篇文章將介紹APP啟動后到顯示在界面這一過程中 又做了些是什么,在上一篇文章中介紹到APP啟動之后首先會執(zhí)行onCreate()方法,在Oncreate()當(dāng)中去調(diào)用setContentView() 將我們的布局傳入到參數(shù)中,下面我們就從setContentView 方法開始分析.

1. setContentView()

調(diào)用這個方法是,super到了Activity.java中, 可看到直接調(diào)用了PhoneWindow 的setContentView方法

  public void setContentView(View view) {
        getWindow().setContentView(view);
        initWindowDecorActionBar();
    }

在PhoneWindow 中的setContentView中 首先創(chuàng)建根布局 DecorView 再將我們的布局文件引用inflate到 decorView上

 @Override
    public void setContentView(int layoutResID) {
        // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
        // decor, when theme attributes and the like are crystalized. Do not check the feature
        // before this happens.
        if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

1.創(chuàng)建DecorView

首先會執(zhí)行installDecor()方法里的generateDecir去創(chuàng)建DecorView ,可以看到是直接new 一個DecorView 對象,并將當(dāng)前Window 通過構(gòu)造函數(shù)傳入

protected Deco   rView generateDecor(int featureId) {
        ...................... 
        return new DecorView(context, featureId, this, getAttributes());
    }

2. 設(shè)置Them 以及創(chuàng)建ContentView

接下來 又調(diào)用了generateLayout()方法去設(shè)置主題以及 布局的rootView ,在下面可以看到 首先會將根據(jù)主題配置 獲取到對應(yīng)的layoutResource 資源引用,調(diào)用mDecor.onResourcesLoaded(mLayoutInflater, layoutResource); 將布局文件綁定到 DecorView 之上,再通過ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);獲取到內(nèi)容布局返回到上一級,到這里 我們就創(chuàng)建好了頁面需要的根布局,下一步就是講我們自己的布局綁定到 系統(tǒng)根布局上.

    protected ViewGroup generateLayout(DecorView decor) {

        ............. 
        
        // Inflate the window decor.

        int layoutResource;
        ..........
        
        mDecor.startChanging();
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);

        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        .............
        return contentParent;
    }

3. inflate 自己的布局

在調(diào)用setContentView時傳入了自己的布局文件,下一步就需要將 這個布局文件進(jìn)行綁定,執(zhí)行mLayoutInflater
inflate 方法,去解析布局文件 得到不想對應(yīng)的屬性信息,追蹤帶LayoutInflate.java中 可以看到面對我們傳入的參數(shù)進(jìn)行了 xml 解析,獲取到屬性以及控件信息,通過Factory 的 onCreateVeiw 方法去創(chuàng)建這個對象.這里我們不在帶大家了解Inflate的源碼,后續(xù)文章再做詳解.

 @Override
    public void setContentView(int layoutResID) {
       ................
        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
      ............
    }

4. 總結(jié)

到這里我們就分析完了Activity 的生命周期,可以知道 自己的布局并沒有在OnCreate 方法中繪制到頁面上,只是 對頁面的布局文件進(jìn)行了解析.所以接著往下看ActivityThread的源碼,也就是追蹤下一個生命周期.

2. handleResumeActivity

接著上篇文章講解, ActivityThread 在調(diào)用onCreate 回調(diào)之后會執(zhí)行 handleResumeActivity 通過源碼分析可以知道 在這里,執(zhí)行了 另外的周期方法 onStart() onResume(),并且windowManager 會將根布局DecorView 添加到Phonewindow 上,


    @Override
    public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
            String reason) {
            ......................
        // 周期回調(diào)
        final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
       
        final Activity a = r.activity;
            .......................
            
        if (r.window == null && !a.mFinished && willBeVisible) {
        //獲取window  decorview 
            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;
                    //將根布局添加到WindowManagerImpl上
                    wm.addView(decor, l);
                } else {
                    a.onWindowAttributesChanged(l);
                }
            }

            // If the window has already been added, but during resume
            // we started another activity, then don't yet make the
            // window visible.
        } else if (!willBeVisible) {
            if (localLOGV) Slog.v(TAG, "Launch " + r + " mStartedActivity set");
            r.hideForNow = true;
        }
            ...................
    }

1. WindowManagerImpl

上面可以看到 我們的decorView 添加到了WindowManagerImpl中 ,接著會在WindowManagerGlobal中調(diào)用AddView()方法,這里記錄了傳入的view 和ViewRootImpl 所以在這里就記錄了整個APP的根布局, 再之后會 走到和ViewRootImpl里的setView 方法


private void addView(...){
     root = new ViewRootImpl(view.getContext(), display);
        .......................
            view.setLayoutParams(wparams);

            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);

            // do this last because it fires off messages to start doing things
            try {
                root.setView(view, wparams, panelParentView);
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
        }
        
        }

2.ViewRootImpl

這個方法里就看到了我們熟悉的requestLayout();方法,所以頁面繪制是從這里開始的 是在onresume()之后進(jìn)行繪制的

    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    ................
        //全局賦值mView 
            mView = view;
            ...
        //頁面繪制的起點
            requestLayout();
            ...
            try {
                mOrigWindowType = mWindowAttributes.type;
                mAttachInfo.mRecomputeGlobalAttributes = true;
                collectViewAttributes();
            //通過WindowSession將window添加到屏幕
                res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                        getHostVisibility(), mDisplay.getDisplayId(),
                        mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                        mAttachInfo.mOutsets, mInputChannel);
            } 
     ......................
}

requestLayout()中先做了線程判斷,子線程不能更新UI的錯誤就是在這里報出的,接著調(diào)用了scheduleTraversals()方法 ,將回執(zhí)信號發(fā)送到了 Choreographer的postCallback()中 并且傳入了一個runnable 對象,這個runnable 會在之后的操作流程中,執(zhí)行里面的run方法

 @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
        //線程檢測
            checkThread();
            mLayoutRequested = true;
            //繪制操作
            scheduleTraversals();
        }
    }
    
      void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

3.Choreographer

postCallback()-->postCallbackDelayed()-->postCallbackDelayedInternal()-->scheduleFrameLocked()


    private void postCallbackDelayedInternal(int callbackType,
            Object action, Object token, long delayMillis) {
        if (DEBUG_FRAMES) {
            Log.d(TAG, "PostCallback: type=" + callbackType
                    + ", action=" + action + ", token=" + token
                    + ", delayMillis=" + delayMillis);
        }

        synchronized (mLock) {
            final long now = SystemClock.uptimeMillis();
            final long dueTime = now + delayMillis;
            mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
            //delayMillis 傳入的值為0  所以條件判斷為true
            if (dueTime <= now) {
                scheduleFrameLocked(now);
            } else {
                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
                msg.arg1 = callbackType;
                msg.setAsynchronous(true);
                mHandler.sendMessageAtTime(msg, dueTime);
            }
        }
    }

這里USE_VSYNC 為true 在主線程時,調(diào)用到了scheduleVsyncLocked(),


    private void scheduleFrameLocked(long now) {
        if (!mFrameScheduled) {
            mFrameScheduled = true;
            if (USE_VSYNC) {
                if (DEBUG_FRAMES) {
                    Log.d(TAG, "Scheduling next frame on vsync.");
                }

                // If running on the Looper thread, then schedule the vsync immediately,
                // otherwise post a message to schedule the vsync from the UI thread
                // as soon as possible.
                if (isRunningOnLooperThreadLocked()) {
                    scheduleVsyncLocked();
                } else {
                    Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
                    msg.setAsynchronous(true);
                    mHandler.sendMessageAtFrontOfQueue(msg);
                }
            } else {
                final long nextFrameTime = Math.max(
                        mLastFrameTimeNanos / TimeUtils.NANOS_PER_MS + sFrameDelay, now);
                if (DEBUG_FRAMES) {
                    Log.d(TAG, "Scheduling next frame in " + (nextFrameTime - now) + " ms.");
                }
                Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
                msg.setAsynchronous(true);
                mHandler.sendMessageAtTime(msg, nextFrameTime);
            }
        }
    }


DisplayEventReceiverd的實現(xiàn)類FrameDisplayEventReceiver 對象去調(diào)用native代碼,去做16ms 間隔刷新處理, 16毫秒之后又會回調(diào)會java 層面,做信號分發(fā), 調(diào)用到dispatchVsync()里的onVsync()方法

 private void scheduleVsyncLocked() {
        mDisplayEventReceiver.scheduleVsync();
    }
     public void scheduleVsync() {
        if (mReceiverPtr == 0) {
            Log.w(TAG, "Attempted to schedule a vertical sync pulse but the display event "
                    + "receiver has already been disposed.");
        } else {
        //發(fā)送vsync 信號
            nativeScheduleVsync(mReceiverPtr);
        }
    }
    
     // Called from native code.
    @SuppressWarnings("unused")
    private void dispatchVsync(long timestampNanos, int builtInDisplayId, int frame) {
    //16毫秒之后回調(diào)回java層
        onVsync(timestampNanos, builtInDisplayId, frame);
    }

在實現(xiàn)類FrameDisplayEventReceiver中做信號分發(fā),這里使用了handler ,在handler 中 調(diào)用doFrame(System.nanoTime(), 0); 然后是doCallbacks()這里面直接將ViewRootImpl 中傳入的Runnable 獲取到去調(diào)用run方法 ,這里需要注意下 信號同步是用handler 來通知的如果主線程的任務(wù)繁重 這個信號就會延遲,會使頁面得不到及時重繪,出現(xiàn)頁面卡頓問題,所以這里就解釋了頁面卡頓的原因 ,當(dāng)然引起拉頓的原因不僅僅只有這個.

 @Override
        public 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);
        }
```\


void doFrame(long frameTimeNanos, int frame) {
   ...........................
    try {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame");
        AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);

        mFrameInfo.markInputHandlingStart();
        doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);

        mFrameInfo.markAnimationsStart();
        doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);

        mFrameInfo.markPerformTraversalsStart();
        //這個方法響應(yīng) 信號同步
        doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);

        doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
    } finally {
        AnimationUtils.unlockAnimationClock();
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }

    if (DEBUG_FRAMES) {
        final long endNanos = System.nanoTime();
        Log.d(TAG, "Frame " + frame + ": Finished, took "
                + (endNanos - startNanos) * 0.000001f + " ms, latency "
                + (startNanos - frameTimeNanos) * 0.000001f + " ms.");
    }
}

void doCallbacks(int callbackType, long frameTimeNanos) {
    CallbackRecord callbacks;
    synchronized (mLock) {
    
        final long now = System.nanoTime();
        callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
                now / TimeUtils.NANOS_PER_MS);
        if (callbacks == null) {
            return;
        }
        mCallbacksRunning = true;
        ....................
    }
    try {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, CALLBACK_TRACE_TITLES[callbackType]);
        for (CallbackRecord c = callbacks; c != null; c = c.next) {
            if (DEBUG_FRAMES) {
                Log.d(TAG, "RunCallback: type=" + callbackType
                        + ", action=" + c.action + ", token=" + c.token
                        + ", latencyMillis=" + (SystemClock.uptimeMillis() - c.dueTime));
            }
            //回調(diào)run方法
            c.run(frameTimeNanos);
        }
    } finally {
        synchronized (mLock) {
            mCallbacksRunning = false;
            do {
                final CallbackRecord next = callbacks.next;
                recycleCallbackLocked(callbacks);
                callbacks = next;
            } while (callbacks != null);
        }
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
}

### 4. ViewRootImpl
上面會返回ViewRootImpl 中開始做頁面的繪制,也就是View的繪制分發(fā).

doTraversal()--> performTraversals() 這個方法里就是我們熟悉的 繪制的三大方法 以及關(guān)聯(lián)suface 的部分

final class TraversalRunnable implements Runnable {
    @Override
    public void run() {
        doTraversal();
    }
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();


//下面這三個方法  會使用我們開始傳入的mView 也就是DecorView 來調(diào)用內(nèi)部的測量 排列 繪制方法,之后及時View繪制流程了 這里不再贅述這一部分
public void  performTraversals(){
    ......................
   performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    .......................
     performLayout(lp, mWidth, mHeight);
     .......................
      performDraw();
    
}


performMeasure()--->mView.measure()
performLayout()--> host.layout()
performDraw()-->draw()-->drawSoftware()
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容