优化日志逻辑;修改除湿机自动重发指令的时间间隔;

This commit is contained in:
BBIT-Kai
2026-07-24 18:17:06 +08:00
parent 5612c478bd
commit a42d6ceec6
9 changed files with 162 additions and 118 deletions
+2 -2
View File
@@ -10,8 +10,8 @@ android {
applicationId "com.example.iot_controlhost" applicationId "com.example.iot_controlhost"
minSdkVersion 25 minSdkVersion 25
targetSdkVersion 30 targetSdkVersion 30
versionCode 161 versionCode 163
versionName "3.5.0.161" versionName "3.5.0.163"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
ndk { ndk {
moduleName "mcu" moduleName "mcu"
@@ -14,16 +14,17 @@ import com.xuexiang.rxutil2.rxbus.RxBusUtils;
public class FrpStartReceiver extends BroadcastReceiver { public class FrpStartReceiver extends BroadcastReceiver {
@Override @Override
public void onReceive(Context mContext, Intent intent) { public void onReceive(Context mContext, Intent intent) {
if (intent.getAction().equals("receiver_frp_version")) { String action = intent == null ? null : intent.getAction();
if ("receiver_frp_version".equals(action)) {
int frpVersion = intent.getIntExtra("version", 0); int frpVersion = intent.getIntExtra("version", 0);
MyLog.frp("收到FRP版本:" + frpVersion); MyLog.frp("收到FRP版本:" + frpVersion);
MMKVUtil.put(HardwareSetting.FRP_VERSION, frpVersion); MMKVUtil.put(HardwareSetting.FRP_VERSION, frpVersion);
} else if (intent.getAction().equals("receiver_start_frp")) { } else if ("receiver_start_frp".equals(action)) {
if (intent.getBooleanExtra("start_frp", false)) { if (intent.getBooleanExtra("start_frp", false)) {
MyLog.frp("收到启动FRP指令"); MyLog.frp("收到启动FRP指令");
MyUtil.relaunchFrp(); MyUtil.relaunchFrp();
} }
} else if (intent.getAction().equals("receiver_frp_info")) { } else if ("receiver_frp_info".equals(action)) {
String info = intent.getStringExtra("info"); String info = intent.getStringExtra("info");
MyLog.frp(info); MyLog.frp(info);
RxBusUtils.get().post(RxTag.UPDATE_MAIN_MSG, info); RxBusUtils.get().post(RxTag.UPDATE_MAIN_MSG, info);
@@ -25,7 +25,6 @@ import com.example.iot_controlhost.utils.database.dynamicSensor.IndoorAmmoniaSen
import com.example.iot_controlhost.utils.database.dynamicSensor.IndoorCO2SensorUtil; import com.example.iot_controlhost.utils.database.dynamicSensor.IndoorCO2SensorUtil;
import com.example.iot_controlhost.utils.database.dynamicSensor.IndoorFloorTSensorUtil; import com.example.iot_controlhost.utils.database.dynamicSensor.IndoorFloorTSensorUtil;
import com.example.iot_controlhost.utils.database.dynamicSensor.IndoorTHSensorUtil; import com.example.iot_controlhost.utils.database.dynamicSensor.IndoorTHSensorUtil;
import com.example.iot_controlhost.utils.database.LogDBManager;
import com.example.iot_controlhost.utils.global.AutoModelSet; import com.example.iot_controlhost.utils.global.AutoModelSet;
import com.example.iot_controlhost.utils.global.HardwareSetting; import com.example.iot_controlhost.utils.global.HardwareSetting;
import com.example.iot_controlhost.utils.global.RoomController; import com.example.iot_controlhost.utils.global.RoomController;
@@ -261,8 +260,6 @@ public class SplashActivityPresenter extends BasePresenter {
public void onGranted(@NonNull List<String> permissions, boolean allGranted) { public void onGranted(@NonNull List<String> permissions, boolean allGranted) {
if (allGranted) { if (allGranted) {
processLiveData.postValue(new ProcessInfo(100, "系统启动成功")); processLiveData.postValue(new ProcessInfo(100, "系统启动成功"));
// 索引首次创建可能较慢,放在启动完成后后台执行,不阻塞启动页。
LogDBManager.prepareAsync();
MyLog.app("系统启动成功"); MyLog.app("系统启动成功");
} else { } else {
MyLog.appError("获取部分权限成功,但部分权限未正常授予"); MyLog.appError("获取部分权限成功,但部分权限未正常授予");
@@ -466,17 +466,38 @@ public class MyUtil {
} }
public static void relaunchFrp(){ public static void relaunchFrp() {
Context mContext = ActivityUtils.getTopActivity(); Context context = ActivityUtils.getTopActivity();
Intent intent = new Intent(); if (context == null) {
intent.setAction("receiver_control"); context = MyApp.getAppContext();
intent.putExtra("control", -1); }
mContext.sendBroadcast(intent); if (context == null) {
// 等待2sFRP关闭,然后重启 MyLog.frpError("启动FRP失败:无法获取应用上下文");
return;
}
Context launchContext = context;
try {
Intent stopIntent = new Intent("receiver_control");
stopIntent.putExtra("control", -1);
launchContext.sendBroadcast(stopIntent);
} catch (Exception e) {
MyLog.frpError("发送FRP停止指令失败:" + e.getMessage());
}
// 等待2s让FRP释放资源,然后重新启动。
RxJavaUtils.delay(2, aLong -> { RxJavaUtils.delay(2, aLong -> {
Intent intent2 = mContext.getPackageManager().getLaunchIntentForPackage("com.bbitcn.bbit_frp2"); try {
intent2.putExtra("host_id", HardwareSetting.getHostId()); Intent launchIntent = launchContext.getPackageManager()
mContext.startActivity(intent2); .getLaunchIntentForPackage("com.bbitcn.bbit_frp2");
if (launchIntent == null) {
MyLog.frpError("启动FRP失败:未找到远程协助应用");
return;
}
launchIntent.putExtra("host_id", HardwareSetting.getHostId());
launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
launchContext.startActivity(launchIntent);
} catch (Exception e) {
MyLog.frpError("启动FRP失败:" + e.getMessage());
}
}); });
} }
} }
@@ -1,5 +1,7 @@
package com.example.iot_controlhost.utils.database; package com.example.iot_controlhost.utils.database;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log; import android.util.Log;
import com.example.iot_controlhost.MyApp; import com.example.iot_controlhost.MyApp;
@@ -8,7 +10,6 @@ import com.example.iot_controlhost.utils.log.MyLog;
import com.example.iot_controlhost.utils.log.UserLog; import com.example.iot_controlhost.utils.log.UserLog;
import org.greenrobot.greendao.query.QueryBuilder; import org.greenrobot.greendao.query.QueryBuilder;
import org.greenrobot.greendao.query.WhereCondition;
import java.util.Calendar; import java.util.Calendar;
import java.util.Collections; import java.util.Collections;
@@ -16,8 +17,11 @@ import java.util.Date;
import java.util.List; import java.util.List;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
/** /**
* 日志数据库访问入口。 * 日志数据库访问入口。
@@ -30,15 +34,14 @@ public final class LogDBManager {
private static final String LOGCAT_TAG = "LogDBManager"; private static final String LOGCAT_TAG = "LogDBManager";
private static final int PAGE_SIZE = 20; private static final int PAGE_SIZE = 20;
private static final int RETENTION_DAYS = 15; private static final int RETENTION_DAYS = 15;
private static final String CREATE_DATETIME_INDEX = private static final int CLEANUP_BATCH_SIZE = 500;
"CREATE INDEX IF NOT EXISTS IDX_LOG_DATETIME_ID ON LOG (DATETIME DESC, _id DESC)"; private static final long CLEANUP_BATCH_DELAY_MS = 30L;
private static final String CREATE_TAG_DATETIME_INDEX = private static final long SYNC_WAIT_SECONDS = 10L;
"CREATE INDEX IF NOT EXISTS IDX_LOG_TAG_DATETIME_ID ON LOG (TAG, DATETIME DESC, _id DESC)";
private static volatile Thread databaseThread; private static volatile Thread databaseThread;
private static volatile boolean indexesReady; private static final AtomicBoolean CLEANUP_RUNNING = new AtomicBoolean(false);
private static final ExecutorService EXECUTOR = Executors.newSingleThreadExecutor(runnable -> { private static final ScheduledExecutorService EXECUTOR = Executors.newSingleThreadScheduledExecutor(runnable -> {
Thread thread = new Thread(() -> { Thread thread = new Thread(() -> {
databaseThread = Thread.currentThread(); databaseThread = Thread.currentThread();
runnable.run(); runnable.run();
@@ -52,13 +55,6 @@ public final class LogDBManager {
private LogDBManager() { private LogDBManager() {
} }
/**
* 后台准备日志索引。首次升级可能需要一定时间,但不会阻塞应用启动流程。
*/
public static void prepareAsync() {
EXECUTOR.execute(LogDBManager::ensureIndexes);
}
/** /**
* 异步增加普通诊断日志。 * 异步增加普通诊断日志。
*/ */
@@ -67,10 +63,15 @@ public final class LogDBManager {
} }
/** /**
* 步落库,供需要确保“本地成功后再上传”的用户日志使用 * 步落库并回调结果。回调在日志数据库线程执行,不允许执行耗时或UI操作
*/ */
public static boolean insertLogSync(com.example.iot_controlhost.model.Log log) { public static void insertLog(com.example.iot_controlhost.model.Log log, InsertCallback callback) {
return executeSync(() -> insertInternal(log), false); EXECUTOR.execute(() -> {
boolean saved = insertInternal(log);
if (callback != null) {
callback.onComplete(saved);
}
});
} }
private static boolean insertInternal(com.example.iot_controlhost.model.Log log) { private static boolean insertInternal(com.example.iot_controlhost.model.Log log) {
@@ -108,18 +109,13 @@ public final class LogDBManager {
private static List<com.example.iot_controlhost.model.Log> queryPage( private static List<com.example.iot_controlhost.model.Log> queryPage(
String filter, com.example.iot_controlhost.model.Log cursor) { String filter, com.example.iot_controlhost.model.Log cursor) {
return executeSync(() -> { return executeSync(() -> {
ensureIndexes();
QueryBuilder<com.example.iot_controlhost.model.Log> qb = LOG_DAO.queryBuilder(); QueryBuilder<com.example.iot_controlhost.model.Log> qb = LOG_DAO.queryBuilder();
List<String> tags = UserLog.getStorageTagsForFilter(filter); List<String> tags = UserLog.getStorageTagsForFilter(filter);
qb.where(LogDao.Properties.Tag.in(tags)); qb.where(LogDao.Properties.Tag.in(tags));
if (cursor != null) { if (cursor != null && cursor.getId() != null) {
WhereCondition olderDate = LogDao.Properties.Datetime.lt(cursor.getDatetime()); qb.where(LogDao.Properties.Id.lt(cursor.getId()));
WhereCondition sameDateAndOlderId = qb.and(
LogDao.Properties.Datetime.eq(cursor.getDatetime()),
LogDao.Properties.Id.lt(cursor.getId()));
qb.where(qb.or(olderDate, sameDateAndOlderId));
} }
return qb.orderDesc(LogDao.Properties.Datetime, LogDao.Properties.Id) return qb.orderDesc(LogDao.Properties.Id)
.limit(PAGE_SIZE) .limit(PAGE_SIZE)
.list(); .list();
}, Collections.emptyList()); }, Collections.emptyList());
@@ -130,10 +126,9 @@ public final class LogDBManager {
*/ */
public static List<com.example.iot_controlhost.model.Log> queryList(String filter, int currentPage) { public static List<com.example.iot_controlhost.model.Log> queryList(String filter, int currentPage) {
return executeSync(() -> { return executeSync(() -> {
ensureIndexes();
return LOG_DAO.queryBuilder() return LOG_DAO.queryBuilder()
.where(LogDao.Properties.Tag.in(UserLog.getStorageTagsForFilter(filter))) .where(LogDao.Properties.Tag.in(UserLog.getStorageTagsForFilter(filter)))
.orderDesc(LogDao.Properties.Datetime, LogDao.Properties.Id) .orderDesc(LogDao.Properties.Id)
.offset(Math.max(0, currentPage - 1) * PAGE_SIZE) .offset(Math.max(0, currentPage - 1) * PAGE_SIZE)
.limit(PAGE_SIZE) .limit(PAGE_SIZE)
.list(); .list();
@@ -142,10 +137,9 @@ public final class LogDBManager {
public static List<com.example.iot_controlhost.model.Log> queryDateRange(Date startDate, Date endDate) { public static List<com.example.iot_controlhost.model.Log> queryDateRange(Date startDate, Date endDate) {
return executeSync(() -> { return executeSync(() -> {
ensureIndexes();
return LOG_DAO.queryBuilder() return LOG_DAO.queryBuilder()
.where(LogDao.Properties.Datetime.between(startDate, endDate)) .where(LogDao.Properties.Datetime.between(startDate, endDate))
.orderDesc(LogDao.Properties.Datetime, LogDao.Properties.Id) .orderDesc(LogDao.Properties.Id)
.list(); .list();
}, Collections.emptyList()); }, Collections.emptyList());
} }
@@ -155,7 +149,6 @@ public final class LogDBManager {
*/ */
public static List<com.example.iot_controlhost.model.Log> queryAllList() { public static List<com.example.iot_controlhost.model.Log> queryAllList() {
return executeSync(() -> { return executeSync(() -> {
ensureIndexes();
Calendar calendar = Calendar.getInstance(); Calendar calendar = Calendar.getInstance();
Date currentDate = calendar.getTime(); Date currentDate = calendar.getTime();
calendar.add(Calendar.DAY_OF_YEAR, -7); calendar.add(Calendar.DAY_OF_YEAR, -7);
@@ -167,7 +160,7 @@ public final class LogDBManager {
MyLog.TAG_APP + MyLog.TAG_ERROR, MyLog.TAG_APP + MyLog.TAG_ERROR,
MyLog.TAG_NETWORK, MyLog.TAG_NETWORK,
MyLog.TAG_NETWORK + MyLog.TAG_ERROR)) MyLog.TAG_NETWORK + MyLog.TAG_ERROR))
.orderDesc(LogDao.Properties.Datetime, LogDao.Properties.Id) .orderDesc(LogDao.Properties.Id)
.list(); .list();
}, Collections.emptyList()); }, Collections.emptyList());
} }
@@ -198,46 +191,66 @@ public final class LogDBManager {
} }
/** /**
* 每日后台清理十五天前的日志。 * 每日后台分批清理十五天前的日志。每批之间主动让出数据库线程,
* 避免巨量历史数据形成大事务或长时间独占SQLite连接。
*/ */
public static void clearExpiredLogsAsync() { public static void clearExpiredLogsAsync() {
EXECUTOR.execute(() -> { if (!CLEANUP_RUNNING.compareAndSet(false, true)) {
long start = System.currentTimeMillis(); Log.i(LOGCAT_TAG, "日志清理任务已在执行,本次跳过");
ensureIndexes();
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.DAY_OF_YEAR, -RETENTION_DAYS);
Date cutoff = calendar.getTime();
try {
long count = LOG_DAO.queryBuilder()
.where(LogDao.Properties.Datetime.lt(cutoff))
.count();
if (count > 0) {
LOG_DAO.queryBuilder()
.where(LogDao.Properties.Datetime.lt(cutoff))
.buildDelete()
.executeDeleteWithoutDetachingEntities();
LOG_DAO.detachAll();
}
long duration = System.currentTimeMillis() - start;
MyLog.app("日志维护完成:保留" + RETENTION_DAYS + "天,清理" + count
+ "条,耗时" + duration + "ms");
} catch (Exception e) {
Log.e(LOGCAT_TAG, "清理过期日志失败", e);
}
});
}
private static void ensureIndexes() {
if (indexesReady) {
return; return;
} }
try { Calendar calendar = Calendar.getInstance();
LOG_DAO.getDatabase().execSQL(CREATE_DATETIME_INDEX); calendar.add(Calendar.DAY_OF_YEAR, -RETENTION_DAYS);
LOG_DAO.getDatabase().execSQL(CREATE_TAG_DATETIME_INDEX); CleanupState state = new CleanupState(calendar.getTimeInMillis(), System.currentTimeMillis());
indexesReady = true; EXECUTOR.execute(() -> clearNextBatch(state));
} catch (Exception e) {
Log.e(LOGCAT_TAG, "创建日志索引失败", e);
} }
private static void clearNextBatch(CleanupState state) {
try {
SQLiteDatabase database = getRawDatabase();
Batch batch = findExpiredBatch(database, state.lastId, state.cutoffMillis);
if (batch.count == 0) {
LOG_DAO.detachAll();
CLEANUP_RUNNING.set(false);
long duration = System.currentTimeMillis() - state.startMillis;
MyLog.app("日志维护完成:保留" + RETENTION_DAYS + "天,分批清理"
+ state.deletedCount + "条,耗时" + duration + "ms");
return;
}
database.execSQL(
"DELETE FROM LOG WHERE _id > ? AND _id <= ? AND DATETIME < ?",
new Object[]{state.lastId, batch.lastId, state.cutoffMillis});
state.lastId = batch.lastId;
state.deletedCount += batch.count;
EXECUTOR.schedule(() -> clearNextBatch(state), CLEANUP_BATCH_DELAY_MS, TimeUnit.MILLISECONDS);
} catch (Exception e) {
CLEANUP_RUNNING.set(false);
Log.e(LOGCAT_TAG, "分批清理过期日志失败", e);
}
}
private static Batch findExpiredBatch(SQLiteDatabase database, long lastId, long cutoffMillis) {
long batchLastId = lastId;
int count = 0;
String sql = "SELECT _id FROM LOG WHERE _id > ? AND DATETIME < ? ORDER BY _id LIMIT "
+ CLEANUP_BATCH_SIZE;
try (Cursor cursor = database.rawQuery(sql,
new String[]{String.valueOf(lastId), String.valueOf(cutoffMillis)})) {
while (cursor.moveToNext()) {
batchLastId = cursor.getLong(0);
count++;
}
}
return new Batch(batchLastId, count);
}
private static SQLiteDatabase getRawDatabase() {
Object rawDatabase = LOG_DAO.getDatabase().getRawDatabase();
if (!(rawDatabase instanceof SQLiteDatabase)) {
throw new IllegalStateException("日志数据库类型不受支持:" + rawDatabase);
}
return (SQLiteDatabase) rawDatabase;
} }
private static <T> T executeSync(Callable<T> task, T fallback) { private static <T> T executeSync(Callable<T> task, T fallback) {
@@ -250,13 +263,41 @@ public final class LogDBManager {
} }
} }
try { try {
return EXECUTOR.submit(task).get(); return EXECUTOR.submit(task).get(SYNC_WAIT_SECONDS, TimeUnit.SECONDS);
} catch (InterruptedException e) { } catch (InterruptedException e) {
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
Log.e(LOGCAT_TAG, "等待日志数据库操作时被中断", e); Log.e(LOGCAT_TAG, "等待日志数据库操作时被中断", e);
} catch (ExecutionException e) { } catch (ExecutionException e) {
Log.e(LOGCAT_TAG, "日志数据库操作失败", e.getCause()); Log.e(LOGCAT_TAG, "日志数据库操作失败", e.getCause());
} catch (TimeoutException e) {
Log.e(LOGCAT_TAG, "等待日志数据库操作超时", e);
} }
return fallback; return fallback;
} }
public interface InsertCallback {
void onComplete(boolean saved);
}
private static final class CleanupState {
private final long cutoffMillis;
private final long startMillis;
private long lastId;
private long deletedCount;
private CleanupState(long cutoffMillis, long startMillis) {
this.cutoffMillis = cutoffMillis;
this.startMillis = startMillis;
}
}
private static final class Batch {
private final long lastId;
private final int count;
private Batch(long lastId, int count) {
this.lastId = lastId;
this.count = count;
}
}
} }
@@ -298,7 +298,7 @@ public class RoomController {
// 检查除湿机功率是否与除湿机物理电源状态匹配,时刻监测当前除湿机功率状态,使之与软件开关匹配 // 检查除湿机功率是否与除湿机物理电源状态匹配,时刻监测当前除湿机功率状态,使之与软件开关匹配
// 防止出现软件界面中开启除湿器但实际上除湿器未开启(功率不足100W)的情况 // 防止出现软件界面中开启除湿器但实际上除湿器未开启(功率不足100W)的情况
if (RoomController.dehumidifier.isAvailable() || RoomController.dehumidifier485Relay.isAvailable()) { if (RoomController.dehumidifier.isAvailable() || RoomController.dehumidifier485Relay.isAvailable()) {
PollingTask.getInstance().startPollingTaskOnIOThread(RxTag.TAG_DEHUMIDIFIER_STATUS, 60, () -> { PollingTask.getInstance().startPollingTaskOnIOThread(RxTag.TAG_DEHUMIDIFIER_STATUS, 60 * 5, () -> {
if (RoomController.dehumidifier.isAvailable() && RoomController.dehumidifier.isPowerSupply() && RoomController.dehumidifier.getPower() < 100) { if (RoomController.dehumidifier.isAvailable() && RoomController.dehumidifier.isPowerSupply() && RoomController.dehumidifier.getPower() < 100) {
MyLog.controllerError("除湿机电源已开启但功率不足100W,现尝试再次开启除湿机红外电源");//红外开关是一样的指令 所以这里等于是重新发送 MyLog.controllerError("除湿机电源已开启但功率不足100W,现尝试再次开启除湿机红外电源");//红外开关是一样的指令 所以这里等于是重新发送
MyQueue.getInstance(MyQueue.TYPE_CONTROLLER).addTask(new QueueIOTask(() -> RoomController.dehumidifier.setPowerSupply(true))); MyQueue.getInstance(MyQueue.TYPE_CONTROLLER).addTask(new QueueIOTask(() -> RoomController.dehumidifier.setPowerSupply(true)));
@@ -35,11 +35,12 @@ public class CrashHandlerUtil implements Thread.UncaughtExceptionHandler {
public void uncaughtException(Thread thread, Throwable ex) { public void uncaughtException(Thread thread, Throwable ex) {
ex.printStackTrace(); ex.printStackTrace();
MyLog.appError("软件已崩溃,重启应用:" + getFormattedException(ex)); MyLog.appError("软件已崩溃,重启应用:" + getFormattedException(ex));
UserLog.systemErrorImmediately("程序运行异常,系统将自动重启"); // 先启动重启保护,任何日志或数据库异常都不能阻断恢复流程。
new Thread(() -> { new Thread(() -> {
try { Thread.sleep(500); } catch (Exception ignored) { } try { Thread.sleep(500); } catch (Exception ignored) { }
ProcessPhoenix.triggerRebirth(mContext); ProcessPhoenix.triggerRebirth(mContext);
}).start(); }).start();
UserLog.systemErrorImmediately("程序运行异常,系统将自动重启");
} }
@@ -8,8 +8,6 @@ import static com.example.iot_controlhost.utils.log.UserLog.TAG_USER_TASK;
import android.util.Log; import android.util.Log;
import com.example.iot_controlhost.utils.database.LogDBManager; import com.example.iot_controlhost.utils.database.LogDBManager;
import com.xuexiang.rxutil2.rxjava.RxJavaUtils;
import com.xuexiang.rxutil2.rxjava.task.RxIOTask;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
@@ -152,19 +150,13 @@ public class MyLog extends Timber.Tree {
@Override @Override
protected void log(int priority, String tag, String message, Throwable t) { protected void log(int priority, String tag, String message, Throwable t) {
// 不处理UserLog日志 // 不处理UserLog日志
if (tag.contains(TAG_USER_OPERATE) || tag.contains(TAG_USER_SENSOR) || tag.contains(TAG_USER_TASK) || tag.contains(TAG_USER_SYSTEM)) { if (tag == null || tag.contains(TAG_USER_OPERATE) || tag.contains(TAG_USER_SENSOR)
|| tag.contains(TAG_USER_TASK) || tag.contains(TAG_USER_SYSTEM)) {
return; return;
} }
RxJavaUtils.doInIOThread(new RxIOTask<>(null) { // 控制台立即输出,数据库写入由日志单线程异步处理,避免重复切换IO线程。
@Override
public Void doInIOThread(Object o) {
//输出日志到控制台
Log.println(priority, tag, message); Log.println(priority, tag, message);
//保存日志到数据库
LogDBManager.insertLog(new com.example.iot_controlhost.model.Log(tag, priority, new Date(), message)); LogDBManager.insertLog(new com.example.iot_controlhost.model.Log(tag, priority, new Date(), message));
return null;
}
});
} }
} }
@@ -3,11 +3,7 @@ package com.example.iot_controlhost.utils.log;
import android.util.Log; import android.util.Log;
import com.example.iot_controlhost.utils.database.LogDBManager; import com.example.iot_controlhost.utils.database.LogDBManager;
import com.example.iot_controlhost.utils.global.RxTag;
import com.example.iot_controlhost.utils.network.TopicClass; import com.example.iot_controlhost.utils.network.TopicClass;
import com.xuexiang.rxutil2.rxbus.RxBusUtils;
import com.xuexiang.rxutil2.rxjava.RxJavaUtils;
import com.xuexiang.rxutil2.rxjava.task.RxIOTask;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@@ -142,7 +138,7 @@ public class UserLog extends Timber.Tree {
} }
/** /**
* 崩溃场景使用同步入口,确保进程重启前日志已经落库 * 崩溃场景使用非阻塞入口,防止数据库繁忙时阻断进程重启
*/ */
public static void systemErrorImmediately(String msg) { public static void systemErrorImmediately(String msg) {
persistAndUpload(TAG_USER_SYSTEM + MyLog.TAG_ERROR, Log.ERROR, msg, new Date()); persistAndUpload(TAG_USER_SYSTEM + MyLog.TAG_ERROR, Log.ERROR, msg, new Date());
@@ -158,13 +154,7 @@ public class UserLog extends Timber.Tree {
if (tag == null || !isUserLogTag(tag)) { if (tag == null || !isUserLogTag(tag)) {
return; return;
} }
RxJavaUtils.doInIOThread(new RxIOTask<>(null) {
@Override
public Void doInIOThread(Object o) {
persistAndUpload(tag, priority, message, new Date()); persistAndUpload(tag, priority, message, new Date());
return null;
}
});
} }
private static boolean isUserLogTag(String tag) { private static boolean isUserLogTag(String tag) {
@@ -177,13 +167,14 @@ public class UserLog extends Timber.Tree {
private static void persistAndUpload(String tag, int priority, String message, Date datetime) { private static void persistAndUpload(String tag, int priority, String message, Date datetime) {
com.example.iot_controlhost.model.Log entry = com.example.iot_controlhost.model.Log entry =
new com.example.iot_controlhost.model.Log(tag, priority, datetime, message); new com.example.iot_controlhost.model.Log(tag, priority, datetime, message);
boolean saved = LogDBManager.insertLogSync(entry);
Log.println(priority, tag, message); Log.println(priority, tag, message);
LogDBManager.insertLog(entry, saved -> {
if (!saved) { if (!saved) {
Log.e(TAG_USER_SYSTEM, "用户日志本地落库失败,已取消上传以避免两端数据不一致"); Log.e(TAG_USER_SYSTEM, "用户日志本地落库失败,已取消上传以避免两端数据不一致");
return; return;
} }
TopicClass.uploadLog(toServerTag(tag), toServerPriority(tag), message, datetime); TopicClass.uploadLog(toServerTag(tag), toServerPriority(tag), message, datetime);
});
} }
private static int toServerTag(String tag) { private static int toServerTag(String tag) {