當前位置:網站首頁>Android-面試官:View-post()-為什麼能够獲取到-View-的寬高-?
Android-面試官:View-post()-為什麼能够獲取到-View-的寬高-?
2022-01-27 15:22:37 【m0_66155412】
…
// 獲取 ComponentName
ComponentName component = r.intent.getComponent();
…
// 創建 ContextImpl 對象
ContextImpl appContext = createBaseContextForActivity;
…
// 反射創建 Activity 對象
activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
…
// 創建 Application 對象
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
…
// attach 方法中會創建 PhoneWindow 對象
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor, window, r.configCallback);
// 執行 onCreate()
if (r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
mInstrumentation.callActivityOnCreate(activity, r.state);
}
…
return activity;
}
mInstrumentation.callActivityOnCreate()
方法最終會回調 Activity.onCreate()
方法。到這裏,setContentView()
方法就被執行了。setContentView()
邏輯很複雜,但幹的事情很直白。創建 DecorView
,然後根據我們傳入的布局文件 id 解析 xml,將得到的 view 塞進 DecorView 中。注意,到現在,我們得到的只是一個 空殼子 View 樹,它並沒有被添加到屏幕上,其實也不能添加到屏幕上。所以,在 onCreate()
回調中獲取視圖寬高顯然是不可取的。
看完 onCreate()
, 我們跳過 onStart()
,裏面沒幹啥太重要的事情,直接來到 onResume()
。
注:Activity 的生命周期是由
ClientLifecycleManager
類來調度的,具體原理可以看這篇文章 從源碼看 Activity 生命周期 。
ActivityThread.java
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
String reason) {
…
// 1. 回調 onResume
final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
···
View decor = r.window.getDec
orView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
// 2. 添加 decorView 到 WindowManager
wm.addView(decor, l);
…
}
兩件事,回調 onResume
和 添加 DecorView 到 WindowManager 。所以,在 onResume()
回調中獲取 view 的寬高其實和 onCreate()
中沒啥區別,都獲取不到。
wm.addView(decor, l)
最終調用到 WindowManagerGlobal.addView()
。
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
…
// 1. 重點,初始化 ViewRootImpl
root = new ViewRootImpl(view.getContext(), display);
// 2. 重點,發起繪制並顯示到屏幕上
root.setView(view, wparams, panelParentView);
這裏兩行代碼都是重中之重。先來看看注釋 1 處 ViewRootImpl
的構造函數。
public ViewRootImpl(Context context, Display display) {
…
// 1. IWindowSession 代理對象,與 WMS 進行 Binder 通信
mWindowSession = WindowManagerGlobal.getWindowSession();
…
// 2.
mWidth = -1;
mHeight = -1;
…
// 3. 初始化 AttachInfo
// 記住 mAttachInfo 是在這裏被初始化的
mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,
context);
…
// 4. 初始化 Choreographer,通過 Threadlocal 存儲
mChoreographer = Choreographer.getInstance();
}
- 初始化 mWindowSession,它可以 WMS 進行 Binder 通信
- 這裏能看到寬高還未賦值
- 初始化 AttachInfo,這裏著重記一下,後面會再提到
- 初始化 Choreographer,上篇文章 面試官:如何監測應用的 FPS ? 詳細介紹過
再看注釋 2 處的 ViewRootImpl.setView()
方法。
ViewRootImpl.java
// 參數 view 就是 DecorView
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
// 1. 發起首次繪制
requestLayout();
// 2. Binder 調用 Session.addToDisplay(),將 window 添加到屏幕
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);
// 3. 將 decorView 的 parent 賦值為 ViewRootImpl
view.assignParent(this);
}
}
}
requestLayout()
方法發起了首次繪制。
ViewRootImpl.java
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
// 檢查線程
checkThread();
mLayoutRequested = true;
// 重點
scheduleTraversals();
}
}
ViewRootImpl.scheduleTraversals()
方法在 上篇文章 中詳細介紹過,這裏大致總結一下:
ViewRootImpl.scheduleTraversals()
方法中會建立同步屏障,優先處理异步消息。通過Choreographer.postCallback()
方法提交了任務mTraversalRunnable
,這個任務就是負責 View 的測量,布局,繪制。Choreographer.postCallback()
方法通過DisplayEventReceiver.nativeScheduleVsync()
方法向系統底層注册了下一次vsync
信號的監聽。當下一次vsync
來臨時,系統會回調其dispatchVsync()
方法,最終回調FrameDisplayEventReceiver.onVsync()
方法。FrameDisplayEventReceiver.onVsync()
方法中取出之前提交的mTraversalRunnable
並執行。這樣就完成了一次繪制流程。
mTraversalRunnable
中執行的是 doTraversal()
方法。
ViewRootImpl.java
void doTraversal() {
if (mTraversalScheduled) {
// 1. mTraversalScheduled 置為 false
mTraversalScheduled = false;
// 2. 移除同步屏障
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
// 3. 開始布局,測量,繪制流程
performTraversals();
…
}
ViewRootImpl.java
private void performTraversals() {
…
// 1. 綁定 Window,重點記憶一下
host.dispatchAttachedToWindow(mAttachInfo, 0);
mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);
getRunQueue().executeActions(mAttachInfo.mHandler);
// 2. 請求 WMS 計算窗口大小
relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
// 3. 測量
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
// 4. 布局
performLayout(lp, mWidth, mHeight);
// 5. 繪制
performDraw();
}
performTraversals()
方法的邏輯甚是複雜,這裏精簡出幾個重要的方法調用。到這裏,View 的整體繪制流程已經完成,毫無疑問,在這個時候肯定是可以獲取到寬高的。
View 被測量的時機已經找到了。現在就來驗證一下 View.post()
是不是在這個時機執行回調的。
探秘 View.post()
View.java
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
// 1. attachInfo 不為空,通過 mHandler 發送
return attachInfo.mHandler.post(action);
}
// 2. attachInfo 為空,放入隊列中
getRunQueue().post(action);
return true;
}
這裏的關鍵是 attachInfo
是否為空。在上一節中介紹過,再來回顧一下:
attachInfo
是在ViewRootImpl
的構造函數中初始化的,ViewRootImpl
是在WindowManagerGlobal.addView()
創建的WindowManagerGlobal.addView()
是在 ActivityThread 的handleResumeActivity()
中調用的,但是是在Activity.onResume()
回調之後
所以,如果 attachInfo
不為空的話,至少已經處在進行視圖繪制的這次消息處理當中。把 post()
方法要執行的 Runnable 利用 Handler 發送出去,當包含這個 Runnable 的 Message 被執行時,是一定可以獲取到 View 的寬高的。
在 onCreate()
和 onResume()
這兩個回調中,attachInfo
肯定是空的,這時候就要依賴 getRunQueue().post(action)
。原理也很簡單,把 post()
方法要執行的 Runnable 存儲在一個隊列中,在合適的時機(View 已被測量)拿出來執行。先來看看 getRunQueue()
拿到的是一個什麼隊列。
View.java
private HandlerActionQueue getRunQueue() {
if (mRunQueue == null) {
mRunQueue = new HandlerActionQueue();
}
return mRunQueue;
}
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);
}
}
}
隊列 HandlerActionQueue
是一個初始容量是 4 的 HandlerAction
數組。HandlerAction 有兩個成員變量,要執行的 Runnable 和延遲執行的時間。
隊列的執行邏輯在 executeActions(handler)
方法中,通過傳入的 handler 進行任務分發。現在我們只要找到 executeActions()
的調用時機就可以了。在 View.java
中就可以找到,在 dispatchAttachedToWindow()
方法中分發了任務。
Action);
}
}
}
隊列 HandlerActionQueue
是一個初始容量是 4 的 HandlerAction
數組。HandlerAction 有兩個成員變量,要執行的 Runnable 和延遲執行的時間。
隊列的執行邏輯在 executeActions(handler)
方法中,通過傳入的 handler 進行任務分發。現在我們只要找到 executeActions()
的調用時機就可以了。在 View.java
中就可以找到,在 dispatchAttachedToWindow()
方法中分發了任務。
版權聲明
本文為[m0_66155412]所創,轉載請帶上原文鏈接,感謝
https://cht.chowdera.com/2022/01/202201271522370019.html
邊欄推薦
- 編輯vim時突然大部分鍵失靈,只剩下空格還能用時,猜猜原因?
- cmseasy&內網滲透 Writeup
- 【翻譯】Packet 的旅行 — 主機間通過路由器通信
- 數據庫中間件MyCat實戰筆記(第三步),怒斬騰訊和阿裏的Offer
- Fate部署實戰——從零開始實現Fate cluster部署
- 南開大學 | AbiU-Net:基於Transformer的非對稱雙邊U-Net提昇顯著性目標檢測
- 活久見!TCP兩次揮手,你見過嗎?那四次握手呢?
- MQL5 細則手册:保存基於指定標准的“EA 交易”的優化結果
- 【RDS】RDS MySQL如何保證數據庫字符編碼正確?
- @JoinTable會自動删除關聯錶的數據
猜你喜歡
-
Oracle(四):和SQL*Plus命令來場邂逅
-
stm32+mpu6050+四元數解算
-
ScheduledExecutorService scheduleAtFixedRate、scheduleWithFixedDelay以及創建定時心跳
-
跨文化研究:遊戲可能在維持文化多樣性方面扮演著重要角色
-
mysql導出數據庫文檔
-
軟件測試周刊(第47期):要愛具體的人,不要愛抽象的人;要愛生活,不要愛生活的意義。
-
Linux Mysql之中間件Mycat 讀寫分離
-
73.網絡安全滲透測試—[SQL注入篇12]—[SQLSEVER+ASP-視圖查詢報錯注入]
-
Openstack實驗之nova服務的安裝與配置
-
LWM2M設備接入
隨機推薦
- Jedis連接阿裏雲redis
- APP違法使用個人信息?不用怕,華為雲VSS為你保駕護航
- 基於STL的演講比賽流程管理系統
- 地址的地址?
- 百度地圖開發-搭建基礎脚手架 01
- uniapp上傳圖片及組件傳值
- php獲取gmt時間及時區修改
- lnmp 三之haproxy的使用
- 華為手機USB連不上電腦的解决方法
- Alink & FlinkMLlib 文章匯總
- 關於用ffmpeg轉手機視頻發現視頻長寬倒了的問題
- 函數 / 類模板--模板2
- NoNodeAvailableException[None of the configured nodes are available: [{#transport#-1}····
- 一場面試結束,某度員工從事Android 5年為何還是初級工程師?
- CISP——關於網絡安全法(分享筆記)
- 通過ReentrantLock源碼看AQS源碼實現
- 記錄在appA裏面打開appB進行登錄,再次點擊桌面圖標appB避免再次重新啟動程序的解决辦法
- 解决延遲有 Wi-Fi 6 就够了!(家裏的 Wi)
- JS中的forEach()和map()方法介紹
- 【ISO15765_UDS&OBD診斷】-01-概述
- 【Unity Shader】HDRP下Amplify Shader Editor透明物體排序不正確
- yum -bash: /usr/bin/yum: /usr/bin/: bad interpreter: Permission denied
- 惡意軟件分析實戰20-內核級軟件逆向Lab10-2
- 220121--測試用例
- 北京大學2022年對元宇宙的全球研究報告
- [渝粵教育] 華中農業大學 經濟學原理 參考 資料
- #全網寒假最火特輯# 【第一章】 C語言之牛客網刷題筆記 【點進來保證讓知識充實你一整個寒假】
- Vulnhub靶機recon: 1滲透
- C語言第一課
- 各比特大佬,Spark的重點難點系列暫時更新完畢
- 基於 esbuild 的 universal bundler 設計
- 亞線性的近似最小支撐樹
- JDBC編碼六步走
- ZZULIOJ 1173: 密碼解密(指針專題)
- 143. 重排鏈錶
- 掃雷初階版
- 【Pytorch(四)】學習如何使用 PyTorch 讀取並處理數據集
- 多模態生成模型ERNIE-VILG
- [渝粵教育] 東南大學 工程熱力學 參考 資料
- 如何將PDF轉換成Word文檔?試試這款PDF轉Word工具—PDF to Word OCR