AccesslibityService - 扫地僧Service

一、内容简介

AccesslibityService辅助功能服务目的是帮助那些具有视觉、身体或年龄相关限制的用户而设计的,主要功能是控制屏幕视图的响应,可以模拟点击,后退,滚动等事件,可用于自动化处理。因此可用来开发自动抢红包等功能,惊奇死我了,开篇第一弹就让我大有收获,迫不及待的分享给我的猿友们。
- 官方API:AccessibilityService

二、代码示例(通过自动抢红包来讲解)

(1)注册方式

(a) 代码注册,继承AccessibilityService,重写onServiceConnected()方法,如下:

    @Override
    protected void onServiceConnected() {
        AccessibilityServiceInfo info = getServiceInfo();
        info.eventTypes = AccessibilityEvent.TYPES_ALL_MASK;
        info.feedbackType = AccessibilityServiceInfo.FEEDBACK_ALL_MASK;
        info.notificationTimeout = 1000;
        info.packageNames = packageNames;
        setServiceInfo(info);
        super.onServiceConnected();
    }

(b) Android4.0以后,可以通过meta-data标签引用xml注册
- (i) 新建accessibility.xml,在res/xml/accessibility.xml,如下:

  <?xml version="1.0" encoding="utf-8"?>
  <accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
    android:accessibilityEventTypes="typeAllMask"
    android:accessibilityFeedbackType="feedbackAllMask"
    android:accessibilityFlags="flagDefault|flagRetrieveInteractiveWindows
    |flagReportViewIds|flagRequestFilterKeyEvents"
    android:canRequestEnhancedWebAccessibility="true"
    android:canRequestTouchExplorationMode="true"
    android:canRetrieveWindowContent="true"
    android:description="@string/lbl_auto_open_red_packet_desc"
    android:notificationTimeout="1000" />

(ii) 在AndroidManifest.xml中注册service,如下

   <service 
            android:name=".service.AutoOpenRedPacketService"
            android:label="@string/lbl_auto_open_red_packet"
            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/accessibility" />
  </service>

(2)功能代码(抢红包功能使用的是方式注册)

(a) 简单的封装了一下,AccessibilityService提供的一些Action操作,定义到接口IAccessbilityAction中,如下:

package com.ronindong.meet.android.dao;

import android.view.accessibility.AccessibilityNodeInfo;

public interface IAccessbilityAction {
    /**
     * 模拟后退事件
     */
    void performBack();

    /**
     * 模拟向上滚动
     */
    void performScrollUp();

    /**
     * 模拟向下滚动
     */
    void performScrollDown();

    /**
     * 模拟view点击
     *
     * @param nodeInfo
     */
    void performViewClick(AccessibilityNodeInfo nodeInfo);

    /**
     * 根据文本获取view控件
     *
     * @param text
     * @return
     */
    AccessibilityNodeInfo findViewByText(String text);

    /**
     * 根据文本获取view控件
     *
     * @param text
     * @param clickable
     * @return
     */
    AccessibilityNodeInfo findViewByText(String text, boolean clickable);

    /**
     * 点击指定text的view
     *
     * @param text
     */
    void clickTextViewByText(String text);

    /**
     * 点击指定viewId的view
     *
     * @param viewId
     */
    void clickTextViewByViewId(String viewId);

    /**
     * 模拟文本框输入
     *
     * @param info
     * @param text
     */
    void performInputText(AccessibilityNodeInfo info, String text);

    /**
     *
     * @param serviceName
     * @return
     */
    boolean checkAccessbilityEnabled(String serviceName);
}

(b) 编写基类 BaseAccessibilityService,代码如下:

package com.ronindong.meet.android.service;

import android.accessibilityservice.AccessibilityService;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;

import com.ronindong.meet.android.dao.IAccessbilityAction;

import java.util.List;


/**
 * @author donghailong
 */
public abstract class BaseAccessibilityService extends AccessibilityService
        implements IAccessbilityAction {
    private static final String TAG = BaseAccessibilityService.class.getSimpleName();
    /**
     *
     */
    private AccessibilityManager mManager;

    public BaseAccessibilityService() {
    }


    @Override
    public void performBack() {
        performGlobalAction(GLOBAL_ACTION_BACK);
    }

    @Override
    public void performScrollUp() {
        performGlobalAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
    }

    @Override
    public void performScrollDown() {
        performGlobalAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
    }

    @Override
    public void performViewClick(AccessibilityNodeInfo nodeInfo) {
        if (nodeInfo == null) {
            return;
        }
        while (nodeInfo != null) {
            if (nodeInfo.isClickable()) {
                nodeInfo.performAction(AccessibilityNodeInfo.ACTION_CLICK);
                break;
            }
            nodeInfo = nodeInfo.getParent();
        }
    }

    @Override
    public AccessibilityNodeInfo findViewByText(String text) {
        return findViewByText(text, false);
    }

    @Override
    public AccessibilityNodeInfo findViewByText(String text, boolean clickable) {
        AccessibilityNodeInfo accessibilityNodeInfo = getRootInActiveWindow();
        if (accessibilityNodeInfo == null) {
            return null;
        }
        List<AccessibilityNodeInfo> nodeInfoList =
                accessibilityNodeInfo.findAccessibilityNodeInfosByText(text);
        if (nodeInfoList != null && !nodeInfoList.isEmpty()) {
            for (AccessibilityNodeInfo nodeInfo : nodeInfoList) {
                if (nodeInfo != null && (nodeInfo.isClickable() == clickable)) {
                    return nodeInfo;
                }
            }
        }
        return null;
    }

    @Override
    public void clickTextViewByText(String text) {
        AccessibilityNodeInfo accessibilityNodeInfo = getRootInActiveWindow();
        if (accessibilityNodeInfo == null) {
            Log.i(TAG, "accessibilityNodeInfo is null");
            return;
        }
        List<AccessibilityNodeInfo> nodeInfoList = accessibilityNodeInfo
                .findAccessibilityNodeInfosByText(text);
        if (nodeInfoList != null && !nodeInfoList.isEmpty()) {
            for (AccessibilityNodeInfo nodeInfo : nodeInfoList) {
                if (nodeInfo != null) {
                    performViewClick(nodeInfo);
                    break;
                }
            }
        }
    }

    @Override
    public void clickTextViewByViewId(String viewId) {
        AccessibilityNodeInfo accessibilityNodeInfo = getRootInActiveWindow();
        if (accessibilityNodeInfo == null) {
            Log.i(TAG, "accessibilityNodeInfo is null");
            return;
        }
        List<AccessibilityNodeInfo> nodeInfoList = accessibilityNodeInfo
                .findAccessibilityNodeInfosByViewId(viewId);
        if (nodeInfoList != null && !nodeInfoList.isEmpty()) {
            for (AccessibilityNodeInfo nodeInfo : nodeInfoList) {
                if (nodeInfo != null) {
                    performViewClick(nodeInfo);
                    break;
                }
            }
        }
    }

    @Override
    public void performInputText(AccessibilityNodeInfo info, String text) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            Bundle arguments = new Bundle();
            arguments.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, text);
            info.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, arguments);
        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
            ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
            ClipData clip = ClipData.newPlainText("label", text);
            clipboard.setPrimaryClip(clip);
            info.performAction(AccessibilityNodeInfo.ACTION_FOCUS);
            info.performAction(AccessibilityNodeInfo.ACTION_PASTE);
        }
    }

    @Override
    public boolean checkAccessbilityEnabled(String serviceName) {
        List<AccessibilityServiceInfo> accessibilityServices =
                mManager.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_GENERIC);
        for (AccessibilityServiceInfo info : accessibilityServices) {
            if (info.getId().equals(serviceName)) {
                return true;
            }
        }
        return false;
    }
}

(c) 抢红包功能实现类AutoOpenRedPacketService,代码如下:

package com.ronindong.meet.android.service;

import android.util.Log;
import android.view.accessibility.AccessibilityEvent;


/**
 * @author donghailong
 */
public class AutoOpenRedPacketService extends BaseAccessibilityService {
    private static final String TAG = AutoOpenRedPacketService.class.getSimpleName();
    /**
     * wx包名
     */
    public static final String WX_PACKAGE_NAME = "com.tencent.mm";
    /**
     * text
     */
    public static final String WX_RED_PACKAGE_TEXT = "微信红包";
    /**
     * 开 对应的viewId(随着微信的更新,可能会变)
     */
    public static final String WX_OPEN_RED_PACKAGE_VIEW_ID = "com.tencent.mm:id/c31";


    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
        Log.i(TAG, "event type:" + event.getEventType());
        if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED
                && event.getPackageName().equals(WX_PACKAGE_NAME)) {
            clickTextViewByText(WX_RED_PACKAGE_TEXT);
            clickTextViewByViewId(WX_OPEN_RED_PACKAGE_VIEW_ID);
        }
    }

    @Override
    public void onInterrupt() {

    }
}

(d) 由于AccessibilityService是系统级别的服务,需要用户主动打开。代码如下:

package com.ronindong.meet.android.helper;

import android.content.Context;
import android.content.Intent;
import android.provider.Settings;

/**
 * @author donghailong
 */
public class CxHelper {
    private Context context;


    public void init(Context cx) {
        if (null != cx) {
            this.context = cx.getApplicationContext();
        }
    }

    /**
     * 打开辅助功能设置
     */
    public void openAccessSetting() {
        Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        context.startActivity(intent);
    }
}

(e)最后一步,只需要在MainActivity中,请求权限即可,如下:

 @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.openAccess:
                SingletonManager.get(CxHelper.class).openAccessSetting();
                break;
            default:
                break;
        }
    }

大功告成!目前只是实现了简单的抢红包功能,还有需要待完善的地方。

注:以上代码亲测可用,若有问题,欢迎随时联系我。


三、用途展望

  • 可实现自动安装APP;模拟各种事件,如点击,滚动等
  • 自动抢红包;
  • 自动打电话功能
  • 解放用户,实现一些自动化操作
  • 你的想象不应该被限制

四、爬坑实录

  • AccessbiliryService辅助服务,属于系统级别的服务,需要用户主动开启.
  • 继承AccessbiliryService,需要实现的逻辑代码,不可在外部调用,否则没有效果;
  • AccessbiliryService的子类服务,和普通service不同,不需要主动启动。用户主动开启权限后,服务就运行了。
  • 辅助服务不容易捕捉没有text或没有设置Id的控件;还有EditText和ImageView也不容易获取。

Github地址:MeetAndroidDemo


参考文章:
AccessibilityService从入门到出轨

猜你喜欢

转载自blog.csdn.net/dhl91604/article/details/80134127
今日推荐