源码分析(setContentView()到底做了什么,布局绘制是在Activity的哪个周期)

很久之前的一边文章介绍了 Android系统的启动路程 以及APP的启动流程 ,这篇文章将介绍APP启动后到显示在界面这一过程中 又做了些是什么,在上一篇文章中介绍到APP启动之后首先会执行onCreate()方法,在Oncreate()当中去调用setContentView() 将我们的布局传入到参数中,下面我们就从setContentView 方法开始分析.

1. setContentView()

调用这个方法是,super到了Activity.java中, 可看到直接调用了PhoneWindow 的setContentView方法

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

在PhoneWindow 中的setContentView中 首先创建根布局 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.创建DecorView

首先会执行installDecor()方法里的generateDecir去创建DecorView ,可以看到是直接new 一个DecorView 对象,并将当前Window 通过构造函数传入

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

2. 设置Them 以及创建ContentView

接下来 又调用了generateLayout()方法去设置主题以及 布局的rootView ,在下面可以看到 首先会将根据主题配置 获取到对应的layoutResource 资源引用,调用mDecor.onResourcesLoaded(mLayoutInflater, layoutResource); 将布局文件绑定到 DecorView 之上,再通过ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);获取到内容布局返回到上一级,到这里 我们就创建好了页面需要的根布局,下一步就是讲我们自己的布局绑定到 系统根布局上.

    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 自己的布局

在调用setContentView时传入了自己的布局文件,下一步就需要将 这个布局文件进行绑定,执行mLayoutInflater
inflate 方法,去解析布局文件 得到不想对应的属性信息,追踪带LayoutInflate.java中 可以看到面对我们传入的参数进行了 xml 解析,获取到属性以及控件信息,通过Factory 的 onCreateVeiw 方法去创建这个对象.这里我们不在带大家了解Inflate的源码,后续文章再做详解.

 @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. 总结

到这里我们就分析完了Activity 的生命周期,可以知道 自己的布局并没有在OnCreate 方法中绘制到页面上,只是 对页面的布局文件进行了解析.所以接着往下看ActivityThread的源码,也就是追踪下一个生命周期.

2. handleResumeActivity

接着上篇文章讲解, ActivityThread 在调用onCreate 回调之后会执行 handleResumeActivity 通过源码分析可以知道 在这里,执行了 另外的周期方法 onStart() onResume(),并且windowManager 会将根布局DecorView 添加到Phonewindow 上,


    @Override
    public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
            String reason) {
            ......................
        // 周期回调
        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中调用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()之后进行绘制的

    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的错误就是在这里报出的,接着调用了scheduleTraversals()方法 ,将回执信号发送到了 Choreographer的postCallback()中 并且传入了一个runnable 对象,这个runnable 会在之后的操作流程中,执行里面的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 在主线程时,调用到了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的实现类FrameDisplayEventReceiver 对象去调用native代码,去做16ms 间隔刷新处理, 16毫秒之后又会回调会java 层面,做信号分发, 调用到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 {
        //发送vsync 信号
            nativeScheduleVsync(mReceiverPtr);
        }
    }
    
     // Called from native code.
    @SuppressWarnings("unused")
    private void dispatchVsync(long timestampNanos, int builtInDisplayId, int frame) {
    //16毫秒之后回调回java层
        onVsync(timestampNanos, builtInDisplayId, frame);
    }

在实现类FrameDisplayEventReceiver中做信号分发,这里使用了handler ,在handler 中 调用doFrame(System.nanoTime(), 0); 然后是doCallbacks()这里面直接将ViewRootImpl 中传入的Runnable 获取到去调用run方法 ,这里需要注意下 信号同步是用handler 来通知的如果主线程的任务繁重 这个信号就会延迟,会使页面得不到及时重绘,出现页面卡顿问题,所以这里就解释了页面卡顿的原因 ,当然引起拉顿的原因不仅仅只有这个.

 @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();
        //这个方法响应 信号同步
        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));
            }
            //回调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的绘制分发.

doTraversal()--> performTraversals() 这个方法里就是我们熟悉的 绘制的三大方法 以及关联suface 的部分

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


//下面这三个方法  会使用我们开始传入的mView 也就是DecorView 来调用内部的测量 排列 绘制方法,之后及时View绘制流程了 这里不再赘述这一部分
public void  performTraversals(){
    ......................
   performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    .......................
     performLayout(lp, mWidth, mHeight);
     .......................
      performDraw();
    
}


performMeasure()--->mView.measure()
performLayout()--> host.layout()
performDraw()-->draw()-->drawSoftware()
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,525评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,203评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,862评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,728评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,743评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,590评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,330评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,244评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,693评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,885评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,001评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,723评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,343评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,919评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,042评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,191评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,955评论 2 355

推荐阅读更多精彩内容