第五天(UI绘制的流程及原理)

每当我们在Activity或者Fragment里面setContentView 是怎么将一个布局或都一个View添加到窗口的呢.

当setContentView(R.layout.activity_main)的时候
会跳转到Window.setContentView这里,从代码里的注释可以看出
Window的唯一实现类是PhotoWindow
所以直接可以看PhotoWindow.setContentView方法
PhotoWindow.setContentView方法里面会对一个成员变量mContentParent进行判断
一般开始都是为null,而mContentParent是一个什么?
这里就直接给出答案,从后面源码可以看出,他就是一个id为com.android.internal.R.id.content

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


    public Window getWindow() {
        return mWindow;
    }
public class PhoneWindow extends Window implements MenuBuilder.Callback { 
   @Override
    public void setContentView(int layoutResID) {
        //  1
        if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }
    }
}

看注释1 第一次过来这个mContentParent必定为空,所以执行installDecor();

这里把installDecor()里面的核心代码贴一下

private void installDecor() {
    mForceDecorInstall = false;
    if (mDecor == null) {
        mDecor = generateDecor(-1);
        mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DE
        mDecor.setIsRootNamespace(true);
        if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFe
            mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
        }
    } else {
        mDecor.setWindow(this);
    }
    if (mContentParent == null) {
        mContentParent = generateLayout(mDecor);
}

上面代码可以看出,首先会通过generateDecor(-1)这个方法创建出DecorView mDecor,面DecorView又是一个Framlayout,然后再通过

mContentParent = generateLayout(mDecor);这个方法创建了mContentParent,

上面出现了二个方法一个是创建mDecor的generateDecor 还有个是创建mContentparent的generateLayout方法

先来看generateDecor(-1)这个方法

protected DecorView generateDecor(int featureId) {
    // activity.
    Context context;
    if (mUseDecorContext) {
        Context applicationContext = getContext().getApplicationContext();
        if (applicationContext == null) {
            context = getContext();
        } else {
            context = new DecorContext(applicationContext, getContext().getResources());
            if (mTheme != -1) {
                context.setTheme(mTheme);
            }
        }
    } else {
        context = getContext();
    }
    return new DecorView(context, featureId, this, getAttributes());
}

这个方法简单,直接new了一个DecorView.

再来看下generateLayout(mDecor)这个方法,它将上面创建好的mDecor当作参数传入进来

protected ViewGroup generateLayout(DecorView decor) {
      
        int layoutResource;
        int features = getLocalFeatures();
if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
            if (mIsFloating) {
                TypedValue res = new TypedValue();
                getContext().getTheme().resolveAttribute(
                        R.attr.dialogTitleDecorLayout, res, true);
                layoutResource = res.resourceId;
            } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
                layoutResource = a.getResourceId(
                        R.styleable.Window_windowActionBarFullscreenDecorLayout,
                        R.layout.screen_action_bar);
            } else {
                layoutResource = R.layout.screen_title;
            }
            // System.out.println("Title!");
        } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
            layoutResource = R.layout.screen_simple_overlay_action_mode;
        }

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

        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);

        if (getContainer() == null) {
            final Drawable background;
            if (mBackgroundResource != 0) {
                background = getContext().getDrawable(mBackgroundResource);
            } else {
                background = mBackgroundDrawable;
            }
            mDecor.setWindowBackground(background);

            final Drawable frame;
            if (mFrameResource != 0) {
                frame = getContext().getDrawable(mFrameResource);
            } else {
                frame = null;
            }
            mDecor.setWindowFrame(frame);

            mDecor.setElevation(mElevation);
            mDecor.setClipToOutline(mClipToOutline);

            if (mTitle != null) {
                setTitle(mTitle);
            }

            if (mTitleColor == 0) {
                mTitleColor = mTextColor;
            }
            setTitleColor(mTitleColor);
        }

        mDecor.finishChanging();

        return contentParent;
    }

这面代码很长,但是很多代码都不是重点,都是根据主题来做一些操作,比如是否是全屏,actitonBar ....最核心的代码其实就是

mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);这一行代码

系统根据我们的主题会创建一个layoutResource,然后onResourcesLoaded会将这个layoutResource布局转换成一个View,然后添加到mDecor里面,下面源码可以得出结论

void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
    mStackId = getStackId();
    if (mBackdropFrameRenderer != null) {
        loadBackgroundDrawablesIfNeeded();
        mBackdropFrameRenderer.onResourcesLoaded(
                this, mResizingBackgroundDrawable, mCaptionBackgroundDrawable,
                mUserCaptionBackgroundDrawable, getCurrentColor(mStatusColorViewState),
                getCurrentColor(mNavigationColorViewState));
    }
    mDecorCaptionView = createDecorCaptionView(inflater);
    final View root = inflater.inflate(layoutResource, null);
    if (mDecorCaptionView != null) {
        if (mDecorCaptionView.getParent() == null) {
            addView(mDecorCaptionView,
                    new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        }
        mDecorCaptionView.addView(root,
                new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
    } else {
        // Put it below the color views.
        addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
    }
    mContentRoot = (ViewGroup) root;
    initializeElevation();
}
当下面这行代码执行完毕的时候 
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
接着就是下面这行代码
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
最后会将contentParent这个View给Return出去
也就是mContentParent这个成员变量.id就是下面这个静态常量
public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;

当上面方法执行完之后,我们回到PhoneWindow的setContentView方法里,会执行到下面这行代码

mLayoutInflater.inflate(layoutResID, mContentParent);
layoutResID:也就是我们的activity_main.xml文件
mContentParent就是根容器,也就是把我们的xml文件给渲染到了window上.

至此,onCreate方法执行完毕,所以onCreate方法执行完的时候创建了mDecro和mContentparent这二个容器.而我们View的绘制在哪?,这就得从源头开始探究,源头来自于,ActivityThread这个类.

看过Android启动流程的都知道,Android是一个通过消息机制来传递信息的,所以我们每次lauchActivity的时候都会通过ActivityThread里的的H这个类来发送消息,最后在handlemessage里面接收消息,第一次启动Activity走的是

case LAUNCH_ACTIVITY:这个what,接着就是下面这行代码
handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");,接着是创建Activity
Activity a = performLaunchActivity(r, customIntent);如果创建成功接着就是下面这行代码
handleResumeActivity(r.token, false, r.isForward,
        !r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);

从handleResumeActivity这个方法里面抓住最关键的几行代码

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;
...
.
.
.
wm.addView(decor, l);

这里有个关键的局部变量 

ViewManager wm  
wm是什么 ViewManager是一个接口,而wm则就是其实现类WindowManager
它是通过Activity.getWindowManager这个方法获取到的一个成员变量mWindowManager
而mWindowManager则是通过mWindow.getWindowManager()获取
这个mWindow就是Window类
再来看看Window.getWindowManager()这个方法里的mWIndowManager是怎么创建的,下面这行代码
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
最后直接new WindowManagerImpl(mContext, parentWindow);
得到了mWindowManager
所以从上面可以看出wm就是WindowManagerImpl
wm.addView就是走的WindowManagerImpl这个类里的addView方法
进来探索一下,看下面这行代码
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
    applyDefaultToken(params);
    mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
mGlobal是WindowManagerGlobal,再看mGlobal.addView方法,去掉一些无关代码,很简洁了
public void addView(View view, ViewGroup.LayoutParams params,
        Display display, Window parentWindow) {
  
    final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
    
    ViewRootImpl root;
    View panelParentView = null;
    synchronized (mLock) {
        int index = findViewLocked(view, false);
        
        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;
        }
    }
}

从上面代码可公分析出来,最后会走到一个root.setView方法里面,root是ViewRootImpl,进入方法里面,在setView里面有一行关键的代码

requestLayout();啥也不说进去看一看,关键性代码来了
@Override
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        checkThread();
        mLayoutRequested = true;
        scheduleTraversals();
    }
}

 void scheduleTraversals() {
     if (!mTraversalScheduled) {
        //这里只有是false的时候才会进来,所以如果下面代码没有执行完是不会继续绘制
         mTraversalScheduled = true;
        //发送一个同步屏障消息,意思就是叫消息轮询器不要管我这边了,去执行其它的消息
         mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
         mChoreographer.postCallback(
                 Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
         if (!mUnbufferedInputDispatch) {
             scheduleConsumeBatchedInput();
         }
         notifyRendererOfFramePending();
         pokeDrawLockIfNeeded();
     }
 }

这里来研究一下Choreographer mChoreographer这个类里面的mChoreographer.postCallback这个方法

    public void postCallback(int callbackType, Runnable action, Object token) {
        postCallbackDelayed(callbackType, action, token, 0);
    }

    public void postCallbackDelayed(int callbackType,
            Runnable action, Object token, long delayMillis) {
        if (action == null) {
            throw new IllegalArgumentException("action must not be null");
        }
        if (callbackType < 0 || callbackType > CALLBACK_LAST) {
            throw new IllegalArgumentException("callbackType is invalid");
        }

        postCallbackDelayedInternal(callbackType, action, token, delayMillis);
    }
     private void postCallbackDelayedInternal(int callbackType,
            Object action, Object token, long delayMillis) {

        synchronized (mLock) {
            final long now = SystemClock.uptimeMillis();
            final long dueTime = now + delayMillis;
            //1 .将一个任务添加到了队列里面.
            mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
            
            //2. 正常情况走这段逻辑
            if (dueTime <= now) {
                scheduleFrameLocked(now);
            } else {
            //3 . TextView有设置不看.
                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
                msg.arg1 = callbackType;
                msg.setAsynchronous(true);
                mHandler.sendMessageAtTime(msg, dueTime);
            }
        }
    }

最后是来到了mChoreographer.postCallbackDelayedInternal(...)这里

从上面逻辑可以看也肯定是走注释2里面的代码scheduleFrameLocked(now);

    private void scheduleFrameLocked(long now) {
        if (!mFrameScheduled) {
            //标志们,防止多次执行
            mFrameScheduled = true;
            if (USE_VSYNC) {

               //1 .如果是UI线程执行 scheduleVsyncLocked();
                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);
            }
        }
    }

注释1,可以看出,如果是UI线程则走scheduleVsyncLocked()这方法,

mDisplayEventReceiver.scheduleVsync(),这个mDisplayEventReceiver是什么,FrameDisplayEventReceiver mDisplayEventReceiver是Choreographer的内部类,继承DisplayEventReceiver实现了Runnable接口,下面会分析这个类

代码如下

private void scheduleVsyncLocked() {
    mDisplayEventReceiver.scheduleVsync();
}

 private final class FrameDisplayEventReceiver extends DisplayEventReceiver
            implements Runnable {
}

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 {
        nativeScheduleVsync(mReceiverPtr);
    }
}
private static native void nativeScheduleVsync(long receiverPtr);

上面可以看是,通过jni调用底层方法,下一个vsync信号来的时候通知一下,然后就会收到JNI的回调,也就是下面二个方法,面onVsync是一个空方法,由实现类去实现,应用必须向底层请求vsync信号,然后下一下vsync信号来的时候才会去执行接下来会分析到的绘制逻辑.

@SuppressWarnings("unused")
private void dispatchVsync(long timestampNanos, int builtInDisplayId, int frame) {
    onVsync(timestampNanos, builtInDisplayId, frame);
}

public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
}

而是谁实现了DisplayEventReceiver,上面分析的FrameDisplayEventReceiver,来看下方法的实现

public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {

            if (builtInDisplayId != SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN) {
                scheduleVsync();
                return;
            }

            long now = System.nanoTime();
            if (timestampNanos > now) {

                timestampNanos = now;
            }

            if (mHavePendingVsync) {

            } else {
                mHavePendingVsync = true;
            }

            mTimestampNanos = timestampNanos;
            mFrame = frame;
            Message msg = Message.obtain(mHandler, this);
            msg.setAsynchronous(true);
            mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
        }

上面会做一堆判断,比如重新请求信号,更正时间,最后就会通过Handler发送一个异步消息,指定了时间,所以最终又会走到异步方法的run里面,也就是自己的Run方法,

        @Override
        public void run() {
            mHavePendingVsync = false;
            doFrame(mTimestampNanos, mFrame);
        }

从上面方法可以得出,只有得上一个消息被处理完,才会执行我们这个run方法,也就是说,如果Handler存在了耗时那么这里就会等待.

才会执行run方法里面的doFrame方法,调用慢了会怎样,看分析

void doFrame(long frameTimeNanos, int frame) {
        final long startNanos;
        synchronized (mLock) {
            if (!mFrameScheduled) {
                return; // no work to do
            }
            long intendedFrameTimeNanos = frameTimeNanos;
            startNanos = System.nanoTime();
              //1 .当前时间减去接收到vsycn信号的时间,也就是主线程耗时
            final long jitterNanos = startNanos - frameTimeNanos;

            //2. 如果这个时间大于1帧的时候,16ms
            if (jitterNanos >= mFrameIntervalNanos) {
                //2. 计算大了多少帧
                final long skippedFrames = jitterNanos / mFrameIntervalNanos;
                //超过了默认的就会Log提示,表示卡顿了
                if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
                    Log.i(TAG, "Skipped " + skippedFrames + " frames!  "
                            + "The application may be doing too much work on its main thread.");
                }
                final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;
                
                frameTimeNanos = startNanos - lastFrameOffset;
            }

            if (frameTimeNanos < mLastFrameTimeNanos) {
                scheduleVsyncLocked();
                return;
            }

            if (mFPSDivisor > 1) {
                long timeSinceVsync = frameTimeNanos - mLastFrameTimeNanos;
                if (timeSinceVsync < (mFrameIntervalNanos * mFPSDivisor) && timeSinceVsync > 0) {
                    scheduleVsyncLocked();
                    return;
                }
            }

            mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);
            mFrameScheduled = false;
            mLastFrameTimeNanos = frameTimeNanos;
        }

        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);
        }


    }

从上面可以得出结论,开始会计算收到vsync信号到执行doFrame的时间差,vsync信号是16ms一次,大于16ms就是掉帧了.如果超过了默认值30就会打印Log提示.如果中途出现了修改系统时间就重新请求vsync信号就会不走下面绘制逻辑.如果一切正常就会按顺序执行doCallbacks(...)

在doCallbacks里面如果一切正常就会取出其他任务出来执行run方法,也就是mTraversalRunnable这个任务.

我们只看postCallback这个方法里的mTraversalRunnable这个参数

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

一看代码就明白,直接来到doTraversal()方法里,,然后就是下面的它它它...

    void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            //解除上面的异步屏障
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }

            performTraversals();

            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
performLayout(lp, mWidth, mHeight);
performDraw();
 private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
     if (mView == null) {
         return;
     }
     Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
     try {
         mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);//mView就是mDecor
     } finally {
         Trace.traceEnd(Trace.TRACE_TAG_VIEW);
     }
 }

至此差不多结束了,最后就是走到

mDecor.measure  mDecor.layout 然后就是自定义View的一些基本原理

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值