Tencent/matrix · error · IllegalStateException
get service manager ClassLoader fail!
Error message
get service manager ClassLoader fail!
What it means
createServiceManagerProxy loads a system service class and its $Stub inner class, then needs the Stub's ClassLoader to build a dynamic proxy implementing IBinder/IInterface/the service interface. A null ClassLoader would make Proxy.newProxyInstance fail, so it throws IllegalStateException 'get service manager ClassLoader fail!' as an environment-invariant guard.
Solutions
- Verify serviceClassName is correct for the target API level (e.g. android.app.INotificationManager vs older names); a wrong class name yields a bogus Stub.
- Fall back to the caller's ClassLoader (SystemServiceBinderHooker.class.getClassLoader() or IBinder.class.getClassLoader()) when null.
- Guard the hook with a try-catch and skip hooking on devices where it fails; the library degrades to un-hooked monitoring.
- Check that the service actually exists before hooking (ServiceManager.getService != null).
Example fix
// before
ClassLoader classLoader = serviceManagerStubCls.getClassLoader();
if (classLoader == null) {
throw new IllegalStateException("get service manager ClassLoader fail!");
}
// after
ClassLoader classLoader = serviceManagerStubCls.getClassLoader();
if (classLoader == null) {
classLoader = SystemServiceBinderHooker.class.getClassLoader();
}
if (classLoader == null) {
return originBinder; // skip hook
} Defensive patterns
Strategy: try-catch
Validate before calling
if (serviceClassName == null || !serviceClassName.contains("$Stub") == false) {
try { Class.forName(serviceClassName + "$Stub"); } catch (Throwable t) { return originBinder; }
} Type guard
static boolean serviceStubExists(String serviceClassName) {
try { Class.forName(serviceClassName + "$Stub"); return true; }
catch (Throwable t) { return false; }
} Try / catch
try {
Object proxy = createServiceManagerProxy(serviceClassName, originBinder, callback);
} catch (Exception e) {
Log.w(TAG, "service hook failed for " + serviceClassName + ", skipping", e);
proxy = originBinder;
} Prevention
- Verify the service class and its $Stub exist on the target API level before hooking.
- Check ServiceManager.getService(name) != null before attempting the hook.
- Wrap every hook attempt in try-catch with fallback to the un-hooked binder.
- Test on min-API and max-API devices; service class names change across Android versions.
When it happens
Trigger: Hooking a system service where Class.forName(serviceClassName + "$Stub").getClassLoader() returns null — serviceClassName pointing to a wrong/hidden service class, bootstrap-classloader services on modified runtimes, or mock environments (Robolectric) where Stub classes behave differently.
Common situations: Passing an incorrect serviceClassName (typo or service absent on that API level); OEM ROMs moving services to different classloaders; hooking frameworks that relocate classes.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- Can not get ClassLoader of
- Both of invoker and fieldName can not be null or nil.
- unable to cast object
- Method is not exists.
- Both of invoker and fieldName can not be null or nil.
AI-assisted analysis of Tencent/matrix@3b8293bd65 (2026-09-08).
Data as JSON: /api/errors/847b51ecf38ef154.
Report an issue: GitHub.
Appendix: source
Thrown at matrix/matrix-android/matrix-battery-canary/src/main/java/com/tencent/matrix/batterycanary/utils/SystemServiceBinderHooker.java:160
new Class<?>[]{IBinder.class},
this
);
}
@SuppressWarnings({"PrivateApi"})
static IBinder getCurrentBinder(String serviceName) throws Exception {
Class<?> serviceManagerCls = Class.forName("android.os.ServiceManager");
Method getService = serviceManagerCls.getDeclaredMethod("getService", String.class);
return (IBinder) getService.invoke(null, serviceName);
}
@SuppressWarnings({"PrivateApi"})
private static Object createServiceManagerProxy(String serviceClassName, IBinder originBinder, final HookCallback callback) throws Exception {
Class<?> serviceManagerCls = Class.forName(serviceClassName);
Class<?> serviceManagerStubCls = Class.forName(serviceClassName + "$Stub");
ClassLoader classLoader = serviceManagerStubCls.getClassLoader();
if (classLoader == null) {
throw new IllegalStateException("get service manager ClassLoader fail!");
}
Method asInterfaceMethod = serviceManagerStubCls.getDeclaredMethod("asInterface", IBinder.class);
final Object originManagerService = asInterfaceMethod.invoke(null, originBinder);
return Proxy.newProxyInstance(classLoader,
new Class[]{IBinder.class, IInterface.class, serviceManagerCls},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (callback != null) {
callback.onServiceMethodInvoke(method, args);
Object result = callback.onServiceMethodIntercept(originManagerService, method, args);
if (result != null) {
return result;
}
}
return method.invoke(originManagerService, args);
}
}View on GitHub (pinned to 3b8293bd65)