原创作品,转载请注明出处,尊重别人的劳动果实。

2017.2.7更新:

*现在适配微信版本更加容易了,只需要替换一个Recourse-ID即可

*可以知道对方发的是小视频还是语音,并获取秒数。

*可以区分聊天信息中的图片或者表情

实现效果:

实时监听当前聊天页面的最新一条消息,效果如图:

                       

实现原理:

同样是利用AccessibilityService辅助服务,关于这个服务类还不了解的同学可以先看下我上一篇关于抢红包的博客,原理都一样:

http://www.cnblogs.com/cxk1995/p/6363574.html

1.首先我们先来看一下微信聊天界面的布局,查看方法:

AndroidStudio--Tools--Android--Android Device Monitor,点击:

2.如图我们可以看到,其实每一条微信聊天记录都是一个RelativeLayout:

3.再往下看,我们又可以发现,其实每一个RelativeLayout下面,又包含了一个TextView,还有一个LinearLayout

TextView就是聊天的时间

LinearLayout下则包含了我们所需要的聊天对象以及聊天信息,除了文字聊天,语音,图片等的聊天信息都会在这个LinearLayout下,看图2

4.这里聊天对象比较容易获得,我们先放在前面讲,如上图我们可以看到有一个ImageView的描述内容里面包含着我们的聊天对象,可能后面还会有很多ImageView的参杂,将它与其他ImageView区分还有很重要两点,一是它是isClickable,二是它存在描述内容,并且描述内容是还包含有“头像”字眼。

综合过滤条件: "android.widget.ImageView"+"isClickable()"+"node.getContentDescription().toString().contains("头像")",代码如下:

    /**
* 遍历所有控件,找到头像Imagview,里面有对联系人的描述
*/
private void GetChatName(AccessibilityNodeInfo node) {
for (int i = 0; i < node.getChildCount(); i++) {
AccessibilityNodeInfo node1 = node.getChild(i);
if ("android.widget.ImageView".equals(node1.getClassName()) && node1.isClickable()) {
//获取聊天对象,这里两个if是为了确定找到的这个ImageView是头像的
if (!TextUtils.isEmpty(node1.getContentDescription())) {
ChatName = node1.getContentDescription().toString();
if (ChatName.contains("头像")) {
ChatName = ChatName.replace("头像", "");
}
} }
GetChatName(node1);
}
}

5.这里我们暂时把微信的聊天信息分为5种:

a.单纯的文字聊天信息:

其实从上面的右边图就可以看到,他就是一个TextView而已,如果你有去打印看看的话,你会发现他的parent父布局其实是一个RelativeLayout,后面同样可能会有其他的TeView干扰,例如我推荐了个名片或者发了个红包等,所以文字聊天TeView区分其他TextView的还有一个很重的过滤条件,就是它是可以长按的(isLongClickable()),这些属性都可以在Android Device Monitor中查看到。

综合过滤条件:"android.widget.TextView"+“android.widget.RelativeLayout”+“isLongClickable()”

b.发了一段语音聊天信息:

我们这里并没有办法获取到语音内容,只能获取语音的秒数,获取方法跟上面的一模一样,只不过这里它不能长按。我们知道语音的秒数格式都是:数字+双引号("),如60",所以我们只要判断取得的TextView内容是不是符合这种格式就行了,就能判断它是语音秒数。

综合过滤条件:"android.widget.TextView"+“android.widget.RelativeLayout”+“符合秒数格式”

c.发了一个表情:

这里的表情指的是收藏的或者自己下载的那些表情,不是指Emoji那些,他其实就是一个ImagView,父布局是一个LinearLayout。

综合过滤条件:"android.widget.ImageView"+"android.widget.LinearLayout"

d.发了一张图片:

这里目前只能做到监听到安装服务的这一端发过去的图片,不能监听到对面发过来的啧啧,其实也是一个ImageView,父布局是FrameLayout,而且还有很重要一点,该节点存在描述内容字眼:"图片"。

综合过滤条件:"android.widget.ImageView"+"android.widget.FrameLayout"+"node.getContentDescription().toString().contains("图片")"

e.发了一段小视频:

同样我们无法知道视频的内容,这里只是单纯的获取视频的秒数,和语音类似,但是获取过滤方法不同,其实小视频秒数也就是个TextView,并且它的父布局是FrameLayout,我们知道视频的秒数都是符合:00:00这种格式的,所以这个也是个很重要的过滤条件。

综合过滤条件:"android.widget.TextView"+“android.widget.FrameLayout”+“符合 00:00格式”

4.分析完后,我们思路就有了:

a.首先我们先取得根布局的节点,然后通过遍历获取到每个RelativeLayout下的LinearLayout,因为该LinearLayout存在resource-id(com.tencent.mm:id/p,微信版本6.5.4),所以我们可以很容易可以获取到符合该ID的所有LinearLayout,然后我们取出最后一个LinearLayout,这个也就是装载着我们最新的那条消息啦。

b.然后我们再在该LinearLayout下遍历它的所有控件,通过上面所讲的各种过滤条件,判断发的是什么类型的消息并取出我们所需要的即可。

注:关于resource-id直接在上一步的查看布局下可看到,因为resource-id随着版本的迭代可能也会发生改变,Demo中那个LinearLayout的resource-id是基于微信6.5.4滴,如果以后有版本更新的话我们直接修改代码中的那个ID就行啦。

获取聊天信息核心代码:

代码不多,也加了注释,直接看代码即可:

 /**
* 遍历所有控件:这里分四种情况
* 文字聊天: 一个TextView,并且他的父布局是android.widget.RelativeLayout
* 语音的秒数: 一个TextView,并且他的父布局是android.widget.RelativeLayout,但是他的格式是0"的格式,所以可以通过这个来区分
* 图片:一个ImageView,并且他的父布局是android.widget.FrameLayout,描述中包含“图片”字样(发过去的图片),发回来的图片现在还无法监听
* 表情:也是一个ImageView,并且他的父布局是android.widget.LinearLayout
* 小视频的秒数:一个TextView,并且他的父布局是android.widget.FrameLayout,但是他的格式是00:00"的格式,所以可以通过这个来区分
*
* @param node
*/
public void GetChatRecord(AccessibilityNodeInfo node) {
for (int i = 0; i < node.getChildCount(); i++) {
AccessibilityNodeInfo nodeChild = node.getChild(i); //聊天内容是:文字聊天(包含语音秒数)
if ("android.widget.TextView".equals(nodeChild.getClassName()) && "android.widget.RelativeLayout".equals(nodeChild.getParent().getClassName().toString())) {
if (!TextUtils.isEmpty(nodeChild.getText())) {
String RecordText = nodeChild.getText().toString();
//这里加个if是为了防止多次触发TYPE_VIEW_SCROLLED而打印重复的信息
if (!RecordText.equals(ChatRecord)) {
ChatRecord = RecordText;
//判断是语音秒数还是正常的文字聊天,语音的话秒数格式为5"
if (ChatRecord.contains("\"")) {
Toast.makeText(this, ChatName + "发了一条" + ChatRecord + "的语音", Toast.LENGTH_SHORT).show(); Log.e("WeChatLog",ChatName + "发了一条" + ChatRecord + "的语音");
} else {
//这里在加多一层过滤条件,确保得到的是聊天信息,因为有可能是其他TextView的干扰,例如名片等
if (nodeChild.isLongClickable()) {
Toast.makeText(this, ChatName + ":" + ChatRecord, Toast.LENGTH_SHORT).show(); Log.e("WeChatLog",ChatName + ":" + ChatRecord);
} }
return;
}
}
} //聊天内容是:表情
if ("android.widget.ImageView".equals(nodeChild.getClassName()) && "android.widget.LinearLayout".equals(nodeChild.getParent().getClassName().toString())) {
Toast.makeText(this, ChatName+"发的是表情", Toast.LENGTH_SHORT).show(); Log.e("WeChatLog",ChatName+"发的是表情"); return;
} //聊天内容是:图片
if ("android.widget.ImageView".equals(nodeChild.getClassName())) {
//安装软件的这一方发的图片(另一方发的暂时没实现)
if("android.widget.FrameLayout".equals(nodeChild.getParent().getClassName().toString())){
if(!TextUtils.isEmpty(nodeChild.getContentDescription())){
if(nodeChild.getContentDescription().toString().contains("图片")){
Toast.makeText(this, ChatName+"发的是图片", Toast.LENGTH_SHORT).show(); Log.e("WeChatLog",ChatName+"发的是图片");
}
}
}
} //聊天内容是:小视频秒数,格式为00:00
if ("android.widget.TextView".equals(nodeChild.getClassName()) && "android.widget.FrameLayout".equals(nodeChild.getParent().getClassName().toString())) {
if (!TextUtils.isEmpty(nodeChild.getText())) {
String second = nodeChild.getText().toString().replace(":", "");
//正则表达式,确定是不是纯数字,并且做重复判断
if (second.matches("[0-9]+") && !second.equals(VideoSecond)) {
VideoSecond = second;
Toast.makeText(this, ChatName + "发了一段" + nodeChild.getText().toString() + "的小视频", Toast.LENGTH_SHORT).show(); Log.e("WeChatLog","发了一段" + nodeChild.getText().toString() + "的小视频");
}
} } GetChatRecord(nodeChild);
}
}

使用方法:

设置-辅助功能-无障碍-点击WeChatLog开启即可(或者在设置中查找辅助功能等)

已知Bug:

没安装服务的另一方发的图片暂时无法监听到,后面改善

图片和表情没做重复信息过滤处理,所以如果触发了TYPE_VIEW_SCROLLED并且最新那条是这两个的话会出现重复。

华为7.0系统无法使用

写在最后:

个人兴趣研究,不建议用在非法途径上!!

上面是大部分的核心代码,不是完整的Demo,其实也就一个服务类而已,想要Demo的留言我发给你。

欢迎一起讨论学习:471497226@qq.com

Android几行代码实现实时监听微信聊天的更多相关文章

  1. Android几行代码实现监听微信聊天

    原创作品,转载请注明出处,尊重别人的劳动果实. 2017.2.7更新: *现在适配微信版本更加容易了,只需要替换一个Recourse-ID即可 *可以知道对方发的是小视频还是语音,并获取秒数. *可以 ...

  2. Android实时监听网络状态

    Android实时监听网络状态(1)   其实手机在网络方面的的监听也比较重要,有时候我们必须实时监控这个程序的实时网络状态,android在网络断开与连接的时候都会发出广播,我们通过接收系统的广播就 ...

  3. 通过jQuery实时监听表格行数变化

    [本文出自天外归云的博客园] 使用bootstrap table组件,当使用过滤器的时候,页面的表格行数发生变化,此时需要统计表格行数.想要监听表格变化,如何做呢? 使用场景:有一个表格里放着许多测试 ...

  4. 移动端用js与jquery实时监听输入框值的改动

    背景: 在一次移动端H5开发中,需要监听输入框值的实时变动. onchange事件肯定抛弃,因为只能失去焦点才触发. 而keyPress在Android可以触发,iOS不可以. 又不想用Android ...

  5. javascript --- 实时监听输入框值的变化

    实时监听文本框值变化是非常常见的功能,通常最简单的办法就是用keyup,keydown来实现,但是这种方法有两个问题,一个是当直接复制粘贴的时候没法监听到事件,另外一个问题是在移动端,使用删除键删除输 ...

  6. 实时监听文本框输入 oninput、onchange与onpropertychange事件的用法和区别

    前端页面开发的很多情况下都需要实时监听文本框输入,比如腾讯微博编写140字的微博时输入框hu9i动态显示还可以输入的字数.过去一般都使用onchange/onkeyup/onkeypress/onke ...

  7. javascript如何处理多级的实时监听

    今日工作中遇到需求,要求js代码对表单中的input内容进行实时监听,当input中的值改变时触发一些事件. 按照常规思维,代码很快写完了. $(function () { $("#inpu ...

  8. DownEditTextView【自定义Edittext对Android 软键盘向下的监听】

    版权声明:本文为HaiyuKing原创文章,转载请注明出处! 前言 记录自定义EditText控件实现监听软键盘隐藏事件的功能.基本上和参考资料相同. 效果图    代码分析 自定义EditText子 ...

  9. jquery实时监听输入框值变化

    在做web开发时候很多时候都需要即时监听输入框值的变化,以便作出即时动作去引导浏览者增强网站的用户体验感.而采用onchange时间又往往是在输入框失去焦点(onblur)时候触发,有时候并不能满足条 ...

随机推荐

  1. 转发:iOS开发系列--触摸事件、手势识别、摇晃事件、耳机线控

    -- iOS事件全面解析 转载来自崔江涛(KenshinCui) 链接:http://www.cnblogs.com/kenshincui/p/3950646.html 概览 iPhone的成功很大一 ...

  2. ZOJ 3946 Highway Project

    1.迪杰斯特拉最小堆 #include<cstdio> #include<cstring> #include<cmath> #include<map> ...

  3. POJ 3449 Geometric Shapes

    判断两个多边形是否相交,只需判断边是否有相交. 编码量有点大,不过思路挺简单的. #include<cstdio> #include<cstring> #include< ...

  4. java中基本类型占用字节数

    之前一直使用c/c++开发c中各种类型占用的位数和java还是有区别的,特地找了篇文章过来对比下. 在处理网络协议的时候需要注意 在Java中一共有8种基本数据类型,其中有4种整型,2种浮点类型,1种 ...

  5. LPC1768外部中断与GPIO中断

    LPC1768的外部中断严格来说只有四个,分别是EINT0,EINT1,EINT2,EINT3,技术手册上有如下说明 控制这四个外部中断靠以下寄存器 这三个寄存器的0 1 2 3位分别代表中断的0 1 ...

  6. IOS第三方数据库--FMDB

    iOS中原生的SQLite API在使用上相当不友好,在使用时,非常不便.于是,就出现了一系列将SQLite API进行封装的库,例如FMDB.PlausibleDatabase.sqlitepers ...

  7. mybatis--常见的错误

    1.没有在configuration.xml配置对应的sql配置文件 错误: Error updating database. Cause: java.lang.IllegalArgumentExce ...

  8. Mac OS10.11更新ruby,gem,安装cocoapods

    1.装cocoapods,ruby版本忒低->开始更新ruby->开始更新gem,这是一条不归路啊同志们,各种permission denied,各种路径不存在,各种路径没有读写权限,各种 ...

  9. OI队内测试——石门一

    T1: 题目大意: 给你一个立方体,每个面上有些数字,给你一个数字K,你可以玩K轮游戏, 每轮你会将每个面上的数均分为4份,分给相邻的面,求K轮游戏后,上面的数字是 依次给你前.后.上.下.左.右的起 ...

  10. driver_register()函数解析

    driver_register()函数解析 /** * driver_register - register driver with bus * @drv: driver to register *  ...