16.源码阅读(View的绘制-android api-26)

简介: 今天带着一个问题来看Android View的绘制流程View的绘制入口在哪?很多时候,在进入到一个页面的时候,会需要动态的获取到布局中某一个view的宽度或者高度,但是我们发现如果直接在onCreate方法或者onResume方法中通过这种方式去取高度值得到的是0int measuredHeight = mTextView.

今天带着一个问题来看Android View的绘制流程

View的绘制入口在哪?

很多时候,在进入到一个页面的时候,会需要动态的获取到布局中某一个view的宽度或者高度,但是我们发现如果直接在onCreate方法或者onResume方法中通过这种方式去取高度值得到的是0

int measuredHeight = mTextView.getMeasuredHeight();

而调用post方法才可以得到正确的值

 mTextView.post(new Runnable() {
            @Override
            public void run() {
                int measuredHeight1 = mTextView.getMeasuredHeight();
                System.out.println("post measuredHeight:"+measuredHeight1);
            }
        });

所以回到我们的第一个问题,view的绘制入口在哪里,只有view绘制完成经过测量才能得到宽高值,是否在onCreate和onResume方法中,view还没有完成绘制测量呢?

在Activity的启动流程中已经了解到,最终要启动Activity并且开始执行Activity生命周期的位置是在ActivityThread的handleLaunchActivity中,我们直接来到这里看,对Activity启动流程不熟悉可以看另一篇https://www.jianshu.com/p/bd5208574430
handleLaunchActivity

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
        ......
        //这里会回调onCreate方法
        Activity a = performLaunchActivity(r, customIntent);

        if (a != null) {
            r.createdConfig = new Configuration(mConfiguration);
            reportSizeConfigurations(r);
            Bundle oldState = r.state;
            //这里会回调onResume方法
            handleResumeActivity(r.token, false, r.isForward,
                    !r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);

            if (!r.activity.mFinished && r.startsNotResumed) {
                // The activity manager actually wants this one to start out paused, because it
                // needs to be visible but isn't in the foreground. We accomplish this by going
                // through the normal startup (because activities expect to go through onResume()
                // the first time they run, before their window is displayed), and then pausing it.
                // However, in this case we do -not- need to do the full pause cycle (of freezing
                // and such) because the activity manager assumes it can just retain the current
                // state it has.
                performPauseActivityIfNeeded(r, reason);

                // We need to keep around the original state, in case we need to be created again.
                // But we only do this for pre-Honeycomb apps, which always save their state when
                // pausing, so we can not have them save their state when restarting from a paused
                // state. For HC and later, we want to (and can) let the state be saved as the
                // normal part of stopping the activity.
                if (r.isPreHoneycomb()) {
                    r.state = oldState;
                }
            }
        } else {
            //如果启动Activity失败,交给ActivityManager处理
            // If there was an error, for any reason, tell the activity manager to stop us.
            try {
                ActivityManager.getService()
                    .finishActivity(r.token, Activity.RESULT_CANCELED, null,
                            Activity.DONT_FINISH_TASK_WITH_ACTIVITY);
            } catch (RemoteException ex) {
                throw ex.rethrowFromSystemServer();
            }
        }
    }

performLaunchActivity
ClassLoader加载出Activity后执行了callActivityOnCreate,会回调onCreate方法

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        ......
        Activity activity = null;
        try {
            java.lang.ClassLoader cl = appContext.getClassLoader();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
            
        ......

                activity.mCalled = false;
                if (r.isPersistable()) {
                    mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
                } else {
                    mInstrumentation.callActivityOnCreate(activity, r.state);
                }

        return activity;
    }

public void callActivityOnCreate(Activity activity, Bundle icicle) {
        prePerformCreate(activity);
        //调用activity中的performCreate,onCreate方法就在里面
        activity.performCreate(icicle);
        postPerformCreate(activity);
    }

可以看到,activity创建出来后就开始回调onCreate,而onCreate中做了什么呢,调用了setContentView方法加载我们的布局文件,setContentView源码分析https://www.jianshu.com/p/2f87ebe77f4e

从源码中可以看到,setContentView做了以下的操作:
1.new了一个DecorView
2.加载了一个id为android.R.id.content的布局并把它加入DecorView
3.我们设置的布局文件被加载到id为android.R.id.content的ViewGroup中

也就是说,setContentView后只是加载了布局,但是还没有进行测量绘制,那么在onResum方法调用时是否完成了测量绘制呢?

performLaunchActivity之后系统开始执行handleResumeActivity

final void handleResumeActivity(IBinder token,
            boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
        ....
        //回调onResum方法
        r = performResumeActivity(token, clearHide, reason);

        if (r != null) {
            ......
            //获取到WindowManager,将包含了我们我们自己的布局的
            //DecorView加入到WindowManager中(ViewManager是一个
            //接口,WindowManager实现了它)
            if (r.window == null && !a.mFinished && willBeVisible) {
                r.window = r.activity.getWindow();
                View decor = r.window.getDecorView();
                decor.setVisibility(View.INVISIBLE);
                ViewManager wm = a.getWindowManager();
                WindowManager.LayoutParams l = r.window.getAttributes();
                a.mDecor = decor;
                l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
                l.softInputMode |= forwardBit;
                if (r.mPreserveWindow) {
                    a.mWindowAdded = true;
                    r.mPreserveWindow = false;
                    // Normally the ViewRoot sets up callbacks with the Activity
                    // in addView->ViewRootImpl#setView. If we are instead reusing
                    // the decor view we have to notify the view root that the
                    // callbacks may have changed.
                    ViewRootImpl impl = decor.getViewRootImpl();
                    if (impl != null) {
                        impl.notifyChildRebuilt();
                    }
                }
                //如果Activity已经可见了,就将DecorView加入Window
                if (a.mVisibleFromClient) {
                    if (!a.mWindowAdded) {
                        a.mWindowAdded = true;
                        wm.addView(decor, l);
                    }
                }

            ......

            // The window is now visible if it has been added, we are not
            // simply finishing, and we are not starting another activity.
            if (!r.activity.mFinished && willBeVisible
                ......
                WindowManager.LayoutParams l = r.window.getAttributes();
                if ((l.softInputMode
                        & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION)
                        != forwardBit) {
                    l.softInputMode = (l.softInputMode
                            & (~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION))
                            | forwardBit;
                    if (r.activity.mVisibleFromClient) {
                        ViewManager wm = a.getWindowManager();
                        View decor = r.window.getDecorView();
                        //window可见之后,开始真正的绘制view了,这个方法很关键
                        wm.updateViewLayout(decor, l);
                    }
                }

                ......
            r.onlyLocalRequest = false;

            // Tell the activity manager we have resumed.
            if (reallyResume) {
                try {
                    ActivityManager.getService().activityResumed(token);
                } catch (RemoteException ex) {
                    throw ex.rethrowFromSystemServer();
                }
            }

        } 
    }

也就是说在onCreate和onResume方法之心的时候,还没有完成View的测量,所以在这两个方法中无法得到实际的高度,具体绘制的代码先不看,我们看看为什么post方法中可以得到高度

public boolean post(Runnable action) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            return attachInfo.mHandler.post(action);
        }

        // Postpone the runnable until we know on which thread it needs to run.
        // Assume that the runnable will be successfully placed after attach.
        //把Runnable加入了队列
        getRunQueue().post(action);
        return true;
    }

    public void post(Runnable action) {
        postDelayed(action, 0);
    }

    public void postDelayed(Runnable action, long delayMillis) {
        final HandlerAction handlerAction = new HandlerAction(action, delayMillis);
        //看得出这是一个数组实现的队列,加入队列后也没有执行
        //那么是什么时候执行的
        synchronized (this) {
            if (mActions == null) {
                mActions = new HandlerAction[4];
            }
            mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction);
            mCount++;
        }
    }

    public static <T> T[] append(T[] array, int currentSize, T element) {
        assert currentSize <= array.length;

        if (currentSize + 1 > array.length) {
            T[] newArray = (T[]) Array.newInstance(array.getClass().getComponentType(),
                    growSize(currentSize));
            System.arraycopy(array, 0, newArray, 0, currentSize);
            array = newArray;
        }
        array[currentSize] = element;
        return array;
    }

dispatchAttachToWindow,这个队列是在这个方法中执行的,后边我们会找到这个方法执行的时机,然后就会知道为什么这个方法中执行就可以得到高度了

void dispatchAttachedToWindow(AttachInfo info, int visibility) {
        ......
        if (mRunQueue != null) {
            mRunQueue.executeActions(info.mHandler);
            mRunQueue = null;
        }
        performCollectViewAttributes(mAttachInfo, visibility);
        onAttachedToWindow();
        ......
    }

我们再回到上边ViewManager的addView和updateViewLayout方法,ViewManager和WindowManager都是接口,找到它的实现类WindowManagerImpl(可以通过PhoneWindow找到WindowManager的实现类是他)

    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    }

    @Override
    public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.updateViewLayout(view, params);
    }

看一下WindowManagerGlobal这个类中做了什么,这里创建了一个ViewRootImpl,并把DecorView加入了这个View中

public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        ......

        ViewRootImpl root;
        View panelParentView = null;

           ......

            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) {
               
            }
        }
    }

//这个方法是给view设置参数的,所以关键还是在addView方法
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
        if (view == null) {
            throw new IllegalArgumentException("view must not be null");
        }
        if (!(params instanceof WindowManager.LayoutParams)) {
            throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
        }

        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;

        view.setLayoutParams(wparams);

        synchronized (mLock) {
            int index = findViewLocked(view, true);
            ViewRootImpl root = mRoots.get(index);
            mParams.remove(index);
            mParams.add(index, wparams);
            root.setLayoutParams(wparams, false);
        }
    }

setView

 public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
                ......
                requestLayout();
        }
}

这里又来到了曾经在invalidate方法中看到的源码,刷新view

    @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }
void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    //执行这个Runnable
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }
final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }

void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }

            performTraversals();

            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }
private void performTraversals() {
      ....省略大量代码
      // Ask host how big it wants to be 回调到measure了
      performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
      ....
      //回调到layout了
      if (didLayout) {
            performLayout(lp, mWidth, mHeight);
      }
      ......
      //回调draw了
      performDraw();
}
总结一下:

1.onCreate和onResume方法调用时还没有进行view的测量
2.onResume方法调用之后将包含着当前布局文件的DecorView交给了WindowManager(实现类WindowManagerImpl)处理
3.WindowManager中将DecorView设置到ViewRootImpl中,在ViewRootImpl中调用requestLayout开始刷新View,执行measure layout 和 draw方法
4.post方法中能得到高度是因为post将runnable加入队列,在view attachToWindow时后才去获取高度,此时view已经完成了测量

相关文章
|
14天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
45 2
|
2月前
|
数据可视化 Android开发 开发者
安卓应用开发中的自定义View组件
【10月更文挑战第5天】在安卓应用开发中,自定义View组件是提升用户交互体验的利器。本篇将深入探讨如何从零开始创建自定义View,包括设计理念、实现步骤以及性能优化技巧,帮助开发者打造流畅且富有创意的用户界面。
89 0
|
25天前
|
Android开发 开发者 UED
安卓开发中自定义View的实现与性能优化
【10月更文挑战第28天】在安卓开发领域,自定义View是提升应用界面独特性和用户体验的重要手段。本文将深入探讨如何高效地创建和管理自定义View,以及如何通过代码和性能调优来确保流畅的交互体验。我们将一起学习自定义View的生命周期、绘图基础和事件处理,进而探索内存和布局优化技巧,最终实现既美观又高效的安卓界面。
32 5
|
2月前
|
缓存 数据处理 Android开发
在 Android 中使用 RxJava 更新 View
【10月更文挑战第20天】使用 RxJava 来更新 View 可以提供更优雅、更高效的解决方案。通过合理地运用操作符和订阅机制,我们能够轻松地处理异步数据并在主线程中进行 View 的更新。在实际应用中,需要根据具体情况进行灵活运用,并注意相关的注意事项和性能优化,以确保应用的稳定性和流畅性。可以通过不断的实践和探索,进一步掌握在 Android 中使用 RxJava 更新 View 的技巧和方法,为开发高质量的 Android 应用提供有力支持。
|
2月前
|
缓存 调度 Android开发
Android 在子线程更新 View
【10月更文挑战第21天】在 Android 开发中,虽然不能直接在子线程更新 View,但通过使用 Handler、AsyncTask 或 RxJava 等方法,可以实现子线程操作并在主线程更新 View 的目的。在实际应用中,需要根据具体情况选择合适的方法,并注意相关的注意事项和性能优化,以确保应用的稳定性和流畅性。可以通过不断的实践和探索,进一步掌握在子线程更新 View 的技巧和方法,为开发高质量的 Android 应用提供支持。
32 2
|
2月前
|
XML 前端开发 Android开发
Android面试高频知识点(3) 详解Android View的绘制流程
Android面试高频知识点(3) 详解Android View的绘制流程
Android面试高频知识点(3) 详解Android View的绘制流程
|
2月前
|
XML 前端开发 Android开发
Android面试高频知识点(3) 详解Android View的绘制流程
Android面试高频知识点(3) 详解Android View的绘制流程
25 2
|
2月前
|
存储 数据可视化 JavaScript
可视化集成API接口请求+变量绑定+源码输出
可视化集成API接口请求+变量绑定+源码输出
54 4
|
2月前
|
XML 前端开发 Android开发
Android View的绘制流程和原理详细解说
Android View的绘制流程和原理详细解说
39 3
|
2月前
|
XML 前端开发 Java
安卓应用开发中的自定义View组件
【10月更文挑战第5天】自定义View是安卓应用开发的一块基石,它为开发者提供了无限的可能。通过掌握其原理和实现方法,可以创造出既美观又实用的用户界面。本文将引导你了解自定义View的创建过程,包括绘制技巧、事件处理以及性能优化等关键步骤。