为什么Android Room在生成它的asynctask的末尾插入一个对象(对象)?

时间:2019-07-07 20:04:52

标签: java android android-asynctask android-room

摘要

Room会立即插入通过UI生成的实体,但是将asynctask发送的实体延迟到生成asynctask的(远端)末尾:接收到的实体对象可用并显示在UI上,但是没有数据库的任何ID,从而妨碍了其他任何对象操作依赖id。 仅当正确停止生成异步任务时,才会发生插入操作:为什么?以及如何解决这个问题?

更多上下文

生成异步任务

我们使用asynctask监视套接字,并将一些事件(作为Room实体)发送回应用程序存储库(如android体系结构组件所预期)。这个异步任务基本上在后台连续运行(定期设置一些睡眠),并且仅在应用程序使用结束前停止一会(如果操作正确)。到目前为止,对于我们而言,与短暂的asynctask的原始概念有很大不同。 我很清楚我们可以改进设计,但这是另一个主题/问题/时间空洞;-)。

房间插入操作

插入是通过专用的asynctask进行的,其中数据库中条目的返回ID会影响回到刚插入的实体(请参见下面的代码)。这被记录下来,并且UI中的实体“立即”保存,它们取回ID,一切都很好。由asynctask生成的实体,以及它们等待其“父”任务停止并随后全部插入。

实体组成

首先,实体是在asynctask内部生成的,并通过进度消息发送。然后,对象的构造移到asynctask之外,并且处于UI事件构造的相同级别,但行为相同。 这些事件是一些long(时间戳)和几个字符串。

从生成的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);
        }

    }

2 个答案:

答案 0 :(得分:1)

  

我很清楚我们可以改进设计,但这是另一个主题/问题/时间空洞

由于我怀疑这是造成您当前问题的原因,所以也许您不应该忽略此问题。

我对您的问题的解释是:您有一个外部AsyncTask(在第一个代码清单中显示的是带有onPublishProgress()方法的那个)。您正在使用execute()执行该操作。在外部AsyncTask的内部,有一个内部AsyncTask(来自存储库的一个)。您正在使用execute()执行该操作。而且,您的抱怨是内部AsyncTask直到外部AsyncTask完成后才运行。

如果是这样,您的问题是execute()是单线程的,并且要通过无限期运行AsyncTask来占用该线程。在外部AsyncTask完成其背景工作并从doInBackground()返回之前,内部AsyncTask被阻止。

“我们可以继续使用黑客吗?”解决方案是继续使用AsyncTask,但切换到executeOnExecutor()而不是execute(),提供要使用的线程池。 AsyncTask.THREAD_POOL_EXECUTOR将是候选人。

“好,我们可以收拾一下吗?”解决方案是用简单的AsyncTask对象或直接使用某些多线程线程池(请参见Thread)替换两个Executors实例。 AsyncTask已过时,但在一定程度上有用,仅在完成后台工作({{1之后,需要在主应用程序线程(onPostExecute())上进行工作时使用它}})。后台工作完成后,您的doInBackground()的实现都不需要在主应用程序线程上进行工作,因此您都不需要AsyncTask。因此,例如,当您使用存储库内部的线程池进行DAO调用时,永远运行的线程可能是AsyncTask

(“嘿,我们可以在使用我们的体系结构组件的同时在线程上实现现代化吗?”解决方案是与Thread一起切换到RxJava或Kotlin协程—这是更多的东西工作,但它们在手动线程管理方面各有千秋)

答案 1 :(得分:0)

为什么?

基本上,它是用AsyncTask documentation编写的:所有异步任务均在唯一的后台线程上串行执行

即使没有嵌套的asynctask,我的代码也用几乎永无休止的任务来阻塞该线程,将所有数据库操作延迟到完成为止(或应用程序崩溃,因此会丢失一些数据)。

快速解决方案:将AsyncTask移至线程

(CommonsWare)[https://stackoverflow.com/a/56925864/9138818]列出了其他替代方案,这是我解决该问题所遵循的步骤。

主要困难是通过与主线程关联的 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”以适合可运行的主方法签名:

// 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中的代码(在doInBackground方法内部由publishProgress调用)移至发布在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;
    }
}

希望它可以帮助...