从APP跳转到微信指定联系人聊天页面功能的实现与采坑之旅
起因:
最近做的APP中有一个新功能:已知用户微信号,可点击直接跳转到当前用户微信聊天窗口页面。
当时第一想法是使用无障碍来做,并且觉得应该不难,只是逻辑有点复杂。没想到最终踩了好多坑,特地把踩过的坑记录下来。
实现逻辑:
在APP中点击按钮→跳转到微信界面→模拟点击微信搜索按钮→在微信搜索页面输入获取的微信号→模拟点击查询到的用户进入用户聊天界面。
效果图:

实现过程:
跳转微信按钮点击事件:
jumpButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(Intent.ACTION_MAIN);
ComponentName cmp = new ComponentName("com.tencent.mm", "com.tencent.mm.ui.LauncherUI");
intent.addCategory(Intent.CATEGORY_LAUNCHER);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setComponent(cmp);
startActivity(intent);
}
});
无障碍监听主要方法:
一些必要的参数:
/**
* 微信主页面的“搜索”按钮id
*/
private final String SEARCH_ID = "com.tencent.mm:id/ij"; /**
* 微信主页面bottom的“微信”按钮id
*/
private final String WECHAT_ID = "com.tencent.mm:id/d3t"; /**
* 微信搜索页面的输入框id
*/
private final String EDIT_TEXT_ID = "com.tencent.mm:id/ka"; /**
* 微信搜索页面活动id
*/
private String SEARCH_ACTIVITY_NAME = "com.tencent.mm.plugin.fts.ui.FTSMainUI"; private String LIST_VIEW_NAME = "android.widget.ListView";
微信组件的id之前有博客说过如何获取,所以在此就不重复说明了。
监听主要方法:
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
List<AccessibilityNodeInfo> searchNode = event.getSource().findAccessibilityNodeInfosByViewId(SEARCH_ID);
List<AccessibilityNodeInfo> wechatNode = event.getSource().findAccessibilityNodeInfosByViewId(WECHAT_ID); if (searchNode.size() > 1) {
// 点击“搜索”按钮
if (searchNode.get(0).getParent().isClickable()) {
searchNode.get(0).getParent().performAction(AccessibilityNodeInfo.ACTION_CLICK);
return;
}
} else if (searchNode.size() == 1) {
// 如果在“我”页面,则进入“微信”页面
for (AccessibilityNodeInfo info : wechatNode) {
if (info.getText().toString().equals("微信") && !info.isChecked()) { if (info.getParent().isClickable()) {
info.getParent().performAction(AccessibilityNodeInfo.ACTION_CLICK);
return;
}
break;
}
}
} // 当前页面是搜索页面
if (SEARCH_ACTIVITY_NAME.equals(event.getClassName().toString())) {
List<AccessibilityNodeInfo> editTextNode = event.getSource().findAccessibilityNodeInfosByViewId(EDIT_TEXT_ID); if (editTextNode.size() > 0) {
// 输入框内输入查询的微信号
Bundle arguments = new Bundle();
arguments.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, Constant.wechatId);
editTextNode.get(0).performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, arguments);
}
} else if (LIST_VIEW_NAME.equals(event.getClassName().toString())) {
// 如果监听到了ListView的内容改变,则找到查询到的人,并点击进入
List<AccessibilityNodeInfo> textNodeList = event.getSource().findAccessibilityNodeInfosByText("微信号: " + Constant.wechatId);
if (textNodeList.size() > 0) {
textNodeList.get(0).getParent().performAction(AccessibilityNodeInfo.ACTION_CLICK);
}
} }
这是最原始的版本,具体逻辑已在注释中说明。
遇到的坑:
1. 搜索内容无法赋值给搜索框
最开始以为是赋值的方法有问题,但是在调试状态下能够赋值成功。因此猜测是因为UI加载太慢的缘故。
在搜索框还没完全加载完全的时候就进行了赋值,因此赋值不成功。
解决办法:
在赋值之前停顿300ms,在30行赋值前先停顿300ms。
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
2. 如何停止监听?
由于监听是一直会进行的,因此只要进入了微信页面就会执行无障碍方法。这是不合理的。理论上应该在点击按钮进入微信才开始监听,而查找到好友之后就停止监听。
解决办法:
可以设置全局的变量用来控制监听。需要在点击按钮设置变量值为监听,而查找到微信好友之后设置为不监听。
全局变量:
public class Constant {
/**
* 判断是否需要监听
*/
public static int flag = 0;
/**
* 微信号
*/
public static String wechatId;
}
按钮点击修改flag值:
jumpButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(Intent.ACTION_MAIN);
ComponentName cmp = new ComponentName("com.tencent.mm", "com.tencent.mm.ui.LauncherUI");
intent.addCategory(Intent.CATEGORY_LAUNCHER);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setComponent(cmp);
startActivity(intent);
Constant.flag = 1;
Constant.wechatId = editText.getText().toString();
}
});
根据flag判断是否需要监听:
在无障碍服务的监听方法中开始位置判断,
// 只有从app进入微信才进行监听
if (Constant.flag == 0) {
return;
}
查询到结果后修改flag值:
// 如果监听到了ListView的内容改变,则找到查询到的人,并点击进入
List<AccessibilityNodeInfo> textNodeList = event.getSource().findAccessibilityNodeInfosByText("微信号: " + Constant.wechatId);
if (textNodeList.size() > 0) {
textNodeList.get(0).getParent().performAction(AccessibilityNodeInfo.ACTION_CLICK); // 模拟点击之后将暂存值置空,类似于取消监听
Constant.flag = 0;
Constant.wechatId = null;
}
3. 没查询到结果如何停止监听?
想必大家都发现了,上面的处理方法还没有考虑到未查询到好友的情况。那么,未查询到好友如何停止监听呢?
最开始想的是找到未查询页面,只要知道了什么情况是未查询的,那就可以停止监听了。
但是未查询到好友的页面查找比较麻烦,因此想了一个取巧的办法。
解决办法:
写一个线程,两秒后执行,因为用户一般在未查询到结果页面会停留至少两秒,两秒误操作就停止监听。
线程实现(线程得是类持有的,而不应该是方法持有的):
Handler handler = new Handler();
Runnable runnable = new Runnable() {
@Override
public void run() {
Constant.flag = 0;
Constant.wechatId = null;
}
};
监听方法内进行线程的开启操作:
// 两秒后如果还没有任何的事件,则停止监听
handler.removeCallbacks(runnable);
handler.postDelayed(runnable, 2000);
由于无障碍的监听方法会反复执行,因此为了保证其正确性,需要保证在最后一次事件才开始计时。
4. 如果在微信其他页面怎么办?
最开始被这个问题难住了。后来产品给了我一个思路,其实很简单,如果判断当前页面并不是微信主页面的话,就执行全局返回按钮事件就行。
解决办法:
如果是页面改变事件,并且当前页面不是主页面也不是搜索页面(搜索页面就可以直接搜索了)的话,就执行全局返回键。
if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED && !LAUNCHER_ACTIVITY_NAME.equals(event.getClassName().toString()) && !SEARCH_ACTIVITY_NAME.equals(event.getClassName().toString())) {
// 如果当前页面不是微信主页面也不是微信搜索页面,就模拟点击返回键
performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK);
return;
}
5. 页面改变UI加载太慢
在解决上述问题时,又遇到了之前遇到的问题,UI加载太慢的问题,因此需要在每次页面改变事件中都得加上300ms的延迟时间。
解决办法:
// 页面改变时需要延迟一段时间进行布局加载
if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
6. 聊天界面和主页面是同一个活动
解决了上述问题之后,又遇到了一个新的问题,经常性的返回到聊天页面就不返回了。
经过调试,发现聊天页面的活动和微信主页面的活动是同一个。
解决办法:
对聊天界面单独做处理,根据聊天界面左上角UI存在不存在来确定是否为聊天界面。

if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED && !LAUNCHER_ACTIVITY_NAME.equals(event.getClassName().toString()) && !SEARCH_ACTIVITY_NAME.equals(event.getClassName().toString())) {
// 如果当前页面不是微信主页面也不是微信搜索页面,就模拟点击返回键
performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK);
return;
} else if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED && LAUNCHER_ACTIVITY_NAME.equals(event.getClassName().toString())) {
List<AccessibilityNodeInfo> list = event.getSource().findAccessibilityNodeInfosByViewId(USERNAME_ID);
if (list.size() > 0) {
// 如果是微信主页面,但是是微信聊天页面,则模拟点击返回键
performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK);
return;
}
}
其中USRENAME_ID为左上角备注部分的UIid。
7. 搜索不到结果时,发现他在搜索结果页面乱跳
经排查,发现搜索结果页面中的搜索布局提示布局id和首页面的搜索按钮id一致,因此就执行了点击搜索按钮的方法。

解决办法:
对于搜索按钮页面(主页面)也要进行单独判断,由于主页面一定有ViewPage布局,因此只要找到ViewPage那就证明是在主页面。
List<AccessibilityNodeInfo> searchNode = event.getSource().findAccessibilityNodeInfosByViewId(SEARCH_ID);
List<AccessibilityNodeInfo> wechatNode = event.getSource().findAccessibilityNodeInfosByViewId(WECHAT_ID);
List<AccessibilityNodeInfo> viewPageNode = event.getSource().findAccessibilityNodeInfosByViewId(VIEW_PAGE_ID); Log.e(TAG, "searchNode:" + searchNode.size());
Log.e(TAG, "viewPageNode:" + viewPageNode.size()); // 由于搜索控件在多个页面都有,所以还得判断是否在主页面
if (searchNode.size() > 1 && viewPageNode.size() > 0) {
// 点击“搜索”按钮
if (searchNode.get(0).getParent().isClickable()) {
searchNode.get(0).getParent().performAction(AccessibilityNodeInfo.ACTION_CLICK);
return;
}
} else if (searchNode.size() == 1) {
// 如果在“我”页面,则进入“微信”页面
for (AccessibilityNodeInfo info : wechatNode) {
if (info.getText().toString().equals("微信") && !info.isChecked()) { if (info.getParent().isClickable()) {
info.getParent().performAction(AccessibilityNodeInfo.ACTION_CLICK);
return;
}
break;
}
}
}
8. 在主页面偶尔找不到搜索按钮
这个问题很奇怪,排查了半天也没发现为什么。这个问题主要出现在进入微信比较深的地方一步步返回之后。我发现找不到搜索按钮主要是通过id找直接就没找到。
于是就换了一种查找控件的方式。
解决办法:
将event.getSource()换成getRootInActiveWindow()。
// 用getRootInActiveWindow是为了防止找不到搜索按钮的问题
List<AccessibilityNodeInfo> searchNode = getRootInActiveWindow().findAccessibilityNodeInfosByViewId(SEARCH_ID);
List<AccessibilityNodeInfo> wechatNode = getRootInActiveWindow().findAccessibilityNodeInfosByViewId(WECHAT_ID);
List<AccessibilityNodeInfo> viewPageNode = getRootInActiveWindow().findAccessibilityNodeInfosByViewId(VIEW_PAGE_ID);
9. 如果通过同一微信号进行查找,会发现在搜索结果页面就停止了
经排查,发现在搜索结果页面直接更改输入框的查询值,如果值一样的话,不会触发任何的事件。出现该问题的原因就在这。
解决办法:
先清空输入框,再输入需要查询的微信号。
if (editTextNode.size() > 0) {
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 输入框内清空
Bundle clear = new Bundle();
clear.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, "");
editTextNode.get(0).performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, clear);
// 输入框内输入查询的微信号
Bundle arguments = new Bundle();
arguments.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, Constant.wechatId);
editTextNode.get(0).performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, arguments);
}
反思:
- 任何一门技术都是说说容易,做做难。因为在实现过程中总会出现各种各样的问题;
- 通过无障碍的方式来实现该功能效率低,并且不稳定,不知是否有更好的方法;
- Android系统真的特别不安全!
GitHub地址:JumpToWeChat
大家如果有什么疑问或者建议可以通过评论或者邮件的方式联系我,欢迎大家的评论~
从APP跳转到微信指定联系人聊天页面功能的实现与采坑之旅的更多相关文章
- 微信平台接入Web页面功能接口(C#)
微信平台接入web页面功能接口 今年因工作需要,通过微信平台接入公司的Wap页面,回忆下,记录内容,方面以后使用. 1.成为开发者后,你才可以使用公众平台的开发功能.需要填写URL和ToKen,接口配 ...
- 微信支付(APP)集成时碰到的问题(.net提示“无权限”、iOS跳转到微信支付页面中间只有一个“确定”按钮)
直入主题之前,请容我吐槽一下微*的官方东西:ASDFQ%#$%$#$%^FG@#$%DSFQ#$%.......:吐槽玩了!大家心照就好. 要完成手机APP跳转到微信的APP进行微信支付,需要进行如下 ...
- iOS版微信开发小结(微信支付,APP跳转微信公众号)
最近公司心血来潮,一心要搞微信.废话不多说,直接上干货. 开发前准备: 1.在微信开发者平台获取开发者认证:(一年300元人民币) PS:具体流程按照微信流程指示操作即可,在这就不废话了. 2.下载微 ...
- 【第十二篇】微信支付(APP)集成时碰到的问题(.net提示“无权限”、iOS跳转到微信支付页面中间只有一个“确定”按钮)(转)
直入主题之前,请容我吐槽一下微*的官方东西:ASDFQ%#$%$#$%^FG@#$%DSFQ#$%.......:吐槽玩了!大家心照就好. 要完成手机APP跳转到微信的APP进行微信支付,需要进行如下 ...
- h5跳转微信公众号关注页面
最近在做h5项目,有个需求是如果用户没有关注公众号,需要引导用户跳转至微信公众号关注页面 制作一个链接,点击该链接跳转到公众号关注页面. 1.从公众平台登进入公众号 2.点击开发>>开发者 ...
- 从手机浏览器或者 APP 中跳转到微信并跳转到指定页原理及行业内幕详解
相信很多朋友遇到过有些网站,可以直接通过一个连接就能让你的手机打开微信且跳转到某个指定的页面,许多程序员很好奇到底是怎么实现的,到处求这种方法的源码,在文本中我会介绍及剖析这种跳转实现的原理. 微信是 ...
- 微信跳转,手机WAP浏览器一键超级跳转微信指定页面
微信跳转,手机WAP浏览器一键超级跳转微信指定页面 这篇文章主要介绍了如何在手机浏览器wap网页中点击链接跳转到微信界面,需要的朋友可以参考下 先说第一种,最简单的唤起微信协议,weixin://主流 ...
- [Swift通天遁地]九、拔剑吧-(1)实现在程序中跳转到微信、App Store、地图
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★➤微信公众号:山青咏芝(shanqingyongzhi)➤博客园地址:山青咏芝(https://www.cnblogs. ...
- 微信跳转,网页跳转微信app跳转公众号关注页面[转载]
[微信跳转链接]之跳转公众号关注页面如何做到在微信内部在这里插入代码片浏览器打开的webview页面中,跳转到微信公众号的关注页面呢!我们可以通过访问微信提供的URL协议(weixin://)来实现这 ...
随机推荐
- 约定Service构建方式
对于DevOps中,将开发好的软件交付给运维人员去部署与维护,过程中参杂着诸多不可控制的变量,如环境问题.版本问题等等,而Docker容器极大程度上解决了这些问题,同时对于服务的持续交付,也变得方便和 ...
- javaScript设计模式之----工厂模式
什么是工厂模式?我们通过一个例子了解一下: 比如我们想要弹出几个字符串 function funA(){ alert('a'); } function funB(){ alert('b'); } fu ...
- Python:游戏:300行代码实现俄罗斯方块
本文代码基于 python3.6 和 pygame1.9.4. 俄罗斯方块是儿时最经典的游戏之一,刚开始接触 pygame 的时候就想写一个俄罗斯方块.但是想到旋转,停靠,消除等操作,感觉好像很难啊, ...
- 24分钟让AI跑起飞车类游戏
本文由云+社区发表 作者:WeTest小编 WeTest 导读 本文主要介绍如何让AI在24分钟内学会玩飞车类游戏.我们使用Distributed PPO训练AI,在短时间内可以取得不错的训练效果. ...
- asp.net core 系列之webapi集成EFCore的简单操作教程
因为官网asp.net core webapi教程部分,给出的是使用内存中的数据即 UseInMemoryDatabase 的方式, 这里记录一下,使用SQL Server数据库的方式即 UseSql ...
- java监听器简述
监听器的概念 所谓监听器就是对内置对象的状态或者属性变化进行监听并且做出反应的特殊servlet,并且也需要在web.xml文件中进行相关配置. 内置对象的状态变化:初始化和销毁,也就是说当内置对象初 ...
- Chrome内核浏览器打开网页报 错误代码: ERR_TIMED_OUT
升级win10之后如果出现chrome内核的浏览器网页总是打不开 打开很慢 而ie和edge是可以正常访问的 用这个方法可以 我弄了几天终于 搞好了我直接转载过来了近期,工程师收到大量反馈360浏 ...
- 从壹开始微服务 [ DDD ] 之四 ║让你明白DDD的小故事 & EFCore初探
缘起 哈喽大家好哟,今天又到了老张的周二四放送时间了,当然中间还有不定期的更新(因为个人看papi酱看多了),这个主要是针对小伙伴提出的问题和优秀解决方案而写的,经过上周两篇DDD领域驱动设计的试水, ...
- IntelliJ IDEA激活,永久有效
2017.3.4版本 正版的idea实在太贵了,有能力请支持正版. 下载jar包,放置在idea的bin目录下,传送门 https://files.cnblogs.com/files/dslx/Jet ...
- java~gradle构建公用包并上传到仓库
java~gradle构建公用包并上传到仓库 我们一般会把公用的代码放在一个包里,然后其它 项目可以直接使用,就像你使用第三方包一样! 仓库 存储包的地方叫做仓库,一般可以分为本地仓库和远程仓库,本地 ...