UiAutomator2 原理介紹 + 原始碼走讀
前言
如果 TesterHome 觀看不方便(目錄不方便),可以去:https://wenjie.store/archives/uiautomator2 看;TesterHome 有些格式問題可能還沒來得及改
- 看完本篇部落格你可能會了解以下事情:
- uiautomator2 原理基本介紹(常見的 uiautomator2 init 命令、預設的點選方案&坑)
- UI 自動化穩定性問題解決(偽解決),適用場景,不適用怎麼辦(對比百度、位元組、面試公司撈到的一些資訊)
- 或許可以幫你定位框架偶現的一些問題
PS:文中的物件 hash 如果出現上下文不一致的情況不要見怪,因為 usb 線有些不穩定,重新除錯時物件 hash 就會發生變化
uiautomator2 運作鏈路
- 首先你需要知道構成 uiautomator2 整體運作的倉庫其實總共有三個:
- python client 層:https://github.com/openatx/uiautomator2
- Android server 層:https://github.com/openatx/atx-agent
- Android App+Server 驅動:https://github.com/openatx/android-uiautomator-server
- 當然了,還有一些第三方庫,比如:minicap、minitouch
- 其中 android-uiautomator-server 雖然是一個倉庫,但實際打出來的包是兩個,從 github ci 的指令碼中也可以看出來這一點,如下圖所示:
-
- 兩個 APK 作用各有不同,不過自動化控制元件 dump、預設點選操作這些都是在 test 字尾的 apk 中
- 整體運作鏈路如下圖所示:
- 基本上整篇文章都是圍繞這個呼叫鏈路進行除錯 + 講解,現在沒看懂也不要緊,跟著程式碼走一遍就清楚了,接下來看看常用的一些介面都做了什麼。
uiautomator2 init 做了什麼
- 要測試這個功能,只需要給
__main__.py
加上init
的啟動引數即可,如下圖所示: - 在進入
cmd_init
前,我們可以看下傳進來的預設引數,如下圖所示: -
- 需要注意的其實只有預設傳的
--addr 127.0.0.1:7912
,這個並非在 python 層使用的,而是後續傳給 atx-agent 作為啟動引數使用
- 需要注意的其實只有預設傳的
- 接下來正式進入
cmd_init
函式了,可以看到一開始如果沒有指定裝置序列號的話,會自動遍歷所有裝置並初始化: - 我們繼續跟進
install
函式,核心邏輯如下圖所示: -
install
的邏輯其實有很多是重合的,下面只挑一些有差異的點來看-
下載邏輯:
- 首先無論你在國內外,下載連結都會被暴力替換成映象地址(我一開始還以為有啥別的判斷,結果沒有),程式碼如下圖所示:
- cache_download 就是先判斷快取有沒有已經下載的,有的話就判斷檔案資訊是否正確,正確就直接拿來用,這裡不再展開有興趣的可以自己看看
- 後續的 minicap、atx-agent、2 個 apk 都是透過這樣的方式下載
- 下載完後都會被 push 到
/data/local/tmp/
目錄下,程式碼如下圖所示:
-
apk 版本校驗:
- 主要就是校驗了版本號、簽名,還有類似安裝時間的警告,程式碼如下:
- apk 的資訊則是透過
pm path
、dumpsys package
獲取的,下面展示部分程式碼:
-
apk 安裝:
- 因為包含了 debug 的包,所以會加
-t
的引數,如下圖所示: - 需要注意的是這裡會解除安裝掉原來的 APP 的,而 minicap、minitouch、atx-agent 不會刪除,個人猜測是因為 app 有的情況下無法覆蓋安裝,需要先解除安裝,而可執行檔案沒有這個問題。
- 因為包含了 debug 的包,所以會加
-
atx-agent 啟動:
- 這裡只簡單看下啟動引數的含義(golang 程式碼中都有其含義),程式碼如下圖所示:
-
server
:表示啟動 atx-agent 內建的 server -
--nouia
:帶上此參數列示啟動 atx-agent 時,不要把 uiautomator 也拉起來 -
-d
:表示後臺執行 -
--addr
:指定監聽的ip:port
-
埠對映:
- 上面的 atx-agent 雖然啟動完成了,atx-agent 也能獲取到手機的 ip,那麼後續 python client 直接使用
ip:7912
請求就完了?實際上並沒有,中間還進行了一次埠對映,python client 使用的其實是對映後的埠 - 說白了就是執行了一次
adb forward tcp:本地電腦隨機埠 tcp:7912
,這個命令很好理解,比如{本地電腦隨機埠}是 8080,那麼你請求127.0.0.1:8080
就等於在請求手機ip:7912:
,程式碼實現如下:
- 上面的 atx-agent 雖然啟動完成了,atx-agent 也能獲取到手機的 ip,那麼後續 python client 直接使用
-
uiautomator 啟動方式:
- 如果你看過 APP 層的程式碼,你一定會很疑惑為什麼會有一堆程式碼放在
androidTest
下,並且還引入了 JUnit 框架,如下圖所示: - 實際上當我們去看 atx-agent 啟動邏輯就明白了,假設我們啟動的時候去掉
--nouia
引數,就表示啟動 atx-agent 時也啟動 uiautomator 服務,此時 golang 程式碼中fNoUiautomator
的值為 false,如下圖所示: - 之後的邏輯中會新增一個啟動 uiautomator 的任務程式碼,就是透過
am instrument
啟動單元測試的命令列,如下圖所示: - 上面的程式碼只是新增了一個任務,實際上還沒執行,到後面判斷
!*fNoUiautomator
為 True 時才會執行,如下圖所示: - 此時如果你嘗試用 kill 命令殺掉 uiautomator 程序,會殺不掉:
- 難道這是
am instrument
的神奇力量嗎?並不是,實際上是因為 atx-agent 使用 goroutine 寫了個死迴圈佔有程序,要退出迴圈釋放程序的話只能自己傳入中斷引數,最後還是使用 kill 命令殺掉程序的,這會在後面的【重置 uiautomator_v2 如何進行】處講到。
- 如果你看過 APP 層的程式碼,你一定會很疑惑為什麼會有一堆程式碼放在
-
下載邏輯:
- 如何 debug 在 Android 中的 golang 程式碼我之後補充
adb forward 的好處
這部分比較偏向猜想,覺得不對歡迎補充
- 先說一個東西,叫
內網穿透
,你可以試著訪問這個頁面(隨緣線上):https://wenjie.store/chat/,如果成功的話說明你可以間接使用我 4090 的算力了 - 說白了我就是使用
內網穿透
使得你可以透過一個公網的伺服器訪問到我本地的物理主機,比如上面的連結,你實際上能訪問到的是我在自己電腦部署的 ChatGLM2(這東西總不能是一臺 1c1g 的電腦能跑起來的) - OK,這時候問題來了,既然我可以透過
內網穿透
訪問到電腦主機,那麼手機是不是也可以?答案是肯定的
以下操作看不懂就 SKIP 吧,你只需要知道能透過外網 adb 連線手機即可
- 我們可以做一做實驗,先來對 Android 手機進行內網穿透,大概就是下面這個樣子
- 我的小米手機先插上一臺 Ubuntu,使用 adb shell 啟動 atx-agent,便於之後有東西可以訪問
- 老牌手機 adb connect 的埠預設是 5555,但目前我的小米比較奇葩要手動開啟無線模式才可用 adb connect 連線,且埠不為 5555+ 每次開關都會變:
-
- 小米的 wifi 連線只支援已配對的裝置,沒配對的裝置是連不上去的,內網穿透也一樣
- 不管如何,對這個埠做對映即可(伺服器的埠也記得開):
- 手機啟動內網穿透:
- 然後試著在另一臺 win 電腦上使用 adb connect 連線,可以看到使用外網訪問完全沒問題:
- 牛逼的就要來了,我在 win 電腦上執行
adb forward
,具體命令如下圖所示: - 之後我可以透過 win 電腦瀏覽器輸入
localhost:8888/info
就訪問到手機上執行的 atx-agent 服務了,如下圖所示: - 到這裡相信
adb forward
的好處已經體現出來了,假如你的裝置是透過某種代理的手段(如內網穿透)開放出來的,那麼 uiautomator 預設獲取的網路卡 IP 就只是內網 IP,如果你不在這內網之中而是透過代理手段訪問的,那返回給你的內網的 IP 你是肯定無法訪問的 - 而
adb forward
的強大之處就在於它不會出現獲取錯 IP 這種情況,並且我上面的操作中,雲端無論是 8888 埠還是 7912 埠的防火牆都是開著的(生效著的),這還意味著adb forward
本身能透過長連線繞過一些規則
PS:你可能會說我都知道
adb connect
的 ip 和 port 了,那我直接訪問不就完了?如果你問出這個問題,那你可能還沒完全理解上面的意思。在知道遠端手機 ip:port 的情況下,如果直接使用ip:7912/info
訪問,是必須要開啟防火牆 7912 埠的,而我上面使用adb forward
根本就沒開啟。
- OK,到此為止
uiautomator2 init
指令的流程就基本解釋清楚了,uiautomator2 stop
就不多說了,有個意料之外的地方在於它沒有停下 atx-agent。 - 下面的篇幅基本就是看一些常見函式的呼叫鏈路了,上面一不小心費了點口水導致開頭的流程圖還沒用上,下面應該就開始對上了。
click 流程
- 注意講的這裡是執行
uiautomator2 purge
後,再執行如下程式碼走的click
邏輯:
import uiautomator2 as u2
if __name__ == '__main__':
d = u2.connect_usb(serial="af80d1e4") # connect to device
d(text="首頁").click(timeout=3)
關於 u2.connect_usb 就不過多講解了,返回的 Devices 物件裡面由多個父類介面組合而成,click 函式也是眾多父類的實現之一
d(text="首頁") 做了什麼
-
d(text="首頁")
其實只做了一些包裝物件的工作,但如果你在這之前執行過 UI 自動化,你會發現此時有些引數怪怪的,即便你之後執行了uiautomator2 purge
把東西都解除安裝乾淨了,接下來就一步步去看 - 首先是
d
的初始化,實際上就是包裝了一個 UIObject,而傳進去的 Selector 其實也只是一層引數包裝: - UIObject 就是對 session、selector、jsonrpc 包裝,咋看之下好像沒啥問題,但當你檢視
session.address
屬性時,你會發現已經存在 ip 埠了: - 而此時你在手機裡試圖尋找 atx-agent 的程序,會發現並不存在(如果存在可能是你訪問了其它屬性):
上面的 session 不要在斷點時展開所有屬性,否則你會發現展開得很慢,因為有些屬性是透過請求 atx-agent 獲取的,而發現 atx-agent 程序不在時,就會自動拉起,正常的啟動邏輯不是這樣的。而只獲取 address 屬性不會有這個問題。
- 在這裡我直接先說結論,之所以 atx-agent 不存在就有 ip+port,是因為 uiautomator 的邏輯裡面會直接複用之前轉發到手機端 7912 的埠,後續 atx-agent 是固定死 7912 埠的所以不會有問題
- 而先前說的
uiautomator2 purge
只是解除安裝 APP+ 可執行檔案,並沒有刪除埠轉發,我們可以使用adb forward --list
檢視已存在的埠對映,會發現正好等於上面獲取到的 port: - 我們可以持續跟進 address 屬性的獲取邏輯,看看是不是這樣:
- 在前面
uiautomator2 init
的流程中是先啟動 atx-agent server 再進行埠對映的,但實際上先進行埠對映也沒關係,因為 atx-agent server 的埠固定 7912,只要保證 jsonrpc 請求前對映到就行。
click(timeout=3) 做了什麼
- 在正式 debug 程式碼前,我先說明一些環境問題,比如你剛進入 click 的斷點時,會發現控制檯的物件一直在載入(前提是你前面的步驟沒有誤啟動過 atx-agent,且之後執行
uiautomator purge
清理),像下面這樣這樣: - 當載入完成後,會發現手機上 atx-agent 也啟動了:
- 那有沒有辦法不讓它成功啟動 atx-agent 呢?有,我目前只想出一個愚鈍的方法,那就是不停地刪除,在 PC 端執行如下指令碼:
while true
do
adb shell rm -rf /data/local/tmp/atx-agent
sleep 0.01
done
- 直接貼進 PC 命令列視窗,然後回車就行(如果還是成功啟動就把 sleep 那行刪掉),如下圖所示:
- 這樣即便 debug 模式中因為特有的屬性訪問而導致嘗試拉起 atx-agent,也可以在下載後、啟動前刪除,不過記得在真正啟動 atx-agent 之前終止停止命令(ctrl+c 即可)
- 我們先進入 click 中的第一個函式
must_wait
,這個函式預設就是在規定時間內看指定元素是否存在,程式碼如下: - 我們繼續跟進上面的
wait
函式,會發現裡面其實是 jsonrpc 的呼叫: - 但不要忘了,我們正常流程下 agx-agent 還沒啟動呢,所以繼續要繼續深入 jsonrpc 的邏輯,在除錯的過程中有一段程式碼可能會讓你產生誤解,如下圖所示:
- 繼續看
_AgentRequestSession#request
的實現,終於發現初始化 atx-agent 的程式碼了: - 到這裡為止就可以停止先前執行的迴圈刪除 atx-agent 的指令碼了,至於
_prepare_atx_agent
的執行邏輯我想應該不用多說太多,最終還是會執行到前面uiautomator2 init
提到的setup_atx_agent
函式,所以啟動引數啥的都是一樣的,呼叫棧如下圖所示: - 之後就是真正的去請求了,只不過還是會請求失敗,失敗的原因我們可以看下 golang 的程式碼(是 debug 手機的 atx-agent,非本地的),如下圖所示:
- 實際上這段 golang 程式碼就是將所有
/jsonrpc/0
的請求都轉發到127.0.0.1:9008
,上面程式碼遮住了可能看不清,下面看下完整的: - 轉發失敗後控制檯也有列印:
- 到這裡你可能就要問了,為啥固定轉發 9008 埠呢,實際上這段邏輯在 APP 層,這裡可以先貼出程式碼看看:
- 上面的介面因為嘗試轉發到 APP 上,但是因為 APP 程序還不存在,所以返回失敗,進入如下邏輯,不難想到肯定有設定 uiautomator 的兜底邏輯:
-
reset_uiautomator
的核心邏輯如下- 再次確認 atx-agent 請求返回:
- 因為 uiautomator 還沒啟動,所以鐵定是不通的,之後確認 atx-agent 版本號,不對則重新呼叫
_prepare_atx_agent
(前面說過這個函式): - 我們 atx-agent 沒問題,所以直接過到下一步,進入
_force_reset_uiautomator_v2
開始重置 ui2 環境,這段邏輯比較長,下面單獨拆分字標題說。
重置 uiautomator_v2 如何進行
- 進入到
_force_reset_uiautomator_v2
,頭部邏輯如下: - 到這裡我先說明一個可能的新問題:你覺得上面的
self.shell(...)
是怎麼呼叫的?你是不是覺得是 python 直接在 pc 端執行的命令?如果你這麼想恭喜你答錯了,實際上self.shell(...)
是把命令給到 atx-agent 去執行的 - 看看這裡 shell 的轉發程式碼。依舊是使用 jsonrpc,只不過這個 path 是 atx-agent 自己處理的:
- golang 側 shell 的實現等啟動 uiautomator 的時候再看,普通命令沒太大區別,後續進入
self.uiautomator.stop()
,我們看看這個stop
幹了啥: - 我們再到 golang 中看一下,發現是在 golang 中是透過之前儲存的字典取出保活程序:
- 我們再跟進
pkeeper.stop()
看看,發現核心就是傳了個True
到p.stopC
: - 前面沒講
pkeeper.start()
是怎麼運作的,實際上它就是執行了一個死迴圈,當p.stopC
傳入 True 時就會結束,然後釋放程序;擷取了部分關鍵程式碼如下圖所示:
- 保活程序釋放後,python 層會使用 kill -9 殺掉 uiautomator 程序:
- 接下來就是安裝 uiautomator 的兩個 apk 了,安裝的邏輯前面也看過了,這裡不再贅述,安裝完成會列印兩條日誌:
- 剩下的
self.uiautomator.start()
跟之前的stop
十分有九分相似,python 層依舊是 jsonrpc 請求,只是變成了 post 方法: - 至於 golang 端的實現,之前已經看過一次了,就是使用
am instrument
啟動單元測試的方式,然後再加個保活鎖: - 到此為止,uiautomator 的程序就都起來了,我們可以用 ps 命令看看(有點亂):
-
reset_uiautomator
函式也到此結束了,後面雖然還有一些兜底邏輯,但大部分都是已經見過的函式實現,所以不再贅述。
判定控制元件是否存在
- 回到之前的
_jsonrpc_retry_call
處,reset_uiautomator
成功後會重新發起一次請求: - 這一次就能正確打到 APP 的程式碼上了,而 APP 是使用
com.googlecode.jsonrpc4j.JsonRpcServer
實現了 jsonrpc 服務,並在AutomatorServiceImpl
中實現了具體實現,其中waitForExists
如下: - 之後還會繼續呼叫
androidx.test.uiautomator
包提供的能力,而uiautomator
提供的能力其實大部分來自AccessibilityService
: - 到此為止,從 python client -> atx-agent server -> app 層都經歷過了,其它實現基本都是這麼流程,我就不再一一展開贅述了。
預設點選實現與坑
- 這裡我就不再從 python 層一個個過了,直接看 APP 層的點選實現,無論你是 xpath、text 還是別的點選方式,最終大機率都會來到
com.github.uiautomator.stub.AutomatorServiceImpl#click(int, int, long)
,按下和鬆開中間有個間隔的就是長按函式了: - 跟進
touchUp
,因為最終的返回值是它決定的,原理大同小異: -
injectEventSync
繼續深入的話需要下載原始碼,這裡就不再深入了,你只需要知道這裡使用的是一個同步的注入方法,如果注入失敗就會返回 fasle:
- 那麼,有哪些坑呢?
- 第一坑:
- 事件注入可能會和其它應用有衝突,比如我曾嘗試和 github 上的 Fastbot_Android 放在一起執行(原因是經常會誤觸一些車控開關,想用自動化識別錯誤時返回)
- 但結果是,每次執行一段時間後,兩者之一就會報錯並且停止
- 第二坑:
- 就是上面
injectEventSync
的返回值,在某款不知道什麼遊戲引擎構建的應用上使用自動化點選時,我指令碼明明只點了一下,但 APP 上總是點兩下。 - 後來發現是在這個 APP 上
injectEventSync
都返回 false,而內部框架額外處理了這個injectEventSync
的返回值,如果返回 false 就額外點一下,氣死個人。
- 就是上面
- 解決方法?如果是點選的話自己寫 adb,如果想效率更快些就考慮 minitouch 這種(明日方舟的 MAA 掛機使用 minitouch 給我感覺就快了很多)
- 到此為止,點選的處理流程也講完了,本來想再講講 dump 控制元件樹的介面,但想想好像都大同小異就算了,接下來基本不用再看程式碼了,來聊些稍微有趣的話題。
UI 自動化穩定性/收益問題
穩定性問題
- 先說一個可能、應該、大概普遍的結論:如果 UI 自動化落地一段時間,且嘗試過各種手段最佳化,但穩定性提升還是不明顯,那大機率是沒救的。
- UI 自動化不穩定/維護難的原因通常如下:
- uiautomator2 自身穩定性問題,但透過外部測試框架排程封裝,增加一些兜底邏輯還是比較容易的避免的(內部自研的也一樣有類似的問題)
- 網路問題,比如 21 年位元組的機房自動化還是會出現白屏,廣州百度早起極爛的網路經常導致入庫失敗等
- 業務變更頻繁,位元組的業務尤為明顯,以至於某些團隊會放棄 UI 自動化的維護;最近面試某些大公司的時候也是因為這個原因放棄
- 業務鏈路太長,比如滴滴使用者端和司機端,美團使用者端&騎手端&商家端,涉及多端聯動 + 鏈路過長大大提高失敗率
- 線上 ab 實驗/UI 適配/降級等變更策略過多,估計是大廠才會出現的通病,分 uid/did/裝置型號輸出頁面/特效,UI 自動化難以持續維護
- 非原生控制元件只能用 CV,比如百度地圖,底層是用 OpenGL ES 繪製的,開源的方案目前來看都沒啥辦法,引擎層的程式碼保密級別又高,基本就只能用 CV 了
- 雖然後續搞出了很多 “智慧” UI 自動化方案,但在職期間看落地效果似乎都不咋地。
收益問題
- UI 自動化打從我開始接觸的那一刻起,就一直被 diss 收益的問題
- 特別是在位元組期間,位元組的自動化一般都是用機房的叢集迴歸的,上面說到的穩定性問題除了 CV 這一項外,基本都是天天出現
- 於是測試報告就各種誤報,誤報還要排查,排查之後還要相容,程式碼變更頻率特別頻繁
- 因為投入的人力與產出不太能成正比,原本一些還在瘋狂投入人力的業務也開始慢慢不投了,或者縮減維護範圍
如何規避問題
- 如上面的結論所說,UI 自動化的問題通常是無法解決的(至少短期內)
- 那麼思路就應該轉變成如何利用 UI 自動化做出收益,並且規避它的短板,比如:長期維護乏力,線上變更多,收益不明確
- 實際上我之前所在的團隊早就意識到該問題,只是解決的思路可能只適用在類似位元組這樣的大廠,下面我就來說一下。
- 結論:團隊轉型日常效能評測專項(偏基礎體驗)+ 活動業務 BP 專項
- 你看著描述可能還有點懵,我來解釋下具體邏輯:
- 效能評測:通常是比對公司業務 APP 與競品的差異,單場景效能的 case 通常不多,且通用性較強,維護成本比起業務 UI 自動化低非常多;之後根據人力接入各個業務,定時輸出對比報告就是穩妥的產出。
- 活動業務 BP:位元組內部各大 APP 都有自己的活動,再加上類似中秋節、國慶節、春節這種節日活動,不愁沒活;同時活動內容一般都偏向使用新技術 + 寫新程式碼,這意味著出現功能、效能、體驗的 BUG 機率會更高;並且,活動自動化的程式碼寫完大機率就可以扔了,基本不需要考慮後續維護,後續也是持續輸出報告就可以規避 UI 自動化原本的缺點。
- 簡而言之,UI 自動化不再像之前一樣是投入產出不明的累贅,而是成為了專項環節中的一個小小的指令碼工具,不是過程指標也不是結果指標,單純就是一個輔助工具。
其它補充
如何遠端除錯 Android 上的 Golang 程式碼
- 核心參考:https://github.com/golang/vscode-go/blob/master/docs/debugging.md
- 不過光有參考資料還不太夠,因為大部分是 PC 環境下的,Android 環境還要小小處理下
- 先說一些踩過的坑:
- golang arm64 架構的包是無法再 Android 上執行的,使用
ldd
檢視可執行檔案會發現少了一些 linux 的 so,目測屬於硬傷救不了 - 上面 debug 文件中,大部分都是使用 dlv(delve)開啟 debug 的,但 dlv 有些命令是依賴 go 相關的指令的,基於上一條 Android 中無法使用 go,有些 dlv 方法是不可用的,比如
dlv debug
就是 - dlv 的 github 倉庫:https://github.com/go-delve/delve 沒有提供 Android 可執行的 dlv 可執行檔案
- golang arm64 架構的包是無法再 Android 上執行的,使用
- 最終我自己的解決方式還是使用 dlv,對應上面參考文件中的如下部分:
- 首先是要自己打一個 Android 上可以執行的 dlv,這樣才能開啟 debug server,主流手機一般都支援 armv7、armv8,armv8 一般就對應 arm64,所以 build 的時候設定
GOARCH=arm64 GOOS=linux
即可 - 然後就是 dlv 倉庫的版本選擇問題,我本地的 golang 是 1.18.10,下載最新的 dlv 時,專案是 1.19.x 的,可以打包成功並執行,但本地 VSCode 開始遠端除錯時就會報出版本對不上的問題,後更換低版本 dlv(golang 1.18.3)遠端除錯成功,目測是不向上相容,向下大版本能相容
- 之後參考 dlv 的 github ci 指令碼,得出完整構建命令如下:
GOOS=linux GOARCH=arm64 go build -ldflags "-extldflags -static" -ldflags= github.com/go-delve/delve/cmd/dlv
- 構建完後直接推手機上就可以,我這裡推的跟 uiautomator2 是同一個目錄:
- 之後我們在手機對應目錄上就可以執行文件中的命令了(
dlv
和./dlv
是有區別的): - 之後按照先前文件,配置 vscode 的
launch.json
檔案如下: - 現在還不能按 F5 啟動,上面配置的 program 指的是 debug 包的路徑,我們 atx-agent 的 debug 包還沒打,打包命令如下(順手推上去):
GOOS=linux GOARCH=arm64 go build -gcflags="all = -N -l"
- 之後我們就可以在 atx-agent 的工程按下 F5 啟動除錯了(有個警告不用管),確認 vscode 進入 debug 狀態:
- 確認手機端的 atx-agent server 也被啟動了:
- 確認斷點是紅色的,不是紅色說明沒生效:
- 最後就是確認能命中斷點和看到引數了,可以訪問
http://手機ip:7912/info
試試看,debug 生效的話上面就會停在上面的斷點:
CV 真的不靠譜嗎
- 老實說,這要看你所在廠的 CV 積累如何,比如我面快手的時候,對於一些自研的渲染引擎,快手的技術中臺基本就不考慮 CV 方案,更傾向於一些深度學習的方案,比如點掉一些突然出現的浮動視窗
- 但在位元組就不是這樣,位元組因為有比較強大的 CV Lab,所以 CV 是可以解決絕大部分問題的,比如 Android 耗時自動化如何判斷起始幀,就是開啟開發者設定的指標位置,然後用 CV 去識別螢幕左上角那個用肉眼都不一定看得清的
X/X
- 在百度車機業務,開源方案的表現也還行,因為一個車廠下不同車型通常解析度都是一致的,如果你是負責一個車廠下的不同車型,那麼複用起來基本沒有太大問題
遇到過適合 UI 自動化落地的專案嗎
- 目前就是百度車機業務比較合適純 UI 自動化做收益,原因如下:
- 多個專案雖然會出現 text 文案不一致的情況,但是 RD 基本保持 resource-id 是一致的
- 業務層面的改動不多,短的專案可能 1 年就交付了,長的 2 年 +,但以大部分車廠的佛性文化來說,需求確認後變更點就不多了
- 車機系統便利,沒有市場上各種自己都不記得密碼的密碼鎖、許可權攔截等,進一步保證執行的穩定性
- 車機可自由 root,這點確實就比較牛逼,通常情況下自動化執行 crash 了,要想抓到要麼靠 logcat,要麼靠 adb bugreport;而前者不一定出現對應日誌,後者又匯出齊慢;而有了系統許可權後就可以直接去系統目錄取 anr、crash、coredump 了,且速度極快,這直接給 UI 自動化附加了一層深度更深的穩定性測試,實際落地中也確實抓到不少跑 Monkey 沒出現的問題。
有對 uiautomator2 擴充套件來滿足需求嗎
- 有,就是百度的車機地圖,車機地圖有一個特殊的業務場景,即:多屏地圖,比如主控副屏、HUD 投影等等
- uiautomator2、weditor 等工具在遇到這些場景時,預設只會顯示主屏的控制元件,除錯起來非常不方便,於是就稍微改造了一下
- 在說具體改造之前,我先說一下多屏地圖的主要方案,如下圖所示:
- Android 原生的 Presentation 基本不會使用,其餘的可抽象成兩種方案:
- 魔改 Presentation:所有螢幕屬於一臺 Android,看到的內容都是真實控制元件,且螢幕是可控的
- 推流:適用於 C/S 架構,即螢幕和主控 Android 不是同一臺機器,螢幕是不可控的
- 對於推流的方式,只能從流中擷取圖片做 CV、OCR 斷言
- 對於真實存在控制元件且可控的魔改 Presentation 方案改造,我這裡就不從頭到尾扯一遍了,就只提一些線索:
- 魔改 Presentation 是基於 Andorid Context -> window service 管理視窗的
- 每個視窗都有對應的 display id
- adb 的 screencap 命令官方文件沒有說全,當我們
screencap --help
後,會發現有如下內容: -
- 沒錯,
-d
引數就是可以指定 display id 截圖
- 沒錯,
- scrcpy 可以根據 display id 來選擇遠控的螢幕,實測多屏地圖確實可以,官方文件中描述如下:
- uiautomator2 的一些實現也是也有用到 window manager:
- 剩下的就是如何拼裝了
最後
- 上面提到在百度、位元組工作過,看起來好 diao,但實際上我目前只工作過兩年,位元組一年,百度一年;並且最近因為業務人員裁撤,處於失業的狀態
- 不過閒著也是閒著,並且發現身為一個測試開發都沒怎麼寫過相關的內容,所以就趁熱寫一篇吧
相關文章
- mybatis原理,配置介紹及原始碼分析MyBatis原始碼
- Canal 原始碼走讀原始碼
- TiCDC 原始碼閱讀(二)TiKV CDC 模組介紹原始碼
- httprunner3原始碼解讀(1)簡單介紹原始碼模組內容HTTP原始碼
- java版JieBa分詞原始碼走讀JavaJieba分詞原始碼
- 條形碼生成原理介紹及簡介
- ReentrantLock介紹及原始碼解析ReentrantLock原始碼
- 介紹 golang net/http 原始碼GolangHTTP原始碼
- DM 原始碼閱讀系列文章(二)整體架構介紹原始碼架構
- 通俗易懂Vuex原始碼導讀0-全域性介紹Vue原始碼
- Spring原始碼分析——spring原始碼之obtainFreshBeanFactory()介紹Spring原始碼AIBean
- Spring原始碼分析——spring原始碼核心方法refresh()介紹Spring原始碼
- Scrapy原始碼閱讀分析_1_整體框架和流程介紹原始碼框架
- java原始碼-ReentrantReadWriteLock寫鎖介紹Java原始碼
- 原始碼閱讀四步走,這才是閱讀原始碼的正確姿勢原始碼
- 帶你讀原始碼:四大視角多維走讀區塊鏈原始碼原始碼區塊鏈
- FFmpeg原理介紹
- Spring:原始碼解讀Spring IOC原理Spring原始碼
- 原始碼管理工具介紹-github原始碼Github
- 原始碼管理工具Github介紹原始碼Github
- RocketMQ--原始碼編譯和介紹MQ原始碼編譯
- Dubbo原始碼學習之-SPI介紹原始碼
- Flutter Dio原始碼分析(一)--Dio介紹Flutter原始碼
- elastic search 原理介紹AST
- Oracle DRM原理介紹Oracle
- Async Commit 原理介紹MIT
- DM 原始碼閱讀系列文章(三)資料同步處理單元介紹原始碼
- Laravel 原始碼閱讀指南 -- 使用者認證系統 (基礎介紹)Laravel原始碼
- Vue原始碼閱讀 – 依賴收集原理Vue原始碼
- Vue原始碼閱讀 - 依賴收集原理Vue原始碼
- 從原始碼解讀Category實現原理原始碼Go
- PostgreSQL 原始碼解讀(240)- HTAB簡介SQL原始碼
- 主流原始碼管理工具Github介紹原始碼Github
- github原始碼管理工具——使用介紹Github原始碼
- 04 原始碼編譯安裝與介紹原始碼編譯
- ArrayList相關方法介紹及原始碼分析原始碼
- Nacos使用和註冊部分原始碼介紹原始碼
- FFmpeg libswscale原始碼分析1-API介紹原始碼API