亚洲精品久久久中文字幕-亚洲精品久久片久久-亚洲精品久久青草-亚洲精品久久婷婷爱久久婷婷-亚洲精品久久午夜香蕉

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

Android View.Post 的原理及缺陷

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

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

讀者可以嘗試以下操作??梢园l(fā)現(xiàn),除了通過(guò) View.post(Runnable)這種方式可以獲得 View 的真實(shí)寬高外,其它方式取得的值都是 0

/** * 作者:leavesC * 時(shí)間: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

從這就可以引申出幾個(gè)疑問(wèn):

View.post(Runnable) 為什么可以得到 View 的真實(shí)寬高 Handler.post(Runnable)和View.post(Runnable)有什么區(qū)別 在 onCreate、onResume 函數(shù)中為什么無(wú)法直接得到 View 的真實(shí)寬高 View.post(Runnable) 中的 Runnable 是由誰(shuí)來(lái)執(zhí)行的,可以保證一定會(huì)被執(zhí)行嗎

后邊就來(lái)一一解答這幾個(gè)疑問(wèn),本文基于 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

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

AttachInfo 是 View 內(nèi)部的一個(gè)靜態(tài)類,其內(nèi)部持有一個(gè) Handler 對(duì)象,從注釋可知它是由 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 的賦值時(shí)機(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)用時(shí)機(jī),可以跟蹤到 ViewRootImpl 類。ViewRootImpl 內(nèi)就包含一個(gè) Handler 對(duì)象 mHandler,并在構(gòu)造函數(shù)中以 mHandler 作為構(gòu)造參數(shù)之一來(lái)初始化 mAttachInfo。ViewRootImpl 的performTraversals()方法就會(huì)調(diào)用 DecorView 的 dispatchAttachedToWindow 方法并傳入 mAttachInfo,從而層層調(diào)用整個(gè)視圖樹中所有 View 的 dispatchAttachedToWindow 方法,使得所有 childView 都能獲取到 mAttachInfo 對(duì)象

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é)啟動(dòng)整個(gè)視圖樹的 Measure、Layout、Draw 流程,只有當(dāng) performLayout 被調(diào)用后 View 才能確定自己的寬高信息。而 performTraversals()本身也是交由 ViewRootHandler 來(lái)調(diào)用的,即整個(gè)視圖樹的繪制任務(wù)也是先插入到 MessageQueue 中,后續(xù)再由主線程取出任務(wù)進(jìn)行執(zhí)行。由于插入到 MessageQueue 中的消息是交由主線程來(lái)順序執(zhí)行的,所以 attachInfo.mHandler.post(action)就保證了 action 一定是在 performTraversals 執(zhí)行完畢后才會(huì)被調(diào)用,因此我們就可以在 Runnable 中獲取到 View 的真實(shí)寬高了

2、HandlerActionQueue

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

HandlerActionQueue 可以看做是一個(gè)專門用于存儲(chǔ) Runnable 的任務(wù)隊(duì)列,mActions 就存儲(chǔ)了所有要執(zhí)行的 Runnable 和相應(yīng)的延時(shí)時(shí)間。兩個(gè)post方法就用于將要執(zhí)行的 Runnable 對(duì)象保存到 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); } } ··· }

所以說(shuō),getRunQueue().post(action)只是將我們提交的 Runnable 對(duì)象保存到了 mActions 中,還需要外部主動(dòng)調(diào)用 executeActions方法來(lái)執(zhí)行任務(wù)

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

@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 的真實(shí)寬高,主要就是因?yàn)榇_保了獲取 View 寬高的操作一定是在 View 繪制完畢之后才被執(zhí)行,而 Handler.post(Runnable)之所以不行,就是其無(wú)法保證這一點(diǎn)

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

三、onCreate & onResume

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

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

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

首先,ActivityThread 的 handleResumeActivity 方法就負(fù)責(zé)來(lái)回調(diào) Activity 的 onResume 方法,且如果當(dāng)前 Activity 是第一次啟動(dòng),則會(huì)向 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; //重點(diǎn) wm.addView(decor, l); } else { a.onWindowAttributesChanged(l); } } } else if (!willBeVisible) { if (localLOGV) Slog.v(TAG, 'Launch ' + r + ' mStartedActivity set'); r.hideForNow = true; }··· }

此處的 ViewManager 的具體實(shí)現(xiàn)類即 WindowManagerImpl,WindowManagerImpl 會(huì)將操作轉(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 就會(huì)完成 ViewRootImpl 的初始化并且調(diào)用其 setView 方法,該方法內(nèi)部就會(huì)再去調(diào)用 performTraversals 方法啟動(dòng)視圖樹的繪制流程

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; } } }

所以說(shuō), performTraversals 方法的調(diào)用時(shí)機(jī)是在 onResume 方法之后,所以我們?cè)?onCreate和onResume 函數(shù)中都無(wú)法獲取到 View 的實(shí)際寬高。當(dāng)然,當(dāng) Activity 在單次生命周期過(guò)程中第二次調(diào)用onResume 方法時(shí)自然就可以獲取到 View 的寬高屬性

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

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

但該結(jié)論也只在 API 24 及之后的版本上才成立,View.post(Runnable) 方法也存在著一個(gè)版本兼容性問(wèn)題,在 API 23 及之前的版本上有著不同的實(shí)現(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 時(shí),會(huì)將 Runnable 保存到 ViewRootImpl 內(nèi)部的一個(gè)靜態(tài)成員變量 sRunQueues 中。而 sRunQueues 內(nèi)部是通過(guò) ThreadLocal 來(lái)保存 RunQueue 的,這意味著不同線程獲取到的 RunQueue 是不同對(duì)象,這也意味著如果我們?cè)谧泳€程中調(diào)用View.post(Runnable) 方法的話,該 Runnable 永遠(yuǎn)不會(huì)被執(zhí)行,因?yàn)橹骶€程根本無(wú)法獲取到子線程的 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)成員變量,主線程會(huì)一直對(duì)應(yīng)同一個(gè) RunQueue 對(duì)象,如果我們是在主線程中調(diào)用View.post(Runnable)方法的話,那么該 Runnable 就會(huì)被添加到和主線程關(guān)聯(lián)的 RunQueue 中,后續(xù)主線程就會(huì)取出該 Runnable 來(lái)執(zhí)行

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

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

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

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

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

標(biāo)簽: Android
相關(guān)文章:
主站蜘蛛池模板: 欧美日韩第三页 | 黑人巨大vs北条麻妃在线播放 | 亚洲精品aⅴ一区二区三区 亚洲精品aⅴ中文字幕乱码 | 成人蜜桃视频网站网址 | 国产91区精品福利在线社区 | 可以直接看黄的网站 | 久久综合精品国产一区二区三区无 | 欧美成人免费xxx大片 | 特黄的欧美毛片 | 成人精品免费视频 | 2021久久精品国产99国产精品 | 国产裸舞福利资源在线视频 | 亚洲色图图 | 免费一级毛片在线视频观看 | 九九在线精品视频播放 | 免费看欧美毛片大片免费看 | 国产精品成 | 自怕偷自怕亚洲精品 | 三级全黄在线观看www桃花 | 国产日产亚洲系列首页 | 黄色一级毛片免费 | 国产午夜精品久久久久九九 | 99视频九九精品视频在线观看 | 色悠久久久久综合欧美99 | 亚洲一区在线免费观看 | 国产福利一区二区在线观看 | 欧洲美女粗暴交视频 | 欧洲成人免费高清视频 | 午夜精品在线 | 国产成人精品日本亚洲18图 | 国模午夜写真福利视频在线 | 国产综合福利 | 欧美成人观看免费完全 | 亚洲十欧美十日韩十国产 | 老司机51精品视频在线观看 | 国内自拍视频在线看免费观看 | 特级黄色片视频 | 777精品成人影院 | 麻豆免费视频网站入口 | 久久黄色影院 | 国产99久久九九精品免费 |