其他分享
首页 > 其他分享> > 安卓使用无障碍服务监听微信和QQ的收款信息

安卓使用无障碍服务监听微信和QQ的收款信息

作者:互联网

导读:

资深的安卓程序员想必都了解,安卓的通知监听服务(NotificationListenerService)可以监听通知栏的信息,从通知栏信息里获取到我们想要的收款信息(比如收款类型、收款金额)。但是,这个通知监听服务有个弊端,如果APP没有发送通知,那就没办法知道有没有收到款,特别是现在的微信 和QQ,二维码收款不再发送通知,而是在自身的APP里给出提示,这样就没办法使用通知监听服务了。因此,我们需要使用安卓的无障碍服务(AccessibilityService),来监听APP界面的变化,实时获取微信、QQ的收款信息。

准备工作:

步骤:

一、创建服务

1、新建一个Service类,继承自AccessibilityService,重写onServiceConnected()方法、onAccessibilityEvent()方法和onInterrupt()方法

package net.zy13.skhelper.service;

import android.accessibilityservice.AccessibilityService;
import android.view.accessibility.AccessibilityEvent;
import net.zy13.skhelper.utils.LogUtil;
/**
 * 收款无障碍服务
 */
public class SkAccessibilityService extends AccessibilityService{
    /**
     * 当服务启动的时候会被调用
     */
    @Override
    public void onServiceConnected(){
        LogUtil.debug( ":无障碍服务连接成功");
    }
    /**
     * 监听窗口变化的回调
     */
    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
        // 获取事件类型,在对应的事件类型中对相应的节点进行操作
        int eventType = event.getEventType();
        //根据事件回调类型进行处理
        switch (eventType) {
            //当通知栏发生改变时
            case AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED:
                break;
            //当窗口的状态发生改变时(界面改变)
            case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
                break;
            //内容改变
            case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED:
                break;
            //滑动变化
            case AccessibilityEvent.TYPE_VIEW_SCROLLED:
                break;
        }
    }
    /**
     * 中断服务的回调
     */
    @Override
    public void onInterrupt() {

    }
}

 2、在AndroidManifest.xml清单里注册服务

<service
            android:name=".service.SkAccessibilityService"
            android:enabled="true"
            android:exported="true"
            android: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/config_accessibility" />
        </service>

3、在res资源目录里创建一个xml目录,然后在xml里创建config_accessibility.xml配置文件,这个config_accessibility.xml文件主要是保存AccessibilityService服务的配置参数,代码如下:

<?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:canRetrieveWindowContent="true"
    android:notificationTimeout="100"
    android:packageNames="com.eg.android.AlipayGphone,com.tencent.mm,com.tencent.mobileqq"
    android:description="@string/access_desc" />

上面的android:description项要提取到strings.xml里,不要问为什么,因为不这样会报错。 

<resources>
    <string name="access_desc">收款助手使用无障碍服务监听收款信息</string>
</resources>

xml参数说明: 

accessibilityEventTypes:表示该服务对界面中的哪些变化感兴趣,即哪些事件通知,比如窗口打开,滑动,焦点变化,长按等。具体的值可以在AccessibilityEvent类中查到,如typeAllMask表示接受所有的事件通知;

accessibilityFeedbackType:表示反馈方式,比如是语音播放,还是震动;

canRetrieveWindowContent:表示该服务能否访问活动窗口中的内容。也就是如果你希望在服务中获取窗体内容,则需要设置其值为true;

notificationTimeout:接受事件的时间间隔,通常将其设置为100即可;

packageNames:表示对该服务是用来监听哪个包的产生的事件,要监听多个APP包,就用逗号隔开(如果不设置就是接收所有的);

description:对该无障碍功能的描述,会显示在系统开启服务的设置界面;

accessibilityEventTypes可选参数
typeAllMask接收所有事件
typeWindowStateChanged监听窗口状态变化,比如打开一个popupWindow,dialog,Activity切换等等
typeWindowContentChanged监听窗口内容改变,比如根布局子view的变化
typeWindowsChanged监听屏幕上显示的系统窗口中的事件更改,此事件类型只应由系统分派
typeNotificationStateChanged监听通知变化,比如notifacation和toast
typeViewClicked监听view点击事件
typeViewLongClicked监听view长按事件
typeViewFocused监听view焦点事件
typeViewSelected监听AdapterView中的上下文选择事件
typeViewTextChanged监听EditText的文本改变事件

typeViewHoverEnter

typeViewHoverExit

监听view的视图悬停进入和退出事件
typeViewScrolled监听view滚动,此类事件通常不直接发送
typeViewTextSelectionChanged监听EditText选择改变事件
typeViewAccessibilityFocused监听view获得可访问性焦点事件
typeViewAccessibilityFocusCleared监听view清除可访问性焦点事件

typeGestureDetectionStart

typeGestureDetectionEnd

监听手势开始和结束事件

typeTouchInteractionStart

typeTouchInteractionEnd

监听用户触摸屏幕事件的开始和结束

typeTouchExplorationGestureStart

typeTouchExplorationGestureEnd

监听触摸探索手势的开始和结束

二、使用服务 

1、当APP退出注销时,无障碍服务也会关闭,因此我们最好是在APP上判断服务是否开启,没开启的话,就提醒用户主动授权开启无障碍服务,判断无障碍服务是否开启的方法如下:

 /**
     * 判断无障碍服务是否开启
     * @param mContext
     * @return
     */
    private boolean isAccessibilitySettingsOn(Context mContext) {
        int accessibilityEnabled = 0;
        final String service = mContext.getPackageName() + "/" + 你自定义的无障碍服务.class.getCanonicalName();
        try {
            accessibilityEnabled = Settings.Secure.getInt(
                    mContext.getApplicationContext().getContentResolver(),
                    android.provider.Settings.Secure.ACCESSIBILITY_ENABLED);
        } catch (Settings.SettingNotFoundException e) {
        }
        TextUtils.SimpleStringSplitter mStringColonSplitter = new TextUtils.SimpleStringSplitter(':');

        if (accessibilityEnabled == 1) {
            String settingValue = Settings.Secure.getString(
                    mContext.getApplicationContext().getContentResolver(),
                    Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
            if (settingValue != null) {
                mStringColonSplitter.setString(settingValue);
                while (mStringColonSplitter.hasNext()) {
                    String accessibilityService = mStringColonSplitter.next();
                    if (accessibilityService.equalsIgnoreCase(service)) {
                        return true;
                    }
                }
            }
        } else {
        }

        return false;
    }

2、只要用户授权,无障碍服务就会自动运行,下面这串代码是跳转到无障碍服务授权页面的,用户授权才可以真正使用无障碍服务

Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
startActivity(intent);

 三、无障碍服务处理事件信息

AccessibilityService中常用的方法的介绍:

  1. disableSelf():禁用当前服务,也就是在服务可以通过该方法停止运行
  2. findFoucs(int falg):查找拥有特定焦点类型的控件
  3. getRootInActiveWindow():如果配置能够获取窗口内容,则会返回当前活动窗口的根结点
  4. getSeviceInfo():获取当前服务的配置信息
  5. onAccessibilityEvent(AccessibilityEvent event):有关AccessibilityEvent事件的回调函数,系统通过sendAccessibiliyEvent()不断的发送AccessibilityEvent到此处
  6. performGlobalAction(int action):执行全局操作,比如返回,回到主页,打开最近等操作
  7. setServiceInfo(AccessibilityServiceInfo info):设置当前服务的配置信息
  8. getSystemService(String name):获取系统服务
  9. onKeyEvent(KeyEvent event):如果允许服务监听按键操作,该方法是按键事件的回调,需要注意,这个过程发生了系统处理按键事件之前
  10. onServiceConnected():系统成功绑定该服务时被触发,也就是当你在设置中开启相应的服务,系统成功的绑定了该服务时会触发,通常我们可以在这里做一些初始化操作
  11. onInterrupt():服务中断时的回调
这里我们主要关注onAccessibilityEvent事件:
/**
     * 监听窗口变化的回调
     */
    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
        // 获取事件类型,在对应的事件类型中对相应的节点进行操作
        int eventType = event.getEventType();
        //根据事件回调类型进行处理
        switch (eventType) {
            //当通知栏发生改变时
            case AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED:
                break;
            //当窗口的状态发生改变时(界面改变)
            case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
                //主要是在这里监听支付宝、微信、QQ界面上的收款信息
                break;
            //内容改变
            case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED:
                break;
            //滑动变化
            case AccessibilityEvent.TYPE_VIEW_SCROLLED:
                break;
        }
    }

 事件回调类型AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED,表示窗口的内容发生改变时,比如微信收到消息,会显示在消息列表里,就会触发这个事件的回调,我们就可以在这个回调里获取相应的信息。

四、获取节点信息

获取了界面窗口变化后,这个时候就要获取控件的节点,整个窗口的节点本质是个树结构,通过以下方式操作节点信息

1、获取窗口节点(根节点)

AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();

2、获取指定子节点(控件节点)

//通过文本找到对应的节点集合
List<AccessibilityNodeInfo> list = nodeInfo.findAccessibilityNodeInfosByText(text);
//通过控件ID找到对应的节点集合,如com.tencent.mm:id/gd
List<AccessibilityNodeInfo> list = nodeInfo.findAccessibilityNodeInfosByViewId(id);

 通过控件ID获取节点是最方便的,下面开始教大家怎么获取APP的控件ID

五、获取APP的控件ID 

 先说明一下,控件ID实际上是我们在界面布局里设定的android:id的编译后的值,这个是唯一的。

1、前面的准备工作里,我们已经在安卓模拟器里安装了支付宝、微信和QQ,这里以微信为例子,我们在安卓模拟器里登录微信

2、在Android的Sdk目录里找到tools工具目录,双击打开里面的monitor.bat

 3、在DDMS的Devices列表里会看到有一个安卓模拟器,如下图:

4、点击Dump View Hierarchy UI Automotor图标,会自动获取当前模拟器中打开的APP界面UI信息

 5、打开后出现如下图,通过鼠标点击到界面的文字或图片上,可以在节点详情里看到控件ID

6、通过控件ID获取节点信息的方法

    /**
     * 通过控件ID获取节点信息
     * @param id
     * @return
     */
    @SuppressLint("NewApi")
    public String getNodeInfo(String id){
        String result="";
        // 获取当前整个活动窗口的根节点
        AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();
        // 获取父节点
        //nodeInfo.getParent();
        // 获取子节点
        //nodeInfo.getChild(0);
        if (nodeInfo != null) {
            // 通过文本找到对应的节点集合
            // List<AccessibilityNodeInfo> list = nodeInfo.findAccessibilityNodeInfosByText("");
            // 通过控件ID找到对应的节点集合,如com.tencent.mm:id/gd
            List<AccessibilityNodeInfo> list = nodeInfo.findAccessibilityNodeInfosByViewId(id);
            for (AccessibilityNodeInfo item : list) {
                String str=item.getText().toString();
                if (str != null && str.length() != 0){
                    result=str;
                    break;
                }
                //模拟点击
                //item.performAction(AccessibilityNodeInfo.ACTION_CLICK);
                //模拟长按
                //item.performAction(AccessibilityNodeInfo.ACTION_LONG_CLICK);
                //模拟获取焦点
                //item.performAction(AccessibilityNodeInfo.ACTION_FOCUS);
                //模拟粘贴
                //item.performAction(AccessibilityNodeInfo.ACTION_PASTE);
            }
        }
        return result;
    }

 六、通过无障碍服务获取微信收款信息的代码:

package net.zy13.skhelper.service;

import android.accessibilityservice.AccessibilityService;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.PowerManager;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;


import net.zy13.skhelper.MainApplication;
import net.zy13.skhelper.handle.PostHandle;
import net.zy13.skhelper.utils.LogUtil;
import net.zy13.skhelper.utils.PreferenceUtil;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 收款无障碍服务
 */
public class SkAccessibilityService extends AccessibilityService{
    private PreferenceUtil preference;
    private PowerManager.WakeLock wakeLock = null;
    private PostHandle postHandle=null;
    /**
     * 当服务启动的时候会被调用
     */
    @Override
    public void onServiceConnected(){
        LogUtil.debug( ":无障碍服务连接成功");
        if(preference==null) {
            preference = PreferenceUtil.getInstance(MainApplication.getAppContext());
        }
        //开启wakelock,使CPU处于不休眠的状态,开启后需要重启手机
        if(preference.isWakelock()){
            PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
            wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, SkAccessibilityService.class.getName());
            wakeLock.acquire();
        }
    }
    /**
     * 监听窗口变化的回调
     */
    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
        // 获取事件类型,在对应的事件类型中对相应的节点进行操作
        int eventType = event.getEventType();
        //根据事件回调类型进行处理
        switch (eventType) {
            //当通知栏发生改变时
            case AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED:
                break;
            //当窗口的状态发生改变时(界面改变)
            case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
                String className = event.getClassName().toString();
                //获取界面信息
                getUiInfo(className);
                break;
            //内容改变
            case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED:
                break;
            //滑动变化
            case AccessibilityEvent.TYPE_VIEW_SCROLLED:
                break;
        }
    }
    /**
     * 中断服务的回调
     */
    @Override
    public void onInterrupt() {

    }

    /**
     * 通过控件ID获取节点信息
     * @param id
     * @return
     */
    @SuppressLint("NewApi")
    public String getNodeInfo(String id){
        String result="";
        // 获取当前整个活动窗口的根节点
        AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();
        // 获取父节点
        //nodeInfo.getParent();
        // 获取子节点
        //nodeInfo.getChild(0);
        if (nodeInfo != null) {
            // 通过文本找到对应的节点集合
            // List<AccessibilityNodeInfo> list = nodeInfo.findAccessibilityNodeInfosByText("");
            // 通过控件ID找到对应的节点集合,如com.tencent.mm:id/gd
            List<AccessibilityNodeInfo> list = nodeInfo.findAccessibilityNodeInfosByViewId(id);
            for (AccessibilityNodeInfo item : list) {
                String str=item.getText().toString();
                if (str != null && str.length() != 0){
                    result=str;
                    break;
                }
                //模拟点击
                //item.performAction(AccessibilityNodeInfo.ACTION_CLICK);
                //模拟长按
                //item.performAction(AccessibilityNodeInfo.ACTION_LONG_CLICK);
                //模拟获取焦点
                //item.performAction(AccessibilityNodeInfo.ACTION_FOCUS);
                //模拟粘贴
                //item.performAction(AccessibilityNodeInfo.ACTION_PASTE);
            }
        }
        return result;
    }
    /**
     * 获取界面信息
     * @param classname
     */
    @SuppressLint("NewApi")
    public void getUiInfo(String classname){
        LogUtil.debug( "无障碍服务窗口状态改变,类名为"+classname);
        //通过类名判断是不是微信
        if(classname.equals("com.tencent.mm.ui.LauncherUI")) {
            LogUtil.debug( "正在使用无障碍服务获取微信收款信息:");
            String title=getNodeInfo("com.tencent.mm:id/fzg");
            if(title.contains("微信支付")||title.contains("微信收款助手")) {
                String content = getNodeInfo("com.tencent.mm:id/e7t");
                //String time = getNodeInfo("com.tencent.mm:id/j0l");
                Map<String,String> postmap=new HashMap<String,String>();
                postmap.put("type","weixin");
                postmap.put("title",title);
                postmap.put("money",extractMoney(content));
                postmap.put("content",content);
                SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                postmap.put("time",sdf.format(new Date()));
                LogUtil.debug("获取到的信息集合:"+postmap.toString());
            }
        }
    }
    /**
     * 从字符串里提取金额
     * @param content
     * @return
     */
    protected  String extractMoney(String content){
        Pattern pattern = Pattern.compile("(收款|收款¥|向你付款|向您付款|入账|到帐)(([1-9]{1}\\d*)|([0]{1}))(\\.(\\d){0,2})?元");
        Matcher matcher = pattern.matcher(content);
        List<String> list = new ArrayList<>();
        while(matcher.find()){
            list.add(matcher.group());
        }
        if(list.size()>0){
            String tmp=list.get(list.size()-1);
            Pattern patternnum = Pattern.compile("(([1-9]{1}\\d*)|([0]{1}))(\\.(\\d){0,2})?");
            Matcher matchernum = patternnum.matcher(tmp);
            if(matchernum.find())
                return matchernum.group();
            return null;
        }else
            return null;
    }
}

1、现在在安卓模拟器里运行我们的APP,来监听微信的收款信息 

2、通过查看debug日志 ,发现无障碍服务已经连接成功了,现在我用另一个微信号扫收款二维码进行付款,看看能不能监听到。

3、可以获取到收款信息,但有个bug,就是你要主动打开微信才可以获取到,如果微信退到后台或者关闭,无障碍服务就不能实时监听到收款信息了。 

标签:QQ,AccessibilityEvent,服务,微信,安卓,节点,获取,import,监听
来源: https://blog.csdn.net/qq15577969/article/details/122633317