AccessibilityService类使用和抢红包功能的实现

来源:互联网 发布:java 高性能 并发库 编辑:程序博客网 时间:2024/04/27 17:39

2017.3.24日可用!以下是代码和说明
AndroidManifest.xml声明

<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />    <uses-permission android:name="android.permission.WAKE_LOCK" />        <service            android:name="com.zhonglou.showtencent.Server.RobServer"            android:label="@string/accessibility_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/accessibility" />        </service>

accessibility.xml

    android:accessibilityEventTypes="typeNotificationStateChanged|typeWindowStateChanged"    android:accessibilityFeedbackType="feedbackGeneric"    android:accessibilityFlags="flagDefault"    android:canRetrieveWindowContent="true"    android:description="@string/accessibility_description"    android:notificationTimeout="100"    android:packageNames="com.tencent.mm" 

重要属性说明:

1.accessibilityEventTypes:表示该服务对界面中的哪些变化感兴趣,即哪些事件通知,比如窗口打开,滑动,焦点变化,长按等.具体的值可以在AccessibilityEvent类中查到,如typeAllMask表示接受所有的事件通知.2.accessibilityFeedbackType:表示反馈方式,比如是语音播放,还是震动3.canRetrieveWindowContent:表示该服务能否访问活动窗口中的内容.也就是如果你希望在服务中获取窗体内容的化,则需要设置其值为true.4.notificationTimeout:接受事件的时间间隔,通常将其设置为100即可.5.packageNames:表示对该服务是用来监听哪个包的产生的事件

RobServer.java

/**    该类提供的方法 * getRootInActiveWindow() 如果配置能够获取窗口内容,则会返回当前活动窗口的根结点 * getSystemService(String name)获取系统服务 * onAccessibilityEvent(AccessibilityEvent event)   有关AccessibilityEvent事件的回调函数.系统通过sendAccessibiliyEvent()不断的发送AccessibilityEvent到此处 * onGesture() * onKeyEvent(KeyEvent event)如果允许服务监听按键操作,该方法是按键事件的回调,需要注意,这个过程发生了系统处理按键事件之前 * onServiceConnected()系统成功绑定该服务时被触发,通常我们可以在这里做一些初始化操作 */public class RobServer extends AccessibilityService {    private boolean newPacket = false;//有没新红包    private boolean enableKeyguard = true;//默认有屏幕锁    //锁屏、解锁相关    private KeyguardManager km;    private KeyguardManager.KeyguardLock kl;    //唤醒屏幕相关    private PowerManager pm;    private PowerManager.WakeLock wl = null;    //唤醒屏幕和解锁    private void wakeAndUnlock(boolean unLock)    {        if(unLock)        {           //若为黑屏状态则唤醒屏幕            if(!pm.isScreenOn()) {        //获取电源管理器对象,ACQUIRE_CAUSES_WAKEUP这个参数能从黑屏唤醒屏幕                wl = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP, "bright");              //点亮屏幕                wl.acquire();            }            //若在锁屏界面则解锁直接跳过锁屏            if(km.inKeyguardRestrictedInputMode()) {               //设置解锁标志,以判断抢完红包能否锁屏                enableKeyguard = false;                   //解锁                kl.disableKeyguard();            }        }        else {           //如果之前解过锁则加锁以恢复原样            if(!enableKeyguard) {                //锁屏                kl.reenableKeyguard();                Log.i("demo", "加锁");            }        //若之前唤醒过屏幕则释放之使屏幕不保持常亮            if(wl != null) {                wl.release();                wl = null;                Log.i("demo", "关灯");            }        }    }    //初始化设置    @Override    protected void onServiceConnected() {        super.onServiceConnected();        //获取电源管理器对象        pm=(PowerManager)getSystemService(Context.POWER_SERVICE);           //得到键盘锁管理器对象        km= (KeyguardManager)getSystemService(Context.KEYGUARD_SERVICE);            //初始化一个键盘锁管理器对象        kl = km.newKeyguardLock("unLock");    }    @Override    public void onAccessibilityEvent(AccessibilityEvent event) {        /**                     AccessibilityEvent提供的方法         * getEventType()   获取事件类型         * getClassName()   获取事件源对应类的类型,比如点击事件是有某个Button产生的,那么此时获取的就是         *                   Button的完整类名         * getText()        获取事件源的文本信息,比如事件是有TextView发出的,此时获取的就是TextView的text属         *                  性.如果该事件源是树结构,那么此时获取的是这个树上所有具有text属性的值的集合         * isEnabled()      事件源(对应的界面控件)是否处在可用状态         * getItemCount()   如果事件源是树结构,将返回该树根节点下子节点的数量         */        int eventType = event.getEventType();        switch (eventType) {            case AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED:// 通知栏事件          /**抢红包步骤:检测通知栏事件是否有[微信红包],有则打开.           * 检测窗口改变事件,然后确定窗口是领红包界面,是则获取id/text           */                List<CharSequence> text = event.getText();                if (!text.isEmpty()) {                    for (CharSequence text1 : text) {                        String content = text1.toString();                        if (content.contains("[微信红包]") || content.contains("[QQ红包]")) {  //检测content中是否有该字符                            wakeAndUnlock(true);                            newPacket = true;                            //监听到微信红包的notification,打开通知                            if (event.getParcelableData() != null && event.getParcelableData() instanceof Notification) {                                Notification notification = (Notification) event.getParcelableData();                                PendingIntent pi = notification.contentIntent;                                try {                                    pi.send();                                } catch (PendingIntent.CanceledException e) {                                    e.printStackTrace();                                }                            }                        }                    }                }                break;            case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:   //窗口改变事件                String className = event.getClassName().toString();                if (className.equals("com.tencent.mm.ui.LauncherUI")) {                    if (newPacket){                    getPacket();      // 领取红包                    }                } else if (className.equals("com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyReceiveUI")) {                    openPacket();                    wakeAndUnlock(false);                } else if (className.equals("com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyDetailUI")) {                    MoneyDetal();                                    //查看红包详情并退出                }                break;        }    }    /**     * 关闭红包详情界面,实现自动返回聊天窗口     */    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)    private void MoneyDetal() {        /**思路:微信中使用uiautomatorviewer查看布局,发现不同的手机相同的控件id是不一样的,比如我们需要查询获取红包        的数量时,需要先查找'元',然后获取其父控件,然后查找金额所在的位置,这个是不变的。 */        AccessibilityNodeInfo rootNode = getRootInActiveWindow();        List<AccessibilityNodeInfo> list = new ArrayList<>();List<AccessibilityNodeInfo> list1 = rootNode.findAccessibilityNodeInfosByText("元");            list.addAll(list1);        AccessibilityNodeInfo parent = list.get(0).getParent();  //群聊和私发不同,这是共同点                    AccessibilityNodeInfo name = parent.getChild(0);                    AccessibilityNodeInfo money = parent.getChild(2);                    Log.d("classname", money.getText().toString());                    Log.d("classname", name.getText().toString());        performBack(this);    }    /**     * 模拟点击,拆开红包     */    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)    private void openPacket() {        AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();        if (nodeInfo != null) {            boolean isHave = false;            int count = nodeInfo.getChildCount();            for (int i = 0; i < count; i++) {                AccessibilityNodeInfo childNode = nodeInfo.getChild(i);                if ("android.widget.Button".equals(childNode.getClassName())) {                    isHave = true;                    childNode.performAction(AccessibilityNodeInfo.ACTION_CLICK);                }            }            if (!isHave) {                List<AccessibilityNodeInfo> list = nodeInfo.findAccessibilityNodeInfosByText("看看大家的手气");                if (list != null && list.size() > 0) {                    performBack(this);                }            }            nodeInfo.recycle();        }    }    /**     * 模拟点击,打开抢红包界面     */    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)    private void getPacket() {        AccessibilityNodeInfo rootinfo = getRootInActiveWindow();        List<AccessibilityNodeInfo> list = new ArrayList<>();        if (rootinfo != null &&rootinfo.getChildCount()>0){            List<AccessibilityNodeInfo> list1 = rootinfo.findAccessibilityNodeInfosByText("领取红包");            if (list1.size()>0){                list.addAll(list1);            }            for (int i = list.size() - 1; i >= 0; i--) {                AccessibilityNodeInfo parent = list.get(i).getParent();                if (parent != null) {                        parent.performAction(AccessibilityNodeInfo.ACTION_CLICK);                }            }            rootinfo.recycle();            newPacket = false;        }    }    //模拟返回事件    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)    public void performBack(AccessibilityService service) {        if(service == null) {            return;        }        service.performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK);    }    @Override    public void onInterrupt() {        Toast.makeText(this, "中断抢红包服务", Toast.LENGTH_SHORT)                .show();    }    @Override    public void onDestroy() {        super.onDestroy();        wakeAndUnlock(false);    }}

注意事项,1.启动服务用intent启动,用的是startserver(intent)
这里写图片描述
别人图拿来用,Dump View Hierarchy for UI Automator可以获取你想要的class和布局方式,在DDMS那边。

扩展
2. APK自动安装

讲完了微信红包插件的实现原理,不难发现其本质是根据相关的界面状态,模拟后续的操作(比如点击等).
既然这样,那么我们完全可以利用该服务实现更多的功能,比如apk自动安装,传统的安装过程大概是如下流程:

点击apk文件,弹出安装信息界面,在该界面点击”下一步”,然后在点击”安装”,最后在安装完成界面点击”完成”.

不难发现,该流程完全可以通过模拟点击操作完成.现在我们简单的讲一下AccessibilityService在这方面的具体应用.我们知道系统的安装程序由PackageInstaller负责,其包名是com.android.packageinstaller,那么我们只需要监听该package下的安装信息界面和安装完成界面,并模拟点击”下一步”,”安装”,完成”“操作即可实现自动安装.

AccessibilityService配置如下:

<?xml version="1.0" encoding="utf-8"?><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/auto_service_des"        android:notificationTimeout="100"        android:packageNames="com.android.packageinstaller"/>

具体实现代码如下:

public class InstallService extends AccessibilityService {    @Override    public void onAccessibilityEvent(AccessibilityEvent event) {        Log.d("InstallService", event.toString());        checkInstall(event);    }    private void checkInstall(AccessibilityEvent event) {        AccessibilityNodeInfo source = event.getSource();        if (source != null) {            boolean installPage = event.getPackageName().equals("com.android.packageinstaller");            if (installPage) {                installAPK(event);            }        }    }    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)    private void installAPK(AccessibilityEvent event) {        AccessibilityNodeInfo source = getRootInActiveWindow();        List<AccessibilityNodeInfo> nextInfos = source.findAccessibilityNodeInfosByText("下一步");        nextClick(nextInfos);        List<AccessibilityNodeInfo> installInfos = source.findAccessibilityNodeInfosByText("安装");        nextClick(installInfos);        List<AccessibilityNodeInfo> openInfos = source.findAccessibilityNodeInfosByText("打开");        nextClick(openInfos);        runInBack(event);    }    private void runInBack(AccessibilityEvent event) {        event.getSource().performAction(AccessibilityService.GLOBAL_ACTION_BACK);    }    private void nextClick(List<AccessibilityNodeInfo> infos) {        if (infos != null)            for (AccessibilityNodeInfo info : infos) {                if (info.isEnabled() && info.isClickable())                    info.performAction(AccessibilityNodeInfo.ACTION_CLICK);            }    }    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)    private boolean checkTilte(AccessibilityNodeInfo source) {        List<AccessibilityNodeInfo> infos = getRootInActiveWindow().findAccessibilityNodeInfosByViewId("@id/app_name");        for (AccessibilityNodeInfo nodeInfo : infos) {            if (nodeInfo.getClassName().equals("android.widget.TextView")) {                return true;            }        }        return false;    }    @Override    public void onInterrupt() {    }    @Override    protected void onServiceConnected() {        Log.d("InstallService", "auto install apk");    }}
  1. 检测前台服务:

在很多情况下,我们需要检测自己的app是不是处在前台,借助该服务同样也能够完成该检测操作.下面,我们就演示一下如何实现:

AccessibilityService配置如下:

<?xml version="1.0" encoding="utf-8"?><accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"                         android:accessibilityEventTypes="typeWindowStateChanged"        android:accessibilityFeedbackType="feedbackGeneric"        android:accessibilityFlags="flagDefault"        android:canRetrieveWindowContent="true"        android:description="@string/auto_detection"        android:notificationTimeout="100"/>

具体实现代码如下:

public class DetectionService extends AccessibilityService {    private static volatile String foregroundPackageName = "error";    /**     * 检测是否是前台服务     *     * @param packagenName     * @return     */    public static boolean isForeground(String packagenName) {        return foregroundPackageName.equals(packagenName);    }    @Override    public void onAccessibilityEvent(AccessibilityEvent event) {        if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {            foregroundPackageName = event.getPackageName().toString();        }    }    @Override    public void onInterrupt() {    }}

在使用时,需要引导用户开启该服务,之后通过调用DetectionService.isForeground()即可.

        TYPE_VIEW_CLICKED        TYPE_VIEW_FOCUSED        TYPE_VIEW_LONG_CLICKED        TYPE_VIEW_SELECTED      TYPE_VIEW_TEXT_CHANGED    TYPE_WINDOW_STATE_CHANGED        TYPE_NOTIFICATION_STATE_CHANGED        TYPE_TOUCH_EXPLORATION_GESTURE_END        TYPE_ANNOUNCEMENT        TYPE_TOUCH_EXPLORATION_GESTURE_START        TYPE_VIEW_HOVER_ENTER        TYPE_VIEW_HOVER_EXIT         TYPE_VIEW_SCROLLED        TYPE_VIEW_TEXT_SELECTION_CHANGED        TYPE_WINDOW_CONTENT_CHANGED
0 0
原创粉丝点击