源码分析基于Android-23
1.基本知识
(1)图中黄色部分为Activity,绿色为Window,浅蓝色为DecorView;
(2) 一个Activity对应一个Window,Window的唯一实现是PhoneWindow
(3) 一个Window对应一个DecorView;
(4)DecorView其实是一个FrameLayout,包含一个id为ID_ANDROID_CONTENT的控件;
(5)我们通过setContentView设置的布局文件,是加载到ID_ANDROID_CONTENT当中的。
关系图
2.源码分析
Activity.setContentView的发起流程如下:
ActivityStackSupervisor.realStartActivityLocked->ApplicationThread.scheduleLaunchActivity->ActivityThread.handleLaunchActivity->Activity.setContentView,ActivityStackSupervisor是真正的发起者,
我们重点关注:
Step 1:Activity.setContentView
主要工作:
(1)getWindow()获取Activity的成员变量mWindow,执行
mWindow.setContentView;
(2)mWindow是在Activity.attch方法中构建的,具体看ActivityThread.performLaunchActivity;
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
Step 2:Window.setContentView
主要工作:
(1)installDecor,主要构建DecorView以及mContentParent,我们一会跟进这个方法看看;
(2)mLayoutInflater.inflate(layoutResID, mContentParent),将布局layoutResID添加到mContentParent中,mContentParent作为布局的父控件,而它自己是DecorView中的子控件;
@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
......
installDecor();
......
mLayoutInflater.inflate(layoutResID, mContentParent);
......
}
Step 3:Window.installDecor
主要的工作:
(1)generateDecor(-1),方法比较简单,就是构建DecorView;
(2)generateLayout(mDecor),主要是根据配置属性来构建DecorView布局,内部contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT),这是系统定义的布局,并把它赋值给mContentParent ,有兴趣的同学可以跟进去看看,这里就不展开了;
Tips:为什么一定要在setContentView之前调用requestWindowFeature呢?因为在setContentView之前,generateLayout()已经把Window属性都已经确定了;
private void installDecor() {
......
if (mDecor == null) {
mDecor = generateDecor(-1);
......
}
......
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
......
}
我们可以看到,setContentView工作只是把要显示的内容准备好,还没有涉及到绘制。
ActivityThread.handleLaunchActivity执行完以后紧接着就会执行ActivityThread.handleResumeActivity,这里面涉及发起绘制任务;
我们重点关注:
Step 4:ActivityThread.handleResumeActivity
主要工作:
(1)unscheduleGcIdler,从MessageQueue中移除GC任务,因为绘制任务准备要开始了;
(2)performResumeActivity,会调用Activity的onResume方法;
(3)wm.addView,通知WindowManager 添加DecorView,wm是WindowManageImpl类型。
@Override
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
String reason) {
unscheduleGcIdler();
......
ActivityClientRecord r = performResumeActivity(token, clearHide);
......
if (a.mVisibleFromClient) {
a.mWindowAdded = true;
wm.addView(decor, l);
}
......
}
Step 5:WindowManageImpl.addView
主要工作:
调用mGlobal.addView,mGlobal是WindowManagerGlobal类型。WindowManagerGlobal是进程单例。
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
Step 6:WindowManagerGlobal.addView
主要工作:
(1)new ViewRootImpl,构建ViewRootImpl,ViewRootImpl的作用很关键,负责管理渲染工作view树、负责连接view和window之间的联系、负责和WMS联系,一个window都会对应一个ViewRootImpl。
(2)调用ViewRootImpl.setView方法,
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
......
ViewRootImpl root;.
......
root = new ViewRootImpl(view.getContext(), display);
......
root.setView(view, wparams, panelParentView);
......
}
Step 8:ViewRootImpl.setView
主要工作:
(1)触发requestLayout,这里并没有开始绘制,只是post了一个任务;
(2)mWindowSession.addToDisplay,mWindowSession是Session类型,通过它通知WMS完成Window的添加,这里是完成窗口的添加,不是绘制,添加以后WMS就可以管理Window了,一个应用对应一个mWindowSession;
(3)参数mWindow是W类型,不是PhoneWindow类型,它作为WMS与客户端通讯的通道,当WMS有事件需要通知Window,就是通过它来完成;
(4)注意setView第一个参数就是DecorView;
Tips:客户端发消息给WMS,ViewRootImpl->Session->WMS;
Tips:WMS发消息给客户端,WMS->W->ViewRootImpl->DecorView;
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
......
requestLayout();
......
try {
......
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);
......
}
Step 9:ViewRootImpl.requestLayout
主要工作:
(1)checkThread,判断当前线程是否是创建ViewRootImpl的线程,也就是判断是否在主线程,否则就抛出大名鼎鼎的
CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.")异常;
Tips:面试可能会被问到,能不能在子线程更新UI?理论上是可以,让ViewRootImpl也在子线程创建,这样checkThread判断就会通过;
(2)scheduleTraversals,发送一个SyncBarrier消息屏障(同步屏障),让渲染任务优先,以及把绘制任务放入任务队列,等到VSync信号触发;
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
Step 10:ViewRootImpl.scheduleTraversals
主要工作:
(1)mTraversalScheduled标志位防止多次requestLayout,只有在上一帧绘制完成了,下一帧才可以触发;
(2)postSyncBarrier,发送一个消息屏障(同步屏障),让渲染任务优先,以及把绘制任务放入任务队列,等到VSync信号触发;mTraversalRunnable是消息触发的回调任务。
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
......
}
}
如何接收VSync信号,可以参考https://www.jianshu.com/p/c1f6e1181e8e
当VSync信号到来,mTraversalRunnable任务会被执行,它的run方法就会被触发
Tips:TraversalRunnable是ViewRootImpl的内部类
Step 11:TraversalRunnable.run
主要是执行doTraversal方法;
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
Step 12:ViewRootImpl.doTraversal
主要工作:
(1)mTraversalScheduled 标志改为false,可以发送下一帧的绘制任务;
(2)mTraversalScheduled ,移除消息屏障,让普通消息可以被执行;
(3)触发performTraversals方法;
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
......
performTraversals();
......
}
}
Step 12:ViewRootImpl.performTraversals
主要工作:
开始DecorView开始measure、layout、draw,分别回调onMeasure,onLayout,onDraw
private void performTraversals() {
......
windowSizeMayChange |= measureHierarchy(host, lp, res,
desiredWindowWidth, desiredWindowHeight);
......
performMeasure
......
performLayout(lp, mWidth, mHeight);
......
performDraw();
......
}
至此自定义的布局就会显示在屏幕上。
总结作用:
(1)setContentView,只是构造DecorView、初始化DecorView以及把自定义布局添加到mContentParent当中,绘制还没有开始;
(2)handleResumeActivity,发送消息屏障,把绘制任务放入任务队列,等到VSync信号触发开始绘制。
(3)Activity负责生命周期的控制以及事件处理;ViewRootImpl在WindowManager以及DecorView中起纽带作用,Activity是控制单元,Window是载体,View是内容;
(4)PhoneWindow是一个抽象的存在(载体),是以view的形式存在的(真实存在),管理的是一组view,例如Activity的布局,Activity、Dialog、Toast都是依赖PhoneWindow的;
(5)建议结合https://www.jianshu.com/p/c1f6e1181e8e
,才能更好的理解布局是如何显示出来的。
以上分析有不对的地方,请指出,互相学习,谢谢哦!
网友评论