【玩转Android自动化】微信好友状态检查(假转账方式)

2,260 阅读21分钟

上一篇文章【玩转Android自动化】微信好友导出 实现了导出微信好友的功能,有了这些好友,我们就可以一个一个去进行想要的操作,去判断好友状态了。本章我们来介绍一下如何判断好友关系状态。

判断方法

  • 目前已知的判断方法常用的是拉群法假转账法,本篇主要讲解通过假转账的办法去判断好友状态,并提供完整的解决方案。

假转账方式

  • 开始之前先说一下为什么可以通过假转账的方式判断,当你对一个好友进行转账的时候其实微信是会对当前好友关系做一次校验的,如果对方把你拉黑、删除、或者好友账号异常,微信都会弹框提示,如果好友状态正常的就可以进行输入密码转账了,当然之所以称之为假转账的方式,就是我们并不会去真正的转账,只是借助转账这个操作判断状态而已,不需要输入支付密码,所以整个过程不涉及密码泄露之类的情况,可以放心使用。

以上就是我们最终去判断用户状态的地方,那么在判断之前是怎么打开转账页面的纳,都需要哪些流程呐,接下来就来分析具体步骤。

操作流程

  • 打开微信 → 点击【通讯录】tab → 点击一个好友(进入好友信息页) → 点击发消息(进入聊天页) → 点击右下角功能按钮(弹出功能区域) → 点击功能区的转账按钮(进入转账页面) → 输入金额 → 点击键盘里的转账按钮 → 触发弹框判断状态 → 返回到首页。

注意:文章中所用的页面节点元素查看就是使用我们之前文章【玩转Android自动化】布局节点速查器中封装的小工具,不清楚的可以先看看文章介绍。

步骤拆分

因为涉及到的页面比较多,所以我们把同一个页面的功能放到一起,每个页面建一个类,这样更加直观且方便管理。

1、打开微信

  • 打开微信的方法跟上一篇文章介绍的一样直接调用我们的封装好的扩展方法即可
fun Context.goToWx() = Intent(Intent.ACTION_MAIN)
    .apply {
        addCategory(Intent.CATEGORY_LAUNCHER)
        addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP)
        component = ComponentName("com.tencent.mm", "com.tencent.mm.ui.LauncherUI")
    }
    .apply(::startActivity)

2、点击【通讯录】tab

  • 点击通讯录依然需要先找到通讯录tab的节点,然后触发点击事件即可
\--- className = android.widget.RelativeLayout → text =  → id = com.tencent.mm:id/fj3 → description =  → isClickable = false → isScrollable = false → isEditable = false
 \--- className = android.widget.LinearLayout → text =  → id =  → description =  → isClickable = false → isScrollable = false → isEditable = false
   +--- className = android.widget.RelativeLayout → text =  → id = com.tencent.mm:id/kd_ → description =  → isClickable = true → isScrollable = false → isEditable = false
   |  \--- className = android.widget.LinearLayout → text =  → id = com.tencent.mm:id/f1f → description =  → isClickable = false → isScrollable = false → isEditable = false
   |    +--- className = android.widget.RelativeLayout → text =  → id =  → description =  → isClickable = false → isScrollable = false → isEditable = false
   |    |  +--- className = android.widget.ImageView → text =  → id = com.tencent.mm:id/f2a → description =  → isClickable = false → isScrollable = false → isEditable = false
   |    |  \--- className = android.widget.TextView → text = 1 → id = com.tencent.mm:id/l0c → description =  → isClickable = false → isScrollable = false → isEditable = false
   |    \--- className = android.widget.TextView → text = 微信 → id = com.tencent.mm:id/f2s → description =  → isClickable = false → isScrollable = false → isEditable = false
   +--- className = android.widget.RelativeLayout → text =  → id = com.tencent.mm:id/kd_ → description =  → isClickable = true → isScrollable = false → isEditable = false
   |  \--- className = android.widget.LinearLayout → text =  → id = com.tencent.mm:id/f1f → description =  → isClickable = false → isScrollable = false → isEditable = false
   |    +--- className = android.widget.RelativeLayout → text =  → id =  → description =  → isClickable = false → isScrollable = false → isEditable = false
   |    |  \--- className = android.widget.ImageView → text =  → id = com.tencent.mm:id/f2a → description =  → isClickable = false → isScrollable = false → isEditable = false
   |    \--- className = android.widget.TextView → text = 通讯录 → id = com.tencent.mm:id/f2s → description =  → isClickable = false → isScrollable = false → isEditable = false
   +--- className = android.widget.RelativeLayout → text =  → id = com.tencent.mm:id/kd_ → description =  → isClickable = true → isScrollable = false → isEditable = false
   |  \--- className = android.widget.LinearLayout → text =  → id = com.tencent.mm:id/f1f → description =  → isClickable = false → isScrollable = false → isEditable = false
   |    +--- className = android.widget.RelativeLayout → text =  → id =  → description =  → isClickable = false → isScrollable = false → isEditable = false
   |    |  \--- className = android.widget.ImageView → text =  → id = com.tencent.mm:id/f2a → description =  → isClickable = false → isScrollable = false → isEditable = false
   |    \--- className = android.widget.TextView → text = 发现 → id = com.tencent.mm:id/f2s → description =  → isClickable = false → isScrollable = false → isEditable = false
   \--- className = android.widget.RelativeLayout → text =  → id = com.tencent.mm:id/kd_ → description =  → isClickable = true → isScrollable = false → isEditable = false
     \--- className = android.widget.LinearLayout → text =  → id = com.tencent.mm:id/f1f → description =  → isClickable = false → isScrollable = false → isEditable = false
       +--- className = android.widget.RelativeLayout → text =  → id =  → description =  → isClickable = false → isScrollable = false → isEditable = false
       |  \--- className = android.widget.ImageView → text =  → id = com.tencent.mm:id/f2a → description =  → isClickable = false → isScrollable = false → isEditable = false
       \--- className = android.widget.TextView → text = 我 → id = com.tencent.mm:id/f2s → description =  → isClickable = false → isScrollable = false → isEditable = false

  • 有了节点信息,我们就去找到text = 通讯录 → id = com.tencent.mm:id/f2s这个节点就可以了,其中使用我们封装好的findByIdAndText方法,系统原始的方法只有findXXXByIdfindXXXByText。从上面打印的数据可以看出来,底导四个tab的ID是一样的,如果只根据文本匹配的话会不准确,因为系统提供的方法对文本匹配是模糊匹配的,只要包含我们传入的text都会匹配出来,没有办法精准匹配,所以我们就寻找指定id的节点,会得到一个数组,里边只包含了这四个tab的数据,然后再根据文本通讯录去匹配就可以得到唯一的tab,最后触发点击事件就可以打开通讯录页面了。
wxAccessibilityService?.findByIdAndText(
                        NodeInfo.BottomNavContactsTabNode.nodeId,
                        NodeInfo.BottomNavContactsTabNode.nodeText
                    ).click()

3、点击一个好友(进入好友信息页)

  • 在通讯录页我们选中一个好友,为了方便测试,先找到第一个好友
|  \--- className = android.widget.LinearLayout → text =  → id =  → description =  → isClickable = false → isScrollable = false → isEditable = false
|    \--- className = android.widget.RelativeLayout → text =  → id = com.tencent.mm:id/bqy → description =  → isClickable = false → isScrollable = false → isEditable = false
|      \--- className = android.widget.LinearLayout → text =  → id =  → description =  → isClickable = false → isScrollable = false → isEditable = false
|        +--- className = android.widget.ImageView → text =  → id = com.tencent.mm:id/a27 → description =  → isClickable = false → isScrollable = false → isEditable = false
|        \--- className = android.widget.TableLayout → text =  → id = com.tencent.mm:id/hg2 → description =  → isClickable = false → isScrollable = false → isEditable = false
|          \--- className = android.widget.TableRow → text =  → id =  → description =  → isClickable = false → isScrollable = false → isEditable = false
|            \--- className = android.widget.TextView → text = 百里守约 → id = com.tencent.mm:id/hg4 → description =  → isClickable = false → isScrollable = false → isEditable = false
  • 然后调用我们的方法 wxAccessibilityService?.findByText("百里守约").click()就触发了点击百里守约这个好友了,就会进入到好友的信息页

4、点击发消息(进入聊天页)

  • 进入好友信息页后我们就就要分析这个页面的元素,下边展示的节点数据是去掉了一些用不到的信息后的样子。
+--- className = android.widget.LinearLayout → text =  → id = com.tencent.mm:id/gv4 → description =  → isClickable = false → isScrollable = false → isEditable = false
|  \--- className = android.widget.RelativeLayout → text =  → id =  → description =  → isClickable = false → isScrollable = false → isEditable = false
|    \--- className = android.widget.ListView → text =  → id = android:id/list → description =  → isClickable = false → isScrollable = false → isEditable = false
|      +--- className = android.widget.RelativeLayout → text =  → id = com.tencent.mm:id/bq2 → description =  → isClickable = true → isScrollable = false → isEditable = false
|      |  +--- className = android.widget.LinearLayout → text =  → id = com.tencent.mm:id/bpy → description =  → isClickable = false → isScrollable = false → isEditable = 
|      |  |  \--- className = android.widget.LinearLayout → text =  → id = com.tencent.mm:id/bps → description =  → isClickable = false → isScrollable = false → isEditable = 
|      |  |    +--- className = android.widget.ImageView → text =  → id = com.tencent.mm:id/bpr → description = 头像 → isClickable = true → isScrollable = false → isEditable 
|      |  |    \--- className = android.widget.LinearLayout → text =  → id =  → description =  → isClickable = false → isScrollable = false → isEditable = false
|      |  |      +--- className = android.widget.LinearLayout → text =  → id =  → description =  → isClickable = false → isScrollable = false → isEditable = false
|      |  |      |  +--- className = android.widget.TextView → text = 百里守约 → id = com.tencent.mm:id/bq1 → description =  → isClickable = false → isScrollable = false → 
|      |  |      |  +--- className = android.view.View → text =  → id =  → description =  → isClickable = false → isScrollable = false → isEditable = false
|      |  |      |  \--- className = android.widget.LinearLayout → text =  → id =  → description =  → isClickable = false → isScrollable = false → isEditable = false
|      |  |      +--- className = android.widget.TextView → text = 昵称:  测试 → id = com.tencent.mm:id/bq0 → description =  → isClickable = false → isScrollable = false → 
|      |  |      \--- className = android.widget.TextView → text = 微信号:  wxid_xxxxxx → id = com.tencent.mm:id/bq9 → description =  → isClickable = false → isScrollable = false → isEditable = false
|      +--- className = android.widget.LinearLayout → text =  → id = com.tencent.mm:id/bq5 → description =  → isClickable = true → isScrollable = false → isEditable = false
|      |  \--- className = android.widget.LinearLayout → text =  → id =  → description =  → isClickable = false → isScrollable = false → isEditable = false
|      |    +--- className = android.widget.ImageView → text =  → id = com.tencent.mm:id/f15 → description =  → isClickable = false → isScrollable = false → isEditable = false
|      |    \--- className = android.widget.TextView → text = 发消息 → id = com.tencent.mm:id/khj → description =  → isClickable = false → isScrollable = false → isEditable = 
|      +--- className = android.widget.LinearLayout → text =  → id = com.tencent.mm:id/iwg → description =  → isClickable = true → isScrollable = false → isEditable = false
|      |  \--- className = android.widget.LinearLayout → text =  → id =  → description =  → isClickable = false → isScrollable = false → isEditable = false
|      |    +--- className = android.widget.ImageView → text =  → id = com.tencent.mm:id/f15 → description =  → isClickable = false → isScrollable = false → isEditable = false
|      |    \--- className = android.widget.TextView → text = 音视频通话 → id = com.tencent.mm:id/khj → description =  → isClickable = false → isScrollable = false → 
|      +--- className = android.widget.TextView → text =  → id = com.tencent.mm:id/jn6 → description =  → isClickable = true → isScrollable = false → isEditable = false
|      +--- className = android.widget.TextView → text =  → id = android:id/title → description =  → isClickable = true → isScrollable = false → isEditable = false
|      \--- className = android.widget.LinearLayout → text =  → id =  → description =  → isClickable = true → isScrollable = false → isEditable = false
|        \--- className = android.widget.TextView → text =  → id = android:id/title → description =  → isClickable = false → isScrollable = false → isEditable = false
  • 在这个页面可以看到微信昵称和微信号,还有发送按钮,我们在WXContactInfoPage类中定义两个方法,获取好友数据的getUserInfo()和点击发送按钮的clickSendMsg()
            val nickName = wxAccessibilityService
                ?.findById(NodeInfo.ContactInfoNickNameNode.nodeId)
                ?.text
                .default()
            //android.widget.TextView → 微信号: wxid_xxx → com.tencent.mm:id/ini
            val wxCode = wxAccessibilityService
                ?.findById(NodeInfo.ContactInfoWxCodeNode.nodeId)
                ?.text
                ?.split("微信号:")
                ?.getOrNull(1)
                .default()
                .trim()
            if (nickName.isNotBlank() && wxCode.isNotBlank()) {
                WxUserInfo(nickName, wxCode)
            }
        //【发消息】的按钮ID和【音视频通话】的ID一样,所以用文本区分
        wxAccessibilityService.clickByText(NodeInfo.ContactInfoSendMsgNode.nodeText)
  • 经过以上两步操作就可以获取到好友的微信昵称和微信号了,然后点击了【发消息】按钮跳转到聊天详情页

5、在聊天页点击右下角功能按钮(弹出功能区域)和点击功能区的转账按钮(进入转账页面)

  • 聊天详情页我们定义WXChattingPage类,这个页面相关的操作都写在一个类中,便于维护,我们只需要分析出右下角的功能按钮【➕】和点击展开后功能去里的【转账】按钮

聊天页功能区.png

className = android.widget.ImageButton → text =  → id = com.tencent.mm:id/b3q → description = 更多功能按钮,已折叠 → isClickable = true → isScrollable = false → isEditable = false
\--- className = android.widget.FrameLayout → text =  → id = com.tencent.mm:id/b44 → description =  → isClickable = false → isScrollable = false → isEditable = false
  \--- className = android.widget.LinearLayout → text =  → id = com.tencent.mm:id/b1m → description =  → isClickable = false → isScrollable = false → isEditable = false
    \--- className = android.widget.LinearLayout → text =  → id =  → description =  → isClickable = false → isScrollable = false → isEditable = false
      \--- className = android.widget.LinearLayout → text =  → id = com.tencent.mm:id/w6 → description =  → isClickable = false → isScrollable = false → isEditable = false
        +--- className = android.view.ViewGroup → text =  → id = com.tencent.mm:id/w8 → description =  → isClickable = false → isScrollable = false → isEditable = false
        |  +--- className = android.widget.GridView → text =  → id = com.tencent.mm:id/w9 → description =  → isClickable = false → isScrollable = false → isEditable = false
        |  |  +--- className = android.widget.LinearLayout → text =  → id =  → description =  → isClickable = true → isScrollable = false → isEditable = false
        |  |  |  \--- className = android.widget.FrameLayout → text =  → id =  → description =  → isClickable = false → isScrollable = false → isEditable = false
        |  |  |    +--- className = android.widget.LinearLayout → text =  → id =  → description =  → isClickable = false → isScrollable = false → isEditable = false
        |  |  |    \--- className = android.widget.LinearLayout → text =  → id =  → description =  → isClickable = false → isScrollable = false → isEditable = false
        |  |  |      +--- className = android.widget.RelativeLayout → text =  → id =  → description =  → isClickable = false → isScrollable = false → isEditable = false
        |  |  |      |  +--- className = android.widget.ImageView → text =  → id = com.tencent.mm:id/ve → description =  → isClickable = false → isScrollable = false → isEditable = false
        |  |  |      |  \--- className = android.widget.ImageView → text =  → id = com.tencent.mm:id/vf → description =  → isClickable = false → isScrollable = false → isEditable = false
        |  |  |      \--- className = android.widget.TextView → text = 红包 → id = com.tencent.mm:id/vg → description =  → isClickable = false → isScrollable = false → isEditable = false
        |  |  +--- className = android.widget.LinearLayout → text =  → id =  → description =  → isClickable = true → isScrollable = false → isEditable = false
        |  |  |  \--- className = android.widget.FrameLayout → text =  → id =  → description =  → isClickable = false → isScrollable = false → isEditable = false
        |  |  |    +--- className = android.widget.LinearLayout → text =  → id =  → description =  → isClickable = false → isScrollable = false → isEditable = false
        |  |  |    \--- className = android.widget.LinearLayout → text =  → id =  → description =  → isClickable = false → isScrollable = false → isEditable = false
        |  |  |      +--- className = android.widget.RelativeLayout → text =  → id =  → description =  → isClickable = false → isScrollable = false → isEditable = false
        |  |  |      |  +--- className = android.widget.ImageView → text =  → id = com.tencent.mm:id/ve → description =  → isClickable = false → isScrollable = false → isEditable = false
        |  |  |      |  \--- className = android.widget.ImageView → text =  → id = com.tencent.mm:id/vf → description =  → isClickable = false → isScrollable = false → isEditable = false
        |  |  |      \--- className = android.widget.TextView → text = 转账 → id = com.tencent.mm:id/vg → description =  → isClickable = false → isScrollable = false → isEditable = false
        |  |  +--- className = android.widget.LinearLayout → text =  → id =  → description =  → isClickable = true → isScrollable = false → isEditable = false
        |  |  |  \--- className = android.widget.FrameLayout → text =  → id =  → description =  → isClickable = false → isScrollable = false → isEditable = false
        |  |  |    +--- className = android.widget.LinearLayout → text =  → id =  → description =  → isClickable = false → isScrollable = false → isEditable = false
        |  |  |    \--- className = android.widget.LinearLayout → text =  → id =  → description =  → isClickable = false → isScrollable = false → isEditable = false
        |  |  |      +--- className = android.widget.RelativeLayout → text =  → id =  → description =  → isClickable = false → isScrollable = false → isEditable = false
        |  |  |      |  +--- className = android.widget.ImageView → text =  → id = com.tencent.mm:id/ve → description =  → isClickable = false → isScrollable = false → isEditable = false
        |  |  |      |  \--- className = android.widget.ImageView → text =  → id = com.tencent.mm:id/vf → description =  → isClickable = false → isScrollable = false → isEditable = false
        |  |  |      \--- className = android.widget.TextView → text = 语音输入 → id = com.tencent.mm:id/vg → description =  → isClickable = false → isScrollable = false → isEditable = false
        |  \--- className = android.widget.GridView → text =  → id = com.tencent.mm:id/w9 → description =  → isClickable = false → isScrollable = false → isEditable = false
        \--- className = android.widget.LinearLayout → text =  → id = com.tencent.mm:id/w7 → description =  → isClickable = false → isScrollable = false → isEditable = false
          +--- className = android.widget.ImageView → text =  → id = com.tencent.mm:id/gvo → description =  → isClickable = false → isScrollable = false → isEditable = false
          \--- className = android.widget.ImageView → text =  → id = com.tencent.mm:id/gvo → description =  → isClickable = false → isScrollable = false → isEditable = false
  • 从上边的数据可以分析出android.widget.ImageButton → text = → id = com.tencent.mm:id/b3q → description = 更多功能按钮,已折叠 → isClickable = true是我们需要点击的按钮, 转账 → id = com.tencent.mm:id/vg是功能区转账按钮的节点信息,拿到了节点信息后获取节点并触发点击即可。这里会在测试中发现,点击的时机不易太快,如果点的太快会出现页面未跳转的问题,所以在点击事件时候做一个delay处理,而且转账按钮调用performAction(AccessibilityNodeInfo.ACTION_CLICK)是无效的,可能转账涉及到金钱交易,微信做了一层防护吧,禁掉了事件,不过这里可以使用模拟点击的方法去代替。

//点击功能展开按钮
wxAccessibilityService.clickById(NodeInfo.ChattingBottomPlusNode.nodeId)

//获取功能区转账按钮节点
val find = wxAccessibilityService?.findByIdAndText(
                    NodeInfo.ChattingTransferMoneyNode.nodeId,
                    NodeInfo.ChattingTransferMoneyNode.nodeText
                )
if (find != null) {
    //点击按钮
    wxAccessibilityService?.gestureClick(find)
}

//模拟点击代码封装
fun AccessibilityService.gestureClick(node: AccessibilityNodeInfo) {
    val nodeBounds = Rect().apply(node::getBoundsInScreen)
    val x = nodeBounds.centerX().toFloat()
    val y = nodeBounds.centerY().toFloat()
    dispatchGesture(
        GestureDescription.Builder().apply {
            addStroke(
                GestureDescription.StrokeDescription(
                    Path().apply { moveTo(x, y) },
                    0L,
                    200L
                )
            )
        }.build(),
        object : AccessibilityService.GestureResultCallback() {
            override fun onCompleted(gestureDescription: GestureDescription?) {
                super.onCompleted(gestureDescription)
            }
        },
        null
    )
}

6、转账页相关操作

  • 经过上面的操作就进入到转账页面了,接下来就在转账页面进行判断了。在转账页其实还有一个点需要特别关注一下,就是转账对象那里如果显示的是转账给 百里守约(xx名),微信昵称后边如果显示的有真实姓名的话,也是可以说明是正常好友关系的,这一点可以经过反复测试去验证。所以一旦监测到昵称后边有真名就无需在进行输入金额进行假转账判断了,省了后续的操作。

  • 先整体看一下转账页的节点信息吧

+--- className = android.widget.FrameLayout → text =  → id =  → description =  → isClickable = false → isScrollable = false → isEditable = false
  \--- className = android.widget.RelativeLayout → text =  → id =  → description =  → isClickable = false → isScrollable = false → isEditable = false
    +--- className = android.widget.ScrollView → text =  → id = com.tencent.mm:id/iwq → description =  → isClickable = false → isScrollable = false → isEditable = false
    |  \--- className = android.widget.LinearLayout → text =  → id =  → description =  → isClickable = false → isScrollable = false → isEditable = false
    |    +--- className = android.widget.LinearLayout → text =  → id = com.tencent.mm:id/euv → description =  → isClickable = false → isScrollable = false → isEditable = false
    |    |  +--- className = android.widget.LinearLayout → text =  → id =  → description =  → isClickable = false → isScrollable = false → isEditable = false
    |    |  |  +--- className = android.widget.TextView → text = 转账给 百里守约 → id = com.tencent.mm:id/inh → description =  → isClickable = false → isScrollable = false → 
    |    |  |  \--- className = android.widget.TextView → text = 微信号:  wxid_xxxxxx → id = com.tencent.mm:id/ini → description =  → isClickable = false → isScrollable = false
    |    |  \--- className = android.widget.ImageView → text =  → id = com.tencent.mm:id/ing → description = 头像 → isClickable = false → isScrollable = false → isEditable = false
    |    \--- className = android.widget.LinearLayout → text =  → id = com.tencent.mm:id/ah2 → description =  → isClickable = false → isScrollable = false → isEditable = false
    |      \--- className = android.widget.LinearLayout → text =  → id = com.tencent.mm:id/in8 → description =  → isClickable = false → isScrollable = false → isEditable = false
    |        +--- className = android.widget.LinearLayout → text =  → id = com.tencent.mm:id/imt → description =  → isClickable = false → isScrollable = false → isEditable = false
    |        |  +--- className = android.widget.TextView → text = 转账金额 → id = com.tencent.mm:id/inz → description =  → isClickable = false → isScrollable = false → isEditable 
    |        |  \--- className = android.widget.LinearLayout → text =  → id = com.tencent.mm:id/gym → description =  → isClickable = false → isScrollable = false → isEditable = false
    |        |    \--- className = android.widget.LinearLayout → text =  → id = com.tencent.mm:id/gyl → description =  → isClickable = false → isScrollable = false → isEditable = false
    |        |      +--- className = android.widget.TextView → text = ¥ → id = com.tencent.mm:id/ljn → description =  → isClickable = false → isScrollable = false → isEditable = false
    |        |      \--- className = android.widget.EditText → text =  → id = com.tencent.mm:id/lg_ → description =  → isClickable = true → isScrollable = false → isEditable = true
    |        +--- className = android.widget.RelativeLayout → text =  → id =  → description =  → isClickable = false → isScrollable = false → isEditable = false
    |        |  \--- className = android.widget.RelativeLayout → text =  → id =  → description =  → isClickable = false → isScrollable = false → isEditable = false
    |        |    \--- className = android.widget.ImageView → text =  → id = com.tencent.mm:id/lh_ → description =  → isClickable = false → isScrollable = false → isEditable = false
    |        \--- className = android.widget.LinearLayout → text =  → id = com.tencent.mm:id/im3 → description =  → isClickable = false → isScrollable = false → isEditable = false
    |          \--- className = android.widget.TextView → text = 添加转账说明 → id = com.tencent.mm:id/je → description =  → isClickable = true → isScrollable = false → isEditable 
    \--- className = android.widget.RelativeLayout → text =  → id = com.tencent.mm:id/lrk → description =  → isClickable = false → isScrollable = false → isEditable = false
      \--- className = android.widget.LinearLayout → text =  → id = com.tencent.mm:id/ffr → description =  → isClickable = true → isScrollable = false → isEditable = false
        +--- className = android.view.View → text =  → id =  → description =  → isClickable = false → isScrollable = false → isEditable = false
        \--- className = android.view.ViewGroup → text =  → id = com.tencent.mm:id/fg4 → description =  → isClickable = false → isScrollable = false → isEditable = false
          +--- className = android.widget.TextView → text = 1 → id = com.tencent.mm:id/ffg → description =  → isClickable = true → isScrollable = false → isEditable = false
          +--- className = android.widget.TextView → text = 2 → id = com.tencent.mm:id/ffh → description =  → isClickable = true → isScrollable = false → isEditable = false
          +--- className = android.widget.TextView → text = 3 → id = com.tencent.mm:id/ffi → description =  → isClickable = true → isScrollable = false → isEditable = false
          +--- className = android.widget.Button → text =  → id = com.tencent.mm:id/ffw → description = 删除 → isClickable = true → isScrollable = false → isEditable = false
          |  \--- className = android.widget.ImageView → text =  → id =  → description =  → isClickable = false → isScrollable = false → isEditable = false
          +--- className = android.widget.TextView → text = 4 → id = com.tencent.mm:id/ffj → description =  → isClickable = true → isScrollable = false → isEditable = false
          +--- className = android.widget.TextView → text = 5 → id = com.tencent.mm:id/ffk → description =  → isClickable = true → isScrollable = false → isEditable = false
          +--- className = android.widget.TextView → text = 6 → id = com.tencent.mm:id/ffl → description =  → isClickable = true → isScrollable = false → isEditable = false
          +--- className = android.widget.TextView → text = 7 → id = com.tencent.mm:id/ffm → description =  → isClickable = true → isScrollable = false → isEditable = false
          +--- className = android.widget.TextView → text = 8 → id = com.tencent.mm:id/ffn → description =  → isClickable = true → isScrollable = false → isEditable = false
          +--- className = android.widget.TextView → text = 9 → id = com.tencent.mm:id/ffo → description =  → isClickable = true → isScrollable = false → isEditable = false
          +--- className = android.widget.TextView → text = 0 → id = com.tencent.mm:id/fff → description =  → isClickable = true → isScrollable = false → isEditable = false
          +--- className = android.widget.TextView → text = . → id = com.tencent.mm:id/fg3 → description =  → isClickable = true → isScrollable = false → isEditable = false
          \--- className = android.widget.Button → text = 转账 → id = com.tencent.mm:id/ffp → description =  → isClickable = true → isScrollable = false → isEditable = false

6.1、判断昵称后边是否有真名

  • 通过节点信息可以看到微信昵称是text = 转账给 百里守约 → id = com.tencent.mm:id/inh,只需获取这个节点取出text,如果出现 转账给 测试(**名) 即昵称后边有加星的真名就说明好友正常,否则还需要后边进一步验证
val friendName = wxAccessibilityService
    ?.findById(NodeInfo.RemittanceUserNode.nodeId)
    ?.text
    .default()
    .split("转账给")
    .getOrNull(1)
    .default()
    .trim()
if (friendName.isNotBlank()) {
    val isNormal = friendName.contains("(*") && friendName.endsWith(")")
    if (isNormal) {
        WxUserInfo(friendName, wxCode, FriendStatus.NORMAL)
    }
}

6.2、输入金额

  • 如果通过微信昵称无法判断出来,就要通过假转账的方式了,需要先输入转账金额,我们这里就输入最小金额0.01即可。想要对一个可编辑的节点输入内容可以用如下方式
fun AccessibilityNodeInfo.inputText(input: String): Boolean {
    val arguments = Bundle().apply {
        putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, input)
    }
    return performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, arguments)
}
  • 所以获取输入框节点className = android.widget.EditText → text = → id = com.tencent.mm:id/lg_ → description = → isClickable = true → isScrollable = false → isEditable = true然后自动输入金额,代码如下
wxAccessibilityService?.findById(NodeInfo.RemittanceInputMoneyNode.nodeId)?.inputText("0.01")

6.3、点击键盘里的转账按钮

  • 输入完金额后就开始点击转账按钮了,获取按钮触发点击的代码我们已经写过很多次了,直接看代码吧
wxAccessibilityService.clickById(NodeInfo.RemittanceTransferMoneyNode.nodeId)

6.4、触发弹框判断状态

点击完转账按钮后就会向微信服务器发送请求进行好友关系的判断,并弹出对应的弹框。

  • 被拉黑(弹框内容及节点信息)

被拉黑.png

+--- className = android.widget.LinearLayout → text =  → id = com.tencent.mm:id/guj → description =  → isClickable = false → isScrollable = false → isEditable = false
|  \--- className = android.widget.ScrollView → text =  → id = com.tencent.mm:id/o4_ → description =  → isClickable = false → isScrollable = false → isEditable = false
|    \--- className = android.widget.LinearLayout → text =  → id =  → description =  → isClickable = false → isScrollable = false → isEditable = false
|      \--- className = android.widget.LinearLayout → text =  → id = com.tencent.mm:id/guq → description =  → isClickable = false → isScrollable = false → isEditable = false
|        +--- className = android.widget.TextView → text = 请确认你和他(她)的好友关系是否正常 → id = com.tencent.mm:id/guo → description =  → isClickable = false → isScrollable = false → isEditable = false
|        \--- className = android.widget.LinearLayout → text =  → id =  → description =  → isClickable = false → isScrollable = false → isEditable = false
|          \--- className = android.widget.LinearLayout → text =  → id =  → description =  → isClickable = false → isScrollable = false → isEditable = false
\--- className = android.widget.LinearLayout → text =  → id = com.tencent.mm:id/guc → description =  → isClickable = false → isScrollable = false → isEditable = false
  \--- className = android.widget.LinearLayout → text =  → id = com.tencent.mm:id/guh → description =  → isClickable = false → isScrollable = false → isEditable = false
    +--- className = android.widget.ImageView → text =  → id = com.tencent.mm:id/c7p → description =  → isClickable = false → isScrollable = false → isEditable = false
    \--- className = android.widget.Button → text = 我知道了 → id = com.tencent.mm:id/guw → description =  → isClickable = true → isScrollable = false → isEditable = false

  • 被删除(弹框内容及节点信息)

被删除.png

+--- className = android.widget.LinearLayout → text =  → id = com.tencent.mm:id/guj → description =  → isClickable = false → isScrollable = false → isEditable = false
|  \--- className = android.widget.ScrollView → text =  → id = com.tencent.mm:id/o4_ → description =  → isClickable = false → isScrollable = false → isEditable = false
|    \--- className = android.widget.LinearLayout → text =  → id =  → description =  → isClickable = false → isScrollable = false → isEditable = false
|      \--- className = android.widget.LinearLayout → text =  → id = com.tencent.mm:id/guq → description =  → isClickable = false → isScrollable = false → isEditable = false
|        +--- className = android.widget.TextView → text = 你不是收款方好友,对方添加你为好友后才能发起转账 → id = com.tencent.mm:id/guo → description =  → isClickable = false → isScrollable = false → isEditable = false
|        \--- className = android.widget.LinearLayout → text =  → id =  → description =  → isClickable = false → isScrollable = false → isEditable = false
|          \--- className = android.widget.LinearLayout → text =  → id =  → description =  → isClickable = false → isScrollable = false → isEditable = false
\--- className = android.widget.LinearLayout → text =  → id = com.tencent.mm:id/guc → description =  → isClickable = false → isScrollable = false → isEditable = false
  \--- className = android.widget.LinearLayout → text =  → id = com.tencent.mm:id/guh → description =  → isClickable = false → isScrollable = false → isEditable = false
    +--- className = android.widget.ImageView → text =  → id = com.tencent.mm:id/c7p → description =  → isClickable = false → isScrollable = false → isEditable = false
    \--- className = android.widget.Button → text = 我知道了 → id = com.tencent.mm:id/guw → description =  → isClickable = true → isScrollable = false → isEditable = false
  • 好友账号异常(弹框内容及节点信息)

好友账号异常.png

+--- className = android.widget.LinearLayout → text =  → id = com.tencent.mm:id/guj → description =  → isClickable = false → isScrollable = false → isEditable = false
|  \--- className = android.widget.ScrollView → text =  → id = com.tencent.mm:id/o4_ → description =  → isClickable = false → isScrollable = false → isEditable = false
|    \--- className = android.widget.LinearLayout → text =  → id =  → description =  → isClickable = false → isScrollable = false → isEditable = false
|      \--- className = android.widget.LinearLayout → text =  → id = com.tencent.mm:id/guq → description =  → isClickable = false → isScrollable = false → isEditable = false
|        +--- className = android.widget.TextView → text = 对方微信号已被限制登录,为保障你的资金安全,暂时无法完成交易。 → id = com.tencent.mm:id/guo → description =  → isClickable = false → isScrollable = false → isEditable = false
|        \--- className = android.widget.LinearLayout → text =  → id =  → description =  → isClickable = false → isScrollable = false → isEditable = false
|          \--- className = android.widget.LinearLayout → text =  → id =  → description =  → isClickable = false → isScrollable = false → isEditable = false
\--- className = android.widget.LinearLayout → text =  → id = com.tencent.mm:id/guc → description =  → isClickable = false → isScrollable = false → isEditable = false
  \--- className = android.widget.LinearLayout → text =  → id = com.tencent.mm:id/guh → description =  → isClickable = false → isScrollable = false → isEditable = false
    +--- className = android.widget.ImageView → text =  → id = com.tencent.mm:id/c7p → description =  → isClickable = false → isScrollable = false → isEditable = false
    \--- className = android.widget.Button → text = 我知道了 → id = com.tencent.mm:id/guw → description =  → isClickable = true → isScrollable = false → isEditable = false

  • 正常(弹框内容及节点信息)

正常.png

  • 自己未实名认证时候会跳转新页面,让你去实名认证,不过既然是个人微信,基本上都已经实名过了,这种情况就不具体列举出来了,不过在代码逻辑层还是要做的。

实名认证.jpg

  • 有了上面几种情况我们就可以来判断了,因为类型众多,我们定义一个枚举CheckStatusstatus是好友状态,mark就是我们要匹配的字符,这些字符都是唯一的
enum class CheckStatus(val status: String, val mark: String) {
    BLACK("被拉黑", "请确认你和他(她)的好友关系是否正常"),
    DELETE("被删除", "你不是收款方好友,对方添加你为好友后才能发起转账"),
    ACCOUNT_EXCEPTION("帐号异常", "对方微信号已被限制登录,为保障你的资金安全,暂时无法完成交易"),
    NORMAL("正常", "付款方式"),
    PWD_PAY("正常", "请输入支付密码"),
    NO_AUTH("自己未实名认证", "实名认证"),
}
  • 然后就是获取当前窗口的所有节点进行遍历查找枚举中的字符,返回判断后的状态
val find =wxAccessibilityService?.findByContainsText(false, CheckStatus.values().map { it.mark })
if (find != null) {
    val status = CheckStatus.values().find { find.text.contains(it.mark) }
}

7、返回到首页

  • 经过以上操作后就把一个用户的状态检测完了,然后返回首页即可。是不是觉得整个流程很麻烦,说实话步骤确实繁琐,但是我们是抱着学习的态度做的,重点在于学习无障碍模式相关的操作,所以流程繁琐不是问题,重要的是我们能从中学到什么。

按照常规做法文章至此也算是接近尾声了,不过我们做事是追求完整性的,怎么局限于简单分析流程纳,只是检测一个指定的好友其实没什么卵用,如何去自动化检测所有好友才是重点,接下来会继续介绍自动循环检测的方法。

自动循环检测全部好友

1、根据事先准备的好友列表去检测

    suspend fun startCheckFromList(userList: List<String>) {
        App.instance().goToWx()
        //判断当前是否进入到微信
        val inWxApp = WXHomePage.waitEnterWxApp()
        //如果不在微信里边就没必要继续执行了
        if (!inWxApp) return
        //判断当前是否已经到微信首页
        val isHome = WXHomePage.backToHome()
        if (!isHome) return
        //点击底部通讯录Tab 点两次是为了让通讯录好友列表回到顶部初始状态
        val clickContactsTab = WXHomePage.clickContactsTab(true)
        if (!clickContactsTab) return
        //通过循环遍历好友一次执行检测方法
        userList.forEach { singleTask(it) }
        if (WXHomePage.backToHome()) {
            wxAccessibilityService?.pressBackButton()
        }
        App.instance().toast("检测结束,请回到APP查看好友状态")
    }
  • 虽说上边的方案也能满足需求,但是还不够好,每次都要先获取一遍好友列表才能开始检测,无疑是进一步增加了检测时间(假转账方式本来就流程复杂已经很耗时了),那么有其他办法不通过获取列表让他自动一个一个去执行纳,肯定是可以的,接下来就一起来分析一下吧

2、自动从上到下一个一个查找好友并检测

  • 首先我们知道微信通讯录页面展示的是好友列表,要想去自动遍历,我们需要找到一个突破口,该怎么去取出来第一个好友纳,之前文章分析过通讯录页的节点信息,知道这个页面是用RecyclerView做的,他的节点ID是com.tencent.mm:id/js是唯一的,列表中好友节点是TextView,ID是com.tencent.mm:id/hg4,所以我们找到RecyclerView然后遍历他的子节点中ID为com.tencent.mm:id/hg4的节点然后取出来第一个好友就是我们第一次要检测的人,那么怎么自动找到第二个第三个纳,思索片刻发现唯一的突破口就是我们已经知道上一个好友是谁了,有了上一个好友信息我们就可以在列表中去定位到上一个好友的索引位置,然后索引+1就是下一个好友的索引了。
/**
 * scrollViewId 是recyclerview节点的id
 * childViewId 是每个用户节点的id
 * lastText 是上一个好友昵称
 * filterTexts 是我们需要过滤的数据
 */
private fun AccessibilityService?.getNextNodeByCurrentText(
    scrollViewId: String,
    childViewId: String,
    lastText: String?,
    filterTexts: List<String> = listOf()
): AccessibilityNodeInfo? {
    this ?: return null
    //获取RecyclerView的节点
    val parent: AccessibilityNodeInfo = rootInActiveWindow.findNodeById(scrollViewId) ?: return null
    //通过RecyclerView获取他的子节点,同时过滤掉不需要的好友(因为通讯录列表里有微信团队和文件传输助手和自己)
    val find = parent.findNodesById(childViewId).filterNot { filterTexts.contains(it.text.default()) }
    return if (lastText.isNullOrBlank()) {
        //当前没传入上一个好友,说明是第一次检测,需要取第一个
        find.firstOrNull()
    } else {
        //找到上一个用户的索引
        val lastIndex = find.indexOfFirst { it.text.default() == lastText }
        if (lastIndex > -1) {
            //找到了就取他下一个值(这里如果上一个好友正好是的最后一个的话,后边没数据了,索引加一是取不到值的,所以也会返回空)
            find.getOrNull(lastIndex + 1)
        } else {
            //没有找到取第一个,这里解释一下为什么会没有找到要取第一个
            //是因为如果传入的是上一页的最后一个好友,滚动一屏后当前页可能会没有上一页的数据,所以就找不到了,自然就是取第一个了
            find.firstOrNull()
        }
    }
}
  • 上面代码的注释已经写得非常清晰了,如果你足够心细的话,就会对上述代码有一些疑问,刚才也说了会出现下一个值取不到的情况,而出现取不到值的时候说明当前页已经检测完了,所以想要继续往下去查找好友就需要滚动列表了,让RecyclerView滚动一下然后再去取值即可,既然是滚动肯定是会滚动到最底部的,该怎么判断是否滚动到底纳,继续完善代码如下
suspend fun AccessibilityService?.scrollToFindNextNodeByCurrentText(
    scrollViewId: String,
    childViewId: String,
    lastText: String?,
    filterTexts: List<String> = listOf()
): AccessibilityNodeInfo? {
    this ?: return null
    var find = getNextNodeByCurrentText(scrollViewId, childViewId, lastText, filterTexts)
    var isEnd = false
    while (find == null && !isEnd) {
        val parent: AccessibilityNodeInfo = rootInActiveWindow.findNodeById(scrollViewId) ?: return null
        parent.scrollForward()
        delay(200)
        val tryFind = getNextNodeByCurrentText(scrollViewId, childViewId, lastText, filterTexts)
        find = tryFind
        isEnd = tryFind == null
    }
    return find
}

完整流程

  • 自动获取好友的方法我们也写完了,现在就把整个流程串起来吧,中间涉及到很多的细节就不再这里展开详细介绍了,可以看源码自己体会体会。

    suspend fun quickCheck() {
        lastCheckUser = null
        App.instance().goToWx()
        //判断当前是否进入到微信
        val inWxApp = WXHomePage.waitEnterWxApp()
        if (!inWxApp) return
        //回到微信首页
        val isHome = WXHomePage.backToHome()
        if (!isHome) return
        //点击我的tab
        val clickMineTab = WXHomePage.clickMineTab()
        if (!clickMineTab) return
        //取出自己的微信昵称和微信号码,用来在后边自动查找好友时候过滤自己
        myWxInfo = WXMinePage.getMyWxInfo()
        //点击底部通讯录Tab
        val clickContactsTab = WXHomePage.clickContactsTab(true)
        if (!clickContactsTab) return
        val start = System.currentTimeMillis()
        //真正的检测流程
        quickCheckTask()
        val end = System.currentTimeMillis()
        FriendStatusHelper.taskCallBack?.onTaskEnd(end - start)
        if (WXHomePage.backToHome()) {
            wxAccessibilityService?.pressBackButton()
        }
        App.instance().toast("检测结束,请回到APP查看好友状态")
    }

    private suspend fun quickCheckTask() {
        //每次都是从微信首页开始的,回到微信首页
        val isHome = WXHomePage.backToHome()
        if (!isHome) return
        //点击底部通讯录Tab
        val clickContactsTab = WXHomePage.clickContactsTab()
        if (!clickContactsTab) return
        //判断是否从微信服务器拉到了用户信息
        val isShowUserList = WXContactPage.inPage()
        if (!isShowUserList) return
        //当前在通讯录页面,点击某一个用户
        val findUser = WXContactPage.scrollToClickNextNodeByCurrentText(lastCheckUser?.nickName)
        //当找不到好友的时候就说明检测完毕,终止流程
        if (findUser.isNullOrBlank()) return
        //判断是否进入到通讯录用户信息页
        val inPage = WXContactInfoPage.inPage()
        if (!inPage) return
        val userInfo = WXContactInfoPage.getUserInfo() ?: return
        //点击发消息按钮
        val clickSendMsg = WXContactInfoPage.clickSendMsg()
        if (!clickSendMsg) return
        //检查当前是否进入到聊天页(测试中发现有时候点击有效,但是没有进入到聊天页,所有在重试一次)
        val check = WXChattingPage.checkInPage()
        if (!check) {
            //再次点击发消息按钮
            val click = WXContactInfoPage.clickSendMsg()
            if (!click) return
        }
        //点击聊天页功能区按钮
        val clickMoreOption = WXChattingPage.clickMoreOption()
        if (!clickMoreOption) return
        //点击聊天页功能区转账按钮
        val clickTransferMoney = WXChattingPage.clickTransferMoney()
        if (!clickTransferMoney) return
        //第一次判断是否是正常好友
        val friendStatus = WXRemittancePage.checkIsNormalFriend()
        if (friendStatus != null && friendStatus.status == FriendStatus.NORMAL) {
            //提前判断出结果,此次任务结束
            userInfo.status = friendStatus.status
            lastCheckUser = userInfo
            FriendStatusHelper.addCheckResult(userInfo)
            //当次检测结束继续下一位检测
            quickCheckTask()
            return
        }
        //输入转账金额
        val inputMoney = WXRemittancePage.inputMoney()
        if (!inputMoney) return
        //点击转账按钮
        val clickTransfer = WXRemittancePage.clickTransferMoney()
        if (!clickTransfer) return
        //检查页面元素判断好友状态
        val status = WXRemittancePage.checkStatus()
        friendStatus!!.status = status.toFriendStatus()
        userInfo.status = friendStatus.status
        lastCheckUser = userInfo
        FriendStatusHelper.addCheckResult(userInfo)
        if (status == CheckStatus.BLACK || status == CheckStatus.DELETE || status == CheckStatus.ACCOUNT_EXCEPTION) {
            //被删除或者被拉黑和对方帐号异常的 会弹框,需要点击【我知道了】 关闭弹框
            WXRemittancePage.clickIKnow()
        }
        //当次检测结束继续下一位检测
        quickCheckTask()
    }
  • 执行一遍结果如下,检测结果也可以在APP中查看。
nickName: 测试账号1  wxCode: wxid_111111  status: 正常
nickName: 测试账号2  wxCode: wxid_222222  status: 被删除
nickName: 测试账号3  wxCode: wxid_333333  status: 被拉黑
nickName: 测试账号4  wxCode: wxid_444444  status: 账号异常

最后

  • 至此整个分析流程和一套完整的方案已经全部介绍完了,相信你能认真看到这里的话基本已经对假转账法的步骤完全掌握了,想体验的话可以下载源码编译一下或者体验已经编译好的安装包试试效果吧

感兴趣的可以下载demo体验一下,在阅读源码过程遇到任何问题欢迎提Issues,如果对你有帮助,希望动动你的发财小手点个赞呗

预告

  • 看了这篇文章你会发现这种方式检测很繁琐,也很耗时,好友过多的话检测起来是需要很多时间的,我的三百多好友检测一遍大概要四十来分钟,自己玩玩还可以,不能经常用来检测,毕竟太TM耽误时间了。接下来我们就来介绍一下【玩转Android自动化】微信好友状态检查(拉群方式),相信你会更感兴趣。