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
package {你的包名};

import android.accessibilityservice.AccessibilityService;
import android.util.Log;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;

import java.util.List;

public class AutoInstallService extends AccessibilityService {

    private static final String TAG = "AutoInstallService";
    private static String PACKAGE_INSTALLER = "com.android.packageinstaller";

    public AutoInstallService() {
    }

    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
        /*
         * 回调方法,当事件发生时会从这里进入,在这里判断需要捕获的内容,
         * 可通过下面这句log将所有事件详情打印出来,分析决定怎么过滤。
         */
        //log(event.toString());
        if (event.getSource() == null) {
            log("<null> event source");
            return;
        }
        int eventType = event.getEventType();
        /*
         * 在弹出安装界面时会发生 TYPE_WINDOW_STATE_CHANGED 事件,其属主
         * 是系统安装器com.android.packageinstaller
         */
        if (eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
                && event.getPackageName().equals(PACKAGE_INSTALLER)) {
            boolean r = performInstallation(event);
            log("Action Perform: " + r);
        }

    }

    @Override
    public void onInterrupt() {
        log("AutoInstallServiceInterrupted");
    }

    private void log(String s) {
        Log.d(TAG, s);
    }

    private boolean performInstallation(AccessibilityEvent event) {
        List<AccessibilityNodeInfo> nodeInfoList;
        /*
         * 有的手机会弹2次,有的只弹一次,在替换安装时会出现确定按钮,
         * 为了大而全,下面定义了比较多的内容,可按需增减。
         */
        String[] labels = new String[]{"确定", "安装", "下一步", "完成"};
        for (String label : labels) {
            nodeInfoList = event.getSource().findAccessibilityNodeInfosByText(label);
            if (nodeInfoList != null && !nodeInfoList.isEmpty()) {
                boolean performed = performClick(nodeInfoList);
                if (performed) return true;
            }
        }
        return false;
    }

    private boolean performClick(List<AccessibilityNodeInfo> nodeInfoList) {
        for (AccessibilityNodeInfo node : nodeInfoList) {
            /*
             * 这里还可以根据node的类名来过滤,大多数是button类,这里也是为了大而全,
             * 判断只要是可点击的是可用的就点。
             */
            if (node.isClickable() && node.isEnabled()) {
                return node.performAction(AccessibilityNodeInfo.ACTION_CLICK);
            }
        }
        return false;
    }

}

AndroidManifest 里面要声明权限,除了上面从代码里面可以过滤,通过 meta-data 的 xml 里也可直接配置过滤

AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.mrqyoung.autoinstall" >

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:theme="@android:style/Theme.Black" >
        <service
            android:name=".AutoInstallService"
            android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
            <intent-filter>
                <action android:name="android.accessibilityservice.AccessibilityService" />
            </intent-filter>
            <meta-data android:name="android.accessibilityservice" android:resource="@xml/accessibilityservice" />
        </service>
    </application>

</manifest>

在 AndroidManifest 里面引用的 meta-data 文件,样例

@xml/accessibilityservice.xml
<?xml version="1.0" encoding="utf-8"?>
<accessibility-service
    android:accessibilityEventTypes="typeWindowStateChanged"    
    android:packageNames="com.android.packageinstaller"              
    android:description="@string/description"
    android:accessibilityFeedbackType="feedbackVisual"
    android:notificationTimeout="100"
    android:accessibilityFlags="flagDefault"
    android:canRetrieveWindowContent="true"
    xmlns:android="http://schemas.android.com/apk/res/android" />

<!--  第3行等同于过滤 AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED -->
<!--  第4行只监听 com.android.packageinstaller  -->

2. 展示区

进行了 3 种方式的安装测试:

3. 其它区

利用 AccessibilityService 可以自动识别界面上的内容,并进行操作,也能达到自动化操作的目的。绿色守护的自动停止应用,豌豆荚的自动安装,一些抢红包的工具是用它来实现的。在最开始遇到这个问题的时候,我给了一个简单直接暴力的解决方法,在批处理中:

...
start adb shell "sleep 3 && input tap 1200 300"
adb install -r xxx.apk
...

后来为了兼容性强,做成了一个自动安装的工具,也许能勉强解决在自动化过程中出现安装提示的问题吧,期待大家共同验证和完善。


↙↙↙阅读原文可查看相关链接,并与作者交流