区块链技术博客
www.b2bchain.cn

面试官:View.post() 为什么能够获取到 View 的宽高?求职学习资料

D0b2wT.gif

本文介绍了面试官:View.post() 为什么能够获取到 View 的宽高?求职学习资料,有助于帮助完成毕业设计以及求职,是一篇很好的资料。

对技术面试,学习经验等有一些体会,在此分享。

Android 面试进阶指南目录

计算机网络

  1. http 速查

Android

  1. 面试官:任务栈?返回栈?启动模式?傻傻分不清楚?
  2. 面试官:唠唠 Activity 的生命周期
  3. 面试官: 说一说 Context
  4. 面试官:为什么不能使用 Application Context 显示 Dialog?
  5. 面试官:OutOfMemoryError 可以被 try catch 吗 ?
  6. 面试官:为什么 Activity.finish() 之后 10s 才 onDestroy ?
  7. 面试官:如何监测应用的 FPS ?
  8. 面试官:为什么 View.post 可以获取到视图宽高?

目录

  • 小测试:哪里可以获取到 View 的宽高?
  • View 在什么时间点被测量?
  • 探秘 View.post()
  • 还可以怎么获取视图宽高?
  • 最后

小测试:哪里可以获取到 View 的宽高?

今天的文章会比较轻松,相比前面几篇没有那么大段的源码要啃。关于获取 View 的宽高,我们先来一段测试代码:

class MainActivity : BaseLifecycleActivity() {      private val binding by lazy { ActivityMainBinding.inflate(layoutInflater) }      override fun onCreate(savedInstanceState: Bundle?) {         super.onCreate(savedInstanceState)         setContentView(binding.root)          // 在 onCreate() 中获取宽高         Log.e("measure","measure in onCreate: width=${window.decorView.width}, height=${window.decorView.height}")          // 在 View.post() 回调中获取宽高         binding.activity.post {             Log.e("measure","measure in View.post: width=${window.decorView.width}, height=${window.decorView.height}")         }     }      override fun onResume() {         super.onResume()         // 在 onResume() 回调中获取宽高         Log.e("measure","measure in onResume: width=${window.decorView.width}, height=${window.decorView.height}")     } }

大多数人都能直截了当的给出答案:

E/measure: measure in onCreate: width=0, height=0 E/measure: measure in onResume: width=0, height=0 E/measure: measure in View.post: width=1080, height=2340

onCreate()onResume() 中是无法获取到宽高的,而 View.post() 回调中可以。从日志打印顺序可以看出来,View.post() 回调中的打印语句是最后执行的。

抛开代码来思考一下这个问题,什么时候可以获取到 View 的宽高? 毫无疑问,最起码肯定得在 View 被测量 这个时间点之后。从上面的结果来看,onCreate()onResume() 发生在这个时间点之前,View.post() 的回调发生在这个时间点之后。我们只要搞清楚这个时间点,问题就迎刃而解了。

View 在什么时间点被测量?

大家都知道 View 的绘制流程发生在 onResume 流程(并不是指 Activity.onResume() 回调)中,但我还是决定从头开始说起,大家权当复习一下。

当冷启动 (应用进程不存在) 一个 app 时,首先要和 zygote 建立 socket 连接,将创建进程需要的参数发送给 zygote, zygote 服务端接收到参数之后调用 ZygoteConnection.processOneCommand() 处理参数,并 fork 出应用进程。最后通过 findStaticMain() 找到 ActivityThread 类的 main() 方法并执行,应用进程就启动了。

ActivityThread 虽然不是一个线程类,但它是运行在主线程的,你就把它认为是主线程也没有关系。在 main() 方法中, 创建了 ActivityThread 对象,调用其 attach() 方法,并开启了主线程消息循环,基于事件的消息队列机制就开始工作了。

ActivityThread.attach() 方法中,Binder 调用了 AMS.attachApplication() 方法,其中主要做了两件事:

  1. 将当前进程与 AMS 进行绑定。然后再 Binder 调用回应用进程的 ApplicationThread.bindApplication() 方法,进行客户端的准备工作,创建 Context,创建 Application 等等
  2. mStackSupervisor.attachApplicationLocked(app),最终调用到 realStartActivityLocked() 启动 Activity

Activity 的启动流程就不赘述了,之前写过一篇裹脚布式的文章,庖丁解牛 Activity 启动流程
,感兴趣的可以看看。这里直接跳到 ActivityThread.performLaunchActivity() 方法。

> ActivityThread.java  private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {     ...     // 获取 ComponentName     ComponentName component = r.intent.getComponent();     ...     // 创建 ContextImpl 对象     ContextImpl appContext = createBaseContextForActivity(r);     ...     // 反射创建 Activity 对象     activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);     ......     // 创建 Application 对象     Application app = r.packageInfo.makeApplication(false, mInstrumentation);     ......           // attach 方法中会创建 PhoneWindow 对象     activity.attach(appContext, this, getInstrumentation(), r.token,             r.ident, app, r.intent, r.activityInfo, title, r.parent,             r.embeddedID, r.lastNonConfigurationInstances, config,             r.referrer, r.voiceInteractor, window, r.configCallback);       // 执行 onCreate()     if (r.isPersistable()) {         mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);     } else {         mInstrumentation.callActivityOnCreate(activity, r.state);     }     ...     return activity; }

mInstrumentation.callActivityOnCreate() 方法最终会回调 Activity.onCreate() 方法。到这里,setContentView() 方法就被执行了。setContentView() 逻辑很复杂,但干的事情很直白。创建 DecorView ,然后根据我们传入的布局文件 id 解析 xml,将得到的 view 塞进 DecorView 中。注意,到现在,我们得到的只是一个 空壳子 View 树,它并没有被添加到屏幕上,其实也不能添加到屏幕上。所以,在 onCreate() 回调中获取视图宽高显然是不可取的。

看完 onCreate() , 我们跳过 onStart(),里面没干啥太重要的事情,直接来到 onResume()

注:Activity 的生命周期是由 ClientLifecycleManager 类来调度的,具体原理可以看这篇文章 从源码看 Activity 生命周期 。

> ActivityThread.java  public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,         String reason) {     ...     // 1. 回调 onResume     final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);     ···     View decor = r.window.getDecorView();     decor.setVisibility(View.INVISIBLE);     ViewManager wm = a.getWindowManager();     WindowManager.LayoutParams l = r.window.getAttributes();     // 2. 添加 decorView 到 WindowManager     wm.addView(decor, l);     ... }

两件事,回调 onResume 和 添加 DecorView 到 WindowManager 。所以,在 onResume() 回调中获取 view 的宽高其实和 onCreate() 中没啥区别,都获取不到。

wm.addView(decor, l) 最终调用到 WindowManagerGlobal.addView()

public void addView(View view, ViewGroup.LayoutParams params,         Display display, Window parentWindow) {     ...     // 1. 重点,初始化 ViewRootImpl     root = new ViewRootImpl(view.getContext(), display);     // 2. 重点,发起绘制并显示到屏幕上     root.setView(view, wparams, panelParentView);

这里两行代码都是重中之重。先来看看注释 1 处 ViewRootImpl 的构造函数。

public ViewRootImpl(Context context, Display display) {     ...     // 1. IWindowSession 代理对象,与 WMS 进行 Binder 通信     mWindowSession = WindowManagerGlobal.getWindowSession();     ...     // 2.     mWidth = -1;     mHeight = -1;     ...     // 3. 初始化 AttachInfo     // 记住 mAttachInfo 是在这里被初始化的     mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,             context);     ...     // 4. 初始化 Choreographer,通过 Threadlocal 存储     mChoreographer = Choreographer.getInstance(); }
  1. 初始化 mWindowSession,它可以 WMS 进行 Binder 通信
  2. 这里能看到宽高还未赋值
  3. 初始化 AttachInfo,这里着重记一下,后面会再提到
  4. 初始化 Choreographer,上篇文章 面试官:如何监测应用的 FPS ? 详细介绍过

再看注释 2 处的 ViewRootImpl.setView() 方法。

> ViewRootImpl.java  // 参数 view 就是 DecorView public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {     synchronized (this) {         if (mView == null) {             mView = view;              // 1. 发起首次绘制             requestLayout();              // 2. Binder 调用 Session.addToDisplay(),将 window 添加到屏幕                     res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,                             getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,                             mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,                             mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);              // 3. 将 decorView 的 parent 赋值为 ViewRootImpl             view.assignParent(this);         }     } }

requestLayout() 方法发起了首次绘制。

> ViewRootImpl.java  public void requestLayout() {     if (!mHandlingLayoutInLayoutRequest) {         // 检查线程         checkThread();         mLayoutRequested = true;         // 重点         scheduleTraversals();     } }

ViewRootImpl.scheduleTraversals() 方法在 上篇文章 中详细介绍过,这里大致总结一下:

  1. ViewRootImpl.scheduleTraversals() 方法中会建立同步屏障,优先处理异步消息。通过 Choreographer.postCallback() 方法提交了任务 mTraversalRunnable,这个任务就是负责 View 的测量,布局,绘制。

  2. Choreographer.postCallback() 方法通过 DisplayEventReceiver.nativeScheduleVsync() 方法向系统底层注册了下一次 vsync 信号的监听。当下一次 vsync 来临时,系统会回调其 dispatchVsync() 方法,最终回调 FrameDisplayEventReceiver.onVsync() 方法。

  3. FrameDisplayEventReceiver.onVsync() 方法中取出之前提交的 mTraversalRunnable 并执行。这样就完成了一次绘制流程。

    mTraversalRunnable 中执行的是 doTraversal() 方法。

Android 面试进阶指南目录

计算机网络

  1. http 速查

Android

  1. 面试官:任务栈?返回栈?启动模式?傻傻分不清楚?
  2. 面试官:唠唠 Activity 的生命周期
  3. 面试官: 说一说 Context
  4. 面试官:为什么不能使用 Application Context 显示 Dialog?
  5. 面试官:OutOfMemoryError 可以被 try catch 吗 ?
  6. 面试官:为什么 Activity.finish() 之后 10s 才 onDestroy ?
  7. 面试官:如何监测应用的 FPS ?
  8. 面试官:为什么 View.post 可以获取到视图宽高?

目录

  • 小测试:哪里可以获取到 View 的宽高?
  • View 在什么时间点被测量?
  • 探秘 View.post()
  • 还可以怎么获取视图宽高?
  • 最后

小测试:哪里可以获取到 View 的宽高?

今天的文章会比较轻松,相比前面几篇没有那么大段的源码要啃。关于获取 View 的宽高,我们先来一段测试代码:

class MainActivity : BaseLifecycleActivity() {      private val binding by lazy { ActivityMainBinding.inflate(layoutInflater) }      override fun onCreate(savedInstanceState: Bundle?) {         super.onCreate(savedInstanceState)         setContentView(binding.root)          // 在 onCreate() 中获取宽高         Log.e("measure","measure in onCreate: width=${window.decorView.width}, height=${window.decorView.height}")          // 在 View.post() 回调中获取宽高         binding.activity.post {             Log.e("measure","measure in View.post: width=${window.decorView.width}, height=${window.decorView.height}")         }     }      override fun onResume() {         super.onResume()         // 在 onResume() 回调中获取宽高         Log.e("measure","measure in onResume: width=${window.decorView.width}, height=${window.decorView.height}")     } }

大多数人都能直截了当的给出答案:

E/measure: measure in onCreate: width=0, height=0 E/measure: measure in onResume: width=0, height=0 E/measure: measure in View.post: width=1080, height=2340

onCreate()onResume() 中是无法获取到宽高的,而 View.post() 回调中可以。从日志打印顺序可以看出来,View.post() 回调中的打印语句是最后执行的。

抛开代码来思考一下这个问题,什么时候可以获取到 View 的宽高? 毫无疑问,最起码肯定得在 View 被测量 这个时间点之后。从上面的结果来看,onCreate()onResume() 发生在这个时间点之前,View.post() 的回调发生在这个时间点之后。我们只要搞清楚这个时间点,问题就迎刃而解了。

View 在什么时间点被测量?

大家都知道 View 的绘制流程发生在 onResume 流程(并不是指 Activity.onResume() 回调)中,但我还是决定从头开始说起,大家权当复习一下。

当冷启动 (应用进程不存在) 一个 app 时,首先要和 zygote 建立 socket 连接,将创建进程需要的参数发送给 zygote, zygote 服务端接收到参数之后调用 ZygoteConnection.processOneCommand() 处理参数,并 fork 出应用进程。最后通过 findStaticMain() 找到 ActivityThread 类的 main() 方法并执行,应用进程就启动了。

ActivityThread 虽然不是一个线程类,但它是运行在主线程的,你就把它认为是主线程也没有关系。在 main() 方法中, 创建了 ActivityThread 对象,调用其 attach() 方法,并开启了主线程消息循环,基于事件的消息队列机制就开始工作了。

ActivityThread.attach() 方法中,Binder 调用了 AMS.attachApplication() 方法,其中主要做了两件事:

  1. 将当前进程与 AMS 进行绑定。然后再 Binder 调用回应用进程的 ApplicationThread.bindApplication() 方法,进行客户端的准备工作,创建 Context,创建 Application 等等
  2. mStackSupervisor.attachApplicationLocked(app),最终调用到 realStartActivityLocked() 启动 Activity

Activity 的启动流程就不赘述了,之前写过一篇裹脚布式的文章,庖丁解牛 Activity 启动流程
,感兴趣的可以看看。这里直接跳到 ActivityThread.performLaunchActivity() 方法。

> ActivityThread.java  private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {     ...     // 获取 ComponentName     ComponentName component = r.intent.getComponent();     ...     // 创建 ContextImpl 对象     ContextImpl appContext = createBaseContextForActivity(r);     ...     // 反射创建 Activity 对象     activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);     ......     // 创建 Application 对象     Application app = r.packageInfo.makeApplication(false, mInstrumentation);     ......           // attach 方法中会创建 PhoneWindow 对象     activity.attach(appContext, this, getInstrumentation(), r.token,             r.ident, app, r.intent, r.activityInfo, title, r.parent,             r.embeddedID, r.lastNonConfigurationInstances, config,             r.referrer, r.voiceInteractor, window, r.configCallback);       // 执行 onCreate()     if (r.isPersistable()) {         mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);     } else {         mInstrumentation.callActivityOnCreate(activity, r.state);     }     ...     return activity; }

mInstrumentation.callActivityOnCreate() 方法最终会回调 Activity.onCreate() 方法。到这里,setContentView() 方法就被执行了。setContentView() 逻辑很复杂,但干的事情很直白。创建 DecorView ,然后根据我们传入的布局文件 id 解析 xml,将得到的 view 塞进 DecorView 中。注意,到现在,我们得到的只是一个 空壳子 View 树,它并没有被添加到屏幕上,其实也不能添加到屏幕上。所以,在 onCreate() 回调中获取视图宽高显然是不可取的。

看完 onCreate() , 我们跳过 onStart(),里面没干啥太重要的事情,直接来到 onResume()

注:Activity 的生命周期是由 ClientLifecycleManager 类来调度的,具体原理可以看这篇文章 从源码看 Activity 生命周期 。

> ActivityThread.java  public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,         String reason) {     ...     // 1. 回调 onResume     final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);     ···     View decor = r.window.getDecorView();     decor.setVisibility(View.INVISIBLE);     ViewManager wm = a.getWindowManager();     WindowManager.LayoutParams l = r.window.getAttributes();     // 2. 添加 decorView 到 WindowManager     wm.addView(decor, l);     ... }

两件事,回调 onResume 和 添加 DecorView 到 WindowManager 。所以,在 onResume() 回调中获取 view 的宽高其实和 onCreate() 中没啥区别,都获取不到。

wm.addView(decor, l) 最终调用到 WindowManagerGlobal.addView()

public void addView(View view, ViewGroup.LayoutParams params,         Display display, Window parentWindow) {     ...     // 1. 重点,初始化 ViewRootImpl     root = new ViewRootImpl(view.getContext(), display);     // 2. 重点,发起绘制并显示到屏幕上     root.setView(view, wparams, panelParentView);

这里两行代码都是重中之重。先来看看注释 1 处 ViewRootImpl 的构造函数。

public ViewRootImpl(Context context, Display display) {     ...     // 1. IWindowSession 代理对象,与 WMS 进行 Binder 通信     mWindowSession = WindowManagerGlobal.getWindowSession();     ...     // 2.     mWidth = -1;     mHeight = -1;     ...     // 3. 初始化 AttachInfo     // 记住 mAttachInfo 是在这里被初始化的     mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,             context);     ...     // 4. 初始化 Choreographer,通过 Threadlocal 存储     mChoreographer = Choreographer.getInstance(); }
  1. 初始化 mWindowSession,它可以 WMS 进行 Binder 通信
  2. 这里能看到宽高还未赋值
  3. 初始化 AttachInfo,这里着重记一下,后面会再提到
  4. 初始化 Choreographer,上篇文章 面试官:如何监测应用的 FPS ? 详细介绍过

再看注释 2 处的 ViewRootImpl.setView() 方法。

> ViewRootImpl.java  // 参数 view 就是 DecorView public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {     synchronized (this) {         if (mView == null) {             mView = view;              // 1. 发起首次绘制             requestLayout();              // 2. Binder 调用 Session.addToDisplay(),将 window 添加到屏幕                     res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,                             getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,                             mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,                             mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);              // 3. 将 decorView 的 parent 赋值为 ViewRootImpl             view.assignParent(this);         }     } }

requestLayout() 方法发起了首次绘制。

> ViewRootImpl.java  public void requestLayout() {     if (!mHandlingLayoutInLayoutRequest) {         // 检查线程         checkThread();         mLayoutRequested = true;         // 重点         scheduleTraversals();     } }

ViewRootImpl.scheduleTraversals() 方法在 上篇文章 中详细介绍过,这里大致总结一下:

  1. ViewRootImpl.scheduleTraversals() 方法中会建立同步屏障,优先处理异步消息。通过 Choreographer.postCallback() 方法提交了任务 mTraversalRunnable,这个任务就是负责 View 的测量,布局,绘制。

  2. Choreographer.postCallback() 方法通过 DisplayEventReceiver.nativeScheduleVsync() 方法向系统底层注册了下一次 vsync 信号的监听。当下一次 vsync 来临时,系统会回调其 dispatchVsync() 方法,最终回调 FrameDisplayEventReceiver.onVsync() 方法。

  3. FrameDisplayEventReceiver.onVsync() 方法中取出之前提交的 mTraversalRunnable 并执行。这样就完成了一次绘制流程。

    mTraversalRunnable 中执行的是 doTraversal() 方法。

Android 面试进阶指南目录

计算机网络

  1. http 速查

Android

  1. 面试官:任务栈?返回栈?启动模式?傻傻分不清楚?
  2. 面试官:唠唠 Activity 的生命周期
  3. 面试官: 说一说 Context
  4. 面试官:为什么不能使用 Application Context 显示 Dialog?
  5. 面试官:OutOfMemoryError 可以被 try catch 吗 ?
  6. 面试官:为什么 Activity.finish() 之后 10s 才 onDestroy ?
  7. 面试官:如何监测应用的 FPS ?
  8. 面试官:为什么 View.post 可以获取到视图宽高?

目录

  • 小测试:哪里可以获取到 View 的宽高?
  • View 在什么时间点被测量?
  • 探秘 View.post()
  • 还可以怎么获取视图宽高?
  • 最后

小测试:哪里可以获取到 View 的宽高?

今天的文章会比较轻松,相比前面几篇没有那么大段的源码要啃。关于获取 View 的宽高,我们先来一段测试代码:

class MainActivity : BaseLifecycleActivity() {      private val binding by lazy { ActivityMainBinding.inflate(layoutInflater) }      override fun onCreate(savedInstanceState: Bundle?) {         super.onCreate(savedInstanceState)         setContentView(binding.root)          // 在 onCreate() 中获取宽高         Log.e("measure","measure in onCreate: width=${window.decorView.width}, height=${window.decorView.height}")          // 在 View.post() 回调中获取宽高         binding.activity.post {             Log.e("measure","measure in View.post: width=${window.decorView.width}, height=${window.decorView.height}")         }     }      override fun onResume() {         super.onResume()         // 在 onResume() 回调中获取宽高         Log.e("measure","measure in onResume: width=${window.decorView.width}, height=${window.decorView.height}")     } }

大多数人都能直截了当的给出答案:

E/measure: measure in onCreate: width=0, height=0 E/measure: measure in onResume: width=0, height=0 E/measure: measure in View.post: width=1080, height=2340

onCreate()onResume() 中是无法获取到宽高的,而 View.post() 回调中可以。从日志打印顺序可以看出来,View.post() 回调中的打印语句是最后执行的。

抛开代码来思考一下这个问题,什么时候可以获取到 View 的宽高? 毫无疑问,最起码肯定得在 View 被测量 这个时间点之后。从上面的结果来看,onCreate()onResume() 发生在这个时间点之前,View.post() 的回调发生在这个时间点之后。我们只要搞清楚这个时间点,问题就迎刃而解了。

View 在什么时间点被测量?

大家都知道 View 的绘制流程发生在 onResume 流程(并不是指 Activity.onResume() 回调)中,但我还是决定从头开始说起,大家权当复习一下。

当冷启动 (应用进程不存在) 一个 app 时,首先要和 zygote 建立 socket 连接,将创建进程需要的参数发送给 zygote, zygote 服务端接收到参数之后调用 ZygoteConnection.processOneCommand() 处理参数,并 fork 出应用进程。最后通过 findStaticMain() 找到 ActivityThread 类的 main() 方法并执行,应用进程就启动了。

ActivityThread 虽然不是一个线程类,但它是运行在主线程的,你就把它认为是主线程也没有关系。在 main() 方法中, 创建了 ActivityThread 对象,调用其 attach() 方法,并开启了主线程消息循环,基于事件的消息队列机制就开始工作了。

ActivityThread.attach() 方法中,Binder 调用了 AMS.attachApplication() 方法,其中主要做了两件事:

  1. 将当前进程与 AMS 进行绑定。然后再 Binder 调用回应用进程的 ApplicationThread.bindApplication() 方法,进行客户端的准备工作,创建 Context,创建 Application 等等
  2. mStackSupervisor.attachApplicationLocked(app),最终调用到 realStartActivityLocked() 启动 Activity

Activity 的启动流程就不赘述了,之前写过一篇裹脚布式的文章,庖丁解牛 Activity 启动流程
,感兴趣的可以看看。这里直接跳到 ActivityThread.performLaunchActivity() 方法。

> ActivityThread.java  private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {     ...     // 获取 ComponentName     ComponentName component = r.intent.getComponent();     ...     // 创建 ContextImpl 对象     ContextImpl appContext = createBaseContextForActivity(r);     ...     // 反射创建 Activity 对象     activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);     ......     // 创建 Application 对象     Application app = r.packageInfo.makeApplication(false, mInstrumentation);     ......           // attach 方法中会创建 PhoneWindow 对象     activity.attach(appContext, this, getInstrumentation(), r.token,             r.ident, app, r.intent, r.activityInfo, title, r.parent,             r.embeddedID, r.lastNonConfigurationInstances, config,             r.referrer, r.voiceInteractor, window, r.configCallback);       // 执行 onCreate()     if (r.isPersistable()) {         mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);     } else {         mInstrumentation.callActivityOnCreate(activity, r.state);     }     ...     return activity; }

mInstrumentation.callActivityOnCreate() 方法最终会回调 Activity.onCreate() 方法。到这里,setContentView() 方法就被执行了。setContentView() 逻辑很复杂,但干的事情很直白。创建 DecorView ,然后根据我们传入的布局文件 id 解析 xml,将得到的 view 塞进 DecorView 中。注意,到现在,我们得到的只是一个 空壳子 View 树,它并没有被添加到屏幕上,其实也不能添加到屏幕上。所以,在 onCreate() 回调中获取视图宽高显然是不可取的。

看完 onCreate() , 我们跳过 onStart(),里面没干啥太重要的事情,直接来到 onResume()

注:Activity 的生命周期是由 ClientLifecycleManager 类来调度的,具体原理可以看这篇文章 从源码看 Activity 生命周期 。

> ActivityThread.java  public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,         String reason) {     ...     // 1. 回调 onResume     final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);     ···     View decor = r.window.getDecorView();     decor.setVisibility(View.INVISIBLE);     ViewManager wm = a.getWindowManager();     WindowManager.LayoutParams l = r.window.getAttributes();     // 2. 添加 decorView 到 WindowManager     wm.addView(decor, l);     ... }

两件事,回调 onResume 和 添加 DecorView 到 WindowManager 。所以,在 onResume() 回调中获取 view 的宽高其实和 onCreate() 中没啥区别,都获取不到。

wm.addView(decor, l) 最终调用到 WindowManagerGlobal.addView()

public void addView(View view, ViewGroup.LayoutParams params,         Display display, Window parentWindow) {     ...     // 1. 重点,初始化 ViewRootImpl     root = new ViewRootImpl(view.getContext(), display);     // 2. 重点,发起绘制并显示到屏幕上     root.setView(view, wparams, panelParentView);

这里两行代码都是重中之重。先来看看注释 1 处 ViewRootImpl 的构造函数。

public ViewRootImpl(Context context, Display display) {     ...     // 1. IWindowSession 代理对象,与 WMS 进行 Binder 通信     mWindowSession = WindowManagerGlobal.getWindowSession();     ...     // 2.     mWidth = -1;     mHeight = -1;     ...     // 3. 初始化 AttachInfo     // 记住 mAttachInfo 是在这里被初始化的     mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,             context);     ...     // 4. 初始化 Choreographer,通过 Threadlocal 存储     mChoreographer = Choreographer.getInstance(); }
  1. 初始化 mWindowSession,它可以 WMS 进行 Binder 通信
  2. 这里能看到宽高还未赋值
  3. 初始化 AttachInfo,这里着重记一下,后面会再提到
  4. 初始化 Choreographer,上篇文章 面试官:如何监测应用的 FPS ? 详细介绍过

再看注释 2 处的 ViewRootImpl.setView() 方法。

> ViewRootImpl.java  // 参数 view 就是 DecorView public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {     synchronized (this) {         if (mView == null) {             mView = view;              // 1. 发起首次绘制             requestLayout();              // 2. Binder 调用 Session.addToDisplay(),将 window 添加到屏幕                     res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,                             getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,                             mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,                             mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);              // 3. 将 decorView 的 parent 赋值为 ViewRootImpl             view.assignParent(this);         }     } }

requestLayout() 方法发起了首次绘制。

> ViewRootImpl.java  public void requestLayout() {     if (!mHandlingLayoutInLayoutRequest) {         // 检查线程         checkThread();         mLayoutRequested = true;         // 重点         scheduleTraversals();     } }

ViewRootImpl.scheduleTraversals() 方法在 上篇文章 中详细介绍过,这里大致总结一下:

  1. ViewRootImpl.scheduleTraversals() 方法中会建立同步屏障,优先处理异步消息。通过 Choreographer.postCallback() 方法提交了任务 mTraversalRunnable,这个任务就是负责 View 的测量,布局,绘制。

  2. Choreographer.postCallback() 方法通过 DisplayEventReceiver.nativeScheduleVsync() 方法向系统底层注册了下一次 vsync 信号的监听。当下一次 vsync 来临时,系统会回调其 dispatchVsync() 方法,最终回调 FrameDisplayEventReceiver.onVsync() 方法。

  3. FrameDisplayEventReceiver.onVsync() 方法中取出之前提交的 mTraversalRunnable 并执行。这样就完成了一次绘制流程。

    mTraversalRunnable 中执行的是 doTraversal() 方法。

部分转自互联网,侵权删除联系

赞(0) 打赏
部分文章转自网络,侵权联系删除b2bchain区块链学习技术社区 » 面试官:View.post() 为什么能够获取到 View 的宽高?求职学习资料
分享到: 更多 (0)
D0b2wT.gif

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

b2b链

联系我们联系我们