别使用hashCode作为PendingIntent的requestCode

168 阅读2分钟

背景

最近碰到一个崩溃,alarm设置过多,好久没看到这个崩溃了,浅浅分析一下,看能不能解决。

java.lang.IllegalStateException: Maximum limit of concurrent alarms 500 reached for uid: u0a14, callingPackage: com.xxx.xxx
	at android.os.Parcel.createException(Parcel.java:2099)
	at android.os.Parcel.readException(Parcel.java:2059)
	at android.os.Parcel.readException(Parcel.java:2007)
	at android.app.IAlarmManager$Stub$Proxy.set(IAlarmManager.java:318)
	at android.app.AlarmManager.setImpl(AlarmManager.java:701)
	at android.app.AlarmManager.setRepeating(AlarmManager.java:446)  
        ...
	at android.os.Handler.handleCallback(Handler.java:900)
	at android.os.Handler.dispatchMessage(Handler.java:103)
	at android.os.Looper.loop(Looper.java:219)
	at android.os.HandlerThread.run(HandlerThread.java:67)

先分析一下业务代码,setRepeating并没有调用太多次,理论上这行代码只会调用一次。而且肯定是这里设置的alarm过多,因为都是相同的堆栈,如果是加起来超过500个,那么堆栈应该是不同的。

mSender = PendingIntent.getBroadcast(mCtx, mFilter.hashCode(), mIntent, PendingIntent.FLAG_UPDATE_CURRENT);

问问Deepseek

image.png 这里提到的一个很关键的点,requestCode需要唯一

frameworks/base/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java

void setImpl(int type, long triggerAtTime, long windowLength, long interval,
           PendingIntent operation, IAlarmListener directReceiver, String listenerTag,
           int flags, WorkSource workSource, AlarmManager.AlarmClockInfo alarmClock,
           int callingUid, String callingPackage, Bundle idleOptions, int exactAllowReason) {
    synchronized (mLock) {
    
       if (mAlarmsPerUid.get(callingUid, 0) >= mConstants.MAX_ALARMS_PER_UID) {
          final String errorMsg =
                "Maximum limit of concurrent alarms " + mConstants.MAX_ALARMS_PER_UID
                      + " reached for uid: " + UserHandle.formatUid(callingUid)
                      + ", callingPackage: " + callingPackage;
       }
       setImplLocked(type, triggerAtTime, triggerElapsed, windowLength, interval, operation,
             directReceiver, listenerTag, flags, workSource, alarmClock, callingUid,
             callingPackage, idleOptions, exactAllowReason);
    }
}

mAlarmsPerUid会记录每个uid设置的alarm数量,当其超过mConstants.MAX_ALARMS_PER_UID(500)就会崩溃,找下在哪增加的

    private void incrementAlarmCount(int uid) {
        increment(mAlarmsPerUid, uid);
    }
    
    private static void increment(SparseIntArray array, int key) {
        final int index = array.indexOfKey(key);
        if (index >= 0) {
            array.setValueAt(index, array.valueAt(index) + 1);
        } else {
            array.put(key, 1);
        }
    } 
    
    @GuardedBy("mLock")
    private void setImplLocked(int type, long when, long whenElapsed, long windowLength,
            long interval, PendingIntent operation, IAlarmListener directReceiver,
            String listenerTag, int flags, WorkSource workSource,
            AlarmManager.AlarmClockInfo alarmClock, int callingUid, String callingPackage,
            Bundle idleOptions, int exactAllowReason) {
        final Alarm a = new Alarm(type, when, whenElapsed, windowLength, interval,
                operation, directReceiver, listenerTag, workSource, flags, alarmClock,
                callingUid, callingPackage, idleOptions, exactAllowReason);
        if (mActivityManagerInternal.isAppStartModeDisabled(callingUid, callingPackage)) {
            Slog.w(TAG, "Not setting alarm from " + callingUid + ":" + a
                    + " -- package not allowed to start");
            return;
        }
        final int callerProcState = mActivityManagerInternal.getUidProcessState(callingUid);
        removeLocked(operation, directReceiver, REMOVE_REASON_UNDEFINED);
        incrementAlarmCount(a.uid);
        setImplLocked(a);
        MetricsHelper.pushAlarmScheduled(a, callerProcState);
    }

removeLocked() 是用来减少的, 增加之前先移除原有的

    @GuardedBy("mLock")
    void removeLocked(PendingIntent operation, IAlarmListener directReceiver, int reason) {
        if (operation == null && directReceiver == null) {
            if (localLOGV) {
                Slog.w(TAG, "requested remove() of null operation",
                        new RuntimeException("here"));
            }
            return;
        }
        removeAlarmsInternalLocked(a -> a.matches(operation, directReceiver), reason);
    }


@GuardedBy("mLock")
private void removeAlarmsInternalLocked(Predicate<Alarm> whichAlarms, int reason) {
    final ArrayList<Alarm> removedAlarms = mAlarmStore.remove(whichAlarms);
    ...
    for (final Alarm removed : removedAlarms) {
       decrementAlarmCount(removed.uid, 1);
    }
    ...
}

所以问题就是为什么没有remove掉,看看为何没match到

  public boolean matches(PendingIntent pi, IAlarmListener rec) {
        return (operation != null)
                ? operation.equals(pi)
                : rec != null && listener.asBinder().equals(rec.asBinder());
    }

frameworks/base/services/core/java/com/android/server/am/PendingIntentRecord.java

    public boolean equals(Object otherObj) {
            if (otherObj == null) {
                return false;
            }
            try {
                Key other = (Key)otherObj;
                if (type != other.type) {
                    return false;
                }
                if (userId != other.userId){
                    return false;
                }
                if (!Objects.equals(packageName, other.packageName)) {
                    return false;
                }
                if (!Objects.equals(featureId, other.featureId)) {
                    return false;
                }
                if (activity != other.activity) {
                    return false;
                }
                if (!Objects.equals(who, other.who)) {
                    return false;
                }
                if (requestCode != other.requestCode) {
                    return false;
                }
                if (requestIntent != other.requestIntent) {
                    if (requestIntent != null) {
                        if (!requestIntent.filterEquals(other.requestIntent)) {
                            return false;
                        }
                    } else if (other.requestIntent != null) {
                        return false;
                    }
                }
                if (!Objects.equals(requestResolvedType, other.requestResolvedType)) {
                    return false;
                }
                if (flags != other.flags) {
                    return false;
                }
                return true;
            } catch (ClassCastException e) {
            }
            return false;
        }

发现 requestCode参与了比较,所以requestCode不同会认为是不同的alarm,不会被移除

验证

dumpsys alarm 可以打印数量,当使用不同的requestCode创建100个 alarm

image.png 使用相同的requestCode image.png

结论

听deepseek的,别使用不同的requestCode