这真的是一片好文,本人绞尽脑汁之作,还望能帮助看官一二!因为网上对于view测绘开始的文章几乎没有,而且自己找的话需要对activity的启动和view绘制的流程有着深入的理解,所以我认为工作3年内的开发者可能都无法get到这个点。下面开始正文。
view的绘制时机一直在我心里是一个比较困惑的地方,它究竟是从哪里开始的?为啥我只有在onWindoswFocusChanged中才能拿到view的宽高?
其原因就是,onCreate、onStart、onResume中还没有开始view的测绘,网上的误导信息太多,让我一直忽略了这里一点。事实上,view如果真正开始测绘了,我们又有什么理由拿不到view的参数呢?
最后我在开发艺术的Activity的Window的创建过程这一小节得到了启发,分3步,先检查decorview的存在与否,如果不存在,我们需要创建一个decorview;此外,我们的根布局添加到这个decor view中;最后,我们把decorview添加到phone window中。
这个时候我就开始思考了——我们的根布局添加到decor view中,我们是在添加进decor view之前进行的一个测绘还是添加进decor view之后才开始的一个测绘呢?
很快我思考出了答案,是之后。为什么呢?因为我很熟悉自定义view的流程,在measure中,我们做的最重要的工作就是把父容器的信息+子view的layout params——>得出一个子view的measure spec。这里指的父容器的信息,指的有两种:如果你是decorview,那么这个父容器的信息就是phonewindow;如果你不是decorview,那么这个父容器的信息就是measure spec。
所以这就是为什么我们在把decorview加进phonewindow前都不可能进行一个测绘的原因,因为decorview需要phonewindow的信息,而decorview的子view需要父view group的measure spec,而layout draw又是在measure基础上进行的,所以我们可以很快得出这个结论。
上面就是我的思路,下面就要拿着思路去看源码了。我们只需要找到window.add(decor)这种形式的代码就可以了。那么从哪里开始找呢?这个就需要对activity的启动流程有一个比较深入的理解了。我们从startActivity开始,我省略一些步骤,最终发现会调用到这里
app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken,
System.identityHashCode(r), r.info,
// TODO: Have this take the merged configuration instead of separate global and
// override configs.
mergedConfiguration.getGlobalConfiguration(),
mergedConfiguration.getOverrideConfiguration(), r.compat,
r.launchedFromPackage, task.voiceInteractor, app.repProcState, r.icicle,
r.persistentState, results, newIntents, !andResume,
mService.isNextTransitionForward(), profilerInfo);
app.thread就是ApplicationThread,ActivityThread的内部类
ApplicationThread.scheduleLaunchActivity,这个方法中会发送一个消息
sendMessage(H.LAUNCH_ACTIVITY, r);
消息的处理者也是ActivityThread的内部类,H,继承自Handler
看一下处理
public void handleMessage(Message msg) {
if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
switch (msg.what) {
case LAUNCH_ACTIVITY: {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
r.packageInfo = getPackageInfoNoCheck(
r.activityInfo.applicationInfo, r.compatInfo);
handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
} break;
这里的handleLaunchActivity就是正式开始活动生命周期的地方,在这之中会完成onCreate,onStart,onResume,其中代表onResume的方法是handleResumeActivity,在handleResumeActivity中会调用:
r.activity.makeVisible();
我们看看Activity.makeVisible
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);
}
得出了结论。resume的过程中,会进行decor——>add到——>window manager的操作。现在终于开始开始追溯view绘制的开始时机了!
在window manager impl的addView方法中,我们最终能够看到ViewRootImpl对象的实例化,以及 view root impl对象对于decorview开始操作的地方
root = new ViewRootImpl(view.getContext(), display);
root.setView(view, wparams, panelParentView);
在setView方法中
// 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();
见到了我们熟悉的好朋友,上面的注释也证明了这一点
requestLayout
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
scheduleTraversals
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
这里的postCallback就很关键了,里面有一个mTraversalRunnable,毫无疑问你可以分析出,postCallback最终会调用到这个mTraversalRunnable的run方法,我们只需要找到mTraversalRunnable即可
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;
}
}
}
终于见到了亲爱的performTraversals了,到这里,源码追溯完毕。
别看上面追溯了这么一大串,实际上,这一切都不需要去记忆。因为我们的出发点是证明resume的时候,会add decor view到window中,然后才开始的绘制流程,现在我们无疑已经达到了我们的目的。也就是说,上面的源码追溯流程你大可不看,只需要记住我们分析出来的这个结论也是可以的!
最后提一下我在美团一面的时候碰到的问题,addView和直接写在布局里有什么区别?(事实上,这个问题是促使我写这篇文章的原因之一)。当时我回答的时候,就回答的不好。然后百度了,也百度不出什么东西来,只听一帮老外很夸张的说,哇,这个会触发requestLayout2次,oh my god,这太糟糕了!我信以为真。今天一看,非也非也。再来点我们最熟悉的代码吧:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
setContentView也可以这样写:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
View view = LayoutInflater.from(this).inflate(R.layout.activity_main, null, false);
setContentView(view);
如果加上addView的操作又是怎样的?
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ViewGroup view = (ViewGroup) LayoutInflater.from(this).inflate(R.layout.activity_main, null, false);
view.addView(new TextView(this));
setContentView(view);
你觉得和在布局文件里写有区别吗?我们通过xmlParser的方式,获取布局文件中的对象String类型的name,然后根据完整路径name拿到Class对象然后反射创建出来整个view树,和我们动态addView没有啥区别!因为我们直到resume的时候才会进行view测绘。
所以硬要说区别,唯一的区别在于,addView的性能更好,布局文件中还要经历,xml解析和反射创建两个过程,性能上相差3-5倍,不过仅仅是一个view,我觉得这点差距,聊胜于无。
在下篇文章中,我会给出activity的启动的上层流程源码追溯,感兴趣的同学可以继续跟进,and enjoy it。
/////
本文说错了,不是在make visible里WindowManagerImpl.addView的,而是应该更早一点