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

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

Android View.Post 的原理及缺陷

瀏覽:16日期: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
相關文章:
日本不卡不码高清免费观看,久久国产精品久久w女人spa,黄色aa久久,三上悠亚国产精品一区二区三区
亚洲激情av| 成人日韩精品| 久久精品日韩欧美| 国产精品网址| 欧美国产极品| 国产精品v亚洲精品v日韩精品| 综合激情一区| 国产福利亚洲| 国产欧美日韩影院| 久久精品伊人| 亚洲一级在线| 三上悠亚国产精品一区二区三区 | 免费看黄色91| 免费成人在线影院| 久久精品99国产精品| 国产精品videosex极品| 水蜜桃精品av一区二区| 亚洲性图久久| 日韩av资源网| 91日韩在线| 香蕉久久久久久久av网站| 五月国产精品| 日本一区二区高清不卡| 激情中国色综合| 尤物在线精品| 日韩国产欧美在线视频| 黄色网一区二区| 国产一级久久| 免费精品一区| 国产精品毛片| 国产精品久久久久久久久久齐齐| 婷婷激情一区| 日韩avvvv在线播放| 成人午夜精品| 欧美日一区二区三区在线观看国产免| 麻豆成全视频免费观看在线看| 午夜日韩福利| 精品成av人一区二区三区| 久久久久蜜桃| 国产精品啊v在线| 日韩视频在线一区二区三区| 麻豆一区在线| 欧美专区一区二区三区| 狠狠躁少妇一区二区三区| 亚洲深深色噜噜狠狠爱网站| 偷拍精品精品一区二区三区| 日韩高清不卡在线| 亚洲激情另类| 日韩电影免费网站| 四虎精品一区二区免费| 激情欧美亚洲| 国产精品原创| 国产精品一国产精品| 亚洲色诱最新| 欧美va天堂在线| 国产精品成人a在线观看| 日韩精品久久久久久| 亚洲欧美日韩国产综合精品二区 | 国产v综合v| 久久久久久色 | 91精品啪在线观看国产18| 国产精品对白| 日本99精品| 国产亚洲久久| 91大神在线观看线路一区| 免费在线观看成人| 亚洲一区欧美| 亚洲激情黄色| 亚洲精品1区| 午夜宅男久久久| 一区二区三区四区精品视频| 色综合视频一区二区三区日韩 | 国产中文字幕一区二区三区| 国产精品成人**免费视频| 国产欧美精品久久| 国产伦一区二区三区| 91成人精品观看| 麻豆高清免费国产一区| 久久精品国产亚洲aⅴ| 91免费精品| 在线视频观看日韩| 欧美日韩国产精品一区二区亚洲| 日韩在线卡一卡二| 日韩精品国产欧美| 久久av免费| 国产成人精品免费视| 亚洲成人日韩| 欧美一区精品| 国产成人精品亚洲日本在线观看| 欧美日韩国产探花| 国产日韩精品视频一区二区三区| sm久久捆绑调教精品一区| 欧美日韩一二| 欧美精品中文字幕亚洲专区| 国产成人a视频高清在线观看| 日韩一区三区| 免费视频久久| 国产一区2区| 蜜芽一区二区三区| 精品国产午夜| 亚洲一区二区三区四区五区午夜| 国产精品美女午夜爽爽| 久久中文字幕av一区二区不卡| 影音先锋久久精品| 精品三级国产| 亚洲综合福利| 激情综合自拍| 国产精品一区二区av交换 | 自拍日韩欧美| 中文字幕高清在线播放| 日本在线成人| 欧美另类综合| 国产传媒av在线| 欧美日本一区| 九九综合九九| 日韩久久精品网| 国产精品欧美三级在线观看 | 欧洲亚洲一区二区三区| 久久国产精品免费精品3p| 男女精品网站| 亚洲精品国产偷自在线观看| 日韩精品诱惑一区?区三区| 国产精品日本一区二区三区在线| 亚洲精品一级| 1000部精品久久久久久久久| 精品91福利视频| 麻豆国产一区| 国产精品成人国产| 国产精品日韩精品在线播放| 日韩激情一二三区| 亚州欧美在线| 欧美一级网址| 日本午夜免费一区二区| 综合干狼人综合首页| 夜夜嗨网站十八久久| 欧美日韩一区二区三区视频播放| 成人亚洲一区二区| 欧美一级鲁丝片| 久久久久久婷| 中文字幕日韩亚洲| 亚洲黄色影院| 欧美日韩xxxx| 精品一区电影| 日韩欧美精品一区| 欧美精品一区二区久久| 黄色日韩在线| 日韩一区中文| 国产极品一区| 欧美aⅴ一区二区三区视频| 高清一区二区| 日韩在线a电影| 久久这里只有| 亚洲激精日韩激精欧美精品| 美女久久久久久| 精精国产xxxx视频在线野外| 国产 日韩 欧美 综合 一区| 亚洲先锋成人| 69堂精品视频在线播放| 黄色成人91| 国产欧美日韩视频在线| 久久中文字幕二区| 国产精品一区二区av日韩在线| 丁香六月综合| 丝袜美腿一区二区三区| 红杏一区二区三区| 日本在线视频一区二区| 99久久婷婷| 国产美女精品视频免费播放软件| 久草精品视频| 老牛影视一区二区三区| 成人国产精选| 亚洲精品在线二区| 播放一区二区| 国产色99精品9i| 午夜在线观看免费一区| 久久九九精品| 久久中文在线| 国产情侣久久| 亚洲区国产区| 怡红院精品视频在线观看极品| 国产中文欧美日韩在线| 五月天综合网站| 日韩高清中文字幕一区| 久久中文字幕av一区二区不卡| 丝袜美腿成人在线| 日本欧美国产| 国产欧美综合一区二区三区| 精品欧美久久| 97欧美在线视频| 国产精品成人一区二区网站软件| 9色国产精品| 久久九九精品| 精品欠久久久中文字幕加勒比| 亚洲精品在线国产| 免费久久久久久久久| 成人精品高清在线视频| 日本少妇精品亚洲第一区| 亚洲欧美日韩精品一区二区| 久久精品二区三区| 国产精品白浆|