我们来了解下Android的辅助功能AccessibilityService,Android提供这个类的初衷是辅助人们去使用Android设备,但通过我们的了解,才发现它的作用不仅仅只有这样。我们可用它进行自动化抢红包,自动安装的等等。
AccessibilityService
运行在后台,并且能够收到由系统发出的一些事件(AccessibilityEvent
,这些事件表示用户界面一系列的状态变化),比如焦点改变,输入内容变化,按钮被点击了等等,该种服务能够请求获取当前活动窗口并查找其中的内容。在开发过程中我们主要做的只有:get/is/find等获取数据方法。以及performAction/dispatchGesture操作。
Android辅助功能
现在我们通过一个抢红包的实例来了解AccessibilityService
的使用。
- 首先我们先了解下这个服务
public class TestService extends AccessibilityService {
/**
* 必须重写的方法:此方法用了接受系统发来的event。在你注册的event发生是被调用。在整个生命周期会被调用多次。
*/
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
}
/**
* 必须重写的方法:系统要中断此service返回的响应时会调用。在整个生命周期会被调用多次。
*/
@Override
public void onInterrupt() {
}
/**
* 当系统连接上你的服务时被调用
*/
@Override
protected void onServiceConnected() {
super.onServiceConnected();
}
/**
* 在系统要关闭此service时调用。
*/
@Override
public boolean onUnbind(Intent intent) {
return super.onUnbind(intent);
}
}
- 给辅助服务写一个配置文件
<accessibility-service
xmlns:android="http://schemas.android.com/apk/res/android"
android:accessibilityEventTypes="typeAllMask"
android:accessibilityFeedbackType="feedbackGeneric"
android:accessibilityFlags="flagDefault"
android:canRetrieveWindowContent="true"
android:description="@string/accessibility_description"
android:notificationTimeout="10" />
这些属性所代表的含义
- accessibilityEventTypes:响应那种类型的事件,比如
typeAllMask
就是响应全部事件,typeNotificationStateChanged
就是响应通知状态的改变,以及还有typeWindowStateChanged
|typeWindowContentChanged
如果需要响应多种事件类型可以以 ‘ | ’ 隔开。 - accessibilityFeedbackType:用什么方式反馈给用户
- notificationTimeout:响应时间
- packageNames:指定响应哪个应用的事件。如果不填则是响应所有的应用事件(如果以后写抢红包的辅助功能,可以只写微信的包名)
- description:辅助服务的描述信息。
- 在manifest中注册服务
<service
android:name=".TestService"
//辅助功能的名称
android:label="@string/test_service_label"
//此处必须声明一次权限
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="@xml/text_service_config" />
</service>
- 配置权限
<uses-permission android:name="android.permission.BIND_ACCESSIBILITY_SERVICE" />
- 最后就可以在服务类中,写我们的监听方法了
public void onAccessibilityEvent(AccessibilityEvent event) {
//得到事件的包名。如果注册了多个应用的事件,可以在此做一个判断。
String packageName = event.getPackageName().toString();
//得到对应的事件类型,这里有很多很多种的事件类型,具体可以自行翻阅AccessibilityEvent类中的定义。
int eventType = event.getEventType();
//得到根的view节点。可以当做当前acitivity的视图看成是树状结构的(实际上也是~。~),而我们现在就得到了它的根节点。
AccessibilityNodeInfo root = getRootInActiveWindow();
//我们可以得到此节点的文字
String rootText = root.getText().toString();
//得到此节点的class
String rootClass = root.getClass().toString();
//得到子节点的和子节点总数
root.getChild(root.getChildCount()-1);
}
当找到视图上想要的控件。就可以进行下来一写简单操作
//这两个方法如果找不到的话,都会报错。所以请做好对应的处理。
root.findAccessibilityNodeInfosByText("根据文本内容查找节点");
root.findAccessibilityNodeInfosByViewId("根据id查找节点,当然实际上很难知道id是多少~、~");
//点击操作
root.performAction(AccessibilityNodeInfo.ACTION_CLICK);
//滑动操作
root.performAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
抢红包插件
分析主要流程;
- 状态栏出现"[微信红包]"的消息提示,点击进入聊天界面
- 点击相应的红包信息,弹出抢红包界面
- 在抢红包界面点击"开",打开红包
- 在红包详情页面,查看详情,点击返回按钮返回微信聊天界面.
这里我们只演示状态栏出现消息提示的情况。
具体代码如下
public class HongbaoService extends AccessibilityService{
private KeyguardManager.KeyguardLock kl;
/**
* 是否有打开微信页面
*/
private boolean isOpenPage = false;
/**
* 是否点击了红包
*/
private boolean isOpenRP = false;
/**
* 是否点击了开按钮,打开了详情页面
*/
private boolean isOpenDetail = false;
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
PrintUtils.printEvent(event);
int eventType = event.getEventType();
switch (eventType) {
//通知栏
case AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED:
handleNotification(event);
break;
//窗体状态发生改变
case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
//页面跳转事件
case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED:
if (isOpenPage){
isOpenPage = false;
String className = event.getClassName().toString();
if (className.equals("com.tencent.mm.ui.LauncherUI")) {
getPacket();
} else if (className.equals("com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyReceiveUI")) {
// openPacket();
if (!findOpenBtn()){
close();
}
} else if (className.equals("com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyDetailUI")) {
close();
}
}
break;
}
}
@Override
public void onInterrupt() {
}
/**
* 当系统连接上你的服务时被调用
*/
@Override
protected void onServiceConnected() {
Toast.makeText(this, "连接下啦~~", Toast.LENGTH_SHORT).show();
super.onServiceConnected();
}
/**
* 在系统要关闭此service时调用。
*/
@Override
public boolean onUnbind(Intent intent) {
Toast.makeText(this, "拜拜~~", Toast.LENGTH_SHORT).show();
return super.onUnbind(intent);
}
//打开通知信息
private void handleNotification(AccessibilityEvent event){
List<CharSequence> texts = event.getText();
if (!texts.isEmpty()){
for (CharSequence text : texts){
String content = text.toString();
if (content.contains("[微信红包]")){
if (isScreenLocked()){
wakeAndUnlock();
openWeichaPage(event);
} else {
openWeichaPage(event);
}
}
}
}
}
private void openWeichaPage(AccessibilityEvent event){
//获取Parcelable对象,用于传递对象
if (event.getParcelableData() != null && event.getParcelableData() instanceof Notification){
Notification notification = (Notification) event.getParcelableData();
//得到通知栏的信息
String content = notification.tickerText.toString();
String name = content.substring(0, content.indexOf(":"));
String scontent = content.substring(content.indexOf(":"), content.length());
Log.d("mylog", "------openWeichaPage name: " + name + " content: " + scontent);
isOpenPage = true;
PendingIntent pendingIntent = notification.contentIntent;
try {
//打开通知
pendingIntent.send();
} catch (PendingIntent.CanceledException e) {
e.printStackTrace();
}
}
}
//根据查看的id.拆开红包,
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
private void openPacket() {
AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();
if (nodeInfo != null){
//根据id获得节点
List<AccessibilityNodeInfo> list = nodeInfo.findAccessibilityNodeInfosByViewId("@id/cnu");
nodeInfo.recycle();
for (AccessibilityNodeInfo item : list){
item.performAction(AccessibilityNodeInfo.ACTION_CLICK);
}
}
}
//根据唯一button控件,来拆开红包,
private boolean findOpenBtn() {
AccessibilityNodeInfo rootNode = getRootInActiveWindow();
for (int i = 0; i < rootNode.getChildCount(); i++) {
AccessibilityNodeInfo nodeInfo = rootNode.getChild(i);
Log.d("mylog", "--------RP node className = " + nodeInfo.getClassName() + " cd:" + nodeInfo.getContentDescription());
// if ("android.widget.TextView".equals(nodeInfo.getClassName()))
// {
// Log.d("mylog", "----------RPtextview" + nodeInfo.getText());
// }
if ("android.widget.Button".equals(nodeInfo.getClassName())) {
Log.d("mylog", "----------RPbutton");
nodeInfo.performAction(AccessibilityNodeInfo.ACTION_CLICK);
return true;
}
findOpenBtn();
}
return false;
}
//打开红包界面
public void getPacket() {
AccessibilityNodeInfo rootNode = getRootInActiveWindow();
AccessibilityNodeInfo node = recycle(rootNode);
node.performAction(AccessibilityNodeInfo.ACTION_CLICK);
AccessibilityNodeInfo parent = node.getParent();
while (parent != null) {
if (parent.isClickable()) {
parent.performAction(AccessibilityNodeInfo.ACTION_CLICK);
break;
}
parent = parent.getParent();
}
}
//查找聊天中的红包信息
private AccessibilityNodeInfo recycle(AccessibilityNodeInfo node) {
if (node.getChildCount() == 0) {
if (node.getText() != null) {
if ("领取红包".equals(node.getText().toString())) {
return node;
}
}
} else {
for (int i = 0; i < node.getChildCount(); i++) {
AccessibilityNodeInfo nodeInfo = node.getChild(i);
if (nodeInfo != null) {
Log.d("mylog", "--------nodeinfo class = " + nodeInfo.getClassName() + " ds = " + nodeInfo.getContentDescription());
recycle(node.getChild(i));
}
}
}
return node;
}
//关闭红包,返回聊天窗口
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
private void close(){
AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();
if (nodeInfo != null){
List<AccessibilityNodeInfo> infos = nodeInfo.findAccessibilityNodeInfosByViewId("@id/jb");
nodeInfo.recycle();
for (AccessibilityNodeInfo item : infos){
item.performAction(AccessibilityNodeInfo.ACTION_CLICK);
}
}
}
/**
* 系统是否在锁屏状态
*
* @return
*/
private boolean isScreenLocked() {
KeyguardManager keyguardManager = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
return keyguardManager.inKeyguardRestrictedInputMode();
}
private void wakeAndUnlock() {
//获取电源管理器对象
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
//获取PowerManager.WakeLock对象,后面的参数|表示同时传入两个值,最后的是调试用的Tag
PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.ACQUIRE_CAUSES_WAKEUP | PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "bright");
//点亮屏幕
wl.acquire(1000);
//得到键盘锁管理器对象
KeyguardManager km = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
kl = km.newKeyguardLock("unLock");
//解锁
kl.disableKeyguard();
}
/**
* 回到系统桌面
*/
private void back2Home() {
Intent home = new Intent(Intent.ACTION_MAIN);
home.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
home.addCategory(Intent.CATEGORY_HOME);
startActivity(home);
}
}
里面我们获取返回键的时候,直接通过findAccessibilityNodeInfosByViewId()获取制定id控件。那怎么可以知道控件的id呢,可以通过在Android Studio中开启Android Device Monitor,这样我们就可以很方便的查看界面id了。
参考:
你真的理解AccessibilityService吗
自动抢红包,自动安装原理之AccessibilityService
遍历list和输入框的使用
https://www.jianshu.com/p/cd1cd53909d7