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
278 lines
8.7 KiB
Java
278 lines
8.7 KiB
Java
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();
|
||
}
|
||
}
|