Android开发——Accessibility机制实现模拟点击(微信自动抢红包实现)

1. 何为Accessibility机制

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

随着Android系统版本的迭代,Accessibility功能也越来越强大,它能实时地获取当前操作应用的窗口元素信息,并能够双向交互,既能获取用户的输入,也能对窗口元素进行操作,比如点击按钮。Accessibility功能在使用时需要经过用户授权,如果用户拒绝授权,应用将无法实现本身的功能。

需要注意的是,此机制是免Root的,并且需要API14以上。以前做过的一个微信抢红包的小项目就是基于此机制实现的。这里稍微讲一下实现过程。

本文原创,转载请注明出处:http://blog.csdn.net/seu_calvin/article/details/51912738

2. 我们要利用Accessibility机制里的哪些功能实现模拟点击

2.1 我们要使用的Accessibility机制中最常用的三个功能:

(1)拿到用户在指定APP里完成比如点击,滑动,或是屏幕内容变化等用户不可控的情况下的一些回调方法。

拿到这些回调的目的,在本例就是作为触发条件。我们不可能写一个线程,每时每刻都去关注界面上有没有红包,这样做不仅浪费用户的电量,而且可能会造成卡顿。

(2)获取当前操作应用的窗口元素信息

说白了为了点击,我们得知道根据什么去选择点哪个View,当然要提前拿到用户当前界面的一些View的信息。(包括一些TextView,ImageButton等,图片和视频是无法获得的。)我们可以拿到TextView和Button上的文本信息。这对于我们来说很关键。并且提供了筛选的功能,有两种筛选的方式,一种是通过文字内容,即List<AccessibilityNodeInfo> list = nodeInfo.findAccessibilityNodeInfosByText(), 另一种是通过View的ID,List<AccessibilityNodeInfo> list = nodeInfo.findAccessibilityNodeInfosByViewId()。返回的都是AccessibilityNodeInfo的节点集合。

(3)模拟点击

当我们找到目标View的时候,即可实现点击。其实就是一句话。n.performAction(AccessibilityNodeInfo.ACTION_CLICK)。

当然一般的TextView点不点也没什么效果。一般Button,ImageButton,还有大部分APP里面的最下面一栏ViewGrope里的选项按钮也是可以点的。

(这里可能有人要说了,能点的东西好少啊。但毕竟是免Root的,微信红包这种还是可以完成自动点击的,其实这个机制最恐怖的是上面所说的第一个功能,获取当前界面的部分View信息。如果抢红包App里加上几句恶意代码,拿到用户通讯录,聊天记录等极其私人的信息也是可以的。后面再把获取用户隐私信息的过程写一下吧,,这篇重点是Accessibility机制下的模拟点击)。

如果想点击屏幕上的任何一个位置,是需要Root的,在这篇文章有所介绍Android开发——后台获取用户点击位置坐标(可获取用户支付宝密码)

3. 我们如何使用Accessibility机制

3.1 首先我们需要定义自己的类,并继承AccessibilityService类

[java]
  1. public class MyAccessibility extends AccessibilityService {
  2.     private static final String TAG = “MyAccessibility”;
  3.     @SuppressLint(“NewApi”)
  4.     @Override
  5.     public void onAccessibilityEvent(AccessibilityEvent event) {
  6.         // TODO Auto-generated method stub
  7.         int eventType = event.getEventType();
  8.         String eventText = “”;
  9.         Log.i(TAG, “==============Start====================”);
  10.         switch (eventType) {
  11.         case AccessibilityEvent.TYPE_VIEW_CLICKED:
  12.             eventText = “TYPE_VIEW_CLICKED”;
  13.             break;
  14.         case AccessibilityEvent.TYPE_VIEW_LONG_CLICKED:
  15.             eventText = “TYPE_VIEW_LONG_CLICKED”;
  16.             break;
  17.         case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
  18.             eventText = “TYPE_WINDOW_STATE_CHANGED”;
  19.             break;
  20.         case AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED:
  21.             eventText = “TYPE_NOTIFICATION_STATE_CHANGED”;
  22.             break;
  23.         case AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE:
  24.             eventText = “CONTENT_CHANGE_TYPE_SUBTREE”
  25.             break;
  26.         }
  27.         Log.i(TAG, eventText);
  28.         Log.i(TAG, “=============END=====================”);
  29.     }
  30.     @Override
  31.     public void onInterrupt() {
  32.         // TODO Auto-generated method stub
  33.     }
  34. }

通过这个类的onAccessibilityEvent方法,我们可以拿到用户在指定APP里完成比如点击,滑动,或是屏幕内容变化等用户不可控的情况下的一些回调方法,这里就作为上面所说的触发条件。这里我们选择TYPE_NOTIFICATION_STATE_CHANGED作为判断红包消息通知到来的触发条件,每当通知到来,我们就拿到List<CharSequence> texts = event.getText()通知栏上的text,再去循环判断是否含有“微信红包”字样即可。如果含有,就通过如下代码打开通知栏。

[java]
  1. Notification notification = (Notification) event.getParcelableData();
  2. notification.contentIntent.send();

3.2 接着我们监听CONTENT_CHANGE_TYPE_SUBTREE或TYPE_WINDOW_STATE_CHANGED作为进入聊天界面的触发条件。接着根据View上的内容找到一组可以点击的View的集合。再通过for循环去选择点击最后一个红包,这里必须点一个,因为不能做到循环点击的话,因为我们无法点击返回的按钮,所以最好选择点最后一个,如果界面上不只有一个红包的话。这样点进去之后,继续调用getRootInActiveWindow()并拿到含有“拆红包”字样的节点点击即可。点击之后会停顿在领取成功的界面,这时,是不用管的,因为下一个微信红包到来,会继续从点击通知栏进入循环。

[java]
  1. AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();
  2. List<AccessibilityNodeInfo> wxList = nodeInfo.findAccessibilityNodeInfosByText(“领取红包”);

3.3 最后要在Manifest.xml中配置我们的服务。

[java]
  1. <service
  2.    android:name=“.MyAccessibility <span style=”font-family: ‘Microsoft YaHei’;“>”</span>
  3.    android:enabled=“true”
  4.    android:exported=“true”
  5.    android:label=“@string/app_name”
  6.    android:permission=“android.permission.BIND_ACCESSIBILITY_SERVICE” >
  7.    <intent-filter>
  8.        <action android:name=“android.accessibilityservice.AccessibilityService” />
  9.    </intent-filter>
  10.    //这个声明是对这个AccessibilityService的配置
  11.    <meta-data
  12.        android:name=“android.accessibilityservice”
  13.        android:resource=“@xml/qianghongbao_service_config” />
  14. lt;/service>

3.4 其中xml/qianghongbao_service_config是做了初始化的工作,具体实现如下。

[java]
  1. <?xml version=“1.0” encoding=“utf-8”?>
  2. <accessibility-service xmlns:android=“http://schemas.android.com/apk/res/android”
  3.     android:accessibilityEventTypes=“typeAllMask”
  4.     android:accessibilityFeedbackType=“feedbackGeneric”
  5.     android:accessibilityFlags=“flagDefault”
  6.     android:canRetrieveWindowContent=“true”
  7.     android:description=“@string/accessibility_description”
  8.     android:notificationTimeout=“50”
  9.     android:packageNames=“com.tencent.mm” />
  10.     <!–typeAllMask是设置响应事件的类型,typeAllMask当然就是响应所有类型的事件–>
  11.     <!–feedbackGeneric是设置回馈给用户的方式,有语音播出和振动。可以配置一些TTS引擎,让它实现发音。–>
  12.     <!–com.tencent.mm微信的包名,便可以监听微信产生的事件–>

 

最后需要注意的是:

(1)微信必须开通知栏的设置。

(2)微信高版本测试失败,会卡在拆红包的地方。如果不介意可以尝试比较低的微信版本。我的百度网盘里有一个备份的比较低版本的微信apk包,亲测有效。http://pan.baidu.com/s/1skTw7yH

(3)手机必须是API14以上,一般是都满足的。

(4)很明显,在getRootInActiveWindow()时,遍历节点,再循环打印其getText()信息,是可以拿到用户通讯录以及聊天记录等信息的。但是拿到隐私需要“偷偷地”发送给作为“监听者”的我们,后面会专门写文介绍这个过程。

(5)nodeInfo.findAccessibilityNodeInfosByViewId()这个功能我们在本例中没有用到,其实也是很有用的,有些ImageButton上可能没有text内容,但是可以通过反编译apk文件拿到View的ID即可获取到这个节点。(这里需要注意的是,如果你想点击百度云的文件列表上的View,通过反编译是无法拿到他的ID的,因为如果你有开发经验,列表的适配器都是通过getView()去加载一个子布局,这样具体的某一行Item是不存在id这个概念的。)

(6)如果想点击屏幕上的任何一个位置,是需要Root的,这个后面会写文介绍。

发表评论

电子邮件地址不会被公开。 必填项已用*标注