电脑知识|欧美黑人一区二区三区|软件|欧美黑人一级爽快片淫片高清|系统|欧美黑人狂野猛交老妇|数据库|服务器|编程开发|网络运营|知识问答|技术教程文章 - 好吧啦网

您的位置:首頁技術文章
文章詳情頁

Android View.Post 的原理及缺陷

瀏覽:3日期:2022-09-20 14:20:57

很多開發者都了解這么一個知識點:在 Activity 的 onCreate 方法里我們無法直接獲取到 View 的寬高信息,但通過 View.post(Runnable)這種方式就可以,那背后的具體原因你是否有了解過呢?

讀者可以嘗試以下操作。可以發現,除了通過 View.post(Runnable)這種方式可以獲得 View 的真實寬高外,其它方式取得的值都是 0

/** * 作者:leavesC * 時間:2020/03/14 11:05 * 描述: * GitHub:https://github.com/leavesC */class MainActivity : AppCompatActivity() { private val view by lazy { findViewById<View>(R.id.view) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) getWidthHeight('onCreate') view.post { getWidthHeight('view.Post') } Handler().post { getWidthHeight('handler') } } override fun onResume() { super.onResume() getWidthHeight('onResume') } private fun getWidthHeight(tag: String) { Log.e(tag, 'width: ' + view.width) Log.e(tag, 'height: ' + view.height) }}

github.leavesc.view E/onCreate: width: 0github.leavesc.view E/onCreate: height: 0github.leavesc.view E/onResume: width: 0github.leavesc.view E/onResume: height: 0github.leavesc.view E/handler: width: 0github.leavesc.view E/handler: height: 0github.leavesc.view E/view.Post: width: 263github.leavesc.view E/view.Post: height: 263

從這就可以引申出幾個疑問:

View.post(Runnable) 為什么可以得到 View 的真實寬高 Handler.post(Runnable)和View.post(Runnable)有什么區別 在 onCreate、onResume 函數中為什么無法直接得到 View 的真實寬高 View.post(Runnable) 中的 Runnable 是由誰來執行的,可以保證一定會被執行嗎

后邊就來一一解答這幾個疑問,本文基于 Android API 30 進行分析

一、View.post(Runnable)

看下 View.post(Runnable) 的方法簽名,可以看出 Runnable 的處理邏輯分為兩種:

如果 mAttachInfo 不為 null,則將 Runnable 交由mAttachInfo內部的 Handler 進行處理 如果 mAttachInfo 為 null,則將 Runnable 交由 HandlerActionQueue 進行處理

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; } private HandlerActionQueue getRunQueue() { if (mRunQueue == null) { mRunQueue = new HandlerActionQueue(); } return mRunQueue; }1、AttachInfo

先來看View.post(Runnable)的第一種處理邏輯

AttachInfo 是 View 內部的一個靜態類,其內部持有一個 Handler 對象,從注釋可知它是由 ViewRootImpl 提供的

final static class AttachInfo { /** * A Handler supplied by a view’s {@link android.view.ViewRootImpl}. This * handler can be used to pump events in the UI events queue. */ @UnsupportedAppUsage final Handler mHandler; AttachInfo(IWindowSession session, IWindow window, Display display, ViewRootImpl viewRootImpl, Handler handler, Callbacks effectPlayer, Context context) { ··· mHandler = handler; ··· } ···}

查找 mAttachInfo 的賦值時機可以追蹤到 View 的 dispatchAttachedToWindow 方法,該方法被調用就意味著 View 已經 Attach 到 Window 上了

@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) void dispatchAttachedToWindow(AttachInfo info, int visibility) { mAttachInfo = info; ··· }

再查找dispatchAttachedToWindow 方法的調用時機,可以跟蹤到 ViewRootImpl 類。ViewRootImpl 內就包含一個 Handler 對象 mHandler,并在構造函數中以 mHandler 作為構造參數之一來初始化 mAttachInfo。ViewRootImpl 的performTraversals()方法就會調用 DecorView 的 dispatchAttachedToWindow 方法并傳入 mAttachInfo,從而層層調用整個視圖樹中所有 View 的 dispatchAttachedToWindow 方法,使得所有 childView 都能獲取到 mAttachInfo 對象

final ViewRootHandler mHandler = new ViewRootHandler(); public ViewRootImpl(Context context, Display display, IWindowSession session, boolean useSfChoreographer) { ··· mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this, context); ··· } private void performTraversals() { ··· if (mFirst) { ··· host.dispatchAttachedToWindow(mAttachInfo, 0); ··· } ··· performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); performLayout(lp, mWidth, mHeight); performDraw(); ··· }

此外,performTraversals()方法也負責啟動整個視圖樹的 Measure、Layout、Draw 流程,只有當 performLayout 被調用后 View 才能確定自己的寬高信息。而 performTraversals()本身也是交由 ViewRootHandler 來調用的,即整個視圖樹的繪制任務也是先插入到 MessageQueue 中,后續再由主線程取出任務進行執行。由于插入到 MessageQueue 中的消息是交由主線程來順序執行的,所以 attachInfo.mHandler.post(action)就保證了 action 一定是在 performTraversals 執行完畢后才會被調用,因此我們就可以在 Runnable 中獲取到 View 的真實寬高了

2、HandlerActionQueue

再來看View.post(Runnable)的第二種處理邏輯

HandlerActionQueue 可以看做是一個專門用于存儲 Runnable 的任務隊列,mActions 就存儲了所有要執行的 Runnable 和相應的延時時間。兩個post方法就用于將要執行的 Runnable 對象保存到 mActions中,executeActions就負責將mActions中的所有任務提交給 Handler 執行

public class HandlerActionQueue { private HandlerAction[] mActions; private int mCount; 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 void executeActions(Handler handler) { synchronized (this) { final HandlerAction[] actions = mActions; for (int i = 0, count = mCount; i < count; i++) { final HandlerAction handlerAction = actions[i]; handler.postDelayed(handlerAction.action, handlerAction.delay); } mActions = null; mCount = 0; } } private static class HandlerAction { final Runnable action; final long delay; public HandlerAction(Runnable action, long delay) { this.action = action; this.delay = delay; } public boolean matches(Runnable otherAction) { return otherAction == null && action == null || action != null && action.equals(otherAction); } } ··· }

所以說,getRunQueue().post(action)只是將我們提交的 Runnable 對象保存到了 mActions 中,還需要外部主動調用 executeActions方法來執行任務

而這個主動執行任務的操作也是由 View 的 dispatchAttachedToWindow來完成的,從而使得 mActions 中的所有任務都會被插入到 mHandler 的 MessageQueue 中,等到主線程執行完 performTraversals() 方法后就會來執行 mActions,所以此時我們依然可以獲取到 View 的真實寬高

@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) void dispatchAttachedToWindow(AttachInfo info, int visibility) { mAttachInfo = info; ··· // Transfer all pending runnables. if (mRunQueue != null) { mRunQueue.executeActions(info.mHandler); mRunQueue = null; } ··· }二、Handler.post(Runnable)

Handler.post(Runnable)和View.post(Runnable)有什么區別呢?

從上面的源碼分析就可以知道,View.post(Runnable)之所以可以獲取到 View 的真實寬高,主要就是因為確保了獲取 View 寬高的操作一定是在 View 繪制完畢之后才被執行,而 Handler.post(Runnable)之所以不行,就是其無法保證這一點

雖然這兩種post(Runnable)的操作都是往同個 MessageQueue 插入任務,且最終都是交由主線程來執行。但繪制視圖樹的任務是在onResume被回調后才被提交的,所以我們在onCreate中用 Handler 提交的任務就會早于繪制視圖樹的任務被執行,因此也就無法獲取到 View 的真實寬高了

三、onCreate & onResume

在 onCreate、onResume 函數中為什么無法也直接得到 View 的真實寬高呢?

從結果反推原因,這說明當 onCreate、onResume被回調時 ViewRootImpl 的 performTraversals()方法還未執行,那么performTraversals()方法的具體執行時機是什么時候呢?

這可以從 ActivityThread -> WindowManagerImpl -> WindowManagerGlobal -> ViewRootImpl 這條調用鏈上找到答案

首先,ActivityThread 的 handleResumeActivity 方法就負責來回調 Activity 的 onResume 方法,且如果當前 Activity 是第一次啟動,則會向 ViewManager(wm)添加 DecorView

@Override public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward, String reason) { ··· //Activity 的 onResume 方法 final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason); ··· if (r.window == null && !a.mFinished && willBeVisible) { ··· ViewManager wm = a.getWindowManager(); if (a.mVisibleFromClient) { if (!a.mWindowAdded) { a.mWindowAdded = true; //重點 wm.addView(decor, l); } else { a.onWindowAttributesChanged(l); } } } else if (!willBeVisible) { if (localLOGV) Slog.v(TAG, 'Launch ' + r + ' mStartedActivity set'); r.hideForNow = true; }··· }

此處的 ViewManager 的具體實現類即 WindowManagerImpl,WindowManagerImpl 會將操作轉交給 WindowManagerGlobal

@UnsupportedAppUsage private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();@Override public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) { applyDefaultToken(params); mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow, mContext.getUserId()); }

WindowManagerGlobal 就會完成 ViewRootImpl 的初始化并且調用其 setView 方法,該方法內部就會再去調用 performTraversals 方法啟動視圖樹的繪制流程

public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow, int userId) { ··· ViewRootImpl root; View panelParentView = null; synchronized (mLock) { ··· root = new ViewRootImpl(view.getContext(), display); view.setLayoutParams(wparams); mViews.add(view); mRoots.add(root); mParams.add(wparams); // do this last because it fires off messages to start doing things try { root.setView(view, wparams, panelParentView, userId); } catch (RuntimeException e) { // BadTokenException or InvalidDisplayException, clean up. if (index >= 0) { removeViewLocked(index, true); } throw e; } } }

所以說, performTraversals 方法的調用時機是在 onResume 方法之后,所以我們在 onCreate和onResume 函數中都無法獲取到 View 的實際寬高。當然,當 Activity 在單次生命周期過程中第二次調用onResume 方法時自然就可以獲取到 View 的寬高屬性

四、View.post(Runnable) 的兼容性

從以上分析可以得出一個結論:由于 View.post(Runnable)最終都是往和主線程關聯的 MessageQueue 中插入任務且最終由主線程來順序執行,所以即使我們是在子線程中調用View.post(Runnable),最終也可以得到 View 正確的寬高值

但該結論也只在 API 24 及之后的版本上才成立,View.post(Runnable) 方法也存在著一個版本兼容性問題,在 API 23 及之前的版本上有著不同的實現方式

//Android API 24 及之后的版本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; }//Android API 23 及之前的版本public boolean post(Runnable action) { final AttachInfo attachInfo = mAttachInfo; if (attachInfo != null) { return attachInfo.mHandler.post(action); } // Assume that post will succeed later ViewRootImpl.getRunQueue().post(action); return true; }

在 Android API 23 及之前的版本上,當 attachInfo 為 null 時,會將 Runnable 保存到 ViewRootImpl 內部的一個靜態成員變量 sRunQueues 中。而 sRunQueues 內部是通過 ThreadLocal 來保存 RunQueue 的,這意味著不同線程獲取到的 RunQueue 是不同對象,這也意味著如果我們在子線程中調用View.post(Runnable) 方法的話,該 Runnable 永遠不會被執行,因為主線程根本無法獲取到子線程的 RunQueue

static final ThreadLocal<RunQueue> sRunQueues = new ThreadLocal<RunQueue>();static RunQueue getRunQueue() { RunQueue rq = sRunQueues.get(); if (rq != null) { return rq; } rq = new RunQueue(); sRunQueues.set(rq); return rq; }

此外,由于sRunQueues 是靜態成員變量,主線程會一直對應同一個 RunQueue 對象,如果我們是在主線程中調用View.post(Runnable)方法的話,那么該 Runnable 就會被添加到和主線程關聯的 RunQueue 中,后續主線程就會取出該 Runnable 來執行

即使該 View 是我們直接 new 出來的對象(就像以下的示例),以上結論依然生效,當系統需要繪制其它視圖的時候就會順便取出該任務,一般很快就會執行到。當然,由于此時 View 并沒有 AttachedToWindow,所以獲取到的寬高值肯定也是 0

val view = View(Context) view.post { getWidthHeight('view.Post') }

對View.post(Runnable)方法的兼容性問題做下總結:

當 API < 24 時,如果是在主線程進行調用,那么不管 View 是否有 AttachedToWindow,提交的 Runnable 均會被執行。但只有在 View 被 AttachedToWindow 的情況下才可以獲取到 View 的真實寬高 當 API < 24 時,如果是在子線程進行調用,那么不管 View 是否有 AttachedToWindow,提交的 Runnable 都將永遠不會被執行 當 API >= 24 時,不管是在主線程還是子線程進行調用,只要 View 被 AttachedToWindow 后,提交的 Runnable 都會被執行,且都可以獲取到 View 的真實寬高值。如果沒有被 AttachedToWindow 的話,Runnable 也將永遠不會被執行

以上就是Android View.Post 的原理及缺陷的詳細內容,更多關于Android View.Post的資料請關注好吧啦網其它相關文章!

標簽: Android
相關文章:
主站蜘蛛池模板: 超声波成孔成槽质量检测仪-压浆机-桥梁预应力智能张拉设备-上海硕冠检测设备有限公司 | 标准品网_标准品信息网_【中检计量】 | Eiafans.com_环评爱好者 环评网|环评论坛|环评报告公示网|竣工环保验收公示网|环保验收报告公示网|环保自主验收公示|环评公示网|环保公示网|注册环评工程师|环境影响评价|环评师|规划环评|环评报告|环评考试网|环评论坛 - Powered by Discuz! | 哈尔滨京科脑康神经内科医院-哈尔滨治疗头痛医院-哈尔滨治疗癫痫康复医院 | 上海单片机培训|重庆曙海培训分支机构—CortexM3+uC/OS培训班,北京linux培训,Windows驱动开发培训|上海IC版图设计,西安linux培训,北京汽车电子EMC培训,ARM培训,MTK培训,Android培训 | 上海诺狮景观规划设计有限公司| 防水套管|柔性防水套管|伸缩器|伸缩接头|传力接头-河南伟创管道 防水套管_柔性防水套管_刚性防水套管-巩义市润达管道设备制造有限公司 | 冷热冲击试验箱_温度冲击试验箱价格_冷热冲击箱排名_林频厂家 | 生物颗粒燃烧机-生物质燃烧机-热风炉-生物颗粒蒸汽发生器-丽水市久凯能源设备有限公司 | 天津蒸汽/热水锅炉-电锅炉安装维修直销厂家-天津鑫淼暖通设备有限公司 | 世纪豪门官网 世纪豪门集成吊顶加盟电话 世纪豪门售后电话 | 合肥地磅_合肥数控切割机_安徽地磅厂家_合肥世佳电工设备有限公司 | 智慧旅游_智慧景区_微景通-智慧旅游景区解决方案提供商 | 江苏全风,高压风机,全风环保风机,全风环形高压风机,防爆高压风机厂家-江苏全风环保科技有限公司(官网) | SOUNDWELL 编码器|电位器|旋转编码器|可调电位器|编码开关厂家-广东升威电子制品有限公司 | SDI车窗夹力测试仪-KEMKRAFT方向盘测试仪-上海爱泽工业设备有限公司 | 真丝围巾|真丝丝巾|羊绒围巾|围巾品牌|浙江越缇围巾厂家定制 | 金联宇电缆总代理-金联宇集团-广东金联宇电缆实业有限公司 | 圆盘鞋底注塑机_连帮鞋底成型注塑机-温州天钢机械有限公司 | 磁力抛光研磨机_超声波清洗机厂家_去毛刺设备-中锐达数控 | 赛尔特智能移动阳光房-阳光房厂家-赛尔特建筑科技(广东)有限公司 | 【365公司转让网】公司求购|转让|资质买卖_股权转让交易平台 | 上海洗地机-洗地机厂家-全自动洗地机-手推式洗地机-上海滢皓洗地机 | 定坤静电科技静电消除器厂家-除静电设备 | 无锡网站建设_小程序制作_网站设计公司_无锡网络公司_网站制作 | 交变/复合盐雾试验箱-高低温冲击试验箱_安奈设备产品供应杭州/江苏南京/安徽马鞍山合肥等全国各地 | 恒温振荡混匀器-微孔板振荡器厂家-多管涡旋混匀器厂家-合肥艾本森(www.17world.net) | 路斯特伺服驱动器维修,伦茨伺服驱动器维修|万骏自动化百科 | 鑫铭东办公家具一站式定制采购-深圳办公家具厂家直销 | 聚天冬氨酸,亚氨基二琥珀酸四钠,PASP,IDS - 远联化工 | 空气能暖气片,暖气片厂家,山东暖气片,临沂暖气片-临沂永超暖通设备有限公司 | 阳光模拟试验箱_高低温试验箱_高低温冲击试验箱_快速温变试验箱|东莞市赛思检测设备有限公司 | 防潮防水通风密闭门源头实力厂家 - 北京酷思帝克门窗 | 耐压仪-高压耐压仪|徐吉电气| 跨境物流_美国卡派_中大件运输_尾程派送_海外仓一件代发 - 广州环至美供应链平台 | 中央空调维修、中央空调保养、螺杆压缩机维修-苏州东菱空调 | 房车价格_依维柯/大通/东风御风/福特全顺/江铃图片_云梯搬家车厂家-程力专用汽车股份有限公司 | 圈酒招商网【jiushuitv.com】_酒水招商_代理_加盟平台 | 蜂窝块状沸石分子筛-吸附脱硫分子筛-萍乡市捷龙环保科技有限公司 | 洗地机-全自动/手推式洗地机-扫地车厂家_扬子清洁设备 | 苏州防水公司_厂房屋面外墙防水_地下室卫生间防水堵漏-苏州伊诺尔防水工程有限公司 |