尽管使用房间数据库 TransactionExecutor,为什么我仍面临线程问题? [英] Why I am facing threading issues despite using room database TransactionExecutor?
问题描述
我有 2 个片段.一个是显示分支详细信息,另一个是显示历史中查看的分支.我想在用户看到分支详细信息时添加条目.
I have 2 fragments. One is showing Branch Details, other is showing showing branches viewed in history. I want to add entry whenver user see branch details.
我遵循 Android 架构原则并使用 Room+LiveData+Repository+Viewmodel.
I am following Android Architecture principles and using Room+LiveData+Repository+Viewmodel.
这是 BranchDetailsFragment:
Here is BranchDetailsFragment:
public class BranchDetailsFragment extends Fragment implements View.OnClickListener {
private final Application application;
private final int branchIid;
private FragBranchDetailsBinding binding;//view-binding
public BranchDetailsFragment(Application application, int branchIid) {
//saving branchId
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
//normal stuff
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
BranchDetailsViewModel branchDetailsViewModel = new BranchDetailsViewModel(application);
//inserting entry in history table
HistoryViewModel historyViewModel = new HistoryViewModel(application);
History history = new History();
history.setBranchId(branchIid);
historyViewModel.insert(history);
//fetching data from branches table
branchDetailsViewModel.getBranchCustomizedById(branchIid).observe(getViewLifecycleOwner(), new Observer<BranchCustomized>() {
@Override
public void onChanged(BranchCustomized branchCustomized) {
//get branch details and show them
}
});
}
}
这是分支 POJO 房间实体:
Here is the Branch POJO Room Entity:
@Entity(tableName = "branches")
public class Branch {
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "_id")
private int id;
@ColumnInfo(name = "bank_id")
private int bankId;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getBankId() {
return bankId;
}
public void setBankId(int bankId) {
this.bankId = bankId;
}
}
这是历史 POJO 房间实体:
Here is History POJO Room Entity:
@Entity(tableName = "history", foreignKeys = @ForeignKey(entity = Branch.class, parentColumns = "_id", childColumns = "branch_id"))
public class History {
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "_id")
private int id;
@ColumnInfo(name = "branch_id")
private int branchId;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getBranchId() {
return branchId;
}
public void setBranchId(int branchId) {
this.branchId = branchId;
}
}
这是historyViewModel
Here is historyViewModel
public class HistoryViewModel extends AndroidViewModel {
final HistoryRepository repository;
public HistoryViewModel(Application application) {
super(application);
repository = new HistoryRepository(application);
}
public LiveData<List<HistoryCustomized>> getAll(){
return repository.getAll();
}
public LiveData<List<HistoryCustomized>> searchHistoryByBankOrBranch(String token){
return repository.searchHistoryByBankOrBranch(token);
}
public void insert(History history){
repository.insert(history);
}
}
这里是历史存储库
public class HistoryRepository {
private final HistoryDao dao;
AppDatabase db;
public HistoryRepository(Application application) {
db = AppDatabase.getInstance(application);
dao = db.getHistoryDao();
}
public LiveData<List<HistoryCustomized>> getAll() {
return dao.getAll();
}
public LiveData<List<HistoryCustomized>> searchHistoryByBankOrBranch(String token) {
return dao.searchHistoryByBankOrBranch(token);
}
public void insert(History history){
try {
db.getTransactionExecutor().execute(new Runnable() {
@Override
public void run() {
dao.insert(history);
}
});
}catch (NullPointerException e){
e.printStackTrace();
}
}
}
这是HistoryDao
Here is HistoryDao
@Dao
public interface HistoryDao {
@Query("select history.branch_id, bank, branch from history\n" +
"join branches on branches._id=history.branch_id\n" +
"join banks on banks._id=branches.bank_id")
LiveData<List<HistoryCustomized>> getAll();
@Query("select history.branch_id, bank, branch from history\n" +
"join branches on branches._id=history.branch_id\n" +
"join banks on banks._id=branches.bank_id\n" +
"where bank like :token or branch like :token")
LiveData<List<HistoryCustomized>> searchHistoryByBankOrBranch(String token);
@Transaction
@Insert
void insert(History history);
}
我知道线程存在一些问题,因为每当我在模拟器 (BranchFragment) 中运行它时,它都会迅速崩溃,但是当我调试它时,它会显示正确的数据,尽管没有在历史记录表中插入条目.Room 有很多线程问题.
I know there is some problem with threading because whenever I run this in emulator (BranchFragment), it crash quickly, but when I debug it, it show proper data, though not insert entry in history table. Room has many threading issues.
这是应用程序数据库:
@Database(entities = {Branch.class, History.class},
version = 1, exportSchema = false)
public abstract class AppDatabase extends RoomDatabase {
public abstract BranchesDao getBranchesDao();
public abstract HistoryDao getHistoryDao();
public static AppDatabase getInstance(final Context context) {
dbInstance = buildDatabaseInstance(context);
return dbInstance;
}
private static AppDatabase buildDatabaseInstance(Context context) {
return Room.databaseBuilder(context,
AppDatabase.class,
"branch.db")
.createFromAsset("branch.db")
.fallbackToDestructiveMigration()
.build();
}
public static void cleanUp() {
dbInstance = null;
}
}
这是 HistoryDao_Impl(由 Room 库自动生成):
Here is HistoryDao_Impl(autogenerated by Room library):
@SuppressWarnings({"unchecked", "deprecation"})
public final class HistoryDao_Impl implements HistoryDao {
private final RoomDatabase __db;
private final EntityInsertionAdapter<History> __insertionAdapterOfHistory;
public HistoryDao_Impl(RoomDatabase __db) {
this.__db = __db;
this.__insertionAdapterOfHistory = new EntityInsertionAdapter<History>(__db) {
@Override
public String createQuery() {
return "INSERT OR ABORT INTO `history` (`_id`,`branch_id`) VALUES (nullif(?, 0),?)";
}
@Override
public void bind(SupportSQLiteStatement stmt, History value) {
stmt.bindLong(1, value.getId());
stmt.bindLong(2, value.getBranchId());
}
};
}
@Override
public void insert(final History history) {
__db.assertNotSuspendingTransaction();
__db.beginTransaction();
try {
__insertionAdapterOfHistory.insert(history);
__db.setTransactionSuccessful();
} finally {
__db.endTransaction();
}
}
@Override
protected void finalize() {
_statement.release();
}
});
}
}
这是显示下划线 sql 的图像:
Here is image showing underline sql:
这是错误日志(但在我调试时不会发生):
Here are the error logs (but it not happen when I debug):
04-06 13:58:35.080 2471-2471/com.appsbharti.bharatbankdetails E/dalvikvm: Could not find class 'android.graphics.drawable.RippleDrawable', referenced from method com.google.android.material.button.MaterialButtonHelper.createBackground
04-06 13:58:35.080 2471-2471/com.appsbharti.bharatbankdetails E/dalvikvm: Could not find class 'android.graphics.drawable.RippleDrawable', referenced from method com.google.android.material.button.MaterialButtonHelper.setRippleColor
04-06 13:58:35.090 2471-2471/com.appsbharti.bharatbankdetails E/dalvikvm: Could not find class 'com.google.android.material.chip.Chip$2', referenced from method com.google.android.material.chip.Chip.initOutlineProvider
04-06 13:58:35.090 2471-2471/com.appsbharti.bharatbankdetails E/dalvikvm: Could not find class 'android.graphics.drawable.RippleDrawable', referenced from method com.google.android.material.chip.Chip.updateFrameworkRippleBackground
04-06 13:58:35.100 2471-2471/com.appsbharti.bharatbankdetails E/dalvikvm: Could not find class 'android.graphics.drawable.RippleDrawable', referenced from method com.google.android.material.chip.ChipDrawable.updateFrameworkCloseIconRipple
04-06 13:58:35.770 2471-2498/com.appsbharti.bharatbankdetails E/AndroidRuntime: FATAL EXCEPTION: arch_disk_io_2
android.database.sqlite.SQLiteConstraintException: foreign key constraint failed (code 19)
at android.database.sqlite.SQLiteConnection.nativeExecuteForLastInsertedRowId(Native Method)
at android.database.sqlite.SQLiteConnection.executeForLastInsertedRowId(SQLiteConnection.java:775)
at android.database.sqlite.SQLiteSession.executeForLastInsertedRowId(SQLiteSession.java:788)
at android.database.sqlite.SQLiteStatement.executeInsert(SQLiteStatement.java:86)
at androidx.sqlite.db.framework.FrameworkSQLiteStatement.executeInsert(FrameworkSQLiteStatement.java:51)
at androidx.room.EntityInsertionAdapter.insert(EntityInsertionAdapter.java:64)
at com.appsbharti.bharatbankdetails.Daos.HistoryDao_Impl.insert(HistoryDao_Impl.java:48)
at com.appsbharti.bharatbankdetails.Repositories.HistoryRepository$1.run(HistoryRepository.java:41)
at androidx.room.TransactionExecutor$1.run(TransactionExecutor.java:45)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1076)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:569)
at java.lang.Thread.run(Thread.java:856)
04-06 13:58:39.170 1395-1481/system_process E/ThrottleService: problem during onPollAlarm: java.lang.IllegalStateException: problem parsing stats: java.io.FileNotFoundException: /proc/net/xt_qtaguid/iface_stat_all: open failed: ENOENT (No such file or directory)
有时会抛出此错误:
--------- beginning of crash
2021-04-07 17:05:06.152 4247-4279/com.appsbharti.bharatbankdetails E/AndroidRuntime: FATAL EXCEPTION: arch_disk_io_2
Process: com.appsbharti.bharatbankdetails, PID: 4247
android.database.sqlite.SQLiteReadOnlyDatabaseException: attempt to write a readonly database (code 1032 SQLITE_READONLY_DBMOVED)
at android.database.sqlite.SQLiteConnection.nativeExecuteForString(Native Method)
at android.database.sqlite.SQLiteConnection.executeForString(SQLiteConnection.java:655)
at android.database.sqlite.SQLiteConnection.setJournalMode(SQLiteConnection.java:336)
at android.database.sqlite.SQLiteConnection.setWalModeFromConfiguration(SQLiteConnection.java:298)
at android.database.sqlite.SQLiteConnection.open(SQLiteConnection.java:217)
at android.database.sqlite.SQLiteConnection.open(SQLiteConnection.java:195)
at android.database.sqlite.SQLiteConnectionPool.openConnectionLocked(SQLiteConnectionPool.java:503)
at android.database.sqlite.SQLiteConnectionPool.open(SQLiteConnectionPool.java:204)
at android.database.sqlite.SQLiteConnectionPool.open(SQLiteConnectionPool.java:196)
at android.database.sqlite.SQLiteDatabase.openInner(SQLiteDatabase.java:880)
at android.database.sqlite.SQLiteDatabase.open(SQLiteDatabase.java:865)
at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:739)
at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:729)
at android.database.sqlite.SQLiteOpenHelper.getDatabaseLocked(SQLiteOpenHelper.java:355)
at android.database.sqlite.SQLiteOpenHelper.getWritableDatabase(SQLiteOpenHelper.java:298)
at androidx.sqlite.db.framework.FrameworkSQLiteOpenHelper$OpenHelper.getWritableSupportDatabase(FrameworkSQLiteOpenHelper.java:92)
at androidx.sqlite.db.framework.FrameworkSQLiteOpenHelper.getWritableDatabase(FrameworkSQLiteOpenHelper.java:53)
at androidx.room.SQLiteCopyOpenHelper.getWritableDatabase(SQLiteCopyOpenHelper.java:90)
at androidx.room.RoomDatabase.inTransaction(RoomDatabase.java:476)
at androidx.room.RoomDatabase.assertNotSuspendingTransaction(RoomDatabase.java:281)
at com.appsbharti.bharatbankdetails.Daos.HistoryDao_Impl.insert(HistoryDao_Impl.java:45)
at com.appsbharti.bharatbankdetails.Repositories.HistoryRepository$1.run(HistoryRepository.java:37)
at androidx.room.TransactionExecutor$1.run(TransactionExecutor.java:45)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.lang.Thread.run(Thread.java:764)
当我抛出 can't fine locale en_US 错误时,我手动添加了 android_metadata 表.
I have added android_metadata table manually when I throws error of can't fine locale en_US.
CREATE TABLE "android_metadata" (
"locale" TEXT
);
并将 en_US
条目添加到区域设置列.
and added en_US
entry to locale column.
推荐答案
好吧,所有这些错误背后都有一个简单的问题.我还没有在 synchronized
块中获取 AppDatabase.
OK there was a simple issue behind those all errors. I haven't fetched AppDatabase inside synchronized
block.
在 AppDatabse 类中,我将 newInsance
方法从:
Inside AppDatabse class, I changed newInsance
method from:
public static AppDatabase getInstance(final Context context) {
dbInstance = buildDatabaseInstance(context);
return dbInstance;
}
到
public static AppDatabase getInstance(final Context context) {
if (dbInstance == null)
synchronized (AppDatabase.class) {
if (dbInstance == null)
dbInstance = buildDatabaseInstance(context);
}
// dbInstance = buildDatabaseInstance(context);
return dbInstance;
}
问题在于,Room 在获取查询结果时隐式调用了 getReadableDatabase()
而不是 SqliteOpenHelper
类的 getWritableDatabse()
.
Problem is that, Room implicitly call getReadableDatabase()
not getWritableDatabse()
of SqliteOpenHelper
class while fetching query results.
为了获取 BranchDetails,我在 BranchDetailsRepository 中创建了具有只读访问权限的 AppDatabase 实例.
To fetched BranchDetails, I created AppDatabase instance with read-only access inside BranchDetailsRepository.
我在 HistoryRepository 中获取了相同的只读实例(因为 AppDatbase 遵循单例模式).
I fetched same read-only instance inside HistoryRepository(as AppDatbase follows Singleton patter).
所以当我调用 insert(History history)
时,它会抛出错误.
So when I called insert(History history)
, it throws error.
没有外键问题.
我使用 SqliteStudio 创建的触发器也可以正常工作,没有任何问题,也没有问题.
Trigger which I created using SqliteStudio also working fine without any problem, no issue with that either.
这篇关于尽管使用房间数据库 TransactionExecutor,为什么我仍面临线程问题?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!