0. 干货区
如果你不Care这是怎么实现的,只想要个解决方案,那么请戳这里下载AutoInstall的apk来安装并开启服务即可。
如果你顺便还想拿源码来自己定制一下,可从这里找到AndroidStudio工程源码,仅一个Service而已。
如果你想知道一下什么是AccessibilityService,可自行搜索学习或看官方介绍http://developer.android.com/reference/android/accessibilityservice/AccessibilityService.html
开启方法:
普通手机: 设置 -> 无障碍/辅助功能 -> 服务 -> AutoInstall -> 开启 -> 确定
某些手机:设置 -> 其它高级设置 -> 辅助功能 -> 服务 -> AutoInstall -> 开启 -> 确定
注意:
开启自动安装不仅适用于adb install,也适用于主动点击apk来启动安装。所以有安全风险,建议仅在测试机器上安装
1. 代码区
不需要Activity,仅需要一个继承AccessibilityService的服务,在服务里兼听onAccessibilityEvent,当出现安装界面的时候,自动去点击。在安装完成后,到辅助功能里开启即可。
AutoInstallService.java
<code><span class="kn">package</span> <span class="o">{</span><span class="err">你的包名</span><span class="o">};</span>
<span class="kn">import</span> <span class="nn">android.accessibilityservice.AccessibilityService</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">android.util.Log</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">android.view.accessibility.AccessibilityEvent</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">android.view.accessibility.AccessibilityNodeInfo</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.util.List</span><span class="o">;</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">AutoInstallService</span> <span class="kd">extends</span> <span class="n">AccessibilityService</span> <span class="o">{</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="n">String</span> <span class="n">TAG</span> <span class="o">=</span> <span class="s">"AutoInstallService"</span><span class="o">;</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="n">String</span> <span class="n">PACKAGE_INSTALLER</span> <span class="o">=</span> <span class="s">"com.android.packageinstaller"</span><span class="o">;</span>
<span class="kd">public</span> <span class="n">AutoInstallService</span><span class="o">()</span> <span class="o">{</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="n">onAccessibilityEvent</span><span class="o">(</span><span class="n">AccessibilityEvent</span> <span class="n">event</span><span class="o">)</span> <span class="o">{</span>
<span class="cm">/*
* 回调方法,当事件发生时会从这里进入,在这里判断需要捕获的内容,
* 可通过下面这句log将所有事件详情打印出来,分析决定怎么过滤。
*/</span>
<span class="c1">//log(event.toString());</span>
<span class="k">if</span> <span class="o">(</span><span class="n">event</span><span class="o">.</span><span class="na">getSource</span><span class="o">()</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">log</span><span class="o">(</span><span class="s">"<null> event source"</span><span class="o">);</span>
<span class="k">return</span><span class="o">;</span>
<span class="o">}</span>
<span class="kt">int</span> <span class="n">eventType</span> <span class="o">=</span> <span class="n">event</span><span class="o">.</span><span class="na">getEventType</span><span class="o">();</span>
<span class="cm">/*
* 在弹出安装界面时会发生 TYPE_WINDOW_STATE_CHANGED 事件,其属主
* 是系统安装器com.android.packageinstaller
*/</span>
<span class="k">if</span> <span class="o">(</span><span class="n">eventType</span> <span class="o">==</span> <span class="n">AccessibilityEvent</span><span class="o">.</span><span class="na">TYPE_WINDOW_STATE_CHANGED</span>
<span class="o">&&</span> <span class="n">event</span><span class="o">.</span><span class="na">getPackageName</span><span class="o">().</span><span class="na">equals</span><span class="o">(</span><span class="n">PACKAGE_INSTALLER</span><span class="o">))</span> <span class="o">{</span>
<span class="kt">boolean</span> <span class="n">r</span> <span class="o">=</span> <span class="n">performInstallation</span><span class="o">(</span><span class="n">event</span><span class="o">);</span>
<span class="n">log</span><span class="o">(</span><span class="s">"Action Perform: "</span> <span class="o">+</span> <span class="n">r</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="n">onInterrupt</span><span class="o">()</span> <span class="o">{</span>
<span class="n">log</span><span class="o">(</span><span class="s">"AutoInstallServiceInterrupted"</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">private</span> <span class="kt">void</span> <span class="n">log</span><span class="o">(</span><span class="n">String</span> <span class="n">s</span><span class="o">)</span> <span class="o">{</span>
<span class="n">Log</span><span class="o">.</span><span class="na">d</span><span class="o">(</span><span class="n">TAG</span><span class="o">,</span> <span class="n">s</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">private</span> <span class="kt">boolean</span> <span class="n">performInstallation</span><span class="o">(</span><span class="n">AccessibilityEvent</span> <span class="n">event</span><span class="o">)</span> <span class="o">{</span>
<span class="n">List</span><span class="o"><</span><span class="n">AccessibilityNodeInfo</span><span class="o">></span> <span class="n">nodeInfoList</span><span class="o">;</span>
<span class="cm">/*
* 有的手机会弹2次,有的只弹一次,在替换安装时会出现确定按钮,
* 为了大而全,下面定义了比较多的内容,可按需增减。
*/</span>
<span class="n">String</span><span class="o">[]</span> <span class="n">labels</span> <span class="o">=</span> <span class="k">new</span> <span class="n">String</span><span class="o">[]{</span><span class="s">"确定"</span><span class="o">,</span> <span class="s">"安装"</span><span class="o">,</span> <span class="s">"下一步"</span><span class="o">,</span> <span class="s">"完成"</span><span class="o">};</span>
<span class="k">for</span> <span class="o">(</span><span class="n">String</span> <span class="n">label</span> <span class="o">:</span> <span class="n">labels</span><span class="o">)</span> <span class="o">{</span>
<span class="n">nodeInfoList</span> <span class="o">=</span> <span class="n">event</span><span class="o">.</span><span class="na">getSource</span><span class="o">().</span><span class="na">findAccessibilityNodeInfosByText</span><span class="o">(</span><span class="n">label</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">nodeInfoList</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&&</span> <span class="o">!</span><span class="n">nodeInfoList</span><span class="o">.</span><span class="na">isEmpty</span><span class="o">())</span> <span class="o">{</span>
<span class="kt">boolean</span> <span class="n">performed</span> <span class="o">=</span> <span class="n">performClick</span><span class="o">(</span><span class="n">nodeInfoList</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">performed</span><span class="o">)</span> <span class="k">return</span> <span class="kc">true</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="k">return</span> <span class="kc">false</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">private</span> <span class="kt">boolean</span> <span class="n">performClick</span><span class="o">(</span><span class="n">List</span><span class="o"><</span><span class="n">AccessibilityNodeInfo</span><span class="o">></span> <span class="n">nodeInfoList</span><span class="o">)</span> <span class="o">{</span>
<span class="k">for</span> <span class="o">(</span><span class="n">AccessibilityNodeInfo</span> <span class="n">node</span> <span class="o">:</span> <span class="n">nodeInfoList</span><span class="o">)</span> <span class="o">{</span>
<span class="cm">/*
* 这里还可以根据node的类名来过滤,大多数是button类,这里也是为了大而全,
* 判断只要是可点击的是可用的就点。
*/</span>
<span class="k">if</span> <span class="o">(</span><span class="n">node</span><span class="o">.</span><span class="na">isClickable</span><span class="o">()</span> <span class="o">&&</span> <span class="n">node</span><span class="o">.</span><span class="na">isEnabled</span><span class="o">())</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">node</span><span class="o">.</span><span class="na">performAction</span><span class="o">(</span><span class="n">AccessibilityNodeInfo</span><span class="o">.</span><span class="na">ACTION_CLICK</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="k">return</span> <span class="kc">false</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span></code>
AndroidManifest里面要声明权限,除了上面从代码里面可以过滤,通过meta-data的xml里也可直接配置过滤
AndroidManifest.xml
<code><span class="cp"><?xml version="1.0" encoding="utf-8"?></span> <span class="nt"><manifest</span> <span class="na">xmlns:android=</span><span class="s">"http://schemas.android.com/apk/res/android"</span> <span class="na">package=</span><span class="s">"com.mrqyoung.autoinstall"</span> <span class="nt">></span> <span class="nt"><application</span> <span class="na">android:allowBackup=</span><span class="s">"true"</span> <span class="na">android:icon=</span><span class="s">"@mipmap/ic_launcher"</span> <span class="na">android:label=</span><span class="s">"@string/app_name"</span> <span class="na">android:theme=</span><span class="s">"@android:style/Theme.Black"</span> <span class="nt">></span> <span class="nt"><service</span> <span class="na">android:name=</span><span class="s">".AutoInstallService"</span> <span class="na">android:permission=</span><span class="s">"android.permission.BIND_ACCESSIBILITY_SERVICE"</span><span class="nt">></span> <span class="nt"><intent-filter></span> <span class="nt"><action</span> <span class="na">android:name=</span><span class="s">"android.accessibilityservice.AccessibilityService"</span> <span class="nt">/></span> <span class="nt"></intent-filter></span> <span class="nt"><meta-data</span> <span class="na">android:name=</span><span class="s">"android.accessibilityservice"</span> <span class="na">android:resource=</span><span class="s">"@xml/accessibilityservice"</span> <span class="nt">/></span> <span class="nt"></service></span> <span class="nt"></application></span> <span class="nt"></manifest></span></code>
在AndroidManifest里面引用的meta-data文件,样例。
@xml/accessibilityservice.xml
<code><span class="cp"><?xml version="1.0" encoding="utf-8"?></span> <span class="nt"><accessibility-service</span> <span class="na">android:accessibilityEventTypes=</span><span class="s">"typeWindowStateChanged"</span> <span class="na">android:packageNames=</span><span class="s">"com.android.packageinstaller"</span> <span class="na">android:description=</span><span class="s">"@string/description"</span> <span class="na">android:accessibilityFeedbackType=</span><span class="s">"feedbackVisual"</span> <span class="na">android:notificationTimeout=</span><span class="s">"100"</span> <span class="na">android:accessibilityFlags=</span><span class="s">"flagDefault"</span> <span class="na">android:canRetrieveWindowContent=</span><span class="s">"true"</span> <span class="na">xmlns:android=</span><span class="s">"http://schemas.android.com/apk/res/android"</span> <span class="nt">/></span> <span class="c"><!-- 第3行等同于过滤 AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED --></span> <span class="c"><!-- 第4行只监听 com.android.packageinstaller --></span></code>
2. 展示区
进行了3种方式的安装测试:
-
直接在手机上点击apk文件弹出安装,它能自动安装完成。写代码时没有那些特殊机器只好用这种方式来调试。
-
通过adb安装,新安装和覆盖安装,在特殊手机上弹出安装后能自动安装完成。
[无图] -
在运行appium时,appium会安装2个apk,新安装和覆盖安装,弹出安装后能自动完成。
3. 其它区
利用AccessibilityService可以自动识别界面上的内容,并进行操作,也能达到自动化操作的目的。绿色守护的自动停止应用,豌豆荚的自动安装,一些抢红包的工具是用它来实现的。在最开始遇到这个问题的时候,我给了一个简单直接暴力的解决方法,在批处理中:
<code>...
start adb shell "sleep 3 && input tap 1200 300"
adb install -r xxx.apk
...</code>
后来为了兼容性强,做成了一个自动安装的工具,也许能勉强解决在自动化过程中出现安装提示的问题吧,期待大家共同验证和完善。