九.无障碍服务AccessibilityService实现自动化

2021-04-19  本文已影响0人  奔跑的佩恩

前言

在上一节中,我们对AccessibilityService已经做了一个详细的讲解。需要了解的同学可跳转:
Android无障碍服务AccessibilityService详解
利用AccessibilityService可实现界面的点击,输入滑动等。那么这样的话,我们就可以用AccessibilityService来对其他app做简单控制。在日常开发中,我们有时开发了一个app急需做功能测试,有了AccessibilityService,我们便可以对一款app做些简单的自动化测试了。这里我封装了一个无障碍服务的帮助类——AccessibilityHelper,主要用于快速实现界面的点击,输入滑动等操作,为咱们的自动化测试提供快捷方式,这样我们在写自动化测试脚本时,就能只专注测试流程的编写了。

今天涉及知识有:

  1. 为啥封装一个AccessibilityHelper
  2. AccessibilityHelper方法简介
  3. 如何最大限度的让界面某个操作生效
  4. AccessibilityHelper在自定义AccessibilityService中的简单使用
  5. 效果图和项目结构图
  6. AccessibilityHelper源码

先来波效果图


效果图.gif

一. 为啥封装一个AccessibilityHelper

为啥封装一个AccessibilityHelper?多数对于AccessibilityService介绍的文章均停留在Android无障碍服务AccessibilityService详解文章的深度,至于如何找到界面的控件,如何实现界面该按钮的点击,输入滑动等均是绝口不提的,我在研究这玩意的时候,倒是浪费了不少时间。在网上查找,看官网文档,还是有很多不解,于是在一个开发群中请教一位大佬,人家只回复了寥寥数字:这还不简单,遂接着问具体如何实现呢,给我的回答是两字: 鸡儿。但是从对AccessibilityService基础的了解,到如何找到界面控件和实现该控件的具体操作(点击,输入滑动等)仍存在一段距离,于是AccessibilityHelper应运而生。AccessibilityHelper可以帮我们很快的定位到界面上某个控件,也能帮助我们很快的实现该控件的操作(点击,输入滑动等),让你不再烦恼如何才能找到一个界面上的某个控件,如何实现点击,滑动等,而是专注于业务流程(先执行点击跳到xxx界面,再滑动该界面...).这便是AccessibilityHelper存在的意义。既然如此,那么就让我们来看看AccessibilityHelper有哪些主要方法吧。

二. AccessibilityHelper方法简介

AccessibilityHelper作为一个单例工具类,具备以下主要方法:

    /**点击动作**/
    public boolean performClick(AccessibilityNodeInfo targetInfo) 

    /**点击返回键**/
    public boolean clickBackKey(AccessibilityService service)

    /**点击Home键**/
    public boolean clickHomeKey(AccessibilityService service)

    /**点击最近任务**/
    public boolean clickLastTaskKey(AccessibilityService service) 

    /**点击通知栏**/
    public boolean clickNotificationKey(AccessibilityService service) 

    /***
     * 根据控件显示内容text找到的控件是否存在
     *
     * 界面中可能出现多个控件显示同样的内容,则根据text获取的控件不止一个
     * 这时,则需要控件id做辅助筛选,当无viewId做筛选条件时,默认取找到第一个含内容的view返回
     *
     * @param service AccessibilityService对象
     * @param text 视图文字
     * @param viewId 参数是 com.xxx.xxx:id/tv_main,必须要带上包名
     *               当 viewId为null时,默认取找到第一个含内容的view作为查找的返回结果
     *
     * @return  true:该控件存在    false:该控件不存在
     */
    public boolean isExistViewByText(AccessibilityService service, String text,String viewId) 

    /***
     * 根据控件ViewId找到的控件是否存在
     *
     * 当界面中是一个列表的时候,根据viewId查找可能会得到一个控件列表
     * 而所要寻找的不一定是默认的第一项,这时则需要文字即text辅助查找
     *
     * @param service AccessibilityService对象
     * @param viewId 参数是 com.xxx.xxx:id/tv_main,必须要带上包名
     * @param text 控件上显示的内容,当text为null时,默认取根据id获取到的列表的第一个
     *
     * @return  true:该控件存在    false:该控件不存在
     */
    public boolean isExistViewById(AccessibilityService service, String viewId,String text)

    /***
     * 根据 EditText中的内容找到 EditText 对象,并改变EditText中的内容
     *
     * @param service AccessibilityService对象
     * @param viewText EditText原来显示的内容
     * @param viewId 参数是 com.xxx.xxx:id/tv_main,必须要带上包名
     *               当 viewId为null时,默认取找到第一个含内容的view作为查找的返回结果
     * @param message  EditText中要设置的内容
     * @return
     */
    public boolean changeInputByViewText(AccessibilityService service, String viewText,String viewId,String message)

    /***
     * 根据 EditText中的ViewId找到 EditText 对象,并改变EditText中的内容
     *
     * @param service  AccessibilityService对象
     * @param viewId 参数是 com.xxx.xxx:id/tv_main,必须要带上包名
     * @param viewText 控件上显示的内容,当text为null时,默认取根据id获取到的列表的第一个
     * @param message EditText中要设置的内容
     * @return
     */
    public boolean changeInputByViewId(AccessibilityService service, String viewId,String viewText,String message)

    /***
     * 根据控件上显示的文字找到该控件,并执行点击事件
     *
     * 注:若该控件不可点击,则找其父控件甚至父级的父级...来执行点击
     *
     * 当界面中是一个列表的时候,根据viewId查找可能会得到一个控件列表
     * 而所啊哟寻找的不一定是默认的第一项,这时则需要文字即text辅助查找
     *
     * @param service AccessibilityService对象
     * @param text 控件上显示的内容
     * @param viewId 参数是 com.xxx.xxx:id/tv_main,必须要带上包名
     *               当 viewId为null时,默认取找到第一个含内容的view作为查找的返回结果
     * @return
     */
    public boolean performClickByText(AccessibilityService service, String text,String viewId)

    /***
     * 根据控件ViewId找到该控件,并执行点击事件
     *
     * 注:若该控件不可点击,则找其父控件甚至父级的父级...来执行点击
     *
     * 当界面中是一个列表的时候,根据viewId查找可能会得到一个控件列表
     * 而所啊哟寻找的不一定是默认的第一项,这时则需要文字即text辅助查找
     *
     * @param service AccessibilityService对象
     * @param viewId 参数是 com.xxx.xxx:id/tv_main,必须要带上包名
     * @param text 控件上显示的内容,当text为null时,默认取根据id获取到的列表的第一个
     *
     * @return
     */
    public boolean performClickById(AccessibilityService service, String viewId,String text)

   /***
     * 手势滑动
     *
     * 注: 开始滑动时间与滑动延长时间参考值如下:
     *     startTime=100L,
     *     duration=500L
     *
     * @param service  AccessibilityService对象
     * @param startX 起始 x 坐标
     * @param startY 起始 Y 坐标
     * @param endX  结束 X 坐标
     * @param endY  结束 Y 坐标
     * @param startTime  滑动开始时间(单位毫秒)
     * @param duration  滑动持续时间(单位毫秒)
     * @param callback 监听
     * @return
     */
    public boolean performGestureSliding(AccessibilityService service,
                                         float startX,
                                         float startY,
                                         float endX,
                                         float endY,
                                         long startTime,
                                         long duration,
                                         AccessibilityService.GestureResultCallback callback) 

    /***
     * 手势执行点击事件
     *
     * 注: 开始点击时间与点击延长时间参考值如下:
     *     startTime=50L,
     *     duration=500L
     *
     * @param service  AccessibilityService对象
     * @param x 点击屏幕的 x 坐标
     * @param y 点击屏幕的 y 坐标
     * @param startTime  滑动开始时间(单位毫秒)
     * @param duration  滑动持续时间(单位毫秒)
     * @param callback 监听
     * @return
     */
    public boolean performClickByGesture(AccessibilityService service,
                                         float x, float y,
                                         long startTime, long duration,
                                         AccessibilityService.GestureResultCallback callback)

    /***
     * 线程休眠时间
     *
     * @param miao:double类型, 单位秒
     */
    public void waitTime(double miao) 

三. 如何最大限度的让界面某个操作生效

归根结底是要准确的找到界面上该控件,然后进行操作事件。由于一般app界面ui设计的复杂性,可能一个界面有很多控件,列表,弹窗等。这使得要准确寻找到界面上某个控件异常困难,控件找不到,就更别谈操作了。
那么为了最大程度的找到某个特定控件,我们可以遵循以下规则(以点击动作为例):

如何打开要测试的app?
请参考Android实现打开第三方app
如何找到界面控件的viewId内容界面坐标等信息?
请参考Android上DDMS的简单使用

四.AccessibilityHelper在自定义AccessibilityService中的简单使用

以本项目打开另一个项目kotlinTest,并点击该项目中的测试按钮,使之显示文字为例
下面贴出AccessibilityHelper在自定义无障碍服务MyService中的使用代码:

/**
 * Title:无障碍服务
 *
 * description:
 * 这个服务是不需要你在activity里去开启的,属于系统级别辅助服务 需要在设置里去手动开启 和我们平常app里
 * 经常使用的service 是有很大不同的 非常特殊
 * 你可以在 \sdk\samples\android-23\legacy\ApiDemos 这样的目录下 找到这个工程 这个工程下面有一个accessibility
 * 包 里面有关于这个服务的demo 当然他们那个demo 非常复杂,但是信息量很大,有兴趣深入研究的同学可以多看demo
 * 我这里只实现最基本的功能 且没有做冗余和异常处理,只包含基础功能,不能作为实际业务上线!
 *
 * autor:pei
 * created on 2021/4/7
 */
@RequiresApi(api = Build.VERSION_CODES.N)
public class MyService extends AccessibilityService {

    public static final String TEST="com.kotlintest";

    public static MyService mService;

    private static int mCount=0;

    /***
     * AccessibilityService 这个服务可以关联很多属性,这些属性 一般可以通过代码在这个方法里进行设置,
     * 我这里偷懒 把这些设置属性的流程用xml 写好 放在manifest里,如果你们要使用的时候需要区分版本号
     * 做兼容,在老的版本里是无法通过xml进行引用的 只能在这个方法里手写那些属性 一定要注意.
     * 同时你的业务如果很复杂比如需要初始化广播啊之类的工作 都可以在这个方法里写。
     */
    @Override
    protected void onServiceConnected() {
        super.onServiceConnected();
        LogUtil.i("====建立服务链接====");

    }


    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
        LogUtil.i("====启动Event====");
        //屏幕尺寸
        DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
        int x = displayMetrics.widthPixels;
        int y = displayMetrics.heightPixels;
        LogUtil.i("======屏幕尺寸:x="+x+"  y="+y);

        mService=this;

        if (event.getPackageName() == null) {
            LogUtil.e("=======event.getPackageName()为空====");
            return;
        }
        String packageName = event.getPackageName().toString();
        if(StringUtil.isNotEmpty(packageName)){
            switch (packageName) {
                case TEST://测试demo
                    LogUtil.i("========测试demo=========");

                    doTask();
                    break;
                default:
                    break;
            }
        }

    }

    @Override
    public void onInterrupt() {
        LogUtil.i("====无障碍服务要结束了====");
        mService=null;

    }

    /**服务是否启动**/
    public static boolean isStart(){
        return mService!=null;
    }


    private void doTask(){
        //延时一秒
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        LogUtil.i("=========mCount="+mCount);
        switch (mCount) {
            case 1://点击按钮
                //点击测试按钮
                AccessibilityHelper.getInstance().performClickById(mService,"com.kotlintest:id/mBtnTest",null);
                break;
            default:
                break;
        }
        mCount++;

    }

}

五.效果图和项目结构图

效果图.gif

项目结构图


image.png

六. AccessibilityHelper源码

AccessibilityHelper源码如下:

上一篇下一篇

猜你喜欢

热点阅读