根據上一篇文章《[UiAutomator源碼分析之注入事件](http://blog.csdn.net/zhubaitian/article/details/40541927)》開始時提到的計劃,這一篇文章我們要分析的是第二點:
- 如何獲取控件信息
我們在測試腳本中初始化一個UiObject的時候通常是像以下這個樣子:
~~~
UiObject appsTab = new UiObject(new UiSelector().text("Apps"));
appsTab.click()
~~~
那么這個過程發生了什么呢?這就是我們接下來要說的事情了。
## 1. 獲取控件信息順序圖
這里依然是一個手畫的不規范的順序圖,描述了UiObject嘗試獲得一個控件的過程中與相關的類的交互,這些類的關系在《[UiAutomator源碼分析之UiAutomatorBridge框架](http://blog.csdn.net/zhubaitian/article/details/40539103)》中已經進行了描述。

這里整一個過程并不復雜,簡單說明下就這幾點:
- UiObject對象幾經周折通過不同的類最終聯系上UiAutomation,然后通知UiAutomation對象它想取得當前活動窗口的所有元素的AccessibilityNodeInfo類型的根節點
- AccessibilityNodeInfo代表了屏幕中控件元素的一個節點,同時它也擁有一些成員方法可以以當前節點為基礎來獲得其他目標節點。可以把屏幕上的節點想像成是通過類似xml的格式組織起來的,所以一旦知道根節點和由選擇子UiSelector指定的目標控件信息,我們就可以遍歷整個窗口控件
- QueryController對象獲得Root Node之后,就是調用tranlateCompoundSelector這個方法來遍歷窗口所有控件,直到找到選擇子UiSelector指定的那個控件為止。
- 注意一個AccessibilityNodeInfo只代表一個控件,遍歷的時候一旦需要下一個控件的信息是必須要再次通過UiAutomation去獲取的。
## 2.觸發控件查找真正發生的地方
在我沒有去分析uiautomator的源代碼之前,我一直以為空間查找是在通過UiSelector初始化一個UiObject的時候發生的:
~~~
UiObject appsTab = new UiObject(new UiSelector().text("Apps"));
~~~
這讓我有一種先入為主的感覺,一個控件對象初始化好后應該就已經得到了該控件所代表的節點的所有信息了,但看了源碼后發現事實并非如此,以上所做的事情只是以一定的格式準備好UiSelector選擇子而已,真正觸發uiautomator去獲取控件節點信息的是在觸發控件事件的時候,比如:
~~~
appsTab.click()
~~~
我們進入到代表一個控件的UiObject對應的操作控件的方法去看下就清楚了,以上面的click為例:
~~~
/* */ public boolean click()
/* */ throws UiObjectNotFoundException
/* */ {
/* 389 */ Tracer.trace(new Object[0]);
/* 390 */ AccessibilityNodeInfo node = findAccessibilityNodeInfo(this.mConfig.getWaitForSelectorTimeout());
/* 391 */ if (node == null) {
/* 392 */ throw new UiObjectNotFoundException(getSelector().toString());
/* */ }
/* 394 */ Rect rect = getVisibleBounds(node);
/* 395 */ return getInteractionController().clickAndSync(rect.centerX(), rect.centerY(), this.mConfig.getActionAcknowledgmentTimeout());
/* */ }
~~~
正式290行的調用觸發uiautomator去調用UiAutomation去獲取到我們想要的控件節點AccessibilityNodeInfo信息的。
## 3.獲得根節點
下面我們看下uiautomator是怎么去獲取到代表窗口所有控件的根的Root Node的,我們進入UiObject的findAccessibilityNodeInfo這個方法:
~~~
/* */ protected AccessibilityNodeInfo findAccessibilityNodeInfo(long timeout)
/* */ {
/* 164 */ AccessibilityNodeInfo node = null;
/* 165 */ long startMills = SystemClock.uptimeMillis();
/* 166 */ long currentMills = 0L;
/* 167 */ while (currentMills <= timeout) {
/* 168 */ node = getQueryController().findAccessibilityNodeInfo(getSelector());
/* 169 */ if (node != null) {
/* */ break;
/* */ }
/* */
/* 173 */ UiDevice.getInstance().runWatchers();
/* */
/* 175 */ currentMills = SystemClock.uptimeMillis() - startMills;
/* 176 */ if (timeout > 0L) {
/* 177 */ SystemClock.sleep(1000L);
/* */ }
/* */ }
/* 180 */ return node;
/* */ }
~~~
UiObject對象會首先去獲得一個QueryController對象,然后調用該對象的findAccessibilityNodeInfo同名方法:
~~~
/* */ protected AccessibilityNodeInfo findAccessibilityNodeInfo(UiSelector selector, boolean isCounting)
/* */ {
/* 143 */ this.mUiAutomatorBridge.waitForIdle();
/* 144 */ initializeNewSearch();
/* */
/* 146 */ if (DEBUG) {
/* 147 */ Log.d(LOG_TAG, "Searching: " + selector);
/* */ }
/* 149 */ synchronized (this.mLock) {
/* 150 */ AccessibilityNodeInfo rootNode = getRootNode();
/* 151 */ if (rootNode == null) {
/* 152 */ Log.e(LOG_TAG, "Cannot proceed when root node is null. Aborted search");
/* 153 */ return null;
/* */ }
/* */
/* */
/* 157 */ UiSelector uiSelector = new UiSelector(selector);
/* 158 */ return translateCompoundSelector(uiSelector, rootNode, isCounting);
/* */ }
/* */ }
~~~
這里做了兩個重要的事情:
- 150行:通過調用getRootNode來獲得根節點,這個就是我們這個章節的重點
- 158行:通過調用translateCompoundSelector來根據用戶指定的UiSelector格式從上面獲得根節點開始遍歷窗口控件樹,以獲得我們的目標控件
好,我們繼續往下進入getRootNode:
~~~
/* */ protected AccessibilityNodeInfo getRootNode()
/* */ {
/* 168 */ int maxRetry = 4;
/* 169 */ long waitInterval = 250L;
/* 170 */ AccessibilityNodeInfo rootNode = null;
/* 171 */ for (int x = 0; x < 4; x++) {
/* 172 */ rootNode = this.mUiAutomatorBridge.getRootInActiveWindow();
/* 173 */ if (rootNode != null) {
/* 174 */ return rootNode;
/* */ }
/* 176 */ if (x < 3) {
/* 177 */ Log.e(LOG_TAG, "Got null root node from accessibility - Retrying...");
/* 178 */ SystemClock.sleep(250L);
/* */ }
/* */ }
/* 181 */ return rootNode;
/* */ }
~~~
172調用的是UiAutomatorBridge對象的方法,通過我們上面的幾篇文章我們知道UiAutomatorBridge提供的方法大部分都是直接調用UiAutomation的方法的,我們進去看看是否如此:
~~~
/* */ public AccessibilityNodeInfo getRootInActiveWindow() {
/* 66 */ return this.mUiAutomation.getRootInActiveWindow();
/* */ }
~~~
果不其然,最終簡單明了的直接調用UiAutomation的getRootInActiveWindow來獲得根AccessibilityNodeInfo.
## 4.遍歷根節點獲得選擇子UiSelector指定的控件
如前所述,QueryController的方法findAccessibilityNodeInfo在獲得根節點后下來做的第二個事情:
- 158行:通過調用translateCompoundSelector來根據用戶指定的UiSelector格式從上面獲得根節點開始遍歷窗口控件樹,以獲得我們的目標控件
里面的算法細節我就不打算去研究了,里面考慮到選擇子嵌套的情況,分析起來也比較費力,且了解了它的算法對我去立交uiautomator的運行原理并沒有非常大的幫助,我只需要知道給定一棵樹的根,然后制定了我想要的葉子的屬性,那么我遍歷整棵樹肯定是可以找到我想要的那個/些滿足要求的控件的。大家由興趣了解其算法的話還是自行去研究吧。
## 5.最終還是通過坐標點來點擊控件
上面UiObject的Click方法通過UiAutomation這個高大上的新框架獲得了代表我們目標控件的AccessibilityNodeInfo后,跟著是不是就直接調用這個節點的Click方法進行點擊了呢?其實不是的,首先AccessibilityNodeInfo并沒有click這個方法,我們繼續看代碼:
~~~
/* */ public boolean click()
/* */ throws UiObjectNotFoundException
/* */ {
/* 389 */ Tracer.trace(new Object[0]);
/* 390 */ AccessibilityNodeInfo node = findAccessibilityNodeInfo(this.mConfig.getWaitForSelectorTimeout());
/* 391 */ if (node == null) {
/* 392 */ throw new UiObjectNotFoundException(getSelector().toString());
/* */ }
/* 394 */ Rect rect = getVisibleBounds(node);
/* 395 */ return getInteractionController().clickAndSync(rect.centerX(), rect.centerY(), this.mConfig.getActionAcknowledgmentTimeout());
/* */ }
~~~
從395行可以看到,最終還是把控件節點的信息轉換成控件的坐標點進行點擊的,至于怎么點擊,大家可以參照上一篇文章,無非就是通過建立一個runnable的線程進行點擊事件的注入了
## 6.系列結語
UiAutomator源碼分析這個系列到了這篇文章算是完結了,從啟動運行,到核心的UiAutomatorBridge架構,到實例解剖,通過這些文章我相信大家已經很清楚uiautomator這個運用了UiAutomation框架與AccessibilityService通信的測試框架是怎么回事了,置于uiautomator那5個專供測試用例調用的類是怎么回事,網上可獲得的信息不少,我這里就沒有必要做從新造輪子的事情了,況且這些已經不是uiautomator這個框架的核心了,它們只是運用了UiAutomatorBridge這個核心的一些類而已。
<table cellspacing="0" cellpadding="0" width="539" class=" " style="margin:0px 0px 10px; padding:0px; border-collapse:collapse; width:668px; max-width:100%; word-wrap:break-word!important"><tbody style="margin:0px; padding:0px; max-width:100%; word-wrap:break-word!important"><tr style="margin:0px; padding:0px; max-width:100%; word-wrap:break-word!important"><td valign="top" width="112" height="39" style="border-style:solid; border-color:rgb(0,0,0); margin:0px; padding:4px; word-break:break-all; max-width:100%; word-wrap:break-word!important">?</td></tr><tr style="margin:0px; padding:0px; max-width:100%; word-wrap:break-word!important"><td valign="top" width="111" height="13" style="border-style:solid; border-color:rgb(0,0,0); margin:0px; padding:4px; word-break:break-all; max-width:100%; word-wrap:break-word!important; background-color:rgb(190,192,191)"><p style="margin-top:0px; margin-bottom:0px; padding-top:0px; padding-bottom:0px; max-width:100%; clear:both; min-height:1em; white-space:pre-wrap; word-wrap:break-word!important"><span style="margin:0px; padding:0px; max-width:100%; word-wrap:break-word!important">作者</span></p></td><td valign="top" width="112" height="13" style="border-style:solid; border-color:rgb(0,0,0); margin:0px; padding:4px; word-break:break-all; max-width:100%; word-wrap:break-word!important; background-color:rgb(190,192,191)"><p style="margin-top:0px; margin-bottom:0px; padding-top:0px; padding-bottom:0px; max-width:100%; clear:both; min-height:1em; white-space:pre-wrap; word-wrap:break-word!important"><span style="margin:0px; padding:0px; max-width:100%; word-wrap:break-word!important">自主博客</span></p></td><td valign="top" width="111" height="13" style="border-style:solid; border-color:rgb(0,0,0); margin:0px; padding:4px; word-break:break-all; max-width:100%; word-wrap:break-word!important; background-color:rgb(190,192,191)"><p style="margin-top:0px; margin-bottom:0px; padding-top:0px; padding-bottom:0px; max-width:100%; clear:both; min-height:1em; white-space:pre-wrap; word-wrap:break-word!important"><span style="margin:0px; padding:0px; max-width:100%; word-wrap:break-word!important">微信</span></p></td><td valign="top" width="112" height="13" style="border-style:solid; border-color:rgb(0,0,0); margin:0px; padding:4px; word-break:break-all; max-width:100%; word-wrap:break-word!important; background-color:rgb(190,192,191)"><p style="margin-top:0px; margin-bottom:0px; padding-top:0px; padding-bottom:0px; max-width:100%; clear:both; min-height:1em; white-space:pre-wrap; word-wrap:break-word!important"><span style="margin:0px; padding:0px; max-width:100%; font-family:Helvetica; letter-spacing:0px; word-wrap:break-word!important">CSDN</span></p></td></tr><tr style="margin:0px; padding:0px; max-width:100%; word-wrap:break-word!important"><td valign="top" width="111" height="39" style="border-style:solid; border-color:rgb(0,0,0); margin:0px; padding:4px; word-break:break-all; max-width:100%; word-wrap:break-word!important; background-color:rgb(227,228,228)"><p style="margin-top:0px; margin-bottom:0px; padding-top:0px; padding-bottom:0px; max-width:100%; clear:both; min-height:1em; white-space:pre-wrap; word-wrap:break-word!important"><span style="margin:0px; padding:0px; max-width:100%; word-wrap:break-word!important">天地會珠海分舵</span></p></td><td valign="top" width="112" height="39" style="border-style:solid; border-color:rgb(0,0,0); margin:0px; padding:4px; word-break:break-all; max-width:100%; word-wrap:break-word!important"><p style="margin-top:0px; margin-bottom:0px; padding-top:0px; padding-bottom:0px; max-width:100%; clear:both; min-height:1em; white-space:pre-wrap; word-wrap:break-word!important"><span style="margin:0px; padding:0px; max-width:100%; font-size:11px; font-family:Helvetica; letter-spacing:0px; word-wrap:break-word!important"><a target="_blank" href="http://techgogogo.com/">http://techgogogo.com</a></span><span style="margin:0px; padding:0px; max-width:100%; font-family:Helvetica; font-size:11px; letter-spacing:0px; word-wrap:break-word!important"/></p><p style="margin-top:0px; margin-bottom:0px; padding-top:0px; padding-bottom:0px; max-width:100%; clear:both; min-height:14px; white-space:pre-wrap; font-family:Helvetica; word-wrap:break-word!important"><br style="margin:0px; padding:0px; max-width:100%; word-wrap:break-word!important"/></p></td><td valign="top" width="111" height="39" style="border-style:solid; border-color:rgb(0,0,0); margin:0px; padding:4px; word-break:break-all; max-width:100%; word-wrap:break-word!important"><p style="margin-top:0px; margin-bottom:0px; padding-top:0px; padding-bottom:0px; max-width:100%; clear:both; min-height:1em; white-space:pre-wrap; word-wrap:break-word!important"><span style="margin:0px; padding:0px; max-width:100%; word-wrap:break-word!important">服務號</span><span style="margin:0px; padding:0px; max-width:100%; font-size:10px; font-family:Helvetica; letter-spacing:0px; word-wrap:break-word!important">:TechGoGoGo</span></p><p style="margin-top:0px; margin-bottom:0px; padding-top:0px; padding-bottom:0px; max-width:100%; clear:both; min-height:1em; white-space:pre-wrap; word-wrap:break-word!important"><span style="margin:0px; padding:0px; max-width:100%; word-wrap:break-word!important">掃描碼</span><span style="margin:0px; padding:0px; max-width:100%; font-size:10px; font-family:Helvetica; letter-spacing:0px; word-wrap:break-word!important">:</span></p><p style="margin-top:0px; margin-bottom:0px; padding-top:0px; padding-bottom:0px; max-width:100%; clear:both; min-height:14px; white-space:pre-wrap; font-family:Helvetica; word-wrap:break-word!important"><img src="image/47cf4f9ec59b0ef1f807a6c33ab5ce5f.jpg" alt="" style="max-width:100%; margin:0px; padding:0px; height:auto!important; word-wrap:break-word!important; width:auto!important; visibility:visible!important"/></p></td><td valign="top" width="112" height="39" style="border-style:solid; border-color:rgb(0,0,0); margin:0px; padding:4px; word-break:break-all; max-width:100%; word-wrap:break-word!important"><p style="margin-top:0px; margin-bottom:0px; padding-top:0px; padding-bottom:0px; max-width:100%; clear:both; min-height:1em; white-space:pre-wrap; color:rgb(62,62,62); font-family:'Helvetica Neue',Helvetica,'Hiragino Sans GB','Microsoft YaHei',?¢èí??oú,Arial,sans-serif; font-size:18px; line-height:28.7999992370605px; word-wrap:break-word!important"><span style="margin:0px; padding:0px; max-width:100%; color:rgb(0,0,0); font-size:11px; font-family:Helvetica; letter-spacing:0px; word-wrap:break-word!important"><a target="_blank" href="http://blog.csdn.net/zhubaitian">http://blog.csdn.net/zhubaitian</a></span><span style="margin:0px; padding:0px; max-width:100%; color:rgb(0,0,0); font-family:Helvetica; font-size:11px; letter-spacing:0px; line-height:28.7999992370605px; word-wrap:break-word!important"/></p><div><span style="margin:0px; padding:0px; max-width:100%; color:rgb(0,0,0); font-family:Helvetica; font-size:11px; letter-spacing:0px; line-height:28.7999992370605px; word-wrap:break-word!important"><br/></span></div></td></tr></tbody></table>