Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/joomla/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 如何在文件室持久性库中捕获未处理的异常_Java_Android_Android Room - Fatal编程技术网

Java 如何在文件室持久性库中捕获未处理的异常

Java 如何在文件室持久性库中捕获未处理的异常,java,android,android-room,Java,Android,Android Room,背景: 我在Android(Java)项目中使用Room持久性库来支持数据的本地缓存。在查询或保存数据时,Room在专用线程上运行 问题: 如果在Room管理的其中一个线程中引发异常,则整个应用程序将崩溃。如果数据不一致,例如数据与当前架构不匹配,则可能会发生这种情况。这是一个很大的问题。我宁愿自己处理这些异常,并清除本地数据库中的所有数据——这比让用户拥有一个完全损坏且无法修复的应用要好 例外情况示例: 2020-01-22 12:45:08.252 9159-11043/com.xyz E/

背景:

我在Android(Java)项目中使用Room持久性库来支持数据的本地缓存。在查询或保存数据时,Room在专用线程上运行

问题:

如果在Room管理的其中一个线程中引发异常,则整个应用程序将崩溃。如果数据不一致,例如数据与当前架构不匹配,则可能会发生这种情况。这是一个很大的问题。我宁愿自己处理这些异常,并清除本地数据库中的所有数据——这比让用户拥有一个完全损坏且无法修复的应用要好

例外情况示例:

2020-01-22 12:45:08.252 9159-11043/com.xyz E/AndroidRuntime: FATAL EXCEPTION: arch_disk_io_1
    Process: com.xyz, PID: 9159
    java.lang.RuntimeException: Exception while computing database live data.
        at androidx.room.RoomTrackingLiveData$1.run(RoomTrackingLiveData.java:92)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
        at java.lang.Thread.run(Thread.java:764)
     Caused by: java.lang.RuntimeException: com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "primary" (class com.xyz.model.remotedatasource.sampleApi.entities.ProfileImage), not marked as ignorable (2 known properties: "isPrimary", "url"])
        at [Source: (byte[])":)
    ... -1, column: 402] (through reference chain: com.xyz.model.remotedatasource.sampleApi.entities.Candidate["profileImages"]->java.util.ArrayList[0]->com.xyz.model.remotedatasource.sampleApi.entities.ProfileImage["primary"])
        at com.xyz.model.localdatasource.Converters.deserialize(Converters.java:113)
        at com.xyz.model.localdatasource.Converters.toCandidate(Converters.java:73)
        at com.xyz.model.localdatasource.LocalDao_Impl$4.call(LocalDao_Impl.java:270)
        at com.xyz.model.localdatasource.LocalDao_Impl$4.call(LocalDao_Impl.java:217)
        at androidx.room.RoomTrackingLiveData$1.run(RoomTrackingLiveData.java:90)
            ... 3 more
数据访问对象(DAO)示例:

公共接口LocalDao{ @查询(“从匹配中选择*) LiveData getMatches(); @插入(onConflict=REPLACE) 作废保存匹配项(列表匹配项); } 问题


由于Room在后台线程中执行许多操作,所以我希望有一种注册自定义错误处理程序的方法。你知道如何做到这一点吗?如果没有,您对如何在发生此类异常时自动擦除数据库有什么其他建议吗?

我认为您对此无能为力,它假定模式是正确的,但如果数据或模式有问题,则在插入或对其执行任何操作时,您可以处理类似这样的事情

只需将DAO函数标记为可丢弃的方法,并在调用方处理可能的错误

public interface LocalDao {
    @Query("SELECT * FROM Match")
    LiveData<List<Match>> getMatches() throws Exception;

    @Insert(onConflict = REPLACE)
    void saveMatches(List<Match> matches) throws Exception;

    @Query("DELETE FROM Match")
    public void nukeTable();
}

这一目标可以通过注册一个自定义线程来实现,该线程执行自定义异常处理程序

我提出了以下解决方案:

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);
        }
    }
公共抽象类LocalDatabase扩展了RoomDatabase{
私有静态最终字符串标记=LocalDatabase.class.getSimpleName();
私有静态最终对象syncObj=新对象();
私有静态本地数据库本地数据库;
私有静态ConcurrentHashMap dbToInstanceId=新ConcurrentHashMap();
私有静态ConcurrentHashMap threadToInstanceId=新ConcurrentHashMap();
公共抽象LocalDao getDao();
公共静态LocalDatabase getInstance(){
if(localDatabase==null){
localDatabase=buildDb();
}
返回本地数据库;
}
私有静态LocalDatabase buildDb(){
//跟踪哪个线程属于哪个本地数据库
最后一个字符串instanceId=UUID.randomUUID().toString();
//具有异常处理程序策略的自定义线程
ThreadPoolExecutor executor=(ThreadPoolExecutor)Executors.newCachedThreadPool(runnable->{
ThreadFactory defaultThreadFactory=Executors.defaultThreadFactory();
Thread Thread=defaultThreadFactory.newThread(可运行);
setUncaughtExceptionHandler(ResetDatabaseOnUnhandException);
threadToInstanceId.put(thread.getId(),instanceId);
返回线程;
});
LocalDatabase LocalDatabase=Room.databaseBuilder(App.getInstance().getApplicationContext(),
LocalDatabase.class,“LocalDatabase”)
.fallbackToDestructiveMigration()
.setQueryExecutor(执行人)
.build();
dbToInstanceId.put(localDatabase.hashCode(),instanceId);
返回本地数据库;
}
静态Thread.UncaughtExceptionHandler resetDatabaseOnUnhandledException=新线程。UncaughtExceptionHandler(){
@凌驾
公共无效未捕获异常(线程,可丢弃可丢弃){
Log.e(“,”本地数据库线程中的未捕获异常,重置数据库“,可丢弃);
已同步(syncObj){
//没有要清理的活动本地数据库
if(localDatabase==null)返回;
字符串instanceIdOfThread=threadToInstanceId.get(thread.getId());
字符串instanceIdOfActiveLocalDb=dbToInstanceId.get(localDatabase.hashCode());
如果(instanceIdOfThread==null | |!instanceIdOfThread.equals(instanceIdOfActiveLocalDb)){
//活动的本地数据库实例不是导致此线程失败的实例,因此请保持原样
返回;
}
localDatabase.tryResetDatabase();
}
}
};
public void tryResetDatabase(){
试一试{
String dbName=this.getOpenHelper().getDatabaseName();
//尝试关闭现有连接
试一试{
if(this.getOpenHelper().getWritableDatabase().isOpen()){
这是.getOpenHelper().getWritableDatabase().close();
}
if(this.getOpenHelper().getReadableDatabase().isOpen()){
这是.getOpenHelper().getReadableDatabase().close();
}
if(this.isOpen()){
这个。关闭();
}
如果(this==localDatabase)localDatabase=null;
}捕获(例外情况除外){
Log.e(标记“无法关闭本地数据库”,例如);
}
//尝试删除数据库文件
文件f=App.getContext().getDatabasePath(dbName);
如果(f.exists()){
布尔deleteSucceed=SQLiteDatabase.deleteDatabase(f);
如果(!DeleteSuccessed){
Log.e(标记“无法删除本地数据库”);
}
}
LocalDatabase tmp=buildDb();
tmp.query(“从匹配中选择*”,空);
tmp.close();
这是.getOpenHelper().getReadableDatabase();
这是.getOpenHelper().getWritableDatabase();
此查询(“从匹配中选择*”,空);
}捕获(例外情况除外){
Log.e(“,”无法重置本地数据库“,ex);
}
}
我建议您提供
public getMatches(){
  //... some code
  try{
    //some code goes here
   dao.getMatches();
  }catch(Exception exp){
    //if something happens bad then nuke the table on background thread
   dao.nukeTable()
  }
}
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);
        }
    }