在android开发中view绘制的重要性大家应该都知道,那么view绘制的流程开始的入口在哪里呢?DecorView到底是怎么被添加到Window上的呢?那么今天就带大家从源码的角度去了解这一切。
我们从ActivityThread中的handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,String reason)方法开始,小伙伴们可能要问了,为什么要从这里开始呢?因为这里就涉及到了Activity的启动流程了,之前的一篇文章Android中view的显示原理之Activity,Window,DecorView,布局视图之间的联系说明了在Activity的onCreate方法中将当前布局视图添加到DecorView中,那么接下来就需要开始view的绘制流程,通过查看源码得知,view的绘制的入口就ActivityThread中的handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,String reason)方法。不扯其他的了,开始今天的内容!
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) {
r.window = r.activity.getWindow();//获取PhoneWindow对象
View decor = r.window.getDecorView();//获取DecorView对象
decor.setVisibility(View.INVISIBLE);//设置DecorView不可见
ViewManager wm = a.getWindowManager();//获取一个ViewManager对象
WindowManager.LayoutParams l = r.window.getAttributes();//获取PhoneWindow布局属性
***
wm.addView(decor, l);//将DecorView添加到ViewMananger中
}
}
通过上述源码可以看出这里通过调用performResumeActivity(IBinder token, boolean finalStateRequest,String reason)得到一个ActivityClientRecord 类型的对象(Activity client record, used for bookkeeping for the real instance 这是官方注释,大意是Activity对象的持有者,我更愿意理解为其间接代表了一个Activity),此方法最后会调用Activity中onResume()方法,这里不做过多介绍。回到源码中,获取PhoneWindow,通过PhoneWindow获取到DecorView,获取ViewManager 对象,并将DecorView添加至ViewManager 中,这个ViewManager 中的addView()把DecorView添加到了什么地方呢?我们来看看ViewManager 。
public interface ViewManager{//该类是个接口
public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
}
发现ViewManager是个接口,那么它的实现类是哪个呢?handleResumeActivity()方法中wm 是通过a.getWindowManager()得到的,那么我们进入Activity中的getWindowManager()一探究竟。
public WindowManager getWindowManager() {
return mWindowManager;
}
这里返回的是一个成员变量mWindowManager(其类型为WindowManager 为ViewManager的子类,它也是一个接口),那么这个mWindowManager是在哪里赋值的呢?我们继续找。
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor,
Window window, ActivityConfigCallback activityConfigCallback) {
***
//这里创建了PhoneWindow的对象
mWindow = new PhoneWindow(this, window, activityConfigCallback);
***
//这里调用了mWindow (也就是PhoneWindow)的getWindowManager()
mWindowManager = mWindow.getWindowManager();
***
}
这里是在Activity中的attach()方法中对mWindowManager 进行了赋值,这里调用了PhoneWindow中的getWindowManager()方法,我们继续深入。
public WindowManager getWindowManager() { //该方法是PhoneWindow父类Window中实现的方法,这里也是返回了一个mWindowManager
return mWindowManager;
}
在PhoneWindow的父类Window中找到了getWindowManager(),返回了一个mWindowManager,这里面调用真是太多了!我们接着找这个mWindowManager在Window类中什么地方被赋值的。
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
boolean hardwareAccelerated) {
***
//找到了!
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
在Window类的setWindowManager方法中mWindowManager 被赋值了,至于setWindowManager方法在哪里调用这里我们无需关心,我们只查找跟View绘制相关代码。mWindowManager 是通过WindowManagerImpl得来的,那么我们看看WindowManagerImpl。
public final class WindowManagerImpl implements WindowManager
WindowManagerImpl 是WindowManager 的实现类,还记得之前在ActivityThread中的handleResumeActivity方法调用了wm.addView(decor, l)吗?既然WindowManagerImpl 是WindowManager 的实现类,WindowManager 又是ViewManager的子类,那么wm.addView(decor, l)其实就是调用的WindowManagerImpl 中的addView(View view,ViewGroup.LayoutParams params)方法,我们看看WindowManagerImpl 中的addView()方法做了些什么。
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
***
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
这里调用了成员变量mGlobal的addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow)方法,看看mGlobal的数据类型是什么。
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
这里可以看出它是WindowManagerGlobal 的对象实例,那我们就需要进入WindowManagerGlobal 中查看addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow)方法。
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
if (view == null) {
***
ViewRootImpl root;
root = new ViewRootImpl(view.getContext(), display);//这里创建了一个ViewRootImpl对象
view.setLayoutParams(wparams);//这里的view就是在ActivityThread中传进来的DecorView 对象
root.setView(view, wparams, panelParentView);//这里将DecorView和ViewRootImpl进行了关联
***
}
创建了一个ViewRootImpl对象,并将DecorView与之关联起来,紧接着看看setView(view, wparams, panelParentView)到底做了些什么。
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
***
// Schedule the first layout -before- adding to the window
// manager, to make sure we do the relayout before receiving
// any other events from the system.
requestLayout();
***
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel)
***
}
}
}
我们先看下面这个方法mWindowSession.addToDisplay()这个方法最后会调用WindowManagerService中的addWindow()方法,整个视图的添加到这里就结束了。大家都知道自定义View需要重写onMeasure,onLayout,onDraw方法,那么他们的入口在哪里呢?这个requestLayout()方法就是答案了。
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();//主要看这个方法
}
}
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
这里checkThread()方法中检查了当前线程与mThread 是否一致,不一致则会抛出异常,这个异常大家有没有很熟悉?在子线程更新布局时是不是会出现这个异常!那么这个mThread 是在哪里赋值的呢?我们一起来看看。
public ViewRootImpl(Context context, Display display) {
***
mThread = Thread.currentThread();
***
}
这里mThread在ViewRootImpl的构造函数中赋值了,ViewRootImpl的创建又是在主线程中,所以mThread 对应的线程应该是主线程,在子线程中更新布局就会出错了。接着看scheduleTraversals()。
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
这里主要关注mTraversalRunnable,它是Runnable的实现类,那么找到它的run方法。
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();//这里调用了doTraversal() 这里Traversal是遍历 ,遍历什么东西呢?接着看
}
}
void doTraversal() {
***
performTraversals();//这里就是整个view绘制开始的地方
***
}
performTraversals()就是view绘制开始的地方,现在就揭开它神秘的面纱。
private void performTraversals() {
// cache mView since it is used so much below...
final View host = mView;//这里的mView其实就是DecorView的对象,在之前调用setView()时将其传了进来。
***
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
// Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);//这里调用view的measure方法,其中又会调用onMeasure方法。
***
performLayout(lp, mWidth, mHeight)//这里就是调用了view的layput方法,其中又会调用onLayout方法。
***
performDraw();//这里调用了view的draw方法,其中又会调用onDraw方法。
}
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
if (mView == null) {
return;
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
//调用了view的measure
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
***
final View host = mView;
//这里调用了view的layout方法
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
***
private void performDraw() {
***
//里面调用了draw
draw(fullRedrawNeeded);
***
}
performTraversals()这里就是view开始绘制的源头,依次调用performMeasure,performLayout,performDraw方法。这里我们是从ActivityThread中的handleResumeActivity方法一路调用过来的,所以当前绘制的view就是DecorView(即系统的顶层view)。
总结
ActivityThread#handleResumeActivity —>WindowManagerImpl#addView
—>WindowManagerGlobal#addView —> ViewRootImpl#setView
—>ViewRootImpl#requestLayout —>ViewRootImpl#scheduleTraversals()
—>ViewRootImpl#doTraversal() —>ViewRootImpl#performTraversals()—>view绘制入口,我想童鞋们应该大致了解view是如何被添加至窗体以及view绘制流程的入口了。