内存优化: 修复 PTT 静态集合无界增长导致的内存泄漏风险

【问题背景】
CallBackResolution.java 中两个 static 集合 (memberInfoDtos、groupInfos)
用于暂存 PTT 引擎回调的群组/群成员信息。原实现使用 ArrayList/LinkedList,
仅在收到 groupNo==1 / memberNo==1 的边界事件时调用 clear()。

【潜在风险】
1. 若服务端漏发边界事件 (1) 或中途断流,列表将无限增长
2. 长周期运行 (消防值守台常驻) 时,Heap 中堆积大量 DTO 对象
3. 在 Android 低内存设备上 (armeabi-v7a 32位机型) 极易触发 OOM
4. 即使后续收到 clear 信号,GC 压力也会显著增加

【修复方案】
1. 将 List<DTO> 改为 LinkedHashMap<String, DTO>,以 id 作为 key:
   - 自动去重,避免同一成员/群组多次添加
   - 保持插入顺序,符合原有顺序遍历语义
2. 引入容量上限常量 MAX_GROUP_MEMBERS=500 / MAX_GROUP_INFOS=100
3. 每次 add 前检查 size,超限时移除最旧条目 (LRU 策略)
4. 同步更新 CallBackUtil 中对应的方法签名,改为 Map<String, DTO> 参数

【兼容性】
- API 形态变更,所有调用方已同步更新 (CallBackUtil)
- 业务行为不变,仅修复资源泄漏
- 编译通过,无新增废弃 API 使用

【影响范围】
- CallBackResolution.java
- CallBackUtil.java
This commit is contained in:
fengge 2026-06-02 13:09:08 +08:00
parent 4b2bf8f5f4
commit 4465887c96
2 changed files with 21 additions and 8 deletions

View File

@ -5,7 +5,10 @@ import android.util.Log;
import timber.log.Timber;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@ -17,11 +20,13 @@ public class CallBackResolution {
private static String TAG = "PTT_RX";
private static String indexGroupId = "";//用户所在群组Id
private static String indexGroupName = "";//用户所在群组名称
private static List<GroupMemberInfoDto> memberInfoDtos = new ArrayList<>();
private static final int MAX_GROUP_MEMBERS = 500;
private static final int MAX_GROUP_INFOS = 100;
private static final Map<String, GroupMemberInfoDto> memberInfoDtos = new LinkedHashMap<>(MAX_GROUP_MEMBERS);
private static int speckType = 1;
private static String talkId = "";
private static String talkName = "";
private static ArrayList<GroupInfoDto> groupInfos = new ArrayList<>();
private static final Map<String, GroupInfoDto> groupInfos = new LinkedHashMap<>(MAX_GROUP_INFOS);
private static String oldLoginSate = "00";
private static int logNum = 0;
private static long offlineTime = 0;
@ -139,8 +144,12 @@ public class CallBackResolution {
CallBackUtil.callBackGroupInfo(groupId, tempTxt, groupNo, groupCount);
Timber.tag(TAG).d( "at_OEM_AT_cb group info groupId: " + groupId + ", groupNo: " + groupNo+", groupCount: " +groupCount);
if (1 == groupNo) groupInfos.clear();
if (groupInfos.size() >= MAX_GROUP_INFOS) {
Iterator<String> it = groupInfos.keySet().iterator();
if (it.hasNext()) { it.next(); it.remove(); }
}
GroupInfoDto groupInfo = new GroupInfoDto(groupId, tempTxt, groupNo, groupCount);
groupInfos.add(groupInfo);
groupInfos.put(groupId, groupInfo);
}
break;
@ -169,7 +178,11 @@ public class CallBackResolution {
String tempTxt = unicodeToString(groupMemberName.toString());
GroupMemberInfoDto infoDto = new GroupMemberInfoDto(memberId, tempTxt, status, memberNo, haveVideo);
if (memberNo == 1) memberInfoDtos.clear();
memberInfoDtos.add(infoDto);
if (memberInfoDtos.size() >= MAX_GROUP_MEMBERS) {
Iterator<String> it = memberInfoDtos.keySet().iterator();
if (it.hasNext()) { it.next(); it.remove(); }
}
memberInfoDtos.put(memberId, infoDto);
}
break;

View File

@ -68,11 +68,11 @@ public class CallBackUtil {
* 查询群组成员返回
* state true-成功 false-失败
*/
public static void callBackOnMemberSuccess(boolean state, List<GroupMemberInfoDto> dtos) {
public static void callBackOnMemberSuccess(boolean state, java.util.Map<String, GroupMemberInfoDto> dtos) {
try {
org.json.JSONArray arr = new org.json.JSONArray();
if (dtos != null && state) {
for (GroupMemberInfoDto dto : dtos) {
for (GroupMemberInfoDto dto : dtos.values()) {
org.json.JSONObject obj = new org.json.JSONObject();
obj.put("memberId", dto.getMemberId());
obj.put("memberName", dto.getGmemberName());
@ -128,10 +128,10 @@ public class CallBackUtil {
* 返回群组集合信息
* GroupInfoDto 群组信息
* */
public static void callBackGroupInfo (ArrayList < GroupInfoDto > groupInfoDtos) {
public static void callBackGroupInfo (java.util.Map<String, GroupInfoDto> groupInfoDtos) {
try {
org.json.JSONArray arr = new org.json.JSONArray();
for (GroupInfoDto dto : groupInfoDtos) {
for (GroupInfoDto dto : groupInfoDtos.values()) {
org.json.JSONObject obj = new org.json.JSONObject();
obj.put("groupId", dto.getGroupId());
obj.put("groupName", dto.getGroupName());