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.
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
registerDefaultNetworkCallbackis 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.
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.
