我正在尝试使用Room Persistence Library的示例。 我创建了一个实体:
@Entity
public class Agent {
@PrimaryKey
public String guid;
public String name;
public String email;
public String password;
public String phone;
public String licence;
}
创建了一个DAO类:
@Dao
public interface AgentDao {
@Query("SELECT COUNT(*) FROM Agent where email = :email OR phone = :phone OR licence = :licence")
int agentsCount(String email, String phone, String licence);
@Insert
void insertAgent(Agent agent);
}
创建了数据库类:
@Database(entities = {Agent.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
public abstract AgentDao agentDao();
}
在Kotlin中使用以下子类的暴露数据库:
class MyApp : Application() {
companion object DatabaseSetup {
var database: AppDatabase? = null
}
override fun onCreate() {
super.onCreate()
MyApp.database = Room.databaseBuilder(this, AppDatabase::class.java, "MyDatabase").build()
}
}
在我的活动中实现了以下功能:
void signUpAction(View view) {
String email = editTextEmail.getText().toString();
String phone = editTextPhone.getText().toString();
String license = editTextLicence.getText().toString();
AgentDao agentDao = MyApp.DatabaseSetup.getDatabase().agentDao();
//1: Check if agent already exists
int agentsCount = agentDao.agentsCount(email, phone, license);
if (agentsCount > 0) {
//2: If it already exists then prompt user
Toast.makeText(this, "Agent already exists!", Toast.LENGTH_LONG).show();
}
else {
Toast.makeText(this, "Agent does not exist! Hurray :)", Toast.LENGTH_LONG).show();
onBackPressed();
}
}
不幸的是,在执行上述方法时,它崩溃了下面的堆栈跟踪:
FATAL EXCEPTION: main
Process: com.example.me.MyApp, PID: 31592
java.lang.IllegalStateException: Could not execute method for android:onClick
at android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:293)
at android.view.View.performClick(View.java:5612)
at android.view.View$PerformClick.run(View.java:22288)
at android.os.Handler.handleCallback(Handler.java:751)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6123)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:867)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:757)
Caused by: java.lang.reflect.InvocationTargetException
at java.lang.reflect.Method.invoke(Native Method)
at android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:288)
at android.view.View.performClick(View.java:5612)
at android.view.View$PerformClick.run(View.java:22288)
at android.os.Handler.handleCallback(Handler.java:751)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6123)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:867)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:757)
Caused by: java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long periods of time.
at android.arch.persistence.room.RoomDatabase.assertNotMainThread(RoomDatabase.java:137)
at android.arch.persistence.room.RoomDatabase.query(RoomDatabase.java:165)
at com.example.me.MyApp.RoomDb.Dao.AgentDao_Impl.agentsCount(AgentDao_Impl.java:94)
at com.example.me.MyApp.View.SignUpActivity.signUpAction(SignUpActivity.java:58)
at java.lang.reflect.Method.invoke(Native Method)
at android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:288)
at android.view.View.performClick(View.java:5612)
at android.view.View$PerformClick.run(View.java:22288)
at android.os.Handler.handleCallback(Handler.java:751)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6123)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:867)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:757)
似乎该问题与主线程上的db操作的执行有关。但是,上面链接中提供的示例测试代码不能在单独的线程上运行:
@Test
public void writeUserAndReadInList() throws Exception {
User user = TestUtil.createUser(3);
user.setName("george");
mUserDao.insert(user);
List<User> byName = mUserDao.findUsersByName("george");
assertThat(byName.get(0), equalTo(user));
}
我在这里错过任何东西吗?如何让它在没有崩溃的情况下执行?请建议。
答案 0 :(得分:91)
不推荐,但您可以使用allowMainThreadQueries()
MyApp.database = Room.databaseBuilder(this, AppDatabase::class.java, "MyDatabase").allowMainThreadQueries().build()
答案 1 :(得分:45)
主线程上的数据库访问锁定UI是错误,就像Dale所说。
在Activity扩展AsyncTask中创建一个静态嵌套类(以防止内存泄漏)。
private static class AgentAsyncTask extends AsyncTask<Void, Void, Integer> {
//Prevent leak
private WeakReference<Activity> weakActivity;
private String email;
private String phone;
private String license;
public AgentAsyncTask(Activity activity, String email, String phone, String license) {
weakActivity = new WeakReference<>(activity);
this.email = email;
this.phone = phone;
this.license = license;
}
@Override
protected Integer doInBackground(Void... params) {
AgentDao agentDao = MyApp.DatabaseSetup.getDatabase().agentDao();
return agentDao.agentsCount(email, phone, license);
}
@Override
protected void onPostExecute(Integer agentsCount) {
Activity activity = weakActivity.get();
if(activity == null) {
return;
}
if (agentsCount > 0) {
//2: If it already exists then prompt user
Toast.makeText(activity, "Agent already exists!", Toast.LENGTH_LONG).show();
} else {
Toast.makeText(activity, "Agent does not exist! Hurray :)", Toast.LENGTH_LONG).show();
activity.onBackPressed();
}
}
}
或者您可以在自己的文件上创建最终类。
然后在signUpAction(View视图)方法中执行它:
new AgentAsyncTask(this, email, phone, license).execute();
在某些情况下,您可能还希望在活动中保留对AgentAsyncTask的引用,以便在销毁活动时取消它。但你必须自己打断任何交易。
另外,关于Google测试示例的问题...... 他们在该网页中说明:
测试数据库实现的推荐方法是 编写在Android设备上运行的JUnit测试。因为这些 测试不需要创建活动,它们应该更快 执行比你的UI测试。
没有活动,没有用户界面。
- 编辑 -
对于想知道的人......你还有其他选择。 我建议您查看新的ViewModel和LiveData组件。 LiveData适用于Room。 https://developer.android.com/topic/libraries/architecture/livedata.html
另一种选择是RxJava / RxAndroid。比LiveData更强大但更复杂。 https://github.com/ReactiveX/RxJava
- 编辑2 -
由于很多人可能会遇到这个答案...... 一般来说,现在最好的选择是Kotlin Coroutines。 Room现在直接支持它(目前处于测试阶段)。 https://kotlinlang.org/docs/reference/coroutines-overview.html https://developer.android.com/jetpack/androidx/releases/room#2.1.0-beta01
答案 2 :(得分:38)
对于那里的所有RxJava或RxAndroid或RxKotlin爱好者
Observable.just(db)
.subscribeOn(Schedulers.io())
.subscribe { db -> // database operation }
答案 3 :(得分:22)
AsyncTask非常笨重。 Kotlin协同程序是一个更清洁的替代方案(基本上只是同步代码加上几个关键字)。
private fun myFun() {
launch { // coroutine on Main
val query = async(Dispatchers.IO) { // coroutine on IO
MyApp.DatabaseSetup.database.agentDao().agentsCount(email, phone, license)
}
val agentsCount = query.await()
// do UI stuff
}
}
就是这样!!
要从Activity中使用async,您需要一个CoroutineScope。您可以像这样使用您的活动:
class LoadDataActivity : AppCompatActivity(), CoroutineScope {
private val job by lazy { Job() }
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + job
override fun onDestroy() {
super.onDestroy()
job.cancel() // cancels all coroutines under this scope
}
// ...rest of class
}
201-May-2019: 2.1室现在支持suspend
(https://youtu.be/Qxj2eBmXLHg?t=1662)
suspend
关键字确保异步方法仅从异步块中调用,但是(如@Robin所述)这与Room(&lt; 2.1)带注释的方法不相称。
// Wrap API to use suspend (probably not worth it)
public suspend fun agentsCount(...): Int = agentsCountPrivate(...)
@Query("SELECT ...")
protected abstract fun agentsCountPrivate(...): Int
答案 4 :(得分:18)
您无法在主线程上运行它,而是使用处理程序,异步或工作线程。此处提供了示例代码,并在此处阅读了有关房间库的文章: Android's Room Library
/**
* Insert and get data using Database Async way
*/
AsyncTask.execute(new Runnable() {
@Override
public void run() {
// Insert Data
AppDatabase.getInstance(context).userDao().insert(new User(1,"James","Mathew"));
// Get Data
AppDatabase.getInstance(context).userDao().getAllUsers();
}
});
如果你想在主线程上运行它,这不是首选方式。
您可以使用此方法在主线程Room.inMemoryDatabaseBuilder()
答案 5 :(得分:8)
使用Jetbrains Anko库,您可以使用doAsync {..}方法自动执行数据库调用。这样可以解决您在mcastro的答案中遇到的冗长问题。
使用示例:
doAsync {
Application.database.myDAO().insertUser(user)
}
我经常使用它进行插入和更新,但对于选择查询,我建议使用RX工作流程。
答案 6 :(得分:5)
优雅的RxJava / Kotlin解决方案是使用Completable.fromCallable
,它将为您提供一个Observable,它不返回值,但可以在不同的线程上观察和订阅。
public Completable insert(Event event) {
return Completable.fromCallable(new Callable<Void>() {
@Override
public Void call() throws Exception {
return database.eventDao().insert(event)
}
}
}
或者在Kotlin:
fun insert(event: Event) : Completable = Completable.fromCallable {
database.eventDao().insert(event)
}
您可以像往常一样观察和订阅:
dataManager.insert(event)
.subscribeOn(scheduler)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(...)
答案 7 :(得分:4)
不推荐使用asyncTask,我们可以使用执行程序服务。或者,您也可以将LiveData与ViewModel一起使用,如其他答案所述。
要使用执行程序服务,可以使用以下类似内容。
public class DbHelper {
private final Executor executor = Executors.newSingleThreadExecutor();
public void fetchData(DataFetchListener dataListener){
executor.execute(() -> {
Object object = retrieveAgent(agentId);
new Handler(Looper.getMainLooper()).post(() -> {
dataListener.onFetchDataSuccess(object);
});
});
}
}
使用了主循环程序,因此您可以从onFetchDataSuccess
回调中访问UI元素。
答案 8 :(得分:3)
错误消息
无法访问主线程上的数据库,因为它可能会长时间锁定UI。
非常具有描述性和准确性。问题是如何避免在主线程上访问数据库。这是一个很大的主题,但要开始使用,请阅读AsyncTask (click here)
----- ---------- EDIT
当你进行单元测试时,我发现你遇到了问题。你有几个选择来解决这个问题:
直接在开发计算机上运行测试,而不是在Android设备(或模拟器)上运行。这适用于以数据库为中心的测试,并不关心它们是否在设备上运行。
使用注释
public void actionPerformed(ActionEvent event) {
display.setText("");
for(Valuble item : valubles)
if(((JRadioButton)event.getSource()).isSelected()){
//Bunch of code and stuff
}
}
在Android设备上运行测试,但不在具有UI的活动中运行。
有关此问题的更多详细信息,请参见in this tutorial
答案 9 :(得分:3)
如果您对异步任务
更为满意: new AsyncTask<Void, Void, Integer>() {
@Override
protected Integer doInBackground(Void... voids) {
return Room.databaseBuilder(getApplicationContext(),
AppDatabase.class, DATABASE_NAME)
.fallbackToDestructiveMigration()
.build()
.getRecordingDAO()
.getAll()
.size();
}
@Override
protected void onPostExecute(Integer integer) {
super.onPostExecute(integer);
Toast.makeText(HomeActivity.this, "Found " + integer, Toast.LENGTH_LONG).show();
}
}.execute();
答案 10 :(得分:3)
您必须在后台执行请求。 一种简单的方法可以使用Executors:
Executors.newSingleThreadExecutor().execute {
yourDb.yourDao.yourRequest() //Replace this by your request
}
答案 11 :(得分:3)
使用lambda可以轻松地通过AsyncTask运行
AsyncTask.execute(() -> //run your query here );
答案 12 :(得分:2)
更新:当我尝试使用DAO中的@RawQuery和SupportSQLiteQuery构建查询时,我也收到了此消息。
@Transaction
public LiveData<List<MyEntity>> getList(MySettings mySettings) {
//return getMyList(); -->this is ok
return getMyList(new SimpleSQLiteQuery("select * from mytable")); --> this is an error
解决方案:在ViewModel中构建查询并将其传递给DAO。
public MyViewModel(Application application) {
...
list = Transformations.switchMap(searchParams, params -> {
StringBuilder sql;
sql = new StringBuilder("select ... ");
return appDatabase.rawDao().getList(new SimpleSQLiteQuery(sql.toString()));
});
}
或者...
您不应直接在主线程上访问数据库,例如:
public void add(MyEntity item) {
appDatabase.myDao().add(item);
}
您应该使用AsyncTask进行更新,添加和删除操作。
示例:
public class MyViewModel extends AndroidViewModel {
private LiveData<List<MyEntity>> list;
private AppDatabase appDatabase;
public MyViewModel(Application application) {
super(application);
appDatabase = AppDatabase.getDatabase(this.getApplication());
list = appDatabase.myDao().getItems();
}
public LiveData<List<MyEntity>> getItems() {
return list;
}
public void delete(Obj item) {
new deleteAsyncTask(appDatabase).execute(item);
}
private static class deleteAsyncTask extends AsyncTask<MyEntity, Void, Void> {
private AppDatabase db;
deleteAsyncTask(AppDatabase appDatabase) {
db = appDatabase;
}
@Override
protected Void doInBackground(final MyEntity... params) {
db.myDao().delete((params[0]));
return null;
}
}
public void add(final MyEntity item) {
new addAsyncTask(appDatabase).execute(item);
}
private static class addAsyncTask extends AsyncTask<MyEntity, Void, Void> {
private AppDatabase db;
addAsyncTask(AppDatabase appDatabase) {
db = appDatabase;
}
@Override
protected Void doInBackground(final MyEntity... params) {
db.myDao().add((params[0]));
return null;
}
}
}
如果您使用LiveData进行选择操作,则不需要AsyncTask。
答案 13 :(得分:2)
只需在单独的线程中执行数据库操作。像这样(科特琳):
Thread {
//Do your database´s operations here
}.start()
答案 14 :(得分:2)
只需使用以下代码即可解决它:
Executors.newSingleThreadExecutor().execute(new Runnable() {
@Override
public void run() {
appDb.daoAccess().someJobes();//replace with your code
}
});
或者在lambda中,您可以使用以下代码:
Executors.newSingleThreadExecutor().execute(() -> appDb.daoAccess().someJobes());
您可以用自己的代码替换appDb.daoAccess().someJobes()
;
答案 15 :(得分:2)
对于快速查询,您可以允许在UI线程上执行它。
AppDatabase db = Room.databaseBuilder(context.getApplicationContext(),
AppDatabase.class, DATABASE_NAME).allowMainThreadQueries().build();
在我的情况下,我不得不弄清楚数据库中是否存在列表中的被点击用户。如果没有,则创建用户并开始另一个活动
@Override
public void onClick(View view) {
int position = getAdapterPosition();
User user = new User();
String name = getName(position);
user.setName(name);
AppDatabase appDatabase = DatabaseCreator.getInstance(mContext).getDatabase();
UserDao userDao = appDatabase.getUserDao();
ArrayList<User> users = new ArrayList<User>();
users.add(user);
List<Long> ids = userDao.insertAll(users);
Long id = ids.get(0);
if(id == -1)
{
user = userDao.getUser(name);
user.setId(user.getId());
}
else
{
user.setId(id);
}
Intent intent = new Intent(mContext, ChatActivity.class);
intent.putExtra(ChatActivity.EXTRAS_USER, Parcels.wrap(user));
mContext.startActivity(intent);
}
}
答案 16 :(得分:1)
您可以使用Future和Callable。因此,您无需编写冗长的asynctask即可执行查询而无需添加allowMainThreadQueries()。
我的岛查询:-
@Query("SELECT * from user_data_table where SNO = 1")
UserData getDefaultData();
我的存储库方法:-
public UserData getDefaultData() throws ExecutionException, InterruptedException {
Callable<UserData> callable = new Callable<UserData>() {
@Override
public UserData call() throws Exception {
return userDao.getDefaultData();
}
};
Future<UserData> future = Executors.newSingleThreadExecutor().submit(callable);
return future.get();
}
答案 17 :(得分:1)
您可以在主线程上允许数据库访问,但仅用于调试目的,您不应该在生产中执行此操作。
注意:除非您在构建器上调用allowMainThreadQueries(),否则Room不支持主线程上的数据库访问,因为它可能会长时间锁定UI。异步查询 - 返回LiveData或Flowable实例的查询 - 不受此规则的约束,因为它们在需要时在后台线程上异步运行查询。
答案 18 :(得分:0)
我认为正确的做法是使用RxJava将查询委托给IO线程。
我有一个解决我遇到的等效问题的示例。
((ProgressBar) view.findViewById(R.id.progressBar_home)).setVisibility(View.VISIBLE);//Always good to set some good feedback
Completable.fromAction(() -> {
//Creating view model requires DB access
homeViewModel = new ViewModelProvider(this, factory).get(HomeViewModel.class);
}).subscribeOn(Schedulers.io())//The DB access executes on a non-main-thread thread
.observeOn(AndroidSchedulers.mainThread())//Upon completion of the DB-involved execution, the continuation runs on the main thread
.subscribe(
() ->
{
mAdapter = new MyAdapter(homeViewModel.getExams());
recyclerView.setAdapter(mAdapter);
((ProgressBar) view.findViewById(R.id.progressBar_home)).setVisibility(View.INVISIBLE);
},
error -> error.printStackTrace()
);
如果我们要概括该解决方案:
((ProgressBar) view.findViewById(R.id.progressBar_home)).setVisibility(View.VISIBLE);//Always good to set some good feedback
Completable.fromAction(() -> {
someTaskThatTakesTooMuchTime();
}).subscribeOn(Schedulers.io())//The long task executes on a non-main-thread thread
.observeOn(AndroidSchedulers.mainThread())//Upon completion of the DB-involved execution, the continuation runs on the main thread
.subscribe(
() ->
{
taskIWantToDoOnTheMainThreadWhenTheLongTaskIsDone();
},
error -> error.printStackTrace()
);