View是如何添加到屏幕上的?

469 阅读2分钟

简单来说两步走:

1.设置xml布局   始于setContentView(R.layout.activity_main)
2.基于布局绘制  始于ViewRootImpl.scheduleTraversals()

1.设置xml布局

在我们自己的MainActivity里面加载xml的布局setContentView(R.layout.activity_main);

public class MainActivity extends AppCompatActivity {
  @Override
  protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);
  }
}

进入父类AppCompatActivity处理

AppCompatActivity:
  @Override
    public void setContentView(@LayoutRes int layoutResID) {
        getDelegate().setContentView(layoutResID);
    }

getDelegate()指向的是AppCompatDelegateImplV9,其是V11,V14,V23的父类

AppCompatDelegateImplV9:
    @Override
    public void setContentView(int resId) {
        ensureSubDecor();
        ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
        contentParent.removeAllViews();
        LayoutInflater.from(mContext).inflate(resId, contentParent);
        mOriginalWindowCallback.onContentChanged();
    }

从上可知,android.R.id.content的控件是mSubDecor的子View,从inflate(resId, contentParent),跟进源码看,只要contentParent不为null(肯定不为null),那么就将我们的xml布局添加到contentParent里面,父子控件链路:mSubDecor -->android.R.id.content -->我们的xml。

Q :  mSubDecor 是什么?但是肯定不是DecorView

AppCompatDelegateImplV9:
private ViewGroup createSubDecor() {
     TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
      ...省略 基于主题去设置窗体Window的属性,是否显示actionbar、title、Overlay 相关代码....
        mWindow.getDecorView();
        // 基于不同主题设置不同的系统自带的xml文件,这里只以abc_screen_simple为例
        subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple, null);
        mWindow.setContentView(subDecor);
        return subDecor;
    }

mSubDecor其实是基于主题,inflate添加进来的一个容器,其xml名为abc_screen_simple.xml(这只是其中某种情况的例子)

 PhoneWindow:这是window的唯一子类
 @Override
    public void setContentView(View subDecor) {
        if (mContentParent == null) {
           if (mDecor == null) {
             mDecor = new DecorView(context, featureId, this, getAttributes());
             mContentParent = getDecorView().findViewById(com.android.internal.R.id.content);
          }
        } 
         if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            ...
        } else {
            mContentParent.addView(view, params);
        }
      ...省略...
    }

Q :  com.android.internal.R.id.content与android.R.id.content是同一个控件吗? 肯定不是

使用Layout Inspector 配合debug调试发现:

  • mContentParent的id是com.android.internal.R.id.content, 对应上图中的FrameLayout
  • android.R.id.content对应的是ContentFrameLayout。

基于布局绘制

绘制的入口:

 ActivityThread: 绘制的入口方法
  public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward, String reason) {
     // 精简无关代码
            ViewManager wm = a.getWindowManager();
            WindowManager.LayoutParams l = r.window.getAttributes();
            a.mDecor = decor;
            if (a.mVisibleFromClient) {
                if (!a.mWindowAdded) {
                    a.mWindowAdded = true;
                    wm.addView(decor, l);
                } 
            }
    }

绘制关联:

public final class WindowManagerImpl implements WindowManager {
   private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
   @Override
   public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
       applyDefaultToken(params);
       mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
   }
}

WindowManagerGlobal:
public void addView(View view, ViewGroup.LayoutParams params,
           Display display, Window parentWindow) {
       ViewRootImpl root= new ViewRootImpl(view.getContext(), display);
           // do this last because it fires off messages to start doing things
           try {
               root.setView(view, wparams, panelParentView);
           } catch (RuntimeException e) {
           //
           }
       }
   }
   
ViewRootImpl:做了精简  这个类是View和WindowManager之间实现关联的桥梁类
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
               requestLayout();
           }
       }
   }
@Override
   public void requestLayout() {
       if (!mHandlingLayoutInLayoutRequest) {
           checkThread();
           mLayoutRequested = true;
           // Traversals  中文意思:遍历
           scheduleTraversals();
       }
   }
void scheduleTraversals() {
       if (!mTraversalScheduled) {
           mTraversalScheduled = true;
           mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
           mChoreographer.postCallback(
                   Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
           if (!mUnbufferedInputDispatch) {
               scheduleConsumeBatchedInput();
           }
           notifyRendererOfFramePending();
           pokeDrawLockIfNeeded();
       }
   }
final class TraversalRunnable implements Runnable {
       @Override
       public void run() {
           doTraversal();
       }
   }
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
  void doTraversal() {
       if (mTraversalScheduled) {
           mTraversalScheduled = false;
           mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
           performTraversals();
           }
       }
   }
private void performTraversals() {
       performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
       ......
       if (didLayout) {
           performLayout(lp, mWidth, mHeight);
           ......
           performDraw();
       }
   }

进入到measure layout draw就涉及到View和ViewGroup的测量、布局和绘制了。只需要注意ViewGroup的真正绘制逻辑是在disptchDraw方法里。