Mobile Development 16 min read

Why Android Apps Crash on Huawei Devices: TooManyRequestsException Explained and Fixes

Starting in April, several Android projects began crashing on Huawei HarmonyOS devices with TooManyRequestsException errors; this article details the investigation, source code analysis of registerDefaultNetworkCallback across Android versions, and proposes instrumentation and code‑level solutions to prevent excessive network callback registrations.

Huolala Tech
Huolala Tech
Huolala Tech
Why Android Apps Crash on Huawei Devices: TooManyRequestsException Explained and Fixes

Background

Since April, several Android projects suddenly encountered TooManyRequestsException on Huawei HarmonyOS devices. Community reports mentioned the same exception but no clear solution was available.

Investigation Process

1. The crash stack pointed to the WorkManager library where network listeners were registered.

2. We examined all WorkManager usages and found image download logic in splash ads. Although the code hadn't changed, new city splash ads were released before the crash, so we temporarily disabled ads in some cities, which reduced the crash rate.

3. Further analysis showed that the issue stemmed from registering too many network callbacks, hitting the system's limit. Existing third‑party libraries (e.g., Glide) already wrapped calls in try‑catch blocks.

4. To mitigate, we refactored the business logic using WorkManager and added try‑catch protection around every registerDefaultNetworkCallback call. This temporarily alleviated crashes but may affect features relying on network callbacks.

Source analysis of registerDefaultNetworkCallback :

Source Code Analysis

We examined the implementation of registerDefaultNetworkCallback across Android versions. While Android 11 and Android 14 differ slightly, the core logic remains consistent.

Android 11

Source

registerDefaultNetworkCallback

is a cross‑process operation that starts from ConnectivityManager.registerDefaultNetworkCallback and eventually calls ConnectivityService.requestNetwork.

ConnectivityManager cm = (ConnectivityManager) this.getSystemService(Context.CONNECTIVITY_SERVICE);
cm.registerDefaultNetworkCallback(new ConnectivityManager.NetworkCallback(){
});
@RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
public void registerDefaultNetworkCallback(@NonNull NetworkCallback networkCallback,
        @NonNull Handler handler) {
    CallbackHandler cbHandler = new CallbackHandler(handler);
    sendRequestForNetwork(null, networkCallback, 0,
            REQUEST, TYPE_NONE, cbHandler);
}
private NetworkRequest sendRequestForNetwork(NetworkCapabilities need, NetworkCallback callback,
        int timeoutMs, int action, int legacyType, CallbackHandler handler) {
    // ...
    if (action == LISTEN) {
        request = mService.listenForNetwork(
                need, messenger, binder, callingPackageName);
    } else {
        request = mService.requestNetwork(
                need, messenger, timeoutMs, binder, legacyType, callingPackageName);
    }
    // ...
    return request;
}

In ConnectivityService.requestNetwork, a NetworkRequestInfo object is created, which calls enforceRequestCountLimit. If a UID registers 100 or more requests, a TOO_MANY_REQUESTS exception is thrown.

@Override
public NetworkRequest requestNetwork(NetworkCapabilities networkCapabilities,
        Messenger messenger, int timeoutMs, IBinder binder, int legacyType,
        @NonNull String callingPackageName) {
    // ...
    NetworkRequestInfo nri = new NetworkRequestInfo(messenger, networkRequest, binder);
    // ...
    return networkRequest;
}
NetworkRequestInfo(Messenger m, NetworkRequest r, IBinder binder) {
    // ...
    enforceRequestCountLimit();
    // ...
}
private void enforceRequestCountLimit() {
    synchronized (mUidToNetworkRequestCount) {
        int networkRequests = mUidToNetworkRequestCount.get(mUid, 0) + 1;
        if (networkRequests >= MAX_NETWORK_REQUESTS_PER_UID) {
            throw new ServiceSpecificException(ConnectivityManager.Errors.TOO_MANY_REQUESTS);
        }
        mUidToNetworkRequestCount.put(mUid, networkRequests);
    }
}

Android 14

Source

In Android 14, ConnectivityManager adds registerDefaultNetworkCallbackForUid, but the registration flow is similar.

ConnectivityManager cm = (ConnectivityManager) this.getSystemService(Context.CONNECTIVITY_SERVICE);
cm.registerDefaultNetworkCallback(new ConnectivityManager.NetworkCallback(){
});
@RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
public void registerDefaultNetworkCallback(@NonNull NetworkCallback networkCallback,
        @NonNull Handler handler) {
    registerDefaultNetworkCallbackForUid(Process.INVALID_UID, networkCallback, handler);
}
public void registerDefaultNetworkCallbackForUid(int uid,
        @NonNull NetworkCallback networkCallback, @NonNull Handler handler) {
    CallbackHandler cbHandler = new CallbackHandler(handler);
    sendRequestForNetwork(uid, null, networkCallback, 0,
            TRACK_DEFAULT, TYPE_NONE, cbHandler);
}
private NetworkRequest sendRequestForNetwork(int asUid, NetworkCapabilities need,
        NetworkCallback callback, int timeoutMs, NetworkRequest.Type reqType, int legacyType,
        CallbackHandler handler) {
    // ...
    NetworkRequestInfo nri = getNriToRegister(asUid, networkRequest, messenger, binder, callbackFlags, callingAttributionTag);
    // ...
    return networkRequest;
}

The counting logic now resides in RequestInfoPerUidCounter, which still throws TOO_MANY_REQUESTS when the limit is exceeded.

Recommendations

Ensure every registerDefaultNetworkCallback has a matching unregisterNetworkCallback to release resources.

Control the number of registrations or wrap calls in try‑catch blocks to handle potential exceptions.

For third‑party SDKs we cannot modify directly, we propose an instrumentation approach that centralizes all registrations and unregistrations.

Instrumentation Scheme

Using bytecode tools like ASM or AspectJ, replace calls to ConnectivityManager.registerDefaultNetworkCallback and unregisterNetworkCallback with a custom manager class.

Implementation Idea

We create MyConnectivityManager that stores callbacks in a list and forwards network events. When the downgrade flag is off, calls are routed through this manager, allowing unified monitoring and safe unregistration.

Sample Code

public class MyConnectivityManager {

    public static List<ConnectivityManager.NetworkCallback> callbacks = new ArrayList<>();
    public static boolean downgrading = false;

    public static void initConnectivityManager(Context context){
        Log.d("MyConnectivityManager","initConnectivityManager");
        ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            cm.registerDefaultNetworkCallback(new ConnectivityManager.NetworkCallback(){
                @Override
                public void onAvailable(@NonNull Network network) {
                    super.onAvailable(network);
                    Log.d("MyConnectivityManager","分发 onAvailable "+callbacks.size());
                    for (ConnectivityManager.NetworkCallback cb : callbacks) {
                        cb.onAvailable(network);
                    }
                }
                // other callbacks omitted for brevity
            });
        }
    }

    public static void registerDefaultNetworkCallback(ConnectivityManager cm, ConnectivityManager.NetworkCallback callback){
        Log.d("MyConnectivityManager","registerDefaultNetworkCallback:"+callback);
        if (downgrading){
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                cm.registerDefaultNetworkCallback(callback);
            }
        } else {
            callbacks.add(callback);
        }
    }

    public static void unregisterNetworkCallback(ConnectivityManager cm, ConnectivityManager.NetworkCallback callback){
        Log.d("MyConnectivityManager","unregisterNetworkCallback");
        if (downgrading){
            cm.unregisterNetworkCallback(callback);
        } else {
            callbacks.remove(callback);
        }
    }
}

Instrumentation bytecode snippet:

if (methodInsnNode.owner.equals("android/net/ConnectivityManager") && methodInsnNode.name.equals("registerDefaultNetworkCallback") && "(Landroid/net/ConnectivityManager$NetworkCallback;)V".equals(methodInsnNode.desc)){
    methodInsnNode.owner = "com/example/gradledemo/MyConnectivityManager";
    methodInsnNode.desc = "(Landroid/net/ConnectivityManager;Landroid/net/ConnectivityManager$NetworkCallback;)V";
    methodInsnNode.name = "registerDefaultNetworkCallback";
    methodInsnNode.setOpcode(INVOKESTATIC);
}
if (methodInsnNode.owner.equals("android/net/ConnectivityManager") && methodInsnNode.name.equals("unregisterNetworkCallback") && "(Landroid/net/ConnectivityManager$NetworkCallback;)V".equals(methodInsnNode.desc)){
    methodInsnNode.owner = "com/example/gradledemo/MyConnectivityManager";
    methodInsnNode.desc = "(Landroid/net/ConnectivityManager;Landroid/net/ConnectivityManager$NetworkCallback;)V";
    methodInsnNode.name = "unregisterNetworkCallback";
    methodInsnNode.setOpcode(INVOKESTATIC);
}

Monitoring Metrics

Since we cannot confirm whether Huawei modified the system source, we suspect the issue is device‑specific. Potential causes include:

Huawei altered the Android source affecting registerDefaultNetworkCallback.

Huawei vendor pushes changed system services.

We therefore monitor the usage count of registerDefaultNetworkCallback in production, aggregating calls to identify hotspots and optimise the logic.

InstrumentationAndroidWorkManagerHuaweiNetworkCallbackTooManyRequestsException
Huolala Tech
Written by

Huolala Tech

Technology reshapes logistics

0 followers
Reader feedback

How this landed with the community

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.