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]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  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.   
  31.     @Override  
  32.     public void onInterrupt() {  
  33.         // TODO Auto-generated method stub  
  34.     }  
  35. }  
通过这个类的 onAccessibilityEvent方法 ,我们可以拿到用户在指定APP里完成比如点击,滑动,或是屏幕内容变化等用户不可控的情况下的一些回调方法,这里就作为上面所说的 触发条件 。这里我们选择TYPE_NOTIFICATION_STATE_CHANGED作为判断红包消息通知到来的触发条件,每当通知到来,我们就拿到List<CharSequence> texts = event.getText()通知栏上的text,再去循环判断是否含有“微信红包”字样即可。如果含有,就通过如下代码打开通知栏。

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  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]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();  
  2. List<AccessibilityNodeInfo> wxList = nodeInfo.findAccessibilityNodeInfosByText("领取红包");  

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

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  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]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  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.   
  11.     <!--typeAllMask是设置响应事件的类型,typeAllMask当然就是响应所有类型的事件-->  
  12.     <!--feedbackGeneric是设置回馈给用户的方式,有语音播出和振动。可以配置一些TTS引擎,让它实现发音。-->  
  13.     <!--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的,这个后面会写文介绍。

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值