android 辅助功能(无障碍) AccessibilityService 实战入门详解

android 辅助功能(无障碍) AccessibilityService 实战入门详解本君初入辅助功能也是一头雾水,各种百度结果还是一知半解,得到的大多都是对辅助功能类的翻译,仅仅是理论层面上,到实战上确是千差万别,在此记之。一、原理:大致简述一下,谷歌已经在View、ViewGroup、TextView等控件的文字改变、滑动、UI变化埋下了接口,当这些状态变化时控件会回调系统API,API系统然后对这些对象的数据进行组装,为了数据的安全性,系统会重新创建一些对象(Access…

大家好,欢迎来到IT知识分享网。

本君初入辅助功能也是一头雾水,各种百度结果还是一知半解,得到的大多都是对辅助功能类的翻译,仅仅是理论层面上,到实战上确是千差万别,在此记之。

一、原理:大致简述一下,谷歌已经在View、ViewGroup、TextView等控件的文字改变、滑动、UI变化埋下了接口,当这些状态变化时控件会回调系统API,API系统然后对这些对象的数据进行组装,为了数据的安全性,系统会重新创建一些对象(AccessibilityEvent、AccessibilityNodeInfo)来间接保存这些数据,然后通过跨进程将这些数据返回给对应的Service中。

二、使用范围:首先,辅助功能不可能直接操作外部对象,辅助功能只能在本进程调用指定系统方法,由系统再分发给指定外部对象,辅助功能做的事基本和用户能做的差不多

三、实用场景:自动抢红包、自动安装APP等模拟操作(你能做的他都能),辅助用户操作、发音(本意是这样的),后台保活(不活如何辅助😏)

四、特别注意事项:AccessibilityEvent、AccessibilityNodeInfo里面的所有set方法均无效(这些只是方便系统把数据塞进去,跟我们没关系),我们能做的只有:get、is、find等获取数据的方法,以及极少的操作performAction,dispatchGesture等

实战第一步:配置辅助功能的Service:

        <service
            android:name="你的service名字(例如博主的.HongBaoService)"
            android:enabled="true"
            android:exported="true"
            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="res/xml目录新增一个xml(例如博主@xml/qianghongbao)"/>
        </service>

qianghongbao.xml文件:对应对的象及介绍AccessibilityServiceInfo

<?xml version="1.0" encoding="utf-8"?>
<accessibility-service
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:accessibilityEventTypes="typeAllMask"
    android:accessibilityFeedbackType="feedbackGeneric"
    android:accessibilityFlags="flagReportViewIds|flagRetrieveInteractiveWindows"
    android:canPerformGestures="true"
    android:canRetrieveWindowContent="true"
    android:description="@string/hongbao"
    android:notificationTimeout="100"
    android:packageNames="com.tencent.mm,com.android.systemui"
    />

accessibilityEventTypes:响应的事件类型(单击、长按、滑动、通知等),这里当然是全部事件啦

accessibilityFeedbackType:回显给用户的方式(例如:配置TTS引擎,实现发音),辅助嘛…

accessibilityFlags:很关键,你的应用程序需要获取哪些信息:1.flagDefault默认; 2.flagIncludeNotImportantViews显示所有视图节点(主要是效率,才会有这个属性,如下图FL套LL套TV,如果LL均没有任何可交互的属性(如没点击事件),则这个LL会被当做不重要的视图来处理,没有flagIncludeNotImportantViews属性时FL.getChild时只会得到TV);3.flagReportViewIds允许得到View的ID(对应AccessibilityNodeInfo.getViewIdResourceName()方法,如果不写此属性只会得到空); 其他标志flagRequestTouchExplorationMode、flagRequestEnhancedWebAccessibility、flagRequestFilterKeyEvents、flagRetrieveInteractiveWindows的具体介绍见AccessibilityServiceInfo类中以FLAG_开头的静态常量

flagIncludeNotImportantViews对比

canPerformGestures:允许app发送手势(api24及以上才可以使用手势),肯定true了

description:描述(会在开启辅助功能页面看到这段文字)

notificationTimeout:响应时间间隔100就好了

packageNames:需要辅助的app包名(比如博主的只针对wx和系统桌面),不写表示所有app

这些注释都可以在AccessibilityServiceInfo里面搜索到,建议去看看

第二步:写Service

public class HongBaoService extends AccessibilityService {
    private final String TAG = getClass().getName();

    public static HongBaoService mService;

    //初始化
    @Override
    protected void onServiceConnected() {
        super.onServiceConnected();
        Utils.toast("O(∩_∩)O~~\r\n红包锁定中...");
        mService = this;
    }

    //实现辅助功能
    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
    }

    @Override
    public void onInterrupt() {
        Utils.toast("(;′⌒`)\r\n红包功能被迫中断");
        mService = null;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Utils.toast("%>_<%\r\n红包功能已关闭");
        mService = null;
    }

    
    // 公共方法
    

    /**
     * 辅助功能是否启动
     */
    public static boolean isStart() {
        return mService != null;
    }
}

 第三步:在MainActivity中检查并跳转到辅助功能

if (!HongBaoService.isStart()) {
    try {
        mActivity.startActivity(new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS));
    } catch (Exception e) {
        mActivity.startActivity(new Intent(Settings.ACTION_SETTINGS));
        e.printStackTrace();
    }
}

第四步:选择你的app,打开辅助功能

应该立即就可以看到 “红包锁定中……” 的提示语

对service的额外封装已经写在博客最后的demo中了,可以很方便的进行查找控件

常用的操作(核心逻辑就是performAction和dispatchGesture,想了解更多自己看源码,注释很详细)

ddms工具:快速查看手机上的id、text、ContentDescription、控件绝对位置等必不可少的工具,studio3.0以上的打方式:你Android SDK的目录>tools>monitor.bat(这是mac的bat,windows自己瞅瞅, SDK在MAC中的默认位置:当前用户/Library(资源库)/Android/SDK)(如果没有了请下载3.0版本的as:Android Studio 下载文件归档  |  Android 开发者  |  Android Developers

performAction:发送click事件、修改/设置text文本、view获取焦点、点击home键、点击back键等具体见AccessibilityNodeInfo类以ACTION_开头静态常量以及注释会告诉你如何使用

dispatchGesture:分发手势,手势可以模拟用户全部操作,如手指点击、长按、滑动、多指触摸。进行中的手势只允许有一个,多次发送手势,之前正在进行的手势会被取消

findAccessibilityNodeInfosByText:目前大部分明文文本都能find到,但类似微信将关键数据改成了自定义view,不对外开放text内容(现在聊天的文本、好友的微信昵称都无法找到并且id每次更新都变化)

findAccessibilityNodeInfosByViewId:参数是com.xxx.xxx:id/tv_main,必须要带上包名

ContentDescription:被称为描述,webview页或者imageview中经常见到,没有id或text的控件基本只能用它了(小提示:还可以通过查找邻近的控件来获得),作用很大见demo的HongBaoService.findFirst、findAll及相关抽象类

通知栏点击操作

    public void onAccessibilityEvent(AccessibilityEvent event) {
        //通知栏,打开红包
        switch (event.getEventType()) {//先判断是否是通知栏红包和转圈圈界面,这两个任何状态都会去点击
            //第一步:监听通知栏消息,拦截通知的红包
            case AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED:
                for (CharSequence text : event.getText()) {
                    String content = text.toString();
                    //收到红包提醒
                    if (content.contains("[微信红包]") || content.contains("[QQ红包]")) {
                        //模拟打开通知栏消息,打开后会有新的广播进入微信或者qq
                        if (event.getParcelableData() != null && event.getParcelableData() instanceof Notification) {
                            HongBaoService.pingUnLock();//开屏,打开屏幕
                            final PendingIntent contentIntent = ((Notification) event.getParcelableData()).contentIntent;
                            //延时的handler(因为开屏有动画)
                            TimeUtil.mHandler.postDelayed(new Runnable() {
                                @Override
                                public void run() {
                                    try {
                                        contentIntent.send();
                                    } catch (PendingIntent.CanceledException e) {
                                        e.printStackTrace();
                                    }
                                }
                            }, 500);
                        }
                        break;
                    }
                }
                break;
        }
    }

 设置text文本操作(只可用于EditText)

AccessibilityNodeInfo textInfo = service.findFirst(AbstractTF.newClassName(ST_EDITTEXT));//假设只有一个edittext
if (textInfo != null) {
    Bundle arguments = new Bundle();
    arguments.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, "文本内容");
    textInfo.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, arguments);
}

分发手势操作(API>=24) 

//发送一个点击事件
Path mPath=new Path();//线性的path代表手势路径,点代表按下,封闭的没用
mPath.moveTo(500, 500);
service.dispatchGesture(new GestureDescription.Builder().addStroke(new GestureDescription.StrokeDescription
(mPath, 手势开始时间比如立即开始0, 手势总时长比如100)).build(), 回调函数(可以为null), 回调的线程(null表示主线程));
//这是demo里封装好的,点击屏幕(100,1000)的地方
mService.dispatchGestureClick(100, 1000);

  注意:addStroke是模拟的多指触摸,add一次表示一个手指,add多次表示多个手指。如果想横向滑动后再竖向滑动只能分多次调用dispatchGesture方法,举例如下:

//此为2次事件,不连续
Path path = new Path();
path.moveTo(0, 400);
path.lineTo(400, 400);
final GestureDescription.StrokeDescription sd = new GestureDescription.StrokeDescription(path, 0, 500);
//先横滑
service.dispatchGesture(new GestureDescription.Builder().addStroke(sd).build(), new AccessibilityService.GestureResultCallback() {
    @Override
    public void onCompleted(GestureDescription gestureDescription) {
        super.onCompleted(gestureDescription);
        //也可以使用handler延时1.5秒就不用在这里回调了
        Path path2 = new Path();
        path2.moveTo(600, 600);
        path2.lineTo(600, 800);
        final GestureDescription.StrokeDescription sd2 = new GestureDescription.StrokeDescription(path2, 1000, 500);
        //滑完后再过1秒竖滑
        service.dispatchGesture(new GestureDescription.Builder().addStroke(sd2)/*.addStroke(sd2)*/.build(), null, null);
    }

    @Override
    public void onCancelled(GestureDescription gestureDescription) {
        super.onCancelled(gestureDescription);
    }
}, null);



//此为长按后再拖动,一次事件效果
//如果想适配API24和25,就写一个path左右来回移动5像素平均下来持续约1秒,然后再滑动
//API>=26
Path path = new Path();
path.moveTo(500, 1499);
path.lineTo(500, 1500);//连续手势至少移动1像素,不然会立即调用onCompleted导致出bug
final GestureDescription.StrokeDescription sd = new GestureDescription.StrokeDescription(path, 0, 1000, true);
HongBaoService.mService.dispatchGesture(new GestureDescription.Builder().addStroke(sd).build(), new AccessibilityService.GestureResultCallback() {
    @Override
    public void onCompleted(GestureDescription gestureDescription) {
        super.onCompleted(gestureDescription);
//        Toast.makeText(MainActivity.this, "手势成功", Toast.LENGTH_SHORT).show();
        Path path2 = new Path();
        path2.moveTo(500, 1500);//接着上一个
        path2.lineTo(10, 1500);
        final GestureDescription.StrokeDescription sd2 = sd.continueStroke(path2, 0, 1000, false);
        HongBaoService.mService.dispatchGesture(new GestureDescription.Builder().addStroke(sd2).build(), null, null);
    }

    @Override
    public void onCancelled(GestureDescription gestureDescription) {
        super.onCancelled(gestureDescription);
        Toast.makeText(MainActivity.this, "手势失败,请重启手机再试", Toast.LENGTH_SHORT).show();
    }
}, null);

 调用手机的back、home、最近任务、拉出通知栏

        //AccessibilityService.GLOBAL_ACTION_BACK
        //GLOBAL_ACTION_HOME
        //GLOBAL_ACTION_NOTIFICATIONS
        //GLOBAL_ACTION_RECENTS
        service.performGlobalAction(mAction);

粘贴操作(只可用于EditText,包括复制功能)

        AccessibilityNodeInfo idInfo = findFirst(AbstractTF.newId(mId));
        if (idInfo == null) return;
        ((ClipboardManager) service.getSystemService(Context.CLIPBOARD_SERVICE))
                .setPrimaryClip(ClipData.newPlainText("复制", "复制内容"));
        idInfo.performAction(AccessibilityNodeInfo.FOCUS_INPUT);
        idInfo.performAction(AccessibilityNodeInfo.ACTION_PASTE);//粘贴
        idInfo.recycle();//尽量在最后都回收掉

辅助功能的具体属性及方法我就不多说了,BD上到处都是

中国信息无障碍产品联盟的翻译:信息无障碍研究会官网-信息无障碍,信息无障碍优化,适老化,残障青少年(/知识库/技术文档)

注意:再重申一遍AccessibilityEvent、AccessibilityNodeInfo里面的所有set方法均无用

问题1:点击开启没任何反应
你的app崩溃过导致的,Android通病,目前无解,请多加点非空判断以防止崩溃。引用楼下“风吹过01”的回复:重启一下手机就好了…

问题2:开了辅助app还是经常被杀死

由于厂商各种定制,部分手机一键清理、长时间在后台的话辅助功能也会被杀死,这种情况只能参照其他保活方案了。如果仅仅是想推送的话建议直接使用厂商推送,这样就不需要保活了。如果是自己玩或者目标群体能接受的话可以开启任务栏锁定+电池优化白名单,这样肯定不会被清理。

问题3:有些控件明明在手机上却获取不到

大家用辅助功能干啥我就不多说了,那些app对辅助功能的研究不比任何人差。前面说了,辅助功能原理是View、ViewGroup等控件回调给系统服务的,所以这些app直接屏蔽了相关调用方法。无法得到控件的地方:腾讯小程序、app使用腾讯WebView并且开启屏蔽、一些可薅羊毛的app的关键地方。解决方案:无解,已经是辅助功能的极限,可以尝试截屏后进行图像识别。

demo:GitHub – weimingjue/AccessibilityExample: 辅助功能(无障碍)的使用教程,适合新手快速入门(AccessibilityService)

             AccessibilityExample: 辅助功能(无障碍)的使用教程,适合新手快速入门(AccessibilityService)

由于国内第三方厂商各种奇葩定制,demo可能会出现以下问题:

1.打开微信好友页看不到提示语,解决方法以下几种:

    ①一键清理所有的app(包括demo),重新运行app

    ②上述操作无效的话,重启手机,重新运行app

    ③上述操作依然无效的话,可能是第三方厂商屏蔽了Toast,请开启悬浮窗权限(华为最奇葩的定制)或者直接查看Logcat的日志打印

2.手势发送失败:重启手机即可

只能帮你们到这了

转载请注明出处:王能的博客android 辅助功能(无障碍) AccessibilityService 实战入门详解_findaccessibilitynodeinfosbytext_王能的博客-CSDN博客

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://yundeesoft.com/23577.html

(0)

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信