为什么 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;
    }
}

希望能帮到你...