日本不卡不码高清免费观看,久久国产精品久久w女人spa,黄色aa久久,三上悠亚国产精品一区二区三区

您的位置:首頁技術(shù)文章
文章詳情頁

Android View.Post 的原理及缺陷

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

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

讀者可以嘗試以下操作。可以發(fā)現(xiàn),除了通過 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)有什么區(qū)別 在 onCreate、onResume 函數(shù)中為什么無法直接得到 View 的真實寬高 View.post(Runnable) 中的 Runnable 是由誰來執(zhí)行的,可以保證一定會被執(zhí)行嗎

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

一、View.post(Runnable)

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

如果 mAttachInfo 不為 null,則將 Runnable 交由mAttachInfo內(nèi)部的 Handler 進(jìn)行處理 如果 mAttachInfo 為 null,則將 Runnable 交由 HandlerActionQueue 進(jìn)行處理

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 內(nèi)部的一個靜態(tài)類,其內(nèi)部持有一個 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 的賦值時機(jī)可以追蹤到 View 的 dispatchAttachedToWindow 方法,該方法被調(diào)用就意味著 View 已經(jīng) Attach 到 Window 上了

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

再查找dispatchAttachedToWindow 方法的調(diào)用時機(jī),可以跟蹤到 ViewRootImpl 類。ViewRootImpl 內(nèi)就包含一個 Handler 對象 mHandler,并在構(gòu)造函數(shù)中以 mHandler 作為構(gòu)造參數(shù)之一來初始化 mAttachInfo。ViewRootImpl 的performTraversals()方法就會調(diào)用 DecorView 的 dispatchAttachedToWindow 方法并傳入 mAttachInfo,從而層層調(diào)用整個視圖樹中所有 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()方法也負(fù)責(zé)啟動整個視圖樹的 Measure、Layout、Draw 流程,只有當(dāng) performLayout 被調(diào)用后 View 才能確定自己的寬高信息。而 performTraversals()本身也是交由 ViewRootHandler 來調(diào)用的,即整個視圖樹的繪制任務(wù)也是先插入到 MessageQueue 中,后續(xù)再由主線程取出任務(wù)進(jìn)行執(zhí)行。由于插入到 MessageQueue 中的消息是交由主線程來順序執(zhí)行的,所以 attachInfo.mHandler.post(action)就保證了 action 一定是在 performTraversals 執(zhí)行完畢后才會被調(diào)用,因此我們就可以在 Runnable 中獲取到 View 的真實寬高了

2、HandlerActionQueue

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

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

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 中,還需要外部主動調(diào)用 executeActions方法來執(zhí)行任務(wù)

而這個主動執(zhí)行任務(wù)的操作也是由 View 的 dispatchAttachedToWindow來完成的,從而使得 mActions 中的所有任務(wù)都會被插入到 mHandler 的 MessageQueue 中,等到主線程執(zhí)行完 performTraversals() 方法后就會來執(zhí)行 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)有什么區(qū)別呢?

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

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

三、onCreate & onResume

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

從結(jié)果反推原因,這說明當(dāng) onCreate、onResume被回調(diào)時 ViewRootImpl 的 performTraversals()方法還未執(zhí)行,那么performTraversals()方法的具體執(zhí)行時機(jī)是什么時候呢?

這可以從 ActivityThread -> WindowManagerImpl -> WindowManagerGlobal -> ViewRootImpl 這條調(diào)用鏈上找到答案

首先,ActivityThread 的 handleResumeActivity 方法就負(fù)責(zé)來回調(diào) Activity 的 onResume 方法,且如果當(dāng)前 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 的具體實現(xiàn)類即 WindowManagerImpl,WindowManagerImpl 會將操作轉(zhuǎn)交給 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 的初始化并且調(diào)用其 setView 方法,該方法內(nèi)部就會再去調(diào)用 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 方法的調(diào)用時機(jī)是在 onResume 方法之后,所以我們在 onCreate和onResume 函數(shù)中都無法獲取到 View 的實際寬高。當(dāng)然,當(dāng) Activity 在單次生命周期過程中第二次調(diào)用onResume 方法時自然就可以獲取到 View 的寬高屬性

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

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

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

//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 及之前的版本上,當(dāng) attachInfo 為 null 時,會將 Runnable 保存到 ViewRootImpl 內(nèi)部的一個靜態(tài)成員變量 sRunQueues 中。而 sRunQueues 內(nèi)部是通過 ThreadLocal 來保存 RunQueue 的,這意味著不同線程獲取到的 RunQueue 是不同對象,這也意味著如果我們在子線程中調(diào)用View.post(Runnable) 方法的話,該 Runnable 永遠(yuǎn)不會被執(zhí)行,因為主線程根本無法獲取到子線程的 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 是靜態(tài)成員變量,主線程會一直對應(yīng)同一個 RunQueue 對象,如果我們是在主線程中調(diào)用View.post(Runnable)方法的話,那么該 Runnable 就會被添加到和主線程關(guān)聯(lián)的 RunQueue 中,后續(xù)主線程就會取出該 Runnable 來執(zhí)行

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

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

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

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

以上就是Android View.Post 的原理及缺陷的詳細(xì)內(nèi)容,更多關(guān)于Android View.Post的資料請關(guān)注好吧啦網(wǎng)其它相關(guān)文章!

標(biāo)簽: Android
相關(guān)文章:
日本不卡不码高清免费观看,久久国产精品久久w女人spa,黄色aa久久,三上悠亚国产精品一区二区三区
久久精品影视| 夜夜嗨一区二区三区| 亚洲久久视频| 五月亚洲婷婷 | 日精品一区二区三区| 欧美精品激情| 免费人成在线不卡| 国产精品日本一区二区三区在线 | 99久精品视频在线观看视频| 99免费精品| 亚洲深深色噜噜狠狠爱网站| 欧美亚洲一区二区三区| 国产精品成人自拍| 中文另类视频| 亚洲一二av| 美女国产精品久久久| 久久精品免费一区二区三区| 欧美成人精品三级网站| 日韩激情一区| 香蕉成人久久| 99视频精品| 亚洲精品自拍| 日韩高清在线观看一区二区| 日韩二区在线观看| 国产亚洲精aa在线看| 国产色噜噜噜91在线精品| 欧美日韩亚洲一区三区| 国产亚洲精品美女久久| 国产精品22p| 国际精品欧美精品| 日韩欧美字幕| 日韩视频二区| 日韩中文欧美在线| 日韩不卡一区二区| 国产精品一国产精品k频道56| 亚洲丝袜美腿一区| 亚洲免费毛片| 国产私拍福利精品视频二区| 中文字幕色婷婷在线视频| 91视频久久| 免费观看久久av| 天堂va在线高清一区| 国产精久久久| 亚洲精品一区三区三区在线观看| 国产在线日韩| 免费在线观看精品| 国产日本亚洲| 美女视频黄久久| 香蕉成人av| 久久国产精品亚洲77777| 日韩高清二区| 裤袜国产欧美精品一区| 亚洲一区国产| 你懂的亚洲视频| 免费av一区| 国产精品a级| 婷婷丁香综合| 国产亚洲精品美女久久久久久久久久| 精品九九久久| 国产亚洲在线| 国产精品99精品一区二区三区∴| 人人精品亚洲| 久久国产精品毛片| 欧美黑人巨大videos精品| 成人日韩在线| 男女激情视频一区| 日韩精品一区二区三区免费视频| 清纯唯美亚洲综合一区| 捆绑调教美女网站视频一区 | 91精品一区| 日韩三区免费| 日韩高清一区在线| 亚洲成人av观看| 欧美日韩夜夜| 香蕉精品视频在线观看| 天堂俺去俺来也www久久婷婷| 免费一级欧美片在线观看网站 | 国产不卡一区| 午夜天堂精品久久久久| 午夜精品成人av| 欧美日本一区| 99亚洲精品| 国精品产品一区| 欧美www视频在线观看| 免费看日韩精品| 91tv亚洲精品香蕉国产一区| 国产精品久久久久毛片大屁完整版| 宅男噜噜噜66国产日韩在线观看| 国产毛片精品| 中文字幕一区二区三区在线视频| 欧洲av一区二区| 另类欧美日韩国产在线| 日韩av中文字幕一区| 私拍精品福利视频在线一区| 国产欧美一区二区三区精品观看| 激情视频一区二区三区| 久久精品在线| 亚洲成人不卡| 99视频精品视频高清免费| 福利视频一区| 日韩毛片在线| 亚洲美女久久精品| 成人在线网站| 91麻豆国产自产在线观看亚洲| 精品亚洲成人| 久久a爱视频| 国产乱人伦精品一区| 国产亚洲欧美日韩精品一区二区三区 | 国产精品美女久久久浪潮软件| 日韩专区精品| 国产精品欧美一区二区三区不卡| 日韩欧美精品一区二区综合视频| 亚洲欧美日韩国产| 伊人久久亚洲美女图片| 91精品一区二区三区综合| 精品不卡一区| 国产精品久久| 国产精一区二区| 欧美午夜三级| 国产在线观看91一区二区三区| 精品九九久久| 国产色播av在线| 日韩欧美在线中字| 91嫩草亚洲精品| 福利一区二区三区视频在线观看| 欧美日韩网址| 欧美日韩亚洲一区二区三区在线| 日本中文字幕一区二区视频| 亚洲精品在线a| 日韩精品亚洲专区在线观看| 日本一区二区三区视频在线看| 乱人伦精品视频在线观看| 黄色成人精品网站| 午夜国产精品视频免费体验区| 国产一区亚洲| 伊人久久亚洲影院| 亚洲欧洲一区| 欧美精品一二| 国内精品99| 国产精品99久久精品| 日韩电影在线视频| 亚洲国产日韩欧美在线| 水蜜桃久久夜色精品一区的特点| 亚洲九九精品| 国产极品嫩模在线观看91精品| 久久麻豆视频| 在线观看精品| 好吊一区二区三区| 在线视频亚洲欧美中文| 精品99在线| 日韩网站在线| 麻豆mv在线观看| 男人的天堂亚洲一区| 午夜在线精品偷拍| 另类综合日韩欧美亚洲| 久久久久九九精品影院| 在线一区欧美| 欧美日一区二区三区在线观看国产免| 日本高清不卡一区二区三区视频| 男人天堂欧美日韩| 中文在线а√在线8| 国产模特精品视频久久久久| 国产一区二区三区四区大秀| 午夜av一区| 久久国产人妖系列| 桃色av一区二区| 六月婷婷一区| 黄色网一区二区| 99国产精品私拍| 国产精品调教视频| 欧美xxxx中国| 中文字幕色婷婷在线视频| 综合欧美精品| 国产美女高潮在线观看| 综合亚洲自拍| 国产高潮在线| 亚洲精品伊人| 天堂网av成人| 国产亚洲精品美女久久| 99国产精品免费视频观看| 奇米777国产一区国产二区| 久久大逼视频| 日韩一区电影| 久久精品 人人爱| 亚洲国产成人精品女人| 欧美在线不卡| 美女毛片一区二区三区四区| 国产精品第一国产精品| 欧美日韩免费观看一区=区三区| 国产精品4hu.www| 久久精品中文| 群体交乱之放荡娇妻一区二区| 日本精品国产| 国产精品99一区二区| 久久久久亚洲精品中文字幕| 黄色免费成人| 黑森林国产精品av| 欧美精品观看| 香蕉精品999视频一区二区| 日韩av二区|