View的绘制流程梳理

109 阅读4分钟

View与Activity是如何关联起来的?

Android的UI层级绘制体系
  • PhoneWindow:每个Activity都会创建一个Window用来承载View的显示。Window是一个抽象类,它的唯一实现就是PhoneWindow,该类中包含一个DecorView。
  • DecorView:DecorView是最顶层的View,继承于FrameLayout,内部包含两个部分->ActionBar and ContentView。
  • ContentView:我们在Ac中调用setContentView()传入布局(xml/view)就在该View中加载显示。
  • ViewRootImpl: 视图层次结构的顶部。一个Window对应一个ViewRootImpl和一个DecorView。通过该实例对DecorView进行控制。最终通过执行ViewRootImpl的performTraversals()开启对整个View树的绘制。
View的加载流程
  • 从Activity的setContentView(xx)来看,最终调用了PhoneWindow的setContentView(xx)方法
 // Activity.class
 public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }
  • PhoneWindow最终生成一个DecorView对象,加载系统基础布局到mDecor中,并根据R.id.content生成一个mContentParent,
  • 最终将Activity#setContentView()传入的xml/view加载到mContentParent
// PhoneWindow.class
@Override
    public void setContentView(int layoutResID) {
        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 {
            // 将Activity传过来的xml/view解析 并放入R.id.content中
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        ........
    }
    
 private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {
            mDecor = generateDecor(-1);
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        } else {
            // DecorView 绑定PhoneWindow
            mDecor.setWindow(this);
        }
        if (mContentParent == null) {
            // 根据com.android.internal.R.id.content生成
            mContentParent = generateLayout(mDecor);
            .....
        }
       
    }

 protected DecorView generateDecor(int featureId) {
        ......
        // 创建DecorView
        return new DecorView(context, featureId, this, getAttributes());
    }
    
protected ViewGroup generateLayout(DecorView decor) {
        // Apply data from current theme.
        // 做一些窗体样式的判断
        ....

        // Inflate the window decor.
        // 给窗体进行装饰
        int layoutResource;
        int features = getLocalFeatures();
        // System.out.println("Features: 0x" + Integer.toHexString(features));
        // 加载系统布局,判断到底该加载哪个布局
        if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
            layoutResource = R.layout.screen_swipe_dismiss;
            setCloseOnSwipeEnabled(true);
        } 
        .....

        mDecor.startChanging();
        // 将加载到的基础布局添加到mDecor中
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
        // 通过系统的content的资源Id实例化这个控件
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        if (contentParent == null) {
            throw new RuntimeException("Window couldn't find content container view");
        }
        .....
        mDecor.finishChanging();

        return contentParent;
    }
View的视图绘制流程剖析
  • DecorView被加载到Window中。在ActivityThread的handleResumeActivity方法中通过WindowManager将DecorView加载到Window中。
# ActivityThread.class
@Override
    public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
            String reason) {
        .......
        // 此处执行Activity的onResume()方法
        final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
        if (r == null) {
            return;
        }
        if (mActivitiesToBeDestroyed.containsKey(token)) {
            return;
        }

        final Activity a = r.activity;

        if (localLOGV) {
            Slog.v(TAG, "Resume " + r + " started activity: " + a.mStartedActivity
                    + ", hideForNow: " + r.hideForNow + ", finished: " + a.mFinished);
        }

        final int forwardBit = isForward
                ? WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION : 0;
        boolean willBeVisible = !a.mStartedActivity;
        if (!willBeVisible) {
            try {
                willBeVisible = ActivityTaskManager.getService().willActivityBeVisible(
                        a.getActivityToken());
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }
        if (r.window == null && !a.mFinished && willBeVisible) {
            // 获取window对象
            r.window = r.activity.getWindow();
            // 获取decorView
            View decor = r.window.getDecorView();
            decor.setVisibility(View.INVISIBLE);
            // 获取WindowMananger,实际上是获取ViewManager的子类对象WIndowManager; 由WindowManagerImpl实现
            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对象
                ViewRootImpl impl = decor.getViewRootImpl();
                if (impl != null) {
                    impl.notifyChildRebuilt();
                }
            }
            if (a.mVisibleFromClient) {
                if (!a.mWindowAdded) {
                    a.mWindowAdded = true;
                    // 在这里WindowManager将DecorView添加到PhoneWIndow中
                    wm.addView(decor, l);
                }
                ......
            }
        } 
       ......
        Looper.myQueue().addIdleHandler(new Idler());
    }
  • WindowManagerImpl 将DecorView添加到Window中
  # WindowManagerImpl.class viewDecorView
  private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
  @Override
      public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
          applyDefaultToken(params);
          mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
                  mContext.getUserId());
      }
  
  • 新建ViewRootImpl对象将内部mView(DecorView)通过setView赋值,并调用requaetLayout()方法,在 mChoreographer发送了一个遍历绘制的任务,最终开启了View的真正绘制流程。DecorView的行为交给ViewRootImpl来控制
# WindowManagerGlobal.class
public void addView(View view, ViewGroup.LayoutParams params,
                        Display display, Window parentWindow, int userId) {
        ..... 判空处理
        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
        ...... 
        ViewRootImpl root;
        View panelParentView = null;
        ......
        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 tostart doing things
        try {
            // 调用ViewRootImpl的setView()方法 最终执行内部的requestLayout(),
            root.setView(view, wparams, panelParentView,userId);
        } catch (RuntimeException e) {
            // BadTokenException orInvalidDisplayException, clean up.
            if (index >= 0) {
                removeViewLocked(index, true);
            }
            throw e;
        }
    
    }
# ViewRootImpl.class
// 声明
 final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
// 1
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
            int userId) {
        synchronized (this) {
            if (mView == null) {
                mView = view;
                ......
                requestLayout();
                ......
            }
        }
    }
// 2
  @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }
// 3
 @UnsupportedAppUsage
    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }
// 4
 void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }
            // 终于调用了ViewRootImpl的performTraversals()方法,开启了测量 布局 绘制之路
            performTraversals();

            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }
  • ViewRootImpl的perfromTraversals()方法完成具体的视图绘制流程
# ViewRootImpl.class
private void performTraversals() {
    ......
    if (!mStopped || mReportNextDraw) {
        // Ask host how big it wants to be
        View的测量时递归逐层测量,由父布局与子布局共同确认子View的测量模式,在子布局测量完毕时确认父布局的宽高
        performMeasure(childWidthMeasureSpec,
        childHeightMeasureSpec);
                    
        layoutRequested = true;
        }
    ......
    final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
    
    if (didLayout) {
        // 开始布局摆放
        performLayout(lp, mWidth, mHeight);
    }
    ......
    boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;
    if (!cancelDraw) {
        if (mPendingTransitions != null &&mPendingTransitions.size() > 0) {
            for (int i = 0; i < mPendingTransitions.size();++i) {
            mPendingTransitions.get(i).startChangingAnmations();
            }
            mPendingTransitions.clear();
        }
        // 开始绘制 执行View的`onDraw()`方法
        performDraw();
    }
            
}
  • performMeasure() 循环往下遍历,直到当前控件为View为止
# ViewRootImpl.class
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
        if (mView == null) {
            return;
        }
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
        try {
            // 此时的mView其实就是Decor 
            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }
  • MeasureSpec存储了测量模式和具体的宽高信息,高两位代表测量模式,低30位表示具体的宽高信息
  • 测量模式
    • UNSPECFIED:未指定模式,父容器不限制View的大小,这种情况下计算出来的size值基本就用不到,一般来讲可滑动的布局对子View的限制就是UNSPECFIED,实际上就是让子View在他们内部滚动,意味着子View的尺寸要大于父View,所以不应该给它施加约束。
    • EXACTLY:精确模式,对应xml中设置为match_parent或者具体的数值,子View的尺寸是一个确切的值
    • AT_MOST:最大模式,子View最大不能超过得到的size值
  • performLayout流程分析
# ViewRootImpl.class
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
            int desiredWindowHeight) {
            final View host = mView;
            // 调用mView的layout方法开始摆放
            host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
}

  • performDraw()
private void performDraw() {
     boolean canUseAsync = draw(fullRedrawNeeded);
}

private boolean draw(boolean fullRedrawNeeded) {
    drawSoftware(surface, mAttachInfo, xOffset, yOffset,
                        scalingRequired, dirty, surfaceInsets)
}

 private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
            boolean scalingRequired, Rect dirty, Rect surfaceInsets) {
            mView.draw(canvas);
}
requestLayout() 重新绘制视图,子View调用requestLayout()方法,会标记当前View及父容器,同时逐层向上提交,直到ViewRootImpl处理该事件,ViewRooyImpl会调用三大流程,从measure开始,对于每个含有标记位的View及其子View都会进行测量、布局、绘制。
# View.clas
 public void requestLayout() {
        ......
        if (mParent != null && !mParent.isLayoutRequested()) {
            //  最终调用mParent的requestLayout()方法
            mParent.requestLayout();
        }
        ......
    }
  • 一直往上追溯,最顶层的ViewGroup就是DecorView,那DecorView的mParent又是谁呢?没错 就是ViewRootImpl
#ViewRootImpl
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
            int userId) {
    // 设置了DecorView的parent为ViewRootImpl
     view.assignParent(this);
}
  • 最终调用了ViewRootImpl的requestLayout()方法
# ViewRootImpl.class
 @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            // 标记了是由requestLayout发起
            mLayoutRequested = true;
            // 发送一个遍历任务 最终执行了performTraversals()方法 开始新一轮的测量、布局、绘制的流程
            scheduleTraversals();
        }
    }
invalidate()在UI线程中重新绘制视图。当调用了View.invalidate()方法后,会为该View添加一个标记位,同时不断向父容器请求刷新,父容器通过计算得出自身需要重绘的区域,,直到ViewRootImpl处理该事件,最终触发performTraversals()方法,进行开始View树重绘流程(只绘制需要重绘的视图)
# View.class
 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);
     }
}
  • 最终递归遍历到ViewRootImpl的performTraversals()方法
# ViewRootImpl.class
 @Override
    public void invalidateChild(View child, Rect dirty) {
        invalidateChildInParent(null, dirty);
    }

    @Override
    public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
        ....
        invalidateRectOnScreen(dirty);
        return null;
    }
    
    private void invalidateRectOnScreen(Rect dirty) {
        ......
        if (!mWillDrawSoon && (intersected || mIsAnimating)) {
            // 开启新一轮的绘制任务
            scheduleTraversals();
        }
    }