陌生機器的硬體盤點比從應用端反推便宜,因為它把假設換成可核對的值
從應用程式的行為往回推測硬體長什麼樣,每一步的產出都是一個未經核對的假設,而假設會互相污染——兩個假設同時可疑時,一次實驗改不動其中一個。直接把硬體攤開來看,產出換成可以逐項核對的值。在一台不熟悉的機器上,盤點通常更快,而且它的結論不會因為換了一支程式就失效。
這個優勢有它的射程。盤點換掉的是識別與歸屬類的假設(這是什麼裝置、掛在哪、現在歸誰用),換不掉純邏輯的錯誤——硬體讀值正確而程式判斷寫錯時,硬體清單上看不出任何異常。而下面的每一條路徑都建立在能取得 adb shell 這個前提上;量產機為了合規常關閉 USB 偵錯,那時這一整套方法都走不通,得改用機器自己的診斷介面或回到應用端。
從應用端反推的成本結構
從程式行為反推的過程典型上長這樣(這是這條路線的形狀,不是某一次排查的逐字紀錄):程式找不到印表機,所以推測過濾條件太嚴;改寬條件,出現一堆裝置,所以推測某幾個是內部硬體;試著連其中一個,觸控失效,所以推測那個是螢幕。
每一步都要跑一次完整的建置與安裝循環,而每一步得到的是一個間接證據。更麻煩的是這些推測會互相污染——過濾條件與裝置識別兩個假設同時可疑時,一次實驗改不動其中一個。
硬體盤點把這一段換成幾次唯讀查詢,而查詢的對象是作業系統自己記錄的狀態。
各項查詢回答的問題不同
adb shell dumpsys usb 回答「Android 認得哪些 USB 裝置」。它把假設換成值的地方在於:程式端只看得到自己過濾之後的結果,而這裡看到的是過濾之前的全集,兩者的差就是過濾條件實際擋掉了什麼。輸出含 vendor id、product id、製造商與產品名稱字串,以及每個介面的類別——類別代碼記在兩層的那個問題可以在這裡直接看到答案,不必從程式的比對結果反推。
sysfs 底下的 /sys/bus/usb/devices/*/ 給的資訊更完整。每個裝置目錄裡有 idVendor、idProduct、product、manufacturer、bDeviceClass,介面的類別在 <裝置>:1.0/bInterfaceClass。這條路徑額外提供的是拓樸——目錄名稱本身編碼了裝置掛在哪個集線器的哪個埠底下,而一體機的內建印表機可能掛在內部集線器底下——拓樸看得出這件事,而裝置清單看不出來。知道拓樸就能分辨「這台裝置在機器內部」與「這台是使用者接上去的」。
adb shell dumpsys input 回答「哪個裝置是觸控螢幕」。它換掉的假設是歸屬——USB 清單只說有哪些裝置,說不出哪一台正被系統用著,而輸出裡的 DeviceType: touchScreen 是把某個 USB 裝置與螢幕觸控連起來的直接證據——沒有這一步,觸控面板在 USB 清單裡與任何一台 HID 裝置長得一樣。
adb shell pm list packages 與 dumpsys package 回答「裝的是哪一版、是不是可偵錯的建置」。版本這一項換掉的假設是「現場跑的就是我手上這份程式碼」,而那個假設在排查裡經常是錯的。可偵錯的建置一般可以用 run-as 讀應用程式私有目錄,而這條路徑能回答「設定實際存了什麼值」——那個問題的答案決定讀取端驗證該驗什麼。
adb exec-out screencap -p 搭配 adb shell input tap 讓遠端操作介面並取回畫面,不必每一次都請現場的人動手。這條路徑不經過螢幕,所以觸控已經失效的機器仍然操作得了。這裡走 exec-out 而不走 shell,因為 shell 在配置了 pty 的時候會改寫換行、把 PNG 寫壞;adb 遠端診斷時該信哪一種輸入展開這個差別,並給畫面歸屬與 logcat 參數的取法。
adb logcat -v time <TAG>:D "*:S" 過濾特定標籤。這裡有一個會誤導歸因的細節:zsh 底下 *:S 要加引號,否則 shell 會先把它當成萬用字元展開、在找不到相符檔案時報錯。那個錯誤訊息來自 shell 而不是 adb,所以它看起來像是 adb 或裝置出了問題——排查工具本身的失敗被讀成排查對象的失敗,而這正是盤點要避免的那種假設。
內建印表機有兩種,而簽章字串分不出來
一體機標榜的「內建印表機」在實作上分兩類:一類要用廠商自家的 SDK 才驅動得了,另一類就是掛在內部 USB 上的標準 ESC/POS 印表機、走通用路徑即可。
判斷屬於哪一類,硬體盤點直接給答案:dumpsys usb 或 sysfs 裡看得到一台類別為印表機(或 255)的裝置掛在內部集線器底下,走的就是標準 ESC/POS 那一類。看不到,才需要考慮廠商 SDK。
用機器簽章字串(製造商、型號)推斷廠牌有一個結構性的限制:它只對已經認得的機型有效。認不得的機型回傳的正確答案是「不確定」,而系統這時該做的是引導使用者去設定外接裝置,不是猜一個廠牌硬套它的 SDK。判斷不出來時的處置在過濾策略那一篇有一般化的說法。
沒有相機的一體機比想像中多
盤點時值得順手確認的還有相機。某些 POS 一體機沒有相機,相機初始化會回報可用相機為零台——那個零描述的是硬體組態,初始化本身正常完成。
推論是掃條碼要配外接掃碼槍,而掃碼槍是 USB HID,正好落在印表機候選的排除清單裡。這在設計上要確認一次:排除清單擋掉觸控面板的同時也擋掉掃碼槍,而掃碼槍本來就不該出現在印表機候選裡,所以這個結果是對的——但如果哪天要做「選擇掃碼槍」的功能,它需要自己的一份候選條件——印表機排除清單的反面是「類別為印表機或供應商自訂」,而掃碼槍是 HID,沿用反面會把它連同觸控面板、鍵盤、滑鼠一起排除掉。互補集不等於目標集。
判斷標準
面對一台不熟悉的機器,先取硬體清單再讀程式行為——盤點的產出是可核對的值,反推的產出是待驗證的假設,而假設會互相污染。
盤點的完整度用一個條件檢查:機器上每一個會被程式列進候選的裝置,都能說出它是什麼、掛在哪裡、現在歸誰用。第三項是最容易漏的一項,而它正是claim 一個介面等於接管它要的輸入,因為接管之後誰失去了什麼,取決於這塊硬體現在歸誰。