最近忙于工作,经过我‘咸鱼’的时间终于是有了些眉目,期间也走了许多弯路,现在做个笔记让也想学习的朋友们做个参考。
接收的图
发送的图
老规矩,来分析一波吧!
刚开始没发现,看这个布局分析得到微信的聊天列表和这个聊天界面在同一层级,导致我后面hook listview的时候老是跑到首页的的列表上。
看到了关键点-com.tencent.mm.ui.base.MMPullDownView 在我的上篇的提到的ChattingUI$a类里面,这个才是正主。
那么接下来我们在看看撤回消息的时候到底发生了什么。消息是存到本地数据了的,至于为什么这么说,请继续看
com.tencent.mm.ui.chatting.ChattingUI
话说有这么个类,前面已经提到过与之关联的类ChattingUI$a 如果不知道为什么我找到后面几个关联类的请看前面的一篇帖子既可以联系起来
然后这个类里面没有什么实质性的东西,于是我就点开他的父类看了看
com.tencent.mm.ui.MMFragmentActivity
发现了
com.tencent.wcdb.database.SQLiteDatabase
我不说其他人看到这个会怎么想,我平时自己写聊天应用的时候也会把聊天数据存储在数据库,于是乎开始hook起来
/*** 直接hook sql达到获取撤回消息id的目的** @param applicationContext* @param classLoader*/private void hookDB(final Context applicationContext, final ClassLoader classLoader) {
final Class sQLiteDatabase = XposedHelpers.findClass("com.tencent.wcdb.database.SQLiteDatabase", classLoader);
final Class cancellationSignal = XposedHelpers.findClass("com.tencent.wcdb.support.CancellationSignal", classLoader);
if (sQLiteDatabase == null) return;
XposedHelpers.findAndHookConstructor("com.tencent.wcdb.database.SQLiteProgram",
classLoader,
sQLiteDatabase,
String.class,
Object[].class,
cancellationSignal,
new XC_MethodReplacement() {
@Override
protected Object replaceHookedMethod(MethodHookParam param) throws Throwable {
Object[] objArr = (Object[]) param.args[2];
String originalSql = param.args[1].toString();
//打印所有调用SQLiteProgram的sql //LogUtils.e("hookDB", "sql -> " + param.args[1], "objArr:" + JSON.toJSONString(objArr)); if (objArr != null && originalSql.toUpperCase().startsWith("UPDATE MESSAGE")) {
for (Object obj : objArr) {
String sqlParam = obj.toString();//自己撤回10002 别人撤回10000 if (sqlParam.equals("10000")) {//别人撤回 Object[] newObjArr = new Object[2];
//param.args[1] = "UPDATE message SET type=? WHERE msgId=?"; param.args[1] = "select * from message where type=? and msgId=?";
param.args[2] = newObjArr;
newObjArr[0] = 1;
newObjArr[1] = objArr[objArr.length - 1];
//param.args[1] = "UPDATE message SET content=(select (select content from message where msgId = ?)||X'0D'||X'0A'||X'0D'||X'0A'||(\"wxInvoke卧槽,TA竟然要撤回上面的信息wxInvoke\")),msgId=?,type=? WHERE msgId=?"; LogUtils.e("hookDB", "originalSql->" + originalSql, "newSql->" + param.args[1], "sqlParam->" + JSON.toJSONString(newObjArr));
WxChatInvokeMsg msg = new WxChatInvokeMsg();
msg.setMsgId(newObjArr[1].toString());
WxChatInvokeMsgDB.insertData(applicationContext, msg);
}
}
}
return XposedBridge.invokeOriginalMethod(param.method, param.thisObject, param.args);
}
});
}
看到上面我hook的是SQLiteProgram 会有朋友问为啥?
因为我SQLiteDatabase在里面找insert和update的方法发现里面最后走进入了SQLiteProgram 哈哈! 偷个懒我就不具体截图找给大家看了,然后把撤回的消息id记录在本地,作为后面标记的依据。
接下来开始处理撤回效果了
/** * 微信聊天界面 * * @param applicationContext * @param classLoader */ private void hookWxChatUIMM(final Context applicationContext, final ClassLoader classLoader) {
XposedHelpers.findAndHookMethod("com.tencent.mm.ui.base.MMPullDownView",
classLoader,
"onLayout",
boolean.class,
int.class,
int.class,
int.class,
int.class,
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
super.beforeHookedMethod(param);
ViewGroup mMPullDownView = (ViewGroup) param.thisObject;// if (mMPullDownView.getVisibility() == View.GONE) return; for (int i = 0; i < mMPullDownView.getChildCount(); i++) {
View childAt = mMPullDownView.getChildAt(i);
if (childAt instanceof ListView) {
final ListView listView = (ListView) childAt;
final ListAdapter adapter = listView.getAdapter();
XposedHelpers.findAndHookMethod(adapter.getClass(),
"getView",
int.class,
View.class,
ViewGroup.class,
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
super.beforeHookedMethod(param);
int position = (int) param.args[0];
View view = (View) param.args[1];
JSONObject itemData = null;// LogUtils.i(position, view.toString()); if (position < adapter.getCount()) {
itemData = JSON.parseObject(JSON.toJSONString(adapter.getItem(position)), JSONObject.class);
int itemViewType = adapter.getItemViewType(position);// LogUtils.i(itemViewType); //经过以上代码可以知道 itemViewType == 1的时候打印的值是正常对话列表的值 if (itemData != null && (view != null && view.toString().contains("com.tencent.mm.ui.chatting.viewitems.p"))) {// if (itemData != null && itemViewType == 1 && (view != null && view.toString().contains("com.tencent.mm.ui.chatting.viewitems.p"))) { String field_msgId = itemData.getString("field_msgId");
WxChatInvokeMsg wxChatInvokeMsg = WxChatInvokeMsgDB.queryByMsgId(applicationContext, field_msgId);
ViewGroup itemView = (ViewGroup) view;
View itemViewChild = itemView.getChildAt(0);
Object tag = itemViewChild.getTag(R.id.wx_parent_has_invoke_msg);
TextView textView;
if (tag == null) {
textView = new TextView(applicationContext);
textView.setGravity(Gravity.CENTER);
textView.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
itemViewChild.setTag(R.id.wx_parent_has_invoke_msg, textView);
textView.setId(R.id.wx_invoke_msg);
itemView.addView(textView);
} else {
textView = (TextView) itemViewChild.getTag(R.id.wx_parent_has_invoke_msg);
}
textView.setText("");
textView.setVisibility(View.GONE);
if (wxChatInvokeMsg != null) {
textView.setPadding(10, 5, 10, 5);
textView.setBackgroundDrawable(ShapeUtil.getCornerDrawable());
textView.setTextColor(Color.parseColor("#666666"));
View msgView = itemView.getChildAt(3);
RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) textView.getLayoutParams();
lp.addRule(RelativeLayout.CENTER_HORIZONTAL);
lp.addRule(RelativeLayout.BELOW, msgView.getId());
lp.bottomMargin = 50;
textView.setText("这小子想撤回这个消息");
textView.setVisibility(View.VISIBLE);
LogUtils.i(position, view, itemViewType, itemData.toJSONString());
}
}
}// }
});
break;
}
}
}
});
}
记住hook 这个listview的时候会遇到我文章片头说的问题,导致每个item的view 对应错误,所以我加了过滤标识。
以上内容仅仅作为学习交流。代码很简单,乐趣在于找到这些代码位置的过程。
原文链接:[原创]Xposed第三课(微信篇) 防止好友消息撤回
本文由看雪论坛 KingZd 原创
转载请注明来自看雪社区