[Android Pro] Android 4.1 使用 Accessibility实现免Root自动批量安装功能

reference to  :  http://www.infoq.com/cn/articles/android-accessibility-installing?utm_campaign=infoq_content&utm_source=infoq&utm_medium=feed&utm_term=global

对于国内Android设备,应用的自动批量安装/更新一直是一个痛点,在之前,第三方应用商店通常要求设备Root,然后调用系统的 PackageManagerService命令行来实现后台安装。最近,豌豆荚利用Android Accessibility(辅助功能)在业内率先实现了免Root自动批量安装功能。

这个功能实现的原理是,在后台批量下载应用后,调用系统的PackageInstaller,获取安装界面的按钮位置,然后通过Accessibility提供的模拟用户点击功能,代替用户自动点击下一步,直到安装结束。

虽然技术看起来不是特别困难,但在实现中还是有不少坑的,豌豆荚工程师向我们分享了该功能的一些技术细节和实践经验。

Android Accessibility API介绍与调用方法

对于那些由于视力、听力或其它身体原因导致不能方便使用Android智能手机的用户,Android提供了Accessibility功能和服务 帮助这些用户更加简单地操作设备,包括文字转语音、触觉反馈、手势操作、轨迹球和手柄操作。开发者可以搭建自己的Accessibility服务,这可以 加强应用的可用性,例如声音提示,物理反馈,和其他可选的操作模式。

随着Android系统版本的迭代,Accessibility功能也越来越强大,它能实时地获取当前操作应用的窗口元素信息,并能够双向交互,既能获取用户的输入,也能对窗口元素进行操作,比如点击按钮。更多的介绍见Android开发者官网的Accessibility页面

调用Android Accessibility API需要三个步骤:申请权限、注册 Service、配置 Accessibility Service Info。使用Accessibility API需要的权限如下:

<uses-permission android:name="android.permission.BIND_ACCESSIBILITY_SERVICE"/>

注册Service

<service android:name="com.your.AccessibilityImpl.className"
        android:label="@string/acc_auto_install_service_name"
        android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
        android:enabled="@bool/enable_accessibility">
   <intent-filter>
       <action android:name="android.accessibilityservice.AccessibilityService" />
   </intent-filter>
   <meta-data android:name="android.accessibilityservice" android:resource="@xml/accessibility_config" />
</service>

配置Accessibility Service Info

<?xml version="1.0" encoding="utf-8"?>
<accessibility-service
   xmlns:android="http://schemas.android.com/apk/res/android"
   android:description="@string/acc_description" android:accessibilityEventTypes="typeAllMask"
   android:accessibilityFlags="flagDefault"
   android:accessibilityFeedbackType="feedbackGeneric"
   android:notificationTimeout="100"
   android:canRetrieveWindowContent="true"
   android:settingsActivity="com.your.settingActivity"
   android:packageNames="packageName1,packageName2"
/>

需要说明的一点是,在配置配置 Accessibility Service Info时,如果明确的知道目标APP的包名,那一定要使用packageNames属性进行设置。举一个例子:

在一些使用虚拟键盘的APP中,经常会出现这样的逻辑

Button button = (Button) findViewById(R.id.button);
String num = (String) button.getText();

在一般情况下,getText方法的返回值是Java.lang.String类的实例,上面这段代码可以正确运行。但是在开启Accessibility Service之后,如果没有指定packageNames,系统会对所有APP的UI都进行Accessible的处理。在这个例子中的表现就是getText方法的返回值变成了android.text.SpannableString类的实例(Java.lang.Stringandroid.text.SpannableString都实现了java.lang.CharSequence接口),进而造成目标APP崩溃。

所以强烈建议在注册Accessibility Service时指定目标APP的packageName,以减少手机上其他应用的莫名崩溃(代码中有这样的逻辑的各位,也请默默的改为调用toString()方法吧)。

实现AccessibilityService

继承android.accessibilityservice.AccessibilityService并重载onAccessibilityEventonInterrupt方法:

public class AccessibilityImpl extends AccessibilityService {
    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {}
    @Override
    public void onInterrupt() {}
}

作者 徐川 发布于 2015年5月28日 | 讨论

对于国内Android设备,应用的自动批量安装/更新一直是一个痛点,在之前,第三方应用商店通常要求设备Root,然后调用 系统的PackageManagerService命令行来实现后台安装。最近,豌豆荚利用Android Accessibility(辅助功能)在业内率先实现了免Root自动批量安装功能。

这个功能实现的原理是,在后台批量下载应用后,调用系统的PackageInstaller,获取安装界面的按钮位置,然后通过Accessibility提供的模拟用户点击功能,代替用户自动点击下一步,直到安装结束。

虽然技术看起来不是特别困难,但在实现中还是有不少坑的,豌豆荚工程师向我们分享了该功能的一些技术细节和实践经验。

Android Accessibility API介绍与调用方法

对于那些由于视力、听力或其它身体原因导致不能方便使用Android智能手机的用户,Android提供了Accessibility功能和服务 帮助这些用户更加简单地操作设备,包括文字转语音、触觉反馈、手势操作、轨迹球和手柄操作。开发者可以搭建自己的Accessibility服务,这可以 加强应用的可用性,例如声音提示,物理反馈,和其他可选的操作模式。

随着Android系统版本的迭代,Accessibility功能也越来越强大,它能实时地获取当前操作应用的窗口元素信息,并能够双向交互,既能获取用户的输入,也能对窗口元素进行操作,比如点击按钮。更多的介绍见Android开发者官网的Accessibility页面

调用Android Accessibility API需要三个步骤:申请权限、注册 Service、配置 Accessibility Service Info。使用Accessibility API需要的权限如下:

<uses-permission android:name="android.permission.BIND_ACCESSIBILITY_SERVICE"/>

注册Service

<service android:name="com.your.AccessibilityImpl.className"
        android:label="@string/acc_auto_install_service_name"
        android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
        android:enabled="@bool/enable_accessibility">
   <intent-filter>
       <action android:name="android.accessibilityservice.AccessibilityService" />
   </intent-filter>
   <meta-data android:name="android.accessibilityservice" android:resource="@xml/accessibility_config" />
</service>

配置Accessibility Service Info

<?xml version="1.0" encoding="utf-8"?>
<accessibility-service
   xmlns:android="http://schemas.android.com/apk/res/android"
   android:description="@string/acc_description" android:accessibilityEventTypes="typeAllMask"
   android:accessibilityFlags="flagDefault"
   android:accessibilityFeedbackType="feedbackGeneric"
   android:notificationTimeout="100"
   android:canRetrieveWindowContent="true"
   android:settingsActivity="com.your.settingActivity"
   android:packageNames="packageName1,packageName2"
/>

需要说明的一点是,在配置配置 Accessibility Service Info时,如果明确的知道目标APP的包名,那一定要使用packageNames属性进行设置。举一个例子:

在一些使用虚拟键盘的APP中,经常会出现这样的逻辑

Button button = (Button) findViewById(R.id.button);
String num = (String) button.getText();

在一般情况下,getText方法的返回值是Java.lang.String类的实例,上面这段代码可以正确运行。但是在开启Accessibility Service之后,如果没有指定packageNames,系统会对所有APP的UI都进行Accessible的处理。在这个例子中的表现就是getText方法的返回值变成了android.text.SpannableString类的实例(Java.lang.Stringandroid.text.SpannableString都实现了java.lang.CharSequence接口),进而造成目标APP崩溃。

所以强烈建议在注册Accessibility Service时指定目标APP的packageName,以减少手机上其他应用的莫名崩溃(代码中有这样的逻辑的各位,也请默默的改为调用toString()方法吧)。

实现AccessibilityService

继承android.accessibilityservice.AccessibilityService并重载onAccessibilityEventonInterrupt方法:

public class AccessibilityImpl extends AccessibilityService {
    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {}
    @Override
    public void onInterrupt() {}
}

以onAccessibilityEvent与onInterrupt为入口实现业务逻辑代码。

如何获取UI元素

onAccessibilityEvent中,使用参数event的getSource方法获取到的AccessibilityNodeInfo实例,即为触发这次事件的UI节点。

如果需要获取当前界面上的其它元素,需要获取到当前界面UI Tree的根节点后再使用findAccessibilityNodeInfosByText或者findAccessibilityNodeInfosByViewId方法进行获取。

需要注意的一点是,findAccessibilityNodeInfosByText在获取UI元素时的判断逻辑是contains而非equals,在使用时可能要根据具体业务逻辑做进一步的处理。

模拟用户点击

实现AccessibilityService,并获取界面上UI元素之后,可以使用下面的代码来模拟用户点击:

nodeInfo.performAction(AccessibilityNodeInfo.ACTION_CLICK);

需要注意的是,在触发事件之前需要确定该UI元素在界面上是否依旧存在。使用该方法还可以模拟用户的其它操作,甚至是复制粘贴这种行为,具体可以参考AccessibilityNodeInfo

reference to : http://stackoverflow.com/questions/18094982/detect-if-my-accessibility-service-is-enabled

check is  our AccessibilityService is swith on

// To check if service is enabled
    private boolean isAccessibilitySettingsOn(Context mContext) {
        int accessibilityEnabled = 0;
        final String service = mContext.getPackageName() + File.separator + mContext.getPackageName() + ".accesibility.MyAccesibilityService";
        boolean accessibilityFound = false;
        try {
            accessibilityEnabled = Settings.Secure.getInt(
                    mContext.getApplicationContext().getContentResolver(),
                    android.provider.Settings.Secure.ACCESSIBILITY_ENABLED);
            Log.v(TAG, "accessibilityEnabled = " + accessibilityEnabled);
        } catch (Exception e) {
            Log.e(TAG, "Error finding setting, default accessibility to not found: "
                    + e.getMessage());
        }
        TextUtils.SimpleStringSplitter mStringColonSplitter = new TextUtils.SimpleStringSplitter(‘:‘);

        if (accessibilityEnabled == 1) {
            Log.v(TAG, "***ACCESSIBILIY IS ENABLED*** -----------------");
            String settingValue = Settings.Secure.getString(
                    mContext.getApplicationContext().getContentResolver(),
                    Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
            if (settingValue != null) {
                TextUtils.SimpleStringSplitter splitter = mStringColonSplitter;
                splitter.setString(settingValue);
                while (splitter.hasNext()) {
                    String accessabilityService = splitter.next();

                    Log.v(TAG, "-------------- > accessabilityService :: " + accessabilityService);
                    if (accessabilityService.equalsIgnoreCase(service)) {
                        Log.v(TAG, "We‘ve found the correct setting - accessibility is switched on!");
                        return true;
                    }
                }
            }
        } else {
            Log.v(TAG, "***ACCESSIBILIY IS DISABLED***");
        }

        return accessibilityFound;
    }

go to setting ui :

if (!isAccessibilitySettingsOn(this)) {
            startActivity(new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS));
        }
时间: 2024-10-07 20:52:01

[Android Pro] Android 4.1 使用 Accessibility实现免Root自动批量安装功能的相关文章

[Android Pro] Android应用性能测试之CPU和内存占用(转载)

首先稍做分析一下测试环境:我们知道CPU和内存占用是一个实时变化的状态,而市面上还没有具体的哪款android应用能做到实时监控CPU和内存占用并使用log日志保存.考虑到android的底层框架是基于Linux的平台,所有我们可以通过Linux的资源监控命令来实现对android平台的资源实时监控. 要做到上边的测试环境的实现,需要具备以下几点: 1.被测试的手机具备root权限:因为涉及到底层的linux命令,需要读取或执行相应的文件.至于如何root你的手机,不同型号的手机root的方法不

[Android Pro] android 杀死进程的方法

1: 杀死自己进程的方法 android.os.Process.killProcess(Process.myPid()); 2:杀死别人进程的方法(不能杀死自己) -------a: activityManager.killBackgroundProcesses ActivityManager activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); activityManager.killBa

[Android Pro] Android权限设置android.permission完整列表

android.permission.ACCESS_CHECKIN_PROPERTIES允许读写访问"properties”表在checkin数据库中,改值可以修改上传( Allows read/write access to the “properties” table in the checkin database, to change values that get uploaded) android.permission.ACCESS_COARSE_LOCATION允许一个程序访问Cel

[Android Pro] Android系统手机端抓包方法

抓包准备 1. 手机要有root权限 2. 下载tcpdump   http://www.strazzere.com/android/tcpdump 3. adb push c:\wherever_you_put\tcpdump /data/local/tcpdump 4. adb shell chmod 6755 /data/local/tcpdump 5, adb shell,   su获得root权限 6, cd /data/local 7, ./tcpdump -i any -p -s

[Android Pro] Android学习——在线查看android源代码的3种方式

原文:http://blog.csdn.net/chuekup/article/details/8067075 1. https://github.com/android 2. http://grepcode.com/project/repository.grepcode.com/java/ext/com.google.android/android/ 上面2种都是通过第三方网站直接访问,这里主要说说下面这种方法: 3. 一个chrome内核浏览器插件:Android SDK Reference

[Android Pro] android 禁用和开启四大组件的方法(setComponentEnabledSetting )

在用到组件时,有时候我们可能暂时性的不使用组件,但又不想把组件kill掉,比如创建了一个broadcastReceiver广播监听器,用来想监听 第一次开机启动后获得系统的许多相关信息,并保存在文件中,这样以后每次开机启动就不需要再去启动该服务了,也就是说如果没有把receiver关闭掉, 就算是不做数据处理,但程序却还一直在后台运行会消耗电量和内存,这时候就需要把这个receiver给关闭掉. 如何关闭组件?  关闭组件其实并不难,只要创建packageManager对象和ComponentN

[Android Pro] Android开发实践:为什么要继承onMeasure()

reference to : http://www.linuxidc.com/Linux/2014-12/110164.htm Android开 发中偶尔会用到自定义View,一般情况下,自定义View都需要继承View类的onMeasure方法,那么,为什么要继承onMeasure()函 数呢?什么情况下要继承onMeasure()?系统默认的onMeasure()函数行为是怎样的 ?本文就探究探究这些问题. 首先,我们写一个自定义View,直接调用系统默认的onMeasure函数,看看会是怎

[Android Pro] Android开发实践:自定义ViewGroup的onLayout()分析

reference to : http://www.linuxidc.com/Linux/2014-12/110165.htm 前一篇文章主要讲了自定义View为什么要重载onMeasure()方法(见 http://www.linuxidc.com/Linux/2014-12/110164.htm),那么,自定义ViewGroup又都有哪些方法需要重载或者实现呢 ? Android开 发中,对于自定义View,分为两种,一种是自定义控件(继承View类),另一种是自定义布局容器(继承ViewG

[Android Pro] android中permission_group与permisson区别、作用

转载:http://blog.csdn.net/feng88724/article/details/6409313 其实Android在定义 permission 时, 为每个Permission都进行了分组, 每一个Permission都有一个PermissionGroup属性. 来看一下Android源码(在frameworks/base/core/res /AndroidManifest.xml): 可以看到,这边先定义了一个PermissionGroup 后又定义了Permission