View测绘时机的探讨,梦开始的地方

本文深入探讨了Android中View的绘制时机及其背后的工作原理。作者通过详细的源码分析,揭示了从Activity的生命周期到View真正开始测量的具体过程。并对比了在不同阶段添加View到DecorView的区别。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这真的是一片好文,本人绞尽脑汁之作,还望能帮助看官一二!因为网上对于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的,而是应该更早一点

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值