Mobile Development 13 min read

Hooking Android Looper Observer for Message Timing Monitoring and Bypassing Hidden APIs

This article explains how to use reflection and dynamic proxies to hook the hidden Android Looper.Observer API for detailed message‑dispatch timing, demonstrates code examples for installing and removing the hook, discusses workarounds for hidden‑API restrictions and the .dex loading changes introduced in Android 14.

Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Hooking Android Looper Observer for Message Timing Monitoring and Bypassing Hidden APIs

Introduction

When debugging a performance‑critical task on the main thread, the author discovered the need to monitor long‑running messages using Android's Looper and its hidden Observer interface instead of the traditional Printer approach.

Understanding Looper.Observer

Reading the source reveals that Looper contains a static sObserver field of type Observer , which provides three callbacks: messageDispatchStarting , messageDispatched , and dispatchingThrewException . These callbacks give direct access to the Message object and allow precise timing without the overhead of string concatenation.

public final class Looper {
private static Observer sObserver;
...
public static void setObserver(@Nullable Observer observer) { sObserver = observer; }
public static void loop() {
// simplified loop
final Printer logging = me.mLogging;
if (logging != null) { logging.println(...); }
final Observer observer = sObserver;
// dispatch message
if (observer != null) { token = observer.messageDispatchStarting(); }
try { msg.target.dispatchMessage(msg); }
finally { if (observer != null) { observer.messageDispatched(token, msg); } }
}
}

Implementing the Hook

The author creates a dynamic proxy that implements the hidden Observer interface. The proxy records the start time in messageDispatchStarting , calculates wait and work times in messageDispatched , and logs the results with different severity levels based on duration.

public class HandlerLoopHookHelper {
public static void hookLooperObserver(@Nullable Context context) {
Class
sObserverClass = Class.forName("android.os.Looper$Observer");
ObserverInvocation invocation = new ObserverInvocation();
Object proxy = Proxy.newProxyInstance(sObserverClass.getClassLoader(), new Class[]{sObserverClass}, invocation);
Field sObserver = Class.forName("android.os.Looper").getDeclaredField("sObserver");
sObserver.setAccessible(true);
sObserver.set(Looper.getMainLooper(), proxy);
Looper.getMainLooper().setMessageLogging(invocation.printer);
}
... // invoke, timing, logging logic
}

Bypassing Hidden API Restrictions

To access the hidden Observer on newer Android versions, the article recommends using the FreeReflection library (or similar) to unseal the runtime, allowing reflective calls to private APIs. It also mentions alternative strategies such as creating a dummy project that mimics the system classes.

Android 14 .dex Loading Issue

Starting with Android 14, dynamically loaded .dex files must be marked read‑only; otherwise a security exception is thrown. The author shows how to copy the FreeReflection library, modify its source, and set the appropriate file permissions to avoid the crash.

Correct Hook Usage Example

public class HandlerLoopHookHelper {
private static volatile boolean isHooked = false;
private static final String TAG = "HandlerLoopHookHelper";
private static void checkHooked(Context context) {
if (!isHooked) { Reflection.unseal(context); isHooked = true; }
}
public static void hookLooperObserver(@Nullable Context context) {
Log.d(TAG, "hookLoopObserver: " + Build.VERSION.SDK_INT);
try {
checkHooked(context);
Class
sObserverClass = Class.forName("android.os.Looper$Observer");
ObserverInvocation invocation = new ObserverInvocation();
Object proxy = Proxy.newProxyInstance(sObserverClass.getClassLoader(), new Class[]{sObserverClass}, invocation);
Field sObserver = Class.forName("android.os.Looper").getDeclaredField("sObserver");
sObserver.setAccessible(true);
sObserver.set(Looper.getMainLooper(), proxy);
Looper.getMainLooper().setMessageLogging(invocation.printer);
} catch (Throwable e) { e.printStackTrace(); }
}
... // unhook and idle‑handler utilities
}

Conclusion

The hidden Observer provides richer information than Printer and avoids extra string concatenation.

Various techniques (FreeReflection, dummy projects, compile‑only stubs) can bypass hidden‑API restrictions during development.

Observer was introduced in Android 10; older versions still need the Printer method.

On Android 14, dynamically loaded .dex files must be read‑only to prevent security exceptions.

Androidreflectionperformance monitoringLooperHookObserverHidden API
Rare Earth Juejin Tech Community
Written by

Rare Earth Juejin Tech Community

Juejin, a tech community that helps developers grow.

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.