为什么 android room 在生成它的异步任务(对象)的末尾插入一个对象?
Why android room inserts an object at the end of the asynctask generating it (the object)?
总结
Room 立即插入通过 UI 生成的实体,但将异步任务发送的实体延迟到生成异步任务的(远)端:收到的实体对象可用并显示在 UI,但没有来自数据库的任何 id,阻碍了依赖 id 的任何其他操作。
插入操作只有在正确停止生成异步任务时才会发生:为什么?以及如何解决这个问题?
更多上下文
生成异步任务
我们使用异步任务来监视套接字并将一些事件(作为 Room 实体)发送回应用程序存储库(如 android 架构组件所预期的那样)。这个 asynctask 基本上在后台连续运行(定期设置一些睡眠)并且只在应用程序结束前停止一段时间(如果做得好)。到目前为止,它并没有给我们造成任何问题,以至于偏离了短暂的异步任务的原始概念。
我非常清楚我们可以改进设计,但这是另一个 subject/question/time-hole ;-)。
房间插入操作
插入是通过一个专用的异步任务进行的,其中数据库中条目的返回 ID 会影响到刚刚插入的实体(请参见下面的代码)。这被记录下来,来自 UI 的实体被持久化 "immediately",他们取回了他们的 ID,一切都很好。 asynctask 生成的实体,它们会等待 "parent" 任务停止,然后全部插入。
实体构成
起初实体是在asynctask内部生成的,通过progress消息发送。然后对象的构造被移到 asynctask 之外,并与 UI 事件构造处于同一级别,但行为相同。
这些事件是一些 longs(时间戳)和几个字符串。
从这里开始生成asynctask:
@Override
protected void onProgressUpdate(OnProgressObject... values) {
OnProgressObject onProgressObject = values[0];
if (onProgressObject instanceof OnProgressEvent) {
eventRecipient.sendAutoEvent(((OnProgressEvent) onProgressObject).autoEvent);
}
}
eventRecipient 是 EventsRepository:
public void sendAutoEvent(AutoEvent autoEvent) {
Log.d(LOG_TAG, "got an autoevent to treat...");
EventModel newEvent = EventModel.fromCub(
autoEvent.cubTimeStamp,
autoEvent.description,
autoEvent.eventType
);
addEvent(newEvent);
}
public void addEvent(EventModel event) {
new insertEventAsyncTask(event).execute(event);
// other operations using flawlessly the "event"...
}
private class insertEventAsyncTask extends AsyncTask<EventModel, Void, Long> {
private EventModel eventModel;
public insertEventAsyncTask(EventModel eventModel) {
this.eventModel = eventModel;
}
@Override
protected Long doInBackground(EventModel... eventModels) {
// inserting the event "only"
return eventDao.insert(eventModels[0]);
}
@Override
protected void onPostExecute(Long eventId) {
super.onPostExecute(eventId);
// inserting all the medias associated to this event
// only one media is expected this way though.
eventModel.id = eventId;
Log.d(LOG_TAG, "event inserted in DB, got id : " + eventId);
}
}
I am pretty much aware we could improve the design, but this is another subject/question/time-hole
既然我怀疑这是你当前问题的原因,也许你不应该忽略它。
我对你的问题的解释是:你有一个外部 AsyncTask
(第一个代码清单中显示的带有 onPublishProgress()
方法的那个)。您正在使用 execute()
执行它。在那个外部 AsyncTask
里面,你有一个内部 AsyncTask
(来自你的存储库的那个)。您正在使用 execute()
执行它。而且,您的抱怨是内部 AsyncTask
在外部 AsyncTask
完成之前不会 运行。
如果是这样,您的问题是 execute()
是 single-threaded,并且您通过 AsyncTask
运行 无限期地占用了该线程。在您的外部 AsyncTask
完成后台工作并从 doInBackground()
完成 returns 之前,内部 AsyncTask
将被阻止。
"can we keep using hacks?" 解决方案是继续使用 AsyncTask
但切换到 executeOnExecutor()
而不是 execute()
,提供线程池以供使用。 AsyncTask.THREAD_POOL_EXECUTOR
将成为候选人。
"OK, can we clean this up a little bit?" 解决方案是将两个 AsyncTask
实例替换为简单的 Thread
对象或直接使用一些 multi-thread 线程池(参见 Executors
). AsyncTask
已过时,但就其有用程度而言,仅在完成后台工作 (doInBackground()
)。在后台工作完成后,您的 AsyncTask
实现都不需要在主应用程序线程上工作,因此您不需要为它们中的任何一个实现 AsyncTask
。因此,例如,您的 run-forever 线程可以是 Thread
,而您在存储库中使用线程池进行 DAO 调用。
("hey, can we get modern on our threading, to go along with our use of Architecture Components?" 解决方案是切换到 RxJava 或 Kotlin 协程,与 LiveData
结合——这需要更多工作,但与手动线程管理相比,它们各有千秋)
为什么?
基本上,它是在 AsyncTask documentation 中编写的:所有异步任务都在唯一的后台线程上串行执行。
我的代码,即使没有嵌套的 asynctask,也会用几乎 never-ending 任务阻塞这个线程,延迟所有数据库操作直到它完成(或应用程序崩溃,因此一些数据丢失)。
快速解决方案:将 AsyncTask 移至线程
(CommonsWare) 很好地列出了其他替代方案[,这是我为解决此问题而遵循的步骤。
主要困难是通过关联到主线程的 Handler 重定向在 UI 线程(onPreExecute、onProgressUpdate、onPostExecute)上执行的代码。
第一步是获取对处理程序的引用:
// Inside Runnable task's constructor :
// get the handler of the main thread (UI), needed for sending back data.
this.uiHandler = new Handler(Looper.getMainLooper());
然后,"doInBackground" 被重构以适应 Runnable 主方法签名:
// previously "public String doInBackground()"
// returned value handled through publishProgress.
@Override
public void run() {
// recommended by Android Thread documentation
android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
// code previously in doInBackground
现在,onProgressUpdate 中的代码(由 publishProgress 在 doInBackground 方法中调用)被移动到发布在 UI 线程处理程序上的 Runnable 中:
// asynctask method onProgressUpdate was renamed publishProgress =>
// doInBackground's body is almost untouched.
private void publishProgress(final OnProgressObject... values) {
uiHandler.post(new Runnable() {
@Override
public void run() {
// move here code previously in the AsyncTask's publishProgress()
}
});
}
最后,我不得不通过使用 Thread.interrupted
而不是 isCancelled
并在线程之前创建 Runnable 任务来更改创建、运行和停止任务的方式:
public void startCUBMonitoring() {
if (autoEventThread == null) {
Log.d(LOG_TAG, "startCUBMonitoring");
addUIEvent("CUB MONITORING STARTED", "CUB_connexion");
SessionRepository sessionRepository =
ElabsheetApplication.getInstance().getSessionRepository();
// Creation of the task
AutoEventTask autoEventTask = new AutoEventTask(
this,
sessionRepository,
sessionRepository.getCUBConfig()
);
autoEventThread = new Thread(autoEventTask);
autoEventThread.start();
}
}
public void stopCUBMonitoring() {
if (autoEventThread != null) {
Log.d(LOG_TAG, "stopCUBMonitoring");
addUIEvent("CUB MONITORING STOPPED", "CUB_connexion");
autoEventThread.interrupt();
autoEventThread = null;
}
}
希望能帮到你...
总结
Room 立即插入通过 UI 生成的实体,但将异步任务发送的实体延迟到生成异步任务的(远)端:收到的实体对象可用并显示在 UI,但没有来自数据库的任何 id,阻碍了依赖 id 的任何其他操作。 插入操作只有在正确停止生成异步任务时才会发生:为什么?以及如何解决这个问题?
更多上下文
生成异步任务
我们使用异步任务来监视套接字并将一些事件(作为 Room 实体)发送回应用程序存储库(如 android 架构组件所预期的那样)。这个 asynctask 基本上在后台连续运行(定期设置一些睡眠)并且只在应用程序结束前停止一段时间(如果做得好)。到目前为止,它并没有给我们造成任何问题,以至于偏离了短暂的异步任务的原始概念。 我非常清楚我们可以改进设计,但这是另一个 subject/question/time-hole ;-)。
房间插入操作
插入是通过一个专用的异步任务进行的,其中数据库中条目的返回 ID 会影响到刚刚插入的实体(请参见下面的代码)。这被记录下来,来自 UI 的实体被持久化 "immediately",他们取回了他们的 ID,一切都很好。 asynctask 生成的实体,它们会等待 "parent" 任务停止,然后全部插入。
实体构成
起初实体是在asynctask内部生成的,通过progress消息发送。然后对象的构造被移到 asynctask 之外,并与 UI 事件构造处于同一级别,但行为相同。 这些事件是一些 longs(时间戳)和几个字符串。
从这里开始生成asynctask:
@Override
protected void onProgressUpdate(OnProgressObject... values) {
OnProgressObject onProgressObject = values[0];
if (onProgressObject instanceof OnProgressEvent) {
eventRecipient.sendAutoEvent(((OnProgressEvent) onProgressObject).autoEvent);
}
}
eventRecipient 是 EventsRepository:
public void sendAutoEvent(AutoEvent autoEvent) {
Log.d(LOG_TAG, "got an autoevent to treat...");
EventModel newEvent = EventModel.fromCub(
autoEvent.cubTimeStamp,
autoEvent.description,
autoEvent.eventType
);
addEvent(newEvent);
}
public void addEvent(EventModel event) {
new insertEventAsyncTask(event).execute(event);
// other operations using flawlessly the "event"...
}
private class insertEventAsyncTask extends AsyncTask<EventModel, Void, Long> {
private EventModel eventModel;
public insertEventAsyncTask(EventModel eventModel) {
this.eventModel = eventModel;
}
@Override
protected Long doInBackground(EventModel... eventModels) {
// inserting the event "only"
return eventDao.insert(eventModels[0]);
}
@Override
protected void onPostExecute(Long eventId) {
super.onPostExecute(eventId);
// inserting all the medias associated to this event
// only one media is expected this way though.
eventModel.id = eventId;
Log.d(LOG_TAG, "event inserted in DB, got id : " + eventId);
}
}
I am pretty much aware we could improve the design, but this is another subject/question/time-hole
既然我怀疑这是你当前问题的原因,也许你不应该忽略它。
我对你的问题的解释是:你有一个外部 AsyncTask
(第一个代码清单中显示的带有 onPublishProgress()
方法的那个)。您正在使用 execute()
执行它。在那个外部 AsyncTask
里面,你有一个内部 AsyncTask
(来自你的存储库的那个)。您正在使用 execute()
执行它。而且,您的抱怨是内部 AsyncTask
在外部 AsyncTask
完成之前不会 运行。
如果是这样,您的问题是 execute()
是 single-threaded,并且您通过 AsyncTask
运行 无限期地占用了该线程。在您的外部 AsyncTask
完成后台工作并从 doInBackground()
完成 returns 之前,内部 AsyncTask
将被阻止。
"can we keep using hacks?" 解决方案是继续使用 AsyncTask
但切换到 executeOnExecutor()
而不是 execute()
,提供线程池以供使用。 AsyncTask.THREAD_POOL_EXECUTOR
将成为候选人。
"OK, can we clean this up a little bit?" 解决方案是将两个 AsyncTask
实例替换为简单的 Thread
对象或直接使用一些 multi-thread 线程池(参见 Executors
). AsyncTask
已过时,但就其有用程度而言,仅在完成后台工作 (doInBackground()
)。在后台工作完成后,您的 AsyncTask
实现都不需要在主应用程序线程上工作,因此您不需要为它们中的任何一个实现 AsyncTask
。因此,例如,您的 run-forever 线程可以是 Thread
,而您在存储库中使用线程池进行 DAO 调用。
("hey, can we get modern on our threading, to go along with our use of Architecture Components?" 解决方案是切换到 RxJava 或 Kotlin 协程,与 LiveData
结合——这需要更多工作,但与手动线程管理相比,它们各有千秋)
为什么?
基本上,它是在 AsyncTask documentation 中编写的:所有异步任务都在唯一的后台线程上串行执行。
我的代码,即使没有嵌套的 asynctask,也会用几乎 never-ending 任务阻塞这个线程,延迟所有数据库操作直到它完成(或应用程序崩溃,因此一些数据丢失)。
快速解决方案:将 AsyncTask 移至线程
(CommonsWare) 很好地列出了其他替代方案[
主要困难是通过关联到主线程的 Handler 重定向在 UI 线程(onPreExecute、onProgressUpdate、onPostExecute)上执行的代码。
第一步是获取对处理程序的引用:
// Inside Runnable task's constructor :
// get the handler of the main thread (UI), needed for sending back data.
this.uiHandler = new Handler(Looper.getMainLooper());
然后,"doInBackground" 被重构以适应 Runnable 主方法签名:
// previously "public String doInBackground()"
// returned value handled through publishProgress.
@Override
public void run() {
// recommended by Android Thread documentation
android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
// code previously in doInBackground
现在,onProgressUpdate 中的代码(由 publishProgress 在 doInBackground 方法中调用)被移动到发布在 UI 线程处理程序上的 Runnable 中:
// asynctask method onProgressUpdate was renamed publishProgress =>
// doInBackground's body is almost untouched.
private void publishProgress(final OnProgressObject... values) {
uiHandler.post(new Runnable() {
@Override
public void run() {
// move here code previously in the AsyncTask's publishProgress()
}
});
}
最后,我不得不通过使用 Thread.interrupted
而不是 isCancelled
并在线程之前创建 Runnable 任务来更改创建、运行和停止任务的方式:
public void startCUBMonitoring() {
if (autoEventThread == null) {
Log.d(LOG_TAG, "startCUBMonitoring");
addUIEvent("CUB MONITORING STARTED", "CUB_connexion");
SessionRepository sessionRepository =
ElabsheetApplication.getInstance().getSessionRepository();
// Creation of the task
AutoEventTask autoEventTask = new AutoEventTask(
this,
sessionRepository,
sessionRepository.getCUBConfig()
);
autoEventThread = new Thread(autoEventTask);
autoEventThread.start();
}
}
public void stopCUBMonitoring() {
if (autoEventThread != null) {
Log.d(LOG_TAG, "stopCUBMonitoring");
addUIEvent("CUB MONITORING STOPPED", "CUB_connexion");
autoEventThread.interrupt();
autoEventThread = null;
}
}
希望能帮到你...