Android幾行代碼實現監聽微信聊天


原創作品,轉載請注明出處,尊重別人的勞動果實。

 

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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM