更新时间:2023-01-12 08:25:30
该目标可以通过注册一个执行具有自定义异常处理程序的自定义线程来实现.
The goal can be achieved by registering a custom thread executing having a custom exception handler.
我想出了以下解决方案:
I came up with the following solution:
public abstract class LocalDatabase extends RoomDatabase {
private static final String TAG = LocalDatabase.class.getSimpleName();
private static final Object syncObj = new Object();
private static LocalDatabase localDatabase;
private static ConcurrentHashMap<Integer, String> dbToInstanceId = new ConcurrentHashMap<>();
private static ConcurrentHashMap<Long, String> threadToInstanceId = new ConcurrentHashMap<>();
public abstract LocalDao getDao();
public static LocalDatabase getInstance() {
if (localDatabase == null) {
localDatabase = buildDb();
}
return localDatabase;
}
private static LocalDatabase buildDb() {
// keep track of which thread belongs to which local database
final String instanceId = UUID.randomUUID().toString();
// custom thread with an exception handler strategy
ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newCachedThreadPool(runnable -> {
ThreadFactory defaultThreadFactory = Executors.defaultThreadFactory();
Thread thread = defaultThreadFactory.newThread(runnable);
thread.setUncaughtExceptionHandler(resetDatabaseOnUnhandledException);
threadToInstanceId.put(thread.getId(), instanceId);
return thread;
});
LocalDatabase localDatabase = Room.databaseBuilder(App.getInstance().getApplicationContext(),
LocalDatabase.class, "LocalDatabase")
.fallbackToDestructiveMigration()
.setQueryExecutor(executor)
.build();
dbToInstanceId.put(localDatabase.hashCode(), instanceId);
return localDatabase;
}
static Thread.UncaughtExceptionHandler resetDatabaseOnUnhandledException = new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread thread, Throwable throwable) {
Log.e("", "uncaught exception in a LocalDatabase thread, resetting the database", throwable);
synchronized (syncObj) {
// there is no active local database to clean up
if (localDatabase == null) return;
String instanceIdOfThread = threadToInstanceId.get(thread.getId());
String instanceIdOfActiveLocalDb = dbToInstanceId.get(localDatabase.hashCode());
if(instanceIdOfThread == null || !instanceIdOfThread.equals(instanceIdOfActiveLocalDb)) {
// the active local database instance is not the one that caused this thread to fail, so leave it as is
return;
}
localDatabase.tryResetDatabase();
}
}
};
public void tryResetDatabase() {
try {
String dbName = this.getOpenHelper().getDatabaseName();
// try closing existing connections
try {
if(this.getOpenHelper().getWritableDatabase().isOpen()) {
this.getOpenHelper().getWritableDatabase().close();
}
if(this.getOpenHelper().getReadableDatabase().isOpen()) {
this.getOpenHelper().getReadableDatabase().close();
}
if (this.isOpen()) {
this.close();
}
if(this == localDatabase) localDatabase = null;
} catch (Exception ex) {
Log.e(TAG, "Could not close LocalDatabase", ex);
}
// try deleting database file
File f = App.getContext().getDatabasePath(dbName);
if (f.exists()) {
boolean deleteSucceeded = SQLiteDatabase.deleteDatabase(f);
if (!deleteSucceeded) {
Log.e(TAG, "Could not delete LocalDatabase");
}
}
LocalDatabase tmp = buildDb();
tmp.query("SELECT * from Match", null);
tmp.close();
this.getOpenHelper().getReadableDatabase();
this.getOpenHelper().getWritableDatabase();
this.query("SELECT * from Match", null);
} catch (Exception ex) {
Log.e("", "Could not reset LocalDatabase", ex);
}
}