且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

如何在 Room 持久化库中捕获未处理的异常

更新时间: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);
        }
    }