Master Android Hooking: From View OnClickListener to Xposed Framework
This article explains the concept of Android Hook, demonstrates how to intercept View.OnClickListener using Java reflection, shows step‑by‑step code for hooking startActivity via ActivityThread instrumentation, and introduces the Xposed framework’s mechanism for system‑wide method interception, providing practical examples and key implementation tips.
1. What is Hook
Hook ("hook" in English) refers to intercepting and monitoring an event before it reaches its final destination, allowing custom handling. In Android, the system maintains an event dispatch mechanism, and Hook lets code integrate into the target process.
API Hook can redirect API function execution. Because Android uses a sandbox with isolated processes, Hook provides a way to modify other apps' behavior.
Hook types include message Hook, API Hook, etc.
public void setOnClickListener(@Nullable OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
ListenerInfo getListenerInfo() {
if (mListenerInfo != null) {
return mListenerInfo;
}
mListenerInfo = new ListenerInfo();
return mListenerInfo;
}To hook View's OnClickListener, we replace the original listener after it is set.
private void hookOnClickListener(View view) {
try {
Method getListenerInfo = View.class.getDeclaredMethod("getListenerInfo");
getListenerInfo.setAccessible(true);
Object listenerInfo = getListenerInfo.invoke(view);
Class<?> listenerInfoClz = Class.forName("android.view.View$ListenerInfo");
Field mOnClickListener = listenerInfoClz.getDeclaredField("mOnClickListener");
mOnClickListener.setAccessible(true);
View.OnClickListener originOnClickListener = (View.OnClickListener) mOnClickListener.get(listenerInfo);
View.OnClickListener hookedOnClickListener = new HookedOnClickListener(originOnClickListener);
mOnClickListener.set(listenerInfo, hookedOnClickListener);
} catch (Exception e) {
log.warn("hook clickListener failed!", e);
}
}
class HookedOnClickListener implements View.OnClickListener {
private View.OnClickListener origin;
HookedOnClickListener(View.OnClickListener origin) { this.origin = origin; }
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "hook click", Toast.LENGTH_SHORT).show();
log.info("Before click, do what you want to to.");
if (origin != null) { origin.onClick(v); }
log.info("After click, do what you want to to.");
}
}After hooking, clicking the button prints logs: Before click → onClick → After click.
Button btnSend = (Button) findViewById(R.id.btn_send);
btnSend.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
log.info("onClick");
}
});
hookOnClickListener(btnSend);Hooking startActivity follows a similar principle. We obtain the current ActivityThread via reflection, replace its mInstrumentation field with a custom proxy, and intercept the call.
public static void attactContext() throws Exception {
Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
Field currentActivityThreadField = activityThreadClass.getDeclaredField("sCurrentActivityThread");
currentActivityThreadField.setAccessible(true);
Object currentActivityThread = currentActivityThreadField.get(null);
Field mInstrumentationField = activityThreadClass.getField("mInstrumentation");
mInstrumentationField.setAccessible(true);
Instrumentation mInstrumentation = (Instrumentation) mInstrumentationField.get(currentActivityThread);
Instrumentation evilInstrumentation = new EvilInstrumentation(mInstrumentation);
mInstrumentationField.set(currentActivityThread, evilInstrumentation);
}
class EvilInstrumentation extends Instrumentation {
private static final String TAG = "EvilInstrumentation";
Instrumentation mBase;
public EvilInstrumentation(Instrumentation base) { mBase = base; }
public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
Log.d(TAG, "
执行了startActivity, 参数如下:
" + "who = [" + who + "],
" + "contextThread = [" + contextThread + "],
" + "token = [" + token + "],
" + "target = [" + target + "],
" + "intent = [" + intent + "],
" + "requestCode = [" + requestCode + "],
" + "options = [" + options + "]");
try {
Method execStartActivity = Instrumentation.class.getDeclaredMethod("execStartActivity",
Context.class, IBinder.class, IBinder.class, Activity.class, Intent.class, int.class, Bundle.class);
execStartActivity.setAccessible(true);
return (ActivityResult) execStartActivity.invoke(mBase, who, contextThread, token, target, intent, requestCode, options);
} catch (Exception e) {
throw new RuntimeException("do not support!!! pls adapt it");
}
}
}Summary of Hook process:
Find Hook points, preferably static variables or singletons, and target public objects/methods.
Choose an appropriate proxy; use dynamic proxy for interfaces.
Replace the original object with the proxy.
Handle API version differences for compatibility.
2. Xposed
Xposed replaces /system/bin/app_process to load XposedBridge.jar during Zygote startup, allowing it to hook system functions before and after their original execution.
Installation involves a root‑required flash package that replaces Zygote‑related files and places the bridge JAR and native libraries in the system directories.
META-INF/ (configuration scripts)
system/bin/ (replace Zygote files)
system/framework/XposedBridge.jar (bridge JAR)
system/lib & system/lib64 (native .so files)
xposed.prop (version info)During boot, init starts Zygote, which is now the Xposed‑modified version. Xposed_zygote loads native libraries, then runs XposedBridge.main to register native hooks for target Android functions.
Phone boot triggers init → Zygote (replaced by Xposed version).
Xposed_zygote initializes native libraries and calls XposedBridge.main.
Hooks are registered as native functions in the modified ART/Dalvik VM.
Control returns to Zygote to continue normal startup.
Overall, both Android Hook techniques and the Xposed framework provide powerful ways to intercept and modify system behavior, useful for debugging, instrumentation, or creating custom functionality.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Architect's Guide
Dedicated to sharing programmer-architect skills—Java backend, system, microservice, and distributed architectures—to help you become a senior architect.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
