优化日志逻辑;修改除湿机自动重发指令的时间间隔;
This commit is contained in:
+2
-2
@@ -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,48 +191,68 @@ 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;
|
||||||
}
|
}
|
||||||
|
Calendar calendar = Calendar.getInstance();
|
||||||
|
calendar.add(Calendar.DAY_OF_YEAR, -RETENTION_DAYS);
|
||||||
|
CleanupState state = new CleanupState(calendar.getTimeInMillis(), System.currentTimeMillis());
|
||||||
|
EXECUTOR.execute(() -> clearNextBatch(state));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void clearNextBatch(CleanupState state) {
|
||||||
try {
|
try {
|
||||||
LOG_DAO.getDatabase().execSQL(CREATE_DATETIME_INDEX);
|
SQLiteDatabase database = getRawDatabase();
|
||||||
LOG_DAO.getDatabase().execSQL(CREATE_TAG_DATETIME_INDEX);
|
Batch batch = findExpiredBatch(database, state.lastId, state.cutoffMillis);
|
||||||
indexesReady = true;
|
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) {
|
} catch (Exception e) {
|
||||||
Log.e(LOGCAT_TAG, "创建日志索引失败", 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) {
|
||||||
if (Thread.currentThread() == databaseThread) {
|
if (Thread.currentThread() == databaseThread) {
|
||||||
try {
|
try {
|
||||||
@@ -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
|
Log.println(priority, tag, message);
|
||||||
public Void doInIOThread(Object o) {
|
LogDBManager.insertLog(new com.example.iot_controlhost.model.Log(tag, priority, new Date(), message));
|
||||||
//输出日志到控制台
|
|
||||||
Log.println(priority, tag, 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) {
|
persistAndUpload(tag, priority, message, new Date());
|
||||||
@Override
|
|
||||||
public Void doInIOThread(Object o) {
|
|
||||||
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);
|
||||||
if (!saved) {
|
LogDBManager.insertLog(entry, saved -> {
|
||||||
Log.e(TAG_USER_SYSTEM, "用户日志本地落库失败,已取消上传以避免两端数据不一致");
|
if (!saved) {
|
||||||
return;
|
Log.e(TAG_USER_SYSTEM, "用户日志本地落库失败,已取消上传以避免两端数据不一致");
|
||||||
}
|
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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user