zoukankan      html  css  js  c++  java
  • [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));
            }
  • 相关阅读:
    Spring Boot开发Web应用
    使用阿里云Docker镜像加速
    六种微服务架构的设计模式
    HashMap按键排序和按值排序
    Docker搭建本地私有仓库
    Ubuntu 14.04主机上部署k8s集群
    Ubuntu 16.04下搭建kubernetes集群环境
    Docker中images中none的镜像删除
    docker 下 alpine 镜像设置时区的有效办法
    offsetLeft和style.left的区别
  • 原文地址:https://www.cnblogs.com/0616--ataozhijia/p/4915543.html
Copyright © 2011-2022 走看看