Files
notiMessage/app/src/main/java/com/miraclegarden/smsmessage/service/RetryManager.java
Mars 125dfe583b feat: Telegram 双通道抓取、PC 调试台与 Hook 后台转发修复
v2.2.1 更新说明:

- 新增 xposed-module(Telegram/微信/SQLite Hook),双 APK + LSPosed 作用域

- HookMessageReceiver 后台直接 DebugForwarder + goAsync,修复 notiMessage 退后台丢消息

- MessageLogStore 日志持久化;AppConfig 调试/上传开关;PC 调试台 debug-server

- 健康检查去掉联网限制;通知/Hook 通道增加诊断日志

- 安装脚本 install-full/configure-lsposed/start-debug-server;文档 CHANGELOG + HOOK_GUIDE
2026-07-02 16:50:12 +08:00

278 lines
8.7 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package com.miraclegarden.smsmessage.service;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Handler;
import android.os.Looper;
import android.os.SystemClock;
import android.util.Log;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* 上传失败重试管理器(内存队列 + SharedPreferences 持久化)。
* <p>
* 主要职责:
* 1. 入队失败上传任务并立即尝试上传;
* 2. 上传失败后按延迟策略重试;
* 3. 进程重启后从本地恢复重试队列;
* 4. 统计成功上传总数,供前台通知展示。
*/
public class RetryManager {
private static final String TAG = "RetryManager";
private static final String SP_NAME = "server";
private static final String KEY_RETRY_QUEUE = "retry_queue";
private static final String KEY_UPLOADED_COUNT = "uploaded_count";
private static final String KEY_TOTAL_COUNT = "total_count";
private static final String KEY_FAILED_COUNT = "failed_count";
private static final int MAX_QUEUE_SIZE = 100;
private static final long[] RETRY_DELAYS = new long[]{2000L, 5000L, 15000L};
private final SharedPreferences sharedPreferences;
private final Handler handler;
private final List<QueueItem> retryQueue = new ArrayList<>();
private UploadCallback uploadCallback;
private int uploadedCount;
private int totalCount;
private int failedCount;
/**
* 上传抽象回调。
*/
public interface UploadCallback {
void onUpload(String jsonPayload, UploadResultListener listener);
}
/**
* 上传结果监听。
*/
public interface UploadResultListener {
void onSuccess();
void onFailure(String error);
}
/**
* 重试队列元素。
*/
private static class QueueItem {
String jsonPayload;
int retryCount; // starts at 0, max 2 (3 attempts total: initial + 2 retries)
long nextRetryTime;
}
public RetryManager(Context context) {
Context appContext = context.getApplicationContext();
this.sharedPreferences = appContext.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
this.handler = new Handler(Looper.getMainLooper());
this.uploadedCount = sharedPreferences.getInt(KEY_UPLOADED_COUNT, 0);
this.totalCount = sharedPreferences.getInt(KEY_TOTAL_COUNT, 0);
this.failedCount = sharedPreferences.getInt(KEY_FAILED_COUNT, 0);
}
/**
* 设置上传实现。
*/
public void setUploadCallback(UploadCallback callback) {
this.uploadCallback = callback;
}
/**
* 添加任务到队列并立即尝试上传。
*/
public void enqueue(String jsonPayload) {
if (jsonPayload == null || jsonPayload.trim().isEmpty()) {
Log.w(TAG, "enqueue payload 为空,忽略");
return;
}
incrementTotalCount();
QueueItem item = new QueueItem();
item.jsonPayload = jsonPayload;
item.retryCount = 0;
item.nextRetryTime = SystemClock.elapsedRealtime();
synchronized (retryQueue) {
if (retryQueue.size() >= MAX_QUEUE_SIZE) {
QueueItem removed = retryQueue.remove(0);
Log.w(TAG, "重试队列已满,丢弃最旧任务: " + (removed == null ? "null" : removed.jsonPayload));
}
retryQueue.add(item);
persistQueue();
}
attemptUpload(item);
}
/**
* 从持久化恢复重试队列并重新触发上传。
*/
public void restoreFromPersistence() {
String queueJson = sharedPreferences.getString(KEY_RETRY_QUEUE, "");
if (queueJson == null || queueJson.trim().isEmpty()) {
return;
}
List<QueueItem> restoredItems = new ArrayList<>();
try {
JSONArray jsonArray = new JSONArray(queueJson);
for (int i = 0; i < jsonArray.length(); i++) {
JSONObject object = jsonArray.optJSONObject(i);
if (object == null) {
continue;
}
QueueItem item = new QueueItem();
item.jsonPayload = object.optString("jsonPayload", "");
item.retryCount = object.optInt("retryCount", 0);
item.nextRetryTime = object.optLong("nextRetryTime", SystemClock.elapsedRealtime());
if (item.jsonPayload == null || item.jsonPayload.trim().isEmpty()) {
continue;
}
restoredItems.add(item);
}
} catch (JSONException e) {
Log.e(TAG, "恢复重试队列失败", e);
return;
}
synchronized (retryQueue) {
retryQueue.clear();
retryQueue.addAll(restoredItems);
}
for (QueueItem item : new ArrayList<>(restoredItems)) {
long delay = item.nextRetryTime - SystemClock.elapsedRealtime();
if (delay > 0) {
handler.postDelayed(() -> attemptUpload(item), delay);
} else {
attemptUpload(item);
}
}
}
/**
* 获取累计成功上传数量。
*/
public int getUploadedCount() {
return uploadedCount;
}
public int getTotalCount() {
return totalCount;
}
public int getFailedCount() {
return failedCount;
}
/** 本地监听模式下仅累计抓取条数,不上传服务器 */
public void recordLocalCapture() {
incrementTotalCount();
}
private void incrementTotalCount() {
totalCount++;
sharedPreferences.edit()
.putInt(KEY_TOTAL_COUNT, totalCount)
.apply();
}
private void incrementFailedCount() {
failedCount++;
sharedPreferences.edit()
.putInt(KEY_FAILED_COUNT, failedCount)
.apply();
}
/**
* 清理回调,通常在 Service 销毁时调用。
*/
public void destroy() {
handler.removeCallbacksAndMessages(null);
}
private void attemptUpload(final QueueItem item) {
if (item == null) {
return;
}
if (uploadCallback == null) {
Log.w(TAG, "uploadCallback 未设置,无法上传");
return;
}
uploadCallback.onUpload(item.jsonPayload, new UploadResultListener() {
@Override
public void onSuccess() {
synchronized (retryQueue) {
retryQueue.remove(item);
persistQueue();
}
uploadedCount++;
sharedPreferences.edit()
.putInt(KEY_UPLOADED_COUNT, uploadedCount)
.apply();
}
@Override
public void onFailure(String error) {
synchronized (retryQueue) {
if (!retryQueue.contains(item)) {
return;
}
if (item.retryCount < 3) {
long delay = RETRY_DELAYS[Math.min(item.retryCount, RETRY_DELAYS.length - 1)];
item.retryCount++;
item.nextRetryTime = SystemClock.elapsedRealtime() + delay;
handler.postDelayed(() -> attemptUpload(item), delay);
persistQueue();
Log.w(TAG, "上传失败准备重试。retryCount=" + item.retryCount + ", error=" + error);
} else {
retryQueue.remove(item);
persistQueue();
incrementFailedCount();
Log.e(TAG, "上传失败达到上限,丢弃任务: " + error);
}
}
}
});
}
private void persistQueue() {
JSONArray jsonArray = new JSONArray();
synchronized (retryQueue) {
Iterator<QueueItem> iterator = retryQueue.iterator();
while (iterator.hasNext()) {
QueueItem item = iterator.next();
JSONObject object = new JSONObject();
try {
object.put("jsonPayload", item.jsonPayload);
object.put("retryCount", item.retryCount);
object.put("nextRetryTime", item.nextRetryTime);
jsonArray.put(object);
} catch (JSONException e) {
Log.e(TAG, "序列化重试项失败", e);
}
}
}
sharedPreferences.edit()
.putString(KEY_RETRY_QUEUE, jsonArray.toString())
.apply();
}
}