Mobile Development 13 min read

Master Android Hooking: From API Hook to Xposed Framework

This article explains the concept of Hook in Android, demonstrates how to intercept View OnClickListener and startActivity using Java reflection and instrumentation, and introduces the Xposed framework for system‑wide method hooking, providing step‑by‑step code examples and practical deployment tips.

Architect's Guide
Architect's Guide
Architect's Guide
Master Android Hooking: From API Hook to Xposed Framework

1. What is Hook

Hook ("hook") refers to intercepting an event before it reaches its final destination, allowing custom code to run during the interception.

In Android, Hook can embed its code into the target process, becoming part of the process. API Hook redirects the execution result of system APIs. Because Android uses a sandbox where each app runs in an isolated process, Hook provides a way to modify the behavior of other apps.

Use Java reflection to implement API Hook, e.g., modify the ClassLoader to redirect Java function calls.

Below we demonstrate Hooking a View's OnClickListener.

First, examine View's

setOnClickListener

method; the listener is stored in an internal class

ListenerInfo

as

mOnClickListener

.

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;
}

Our goal is to replace the original

OnClickListener

with a custom one.

private void hookOnClickListener(View view) {
    try {
        // Obtain ListenerInfo object
        Method getListenerInfo = View.class.getDeclaredMethod("getListenerInfo");
        getListenerInfo.setAccessible(true);
        Object listenerInfo = getListenerInfo.invoke(view);
        // Get original OnClickListener
        Class<?> listenerInfoClz = Class.forName("android.view.View$ListenerInfo");
        Field mOnClickListener = listenerInfoClz.getDeclaredField("mOnClickListener");
        mOnClickListener.setAccessible(true);
        View.OnClickListener originOnClickListener = (View.OnClickListener) mOnClickListener.get(listenerInfo);
        // Replace with custom listener
        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, code runs before and after the original click handling.

Button btnSend = (Button) findViewById(R.id.btn_send);
btnSend.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        log.info("onClick");
    }
});
hookOnClickListener(btnSend);

We can also hook

startActivity

to log parameters before the call.

public void 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");
    }
}

To replace the system

Instrumentation

, we obtain the current

ActivityThread

instance and set a proxy.

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);
}

Summary of the Hook process:

Find hook points, preferably static variables or singletons, and target public objects/methods.

Choose an appropriate proxy method; use dynamic proxy for interfaces.

Replace the original object with the proxy ("steal the beam").

Handle API compatibility across Android versions.

2. Xposed

Xposed replaces

/system/bin/app_process

so that the Zygote process loads

XposedBridge.jar

, allowing it to hook functions in the Zygote and the Dalvik/ART VM.

Installation involves flashing files to system directories (e.g.,

system/bin

,

system/framework/XposedBridge.jar

,

system/lib

, etc.).

META-INF/   (flash‑script.sh configuration)
system/bin/   (replace Zygote files)
system/framework/XposedBridge.jar   (jar location)
system/lib & system/lib64   (so files)
xposed.prop   (version info)

The key point is that

app_process

(the Zygote executable) is replaced, so all app processes forked from Zygote are under Xposed control.

Xposed registers native functions to replace Java methods; when the VM reaches a hooked method, it first executes the native hook, then the original Java code.

Startup sequence:

During boot, the init process starts Zygote; the replaced

app_process

launches Xposed Zygote.

Xposed Zygote loads native libraries and runs

XposedBridge.main

, initializing hooks.

Hooks are registered as native functions in the modified VM.

The Zygote then continues its normal work, spawning app processes with hooks in place.

For detailed implementation, refer to the Xposed source code.

instrumentationAndroidReflectionHookXposedAPI Hook
Architect's Guide
Written by

Architect's Guide

Dedicated to sharing programmer-architect skills—Java backend, system, microservice, and distributed architectures—to help you become a senior architect.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.