登录

去注册

登录

注册

去登录

注册

每日一问 在Activity 的 onResume 方法中 handler.postRunnable 能获取到 View 宽高吗?

xiaoyang   2019-07-09   收藏

如果不能,为什么不能,怎么写可以呢?

8
不能,MessageQueue同步障碍机制: 可以发现就是把一条Message,注意这个Message是没有设置target的,整个消息循环唯一一处不设置回调的target(hander),因为这个即使标志了同步障碍消息,也是不需要handler来pushMessage到队列中,直接手动循环移动链表插入到合适time的Message之后的即可。

scheduleTraversals中设置了同步障碍消息,就是相当于在MessageQueue中插入了一个Message,并且是在onResume之后插入的,所以在onResume中handler.post(Runnable)之后,这个消息会在同步障碍Message之前,会先被执行,这个时候依然没有刷新绘制界面,待查询到同步障碍Message时候,会等待下个异步Message(刷新Message)出现。

所以在onResume中handler.post(Runnable)是Ui操作失效的。

View.post(Runnable)就可以获取View的宽高 
View.class

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.
   getRunQueue().post(action);
   return true;
}

由于在onResume中执行,这个时候ViewRootImpl还没有初始化(addView时),而mAttachInfo是在ViewRootImpl构造函数中初始化的,过此时mAttachInfo=null,从上文知道 getRunQueue()维护了一个mRunQueue 队列,然后在dispatchAttachedToWindow通过mRunQueue.executeActions(info.mHandler);那这个方法dispatchAttachedToWindow什么会被调用,回顾上文中ViewRootImpl第一次收到Vsync同步刷新信号之后会执行performTraversals,这个函数内部做了个判断当时第一次mFirst时候会调用host.dispatchAttachedToWindow(mAttachInfo, 0);把全局mAttachInfo下发给所有子View,其源码如下: 


View.class

void dispatchAttachedToWindow(AttachInfo info, int visibility) {
   mAttachInfo = info;
   if (mOverlay != null) {
   //向下分发info,其实现在ViewGroup中
       mOverlay.getOverlayView().dispatchAttachedToWindow(info, visibility);
   }
   mWindowAttachCount++;
   // We will need to evaluate the drawable state at least once
.........
   // Transfer all pending runnables.
   if (mRunQueue != null) {
       mRunQueue.executeActions(info.mHandler);
       mRunQueue = null;
   }
   performCollectViewAttributes(mAttachInfo, visibility);
   onAttachedToWindow();
   ........
}

可以看到这个函数同时执行了 mRunQueue.executeActions(info.mHandler);从上文可知就是通过hander把mRunQueue中任务全部push到主线程中。

由此可以知道在performTraversals(Message)中push Message到主线中,肯定会这个performTraversals(Message)之后再执行,并且在doTraversals中移除了同步障碍消息(Message),故会依次执行。所以onResume中View.post的Message就会在performTraversals之后执行,而performTraversals就是完成了View整个测量、布局和绘制。当View的mAttachInfo !=null时也说明肯定完成过UI绘制。

回复
1

View的绘制过程大概如下(包含部分Activity启动过程)

  • 在IApplicationThread收到启动Activity的消息时,在performLaunchActivity中通过Instrumentation#newActivity创建Activity,在newActivity中PhoneWindow以及对应的DecorView
  • 接着执行handleResumeActivity->performResumeActivity,在handleResumeActivity处理Activity生命周期,在handleResumeActivity中通过WM添加Activity的window,同时创建一个ViewRootImpl与DecordView绑定。在ViewRootImpl与DecordView绑定后,ViewRootImpl发送同步障碍消息,然后通过Choreographer发送异步消息,等待下一个VSYNC到来时执行performTraversals开始绘制

onResume与ViewRootImpl与DecordView绑定两者顺序问题

  • performResumeActivity是在handleResumeActivity中被调用
  • 生命周期方法onResume是在performResumeActivity中触发
  • ViewRootImpl与DecordView绑定是在handleResumeActivity中,在performResumeActivity之后

因此可以知道onResume在ViewRootImpl与DecordView绑定之前执行

onResume中调用handler.postRunnable的消息是否在异步消息之前执行

  • Handler收到同步障碍消息后(target = null的消息)会优先执行异步消息
  • handler.postRunnable的消息何时会执行?当前面的同步消息都执行完了,轮到它的时候,它就会被执行
  • 因此handler.postRunnable可能在异步消息前执行完毕,也可能由于阻塞导致handler.postRunnable的消息在异步消息来之前都还没执行,就会在异步消息后执行

总结

通过在Activity 的 onResume 方法中 handler.postRunnable 能获取到 View 宽高是不可靠的

(水平有限,欢迎纠正,讨论)

回复
1

不能

为什么呢?

关于SyncBarrier机制,@yif111同学已经讲的很详细了。
---------------------------------------------------------------------------------------------------------------------------------------------------------
我们知道,启动Activity时会经过ActivityThread的handleLaunchActivity方法,在这个方法中,
会先调用performLaunchActivity方法(Activity的onCreate,onStart,onRestoreInstanceState、onPostCreate等方法都会在这里面调用)
当performResumeActivity执行完成之后,会接着调用handleResumeActivity方法:

final void handleResumeActivity() {

......

final Activity a = r.activity;

r = performResumeActivity(token, clearHide, reason);

ViewManager wm = a.getWindowManager();

View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);

wm.addView(decor, l);

......
}

可以看到,在WindowManager添加DecorView之前,还调用了一个performResumeActivity方法,来看看:

public final ActivityClientRecord performResumeActivity() {

......

if (r != null && !r.activity.mFinished) {

......

try {
r.activity.onStateNotSaved();

r.activity.performResume();
} catch (Exception e) {
}
}

......

return r;
}

它是先回调了Activity的onStateNotSaved方法,然后调用performResume方法,这个performResume方法里面会先回调onRestart(如果mStopped为true的话),接着回调onResume,最后到onPostResume。
也就是说,onResume方法在WindowManager添加DecorView之前,就已经先回调了。

----------------------------------------------------------------------------------------------------------------------------------------------------------

那要怎么样才能获取到呢?

用其他方法获取,@tjie00同学已经说了。


但如果非要用这个方法,能获取到吗?

也不是完全没有办法,就像你在家里肚子饿的很,爸爸准备回来,你让爸爸回来的时候顺便买点吃的。半个小时后,你等不及了,饿到肚子痛,然后直接下楼去吃了。
那我们也可以按照这个思路去做:
既然onResume方法回调时,DecorView还没测量,还没layout,那就手动帮它测量和布局咯:
DecorView一般情况它的宽高都是等于屏幕宽高,在这里我们可以直接获取到屏幕的宽高给它了

//获取屏幕尺寸
val screenSize = Point()
windowManager.defaultDisplay.getSize(screenSize)
//提前测量和布局
val rootView = window.decorView
rootView.measure(
View.MeasureSpec.makeMeasureSpec(screenSize.x, View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(screenSize.y, View.MeasureSpec.EXACTLY)
)
rootView.layout(0, 0, rootView.measuredWidth, rootView.measuredHeight)

Handler().post {
LogUtil.print("${view.width}, ${view.height}")
}

这样做,宽高肯定是可以获取到的。要说副作用的话,就有点多此一举咯,就好像爸爸在你下楼刚点完菜之后,就带吃的回来了,但是当你回家后已经不饿了,那买回来的东西,就浪费啦。

回复
nanchen2251 : @陈小缘 

最后这个比喻,相当相当有意思了。

2019-07-08 回复
1

不一定,如果在post之前还有其他处理,post有可能获取到view的宽高,如果之前的处理时间短,则也可能获取不到。

因为performTraversals是在onResume之后执行,如果还没有执行到指定view的onMeasure(实际是setMeasuredDimension)则获取到的是0.

可以采用ViewTreeObserver, view.getViewTreeObserver .addOnGlobalLayoutListener。

记得获取到宽高后移除监听器,要不然会反复调用。

回复
1

7月 8 号周,1/3。  

回复
0

看了各位楼主的回答,我写了小demo实验了一下,发现在onCreate()、onResume()方法使用handler.post(Runnable)方法都是可以获取到view的宽高的,目前正在找原因,如果有人知道,请告知一声

回复
RangoBai : @RangoBai 

试了很多次,都是可以的

2019-07-17 回复
0

经过学习重新回答一下。


分析

  1. 什么时候能够获取到 view 的宽高

答:当 view 完成测量之后就能获取到宽高尺寸

  1. View 什么时候开始测量流程测量流程的出发点在哪里

答:

  1. PhoneWindow - setContentView() 生成并初始化 DecorView 以及添加 ActivtionBar ContentView
  2. activity 执行完 onresume 后使用 WindowManager addView DecorView 添加至 PhoneWinow(其实调用的时WindowManagerGlobaladdView)
  3. WindowManagerGlobal 初始化了 ViewRootImp ,调用了 addView
  4. ViewRootImp 发送了一个 Handler 同步障碍的异步消息,在异步回调中对DecorView 进行了测量、布局、绘制。
  1. 流程清楚了,onResume执行在前,view 的测量执行在后,那么在 onResume 中是拿不到 view 的尺寸地。

                经过代码测试直接在 onResume 方法中 getWidth 获取到的值为 0 ,也就是获取不到尺寸。

  1. 那么 hadler.postRunable 能获取到吗?按照上述的验证来说也是获取不到的。

                代码测试,在 onResume 中使用 handler.post() 方法也是获取不到 view 的尺寸的。


附上觉得同步障碍讲的比较好的文章 https://blog.csdn.net/asdgbc/article/details/79148180

回复
RangoBai : @白日依山尽_ 

你是怎么测试的?,在 onResume 中使用 handler.post() 方法也是获取不到 view 的尺寸的。我也测试了一下,我是可以获取到View的宽高的  ...查看更多

2019-07-17 回复
0

为什么代码测试是能够获取的到的?这是一个答案是不一定吗?



回复
0

<premenlo';font-size:10.5pt;">override fun onResume() {
super.onResume()

Log.e("tag", "handler onResume 获取width${imageView.width}")
Log.e("tag", "handler onResume 获取measureWidth${imageView.height}")

val handler = Handler()
handler.post {
Log.e("tag", "handler onResume 获取width${imageView.width}")
Log.e("tag", "handler onResume 获取height${imageView.height}")
}

imageView.post {
Log.e("tag", "view.post onResume 获取width${imageView.width}")
Log.e("tag", "view.post onResume 获取height${imageView.height}")
}
}</premenlo';font-size:10.5pt;">

<premenlo';font-size:10.5pt;">代码测试都获取到了 难道是我写的有问题?</premenlo';font-size:10.5pt;">


回复
0

原本对于yif111是持怀疑态度的,仔细一读以及回味知识,确实如此。。

ActivitytThread.handleResumeActivity()中,先执行onResume(), 再在执行 addWindow().

所以即便没有栅栏机制,也是不能获取到宽高的,毕竟view还没有进行绘制

栅栏机制其实是一种“异步”消息,message没有target,在当前messageQueue插入(以当前时间)这么一条消息

@hide

public Handler(boolean async); // async =true 则此handle发送消息的为异步

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}

MessageQueue.next的时候,如果执行到一条异步消息,之后就会优先执行其他异步消息(原本同步消息是按照时间顺序) 直到没有异步消息。。 if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}

message会寻找就近的异步消息,而忽略排在其前面的同步消息!使得

invalidate(),requestLayout()的View会更快被执行

回复
0

我觉得不靠谱

还是用view.post比较靠谱

回复

删除留言

确认删除留言,会导致相关评论丢失?

取消 确定