昨天了解了uiautoamtorviewer新增功能dump --compressed,是一个直接发送到设备端的命令,那么这个命令发送到设备端后,设备端是如何操作的呢?我又成了10万个为什么了?继续源码研究......
源码地址
这个jar包最新的版本只到了4.4.2。说明5.0后的uiautomator设备端是没有改变的,那么说明dump --compressed之前就有,只是我不知道罢鸟。结论:
dump --compressed命令4.4.2时代就有,只是年少无知没发现
源码环境搭建
解压以后项目结果如下所示:
直接用eclipse的import功能导入,整体导入。导入eclipse后,如下图所示,感叹号是因为没有添加android.jar
造成的,加上就好了。
源码分析
当我们在命令行下输入下面命令的时候,android系统就会调用cmds
目录下的Launcher
类中的main
方法中
<span class="o">/</span><span class="n">system</span><span class="o">/</span><span class="n">bin</span><span class="o">/</span><span class="n">uiautomator</span> <span class="n">dump</span> <span class="o">--</span><span class="n">compressed</span> <span class="o">/</span><span class="n">data</span><span class="o">/</span><span class="n">local</span><span class="o">/</span><span class="n">tmp</span><span class="o">/</span><span class="n">uidump</span><span class="o">.</span><span class="na">xml</span>
Launcher
所以我们从main开始我们的大餐:
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// show a meaningful process name in `ps`</span>
<span class="n">Process</span><span class="o">.</span><span class="na">setArgV0</span><span class="o">(</span><span class="s">"uiautomator"</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">args</span><span class="o">.</span><span class="na">length</span> <span class="o">>=</span> <span class="mi">1</span><span class="o">)</span> <span class="o">{</span>
<span class="n">Command</span> <span class="n">command</span> <span class="o">=</span> <span class="n">findCommand</span><span class="o">(</span><span class="n">args</span><span class="o">[</span><span class="mi">0</span><span class="o">]);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">command</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">String</span><span class="o">[]</span> <span class="n">args2</span> <span class="o">=</span> <span class="o">{};</span>
<span class="k">if</span> <span class="o">(</span><span class="n">args</span><span class="o">.</span><span class="na">length</span> <span class="o">></span> <span class="mi">1</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// consume the first arg</span>
<span class="n">args2</span> <span class="o">=</span> <span class="n">Arrays</span><span class="o">.</span><span class="na">copyOfRange</span><span class="o">(</span><span class="n">args</span><span class="o">,</span> <span class="mi">1</span><span class="o">,</span> <span class="n">args</span><span class="o">.</span><span class="na">length</span><span class="o">);</span>
<span class="o">}</span>
<span class="n">command</span><span class="o">.</span><span class="na">run</span><span class="o">(</span><span class="n">args2</span><span class="o">);</span>
<span class="k">return</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="n">HELP_COMMAND</span><span class="o">.</span><span class="na">run</span><span class="o">(</span><span class="n">args</span><span class="o">);</span>
<span class="o">}</span>
下面一步一步解释上面的代码的意思:
1
.首先在进程信息中添加上uiautomator
信息,这样你在命令行中敲adb shell ps
就能查看到uiautomator
进程的信息了。
2
.判断参数数量是否大于0,其中要了解的是上面的命令中dump算第一个参数。不要把system/bin/uiautmator当成了第一个参数。
3
.当参数数量大于0时,获得第一个参数的值args[0],其中findCommand()方法根据命令的名称得到命令的类型。总共有四个命令:help
、events
、runtest
、dump
,你如果想知道各个命令是干什么的,你可以在命令行下敲一下看看输出就知道了。
<span class="kd">private</span> <span class="kd">static</span> <span class="n">Command</span><span class="o">[]</span> <span class="n">COMMANDS</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Command</span><span class="o">[]</span> <span class="o">{</span>
<span class="n">HELP_COMMAND</span><span class="o">,</span>
<span class="k">new</span> <span class="n">RunTestCommand</span><span class="o">(),</span>
<span class="k">new</span> <span class="n">DumpCommand</span><span class="o">(),</span>
<span class="k">new</span> <span class="n">EventsCommand</span><span class="o">(),</span>
<span class="o">};</span>
4
.如果命令不为空,就要执行相应的命令,但是还要将剩下的参数(可能为空的参数,但不是null值)传入run
方法中,让各个类型自己处理。我们的命令是dump命令,所以下一步进入DumpCommand
中。
5
.如果不带参数的话,直接执行help
命令。
DumpCommand
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">run</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
<span class="n">File</span> <span class="n">dumpFile</span> <span class="o">=</span> <span class="n">DEFAULT_DUMP_FILE</span><span class="o">;</span>
<span class="kt">boolean</span> <span class="n">verboseMode</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span>
<span class="k">for</span> <span class="o">(</span><span class="n">String</span> <span class="n">arg</span> <span class="o">:</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">arg</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="s">"--compressed"</span><span class="o">))</span>
<span class="n">verboseMode</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span>
<span class="k">else</span> <span class="k">if</span> <span class="o">(!</span><span class="n">arg</span><span class="o">.</span><span class="na">startsWith</span><span class="o">(</span><span class="s">"-"</span><span class="o">))</span> <span class="o">{</span>
<span class="n">dumpFile</span> <span class="o">=</span> <span class="k">new</span> <span class="n">File</span><span class="o">(</span><span class="n">arg</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="n">UiAutomationShellWrapper</span> <span class="n">automationWrapper</span> <span class="o">=</span> <span class="k">new</span> <span class="n">UiAutomationShellWrapper</span><span class="o">();</span>
<span class="n">automationWrapper</span><span class="o">.</span><span class="na">connect</span><span class="o">();</span>
<span class="k">if</span> <span class="o">(</span><span class="n">verboseMode</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// default</span>
<span class="n">automationWrapper</span><span class="o">.</span><span class="na">setCompressedLayoutHierarchy</span><span class="o">(</span><span class="kc">false</span><span class="o">);</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="n">automationWrapper</span><span class="o">.</span><span class="na">setCompressedLayoutHierarchy</span><span class="o">(</span><span class="kc">true</span><span class="o">);</span>
<span class="o">}</span>
<span class="c1">// It appears that the bridge needs time to be ready. Making calls to the</span>
<span class="c1">// bridge immediately after connecting seems to cause exceptions. So let's also</span>
<span class="c1">// do a wait for idle in case the app is busy.</span>
<span class="k">try</span> <span class="o">{</span>
<span class="n">UiAutomation</span> <span class="n">uiAutomation</span> <span class="o">=</span> <span class="n">automationWrapper</span><span class="o">.</span><span class="na">getUiAutomation</span><span class="o">();</span>
<span class="n">uiAutomation</span><span class="o">.</span><span class="na">waitForIdle</span><span class="o">(</span><span class="mi">1000</span><span class="o">,</span> <span class="mi">1000</span> <span class="o">*</span> <span class="mi">10</span><span class="o">);</span>
<span class="n">AccessibilityNodeInfo</span> <span class="n">info</span> <span class="o">=</span> <span class="n">uiAutomation</span><span class="o">.</span><span class="na">getRootInActiveWindow</span><span class="o">();</span>
<span class="k">if</span> <span class="o">(</span><span class="n">info</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">System</span><span class="o">.</span><span class="na">err</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"ERROR: null root node returned by UiTestAutomationBridge."</span><span class="o">);</span>
<span class="k">return</span><span class="o">;</span>
<span class="o">}</span>
<span class="n">Display</span> <span class="n">display</span> <span class="o">=</span>
<span class="n">DisplayManagerGlobal</span><span class="o">.</span><span class="na">getInstance</span><span class="o">().</span><span class="na">getRealDisplay</span><span class="o">(</span><span class="n">Display</span><span class="o">.</span><span class="na">DEFAULT_DISPLAY</span><span class="o">);</span>
<span class="kt">int</span> <span class="n">rotation</span> <span class="o">=</span> <span class="n">display</span><span class="o">.</span><span class="na">getRotation</span><span class="o">();</span>
<span class="n">Point</span> <span class="n">size</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Point</span><span class="o">();</span>
<span class="n">display</span><span class="o">.</span><span class="na">getSize</span><span class="o">(</span><span class="n">size</span><span class="o">);</span>
<span class="n">AccessibilityNodeInfoDumper</span><span class="o">.</span><span class="na">dumpWindowToFile</span><span class="o">(</span><span class="n">info</span><span class="o">,</span> <span class="n">dumpFile</span><span class="o">,</span> <span class="n">rotation</span><span class="o">,</span> <span class="n">size</span><span class="o">.</span><span class="na">x</span><span class="o">,</span> <span class="n">size</span><span class="o">.</span><span class="na">y</span><span class="o">);</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="n">TimeoutException</span> <span class="n">re</span><span class="o">)</span> <span class="o">{</span>
<span class="n">System</span><span class="o">.</span><span class="na">err</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"ERROR: could not get idle state."</span><span class="o">);</span>
<span class="k">return</span><span class="o">;</span>
<span class="o">}</span> <span class="k">finally</span> <span class="o">{</span>
<span class="n">automationWrapper</span><span class="o">.</span><span class="na">disconnect</span><span class="o">();</span>
<span class="o">}</span>
<span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span>
<span class="n">String</span><span class="o">.</span><span class="na">format</span><span class="o">(</span><span class="s">"UI hierchary dumped to: %s"</span><span class="o">,</span> <span class="n">dumpFile</span><span class="o">.</span><span class="na">getAbsolutePath</span><span class="o">()));</span>
<span class="o">}</span>
run方法执行的步骤有点长,没关系,慢慢来。
1
.首先创建文件用来保存dump下来的信息,这个时候需要注意getLegacyExternalStorageDirectory
是个隐藏的方法,官网上的api没有这个方法的解释,可以在源码上找到,我贴在这里,帮助理解,该文件的路径为/storage/emulated/legacy/window_dump.xml
<span class="cm">/** {@hide} */</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="n">File</span> <span class="n">getLegacyExternalStorageDirectory</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="k">new</span> <span class="n">File</span><span class="o">(</span><span class="n">System</span><span class="o">.</span><span class="na">getenv</span><span class="o">(</span><span class="n">ENV_EXTERNAL_STORAGE</span><span class="o">));</span>
<span class="o">}</span>
2
.然后解析传入的参数得到保存的路径以及是否压缩。
3
.然后创建UiAutomationShellWrapper对象
,启动Handler线程,创建Uiautomation对象,并建立连接。然后设置了压缩属性。这个UiAutomationShellWrapper
也是隐藏的,也只能到源码环境下查看。
4
.然后我们得到了Uiautomation的对象实例
<span class="n">UiAutomation</span> <span class="n">uiAutomation</span> <span class="o">=</span> <span class="n">automationWrapper</span><span class="o">.</span><span class="na">getUiAutomation</span><span class="o">();</span>
<span class="n">uiAutomation</span><span class="o">.</span><span class="na">waitForIdle</span><span class="o">(</span><span class="mi">1000</span><span class="o">,</span> <span class="mi">1000</span> <span class="o">*</span> <span class="mi">10</span><span class="o">);</span>
<span class="n">AccessibilityNodeInfo</span> <span class="n">info</span> <span class="o">=</span> <span class="n">uiAutomation</span><span class="o">.</span><span class="na">getRootInActiveWindow</span><span class="o">();</span>
等待UI界面处于稳定后(idle状态),然后我们调用getRootInActiveWindow
方法获得结果的根节点。这个时候我们整个流程差不多结束了,我们care的--compressed还没看到。
compressed
经过一路追踪,发现compressed属性在整个过程中的作用是给AccessibilityServiceInfo
对象添加了一个 FLAG_INCLUDE_NOT_IMPORTANT_VIEWS标志位。其他的就是正常获取dump信息流程,这个标志位对获取信息时候的影响有多大,留到以后来解释(没有源码环境,不好调试啊,头疼......)。