原文鏈接:https://github.com/xbdcc/CXposed/blob/master/beat.md
Xposed系列之前文章如下:
需求
需求來自于看到的熱搜,微信拍一拍好多人說不支持關(guān)閉,容易誤觸。所以想著Hook下可以屏蔽自己的拍一拍
| 微博熱搜 | 微博評論 |
|---|---|
![]() image
|
![]() image
|
結(jié)果
最后代碼實現(xiàn)的效果如下,屏蔽了雙擊"拍一拍":

分析UI
可以攔截的地方有很多處,這里就從最直觀容易分析Hook的地方入手,分析方法上篇講了很多了,這里就不再詳細介紹了。
找到拍一拍雙擊點,可以看到id為aku的控件就是顯示的頭像,而它的點擊和長按屬性都為true,這個應該也是雙擊的控件

然后通過通過adb shell dumpsys activity top > activity_top.txt,找到id為aku的那一行信息如下:
com.tencent.mm.ui.chatting.view.AvatarImageView{8c74400 V.ED..CL. ........ 0,0-122,122 #7f090708 app:id/aku}
可以看到該ImageView為自定義控件AvatarImageView,所以我們就可以去到這個自定義控件里面去看它的代碼了
分析代碼
反編譯APK查看AvatarImageView類的完整代碼如下:
public class AvatarImageView extends AppCompatImageView implements m {
private boolean Ihh;
private final String TAG;
private int pageType;
private i yXY;
private String zkN;
public AvatarImageView(Context context, AttributeSet attributeSet) {
this(context, attributeSet, 0);
}
public AvatarImageView(Context context, AttributeSet attributeSet, int i) {
super(context, attributeSet, i);
AppMethodBeat.i(36689);
this.TAG = "MicroMsg.AvatarImageView";
this.pageType = -1;
this.yXY = null;
this.zkN = "";
this.Ihh = true;
this.yXY = ((e) g.ad(e.class)).getStoryUIFactory().gq(context);
this.yXY.aZ(this);
setLayerType(1, (Paint) null);
AppMethodBeat.o(36689);
}
/* access modifiers changed from: protected */
public void onDraw(Canvas canvas) {
AppMethodBeat.i(36690);
super.onDraw(canvas);
if (this.Ihh) {
this.yXY.a(canvas, true, 0);
AppMethodBeat.o(36690);
return;
}
this.yXY.a(canvas, false, 0);
AppMethodBeat.o(36690);
}
/* access modifiers changed from: protected */
public void onMeasure(int i, int i2) {
AppMethodBeat.i(36691);
super.onMeasure(i, i2);
AppMethodBeat.o(36691);
}
public void setOnClickListener(View.OnClickListener onClickListener) {
AppMethodBeat.i(36692);
super.setOnClickListener(this.yXY.dUH());
this.yXY.setOnClickListener(onClickListener);
AppMethodBeat.o(36692);
}
public void setOnDoubleClickListener(i.a aVar) {
AppMethodBeat.i(36693);
this.yXY.setOnDoubleClickListener(aVar);
AppMethodBeat.o(36693);
}
public void setShowStoryHint(boolean z) {
AppMethodBeat.i(36694);
this.yXY.setShowStoryHint(z);
AppMethodBeat.o(36694);
}
public final void eM(String str, int i) {
AppMethodBeat.i(36695);
this.yXY.eM(str, i);
this.zkN = str;
AppMethodBeat.o(36695);
}
public void setChattingBG(boolean z) {
this.Ihh = z;
}
public final void bO(String str, boolean z) {
AppMethodBeat.i(36696);
if (TextUtils.isEmpty(str) || getContext() == null) {
AppMethodBeat.o(36696);
return;
}
if (str.equals(this.zkN)) {
setShowStoryHint(!z);
}
AppMethodBeat.o(36696);
}
/* access modifiers changed from: protected */
public void onDetachedFromWindow() {
AppMethodBeat.i(36697);
super.onDetachedFromWindow();
if (this.pageType != -1) {
a.b(this.pageType, this.zkN, this);
}
AppMethodBeat.o(36697);
}
}
可以看到其中有個setOnDoubleClickListener方法,很明顯這個就是我們雙擊事件的監(jiān)聽,所以我們可以Hook這個方法做攔截,
讓他后面拍一拍的操作都不執(zhí)行就可以了,當然你還可以繼續(xù)往里面分析拍一拍執(zhí)行網(wǎng)絡(luò)請求或數(shù)據(jù)庫操作顯示UI等的地方做攔截。
我們看到setOnDoubleClickListener方法里面?zhèn)髁藚?shù)i.a aVar,需要先知道下這個參數(shù)是什么,繼續(xù)看i的代碼如下:
public interface i {
public interface a {
boolean fl(View view);
}
void a(Canvas canvas, boolean z, int i);
void aZ(View view);
View.OnClickListener dUH();
void eM(String str, int i);
void setOnClickListener(View.OnClickListener onClickListener);
void setOnDoubleClickListener(a aVar);
void setShowStoryHint(boolean z);
void setWeakContext(Context context);
}
查看代碼得知i為一個接口,并且內(nèi)部有一個接口a,因為是內(nèi)部類,所以要加$,找到后就可以開始寫Hook代碼了
編寫代碼
最終實現(xiàn)代碼如下:
private fun hookBeat() {
val hookClass = classLoader.loadClass("com.tencent.mm.plugin.story.api.i\$a")
XposedHelpers.findAndHookMethod("com.tencent.mm.ui.chatting.view.AvatarImageView",
classLoader,
"setOnDoubleClickListener",
hookClass,
object : XC_MethodReplacement() {
override fun replaceHookedMethod(param: MethodHookParam?): Any {
xlog("replace double click")
return ""
}
})
}
到這里其實已經(jīng)結(jié)束了,但是代碼還可以做下優(yōu)化,我們查看findAndHookMethod方法源碼及注釋,可以看到注釋里有例子parameterTypesAndCallback參數(shù)也可以直接傳如"com.example.MyClass",
所以內(nèi)部提供了這種方法我們就不用外部去loadClass,直接把類名傳進來就是了。仔細看源碼注釋也會發(fā)現(xiàn)這個例子有個小bug,Hook的時候少傳了方法名doSomething。
/**
* Look up a method and hook it. The last argument must be the callback for the hook.
*
* <p>This combines calls to {@link #findMethodExact(Class, String, Object...)} and
* {@link XposedBridge#hookMethod}.
*
* <p class="warning">The method must be declared or overridden in the given class, inherited
* methods are not considered! That's because each method implementation exists only once in
* the memory, and when classes inherit it, they just get another reference to the implementation.
* Hooking a method therefore applies to all classes inheriting the same implementation. You
* have to expect that the hook applies to subclasses (unless they override the method), but you
* shouldn't have to worry about hooks applying to superclasses, hence this "limitation".
* There could be undesired or even dangerous hooks otherwise, e.g. if you hook
* {@code SomeClass.equals()} and that class doesn't override the {@code equals()} on some ROMs,
* making you hook {@code Object.equals()} instead.
*
* <p>There are two ways to specify the parameter types. If you already have a reference to the
* {@link Class}, use that. For Android framework classes, you can often use something like
* {@code String.class}. If you don't have the class reference, you can simply use the
* full class name as a string, e.g. {@code java.lang.String} or {@code com.example.MyClass}.
* It will be passed to {@link #findClass} with the same class loader that is used for the target
* method, see its documentation for the allowed notations.
*
* <p>Primitive types, such as {@code int}, can be specified using {@code int.class} (recommended)
* or {@code Integer.TYPE}. Note that {@code Integer.class} doesn't refer to {@code int} but to
* {@code Integer}, which is a normal class (boxed primitive). Therefore it must not be used when
* the method expects an {@code int} parameter - it has to be used for {@code Integer} parameters
* though, so check the method signature in detail.
*
* <p>As last argument to this method (after the list of target method parameters), you need
* to specify the callback that should be executed when the method is invoked. It's usually
* an anonymous subclass of {@link XC_MethodHook} or {@link XC_MethodReplacement}.
*
* <p><b>Example</b>
* <pre class="prettyprint">
* // In order to hook this method ...
* package com.example;
* public class SomeClass {
* public int doSomething(String s, int i, MyClass m) {
* ...
* }
* }
*
* // ... you can use this call:
* findAndHookMethod("com.example.SomeClass", lpparam.classLoader, String.class, int.class, "com.example.MyClass", new XC_MethodHook() {
* @Override
* protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
* String oldText = (String) param.args[0];
* Log.d("MyModule", oldText);
*
* param.args[0] = "test";
* param.args[1] = 42; // auto-boxing is working here
* setBooleanField(param.args[2], "great", true);
*
* // This would not work (as MyClass can't be resolved at compile time):
* // MyClass myClass = (MyClass) param.args[2];
* // myClass.great = true;
* }
* });
* </pre>
*
* @param className The name of the class which implements the method.
* @param classLoader The class loader for resolving the target and parameter classes.
* @param methodName The target method name.
* @param parameterTypesAndCallback The parameter types of the target method, plus the callback.
* @throws NoSuchMethodError In case the method was not found.
* @throws ClassNotFoundError In case the target class or one of the parameter types couldn't be resolved.
* @return An object which can be used to remove the callback again.
*/
public static XC_MethodHook.Unhook findAndHookMethod(String className, ClassLoader classLoader, String methodName, Object... parameterTypesAndCallback) {
return findAndHookMethod(findClass(className, classLoader), methodName, parameterTypesAndCallback);
}
優(yōu)化后代碼如下:
private fun hookBeat() {
XposedHelpers.findAndHookMethod("com.tencent.mm.ui.chatting.view.AvatarImageView",
classLoader,
"setOnDoubleClickListener",
"com.tencent.mm.plugin.story.api.i\$a",
object : XC_MethodReplacement() {
override fun replaceHookedMethod(param: MethodHookParam?): Any {
xlog("replace double click")
return ""
}
})
}
接下來分析下為什么也可以直接傳String類型的類的名稱,我們跟進findAndHookMethod找用到parameterTypesAndCallback參數(shù)的方法,
會發(fā)現(xiàn)它最終又會調(diào)用findMethodExact如下:
public static XC_MethodHook.Unhook findAndHookMethod(Class<?> clazz, String methodName, Object... parameterTypesAndCallback) {
if (parameterTypesAndCallback.length == 0 || !(parameterTypesAndCallback[parameterTypesAndCallback.length-1] instanceof XC_MethodHook))
throw new IllegalArgumentException("no callback defined");
XC_MethodHook callback = (XC_MethodHook) parameterTypesAndCallback[parameterTypesAndCallback.length-1];
Method m = findMethodExact(clazz, methodName, getParameterClasses(clazz.getClassLoader(), parameterTypesAndCallback));
return XposedBridge.hookMethod(m, callback);
}
繼續(xù)看getParameterClasses方法如下,可以看到首先判斷了如果type如果為空拋異常,如果為XC_MethodHook則不往下執(zhí)行,
如果為Class則強轉(zhuǎn)為Class,如果為String則調(diào)用findClass((String) type, classLoader)找到Class,方法最后返回Class,
所以parameterTypesAndCallback也可以直接傳包名+類名:
private static Class<?>[] getParameterClasses(ClassLoader classLoader, Object[] parameterTypesAndCallback) {
Class<?>[] parameterClasses = null;
for (int i = parameterTypesAndCallback.length - 1; i >= 0; i--) {
Object type = parameterTypesAndCallback[i];
if (type == null)
throw new ClassNotFoundError("parameter type must not be null", null);
// ignore trailing callback
if (type instanceof XC_MethodHook)
continue;
if (parameterClasses == null)
parameterClasses = new Class<?>[i+1];
if (type instanceof Class)
parameterClasses[i] = (Class<?>) type;
else if (type instanceof String)
parameterClasses[i] = findClass((String) type, classLoader);
else
throw new ClassNotFoundError("parameter type must either be specified as Class or String", null);
}
// if there are no arguments for the method
if (parameterClasses == null)
parameterClasses = new Class<?>[0];
return parameterClasses;
}

