Background thread locking UI for several seconds

fehbari picture fehbari · Feb 16, 2015 · Viewed 14.3k times · Source

I'm experiencing a weird issue. I have my app's 2-way sync code running inside a ScheduledThreadPoolExecutor. The code iterates over a large list of objects from the server's response and saves them to the local database (SQLite with GreenDAO).

The problem is, while this thread is processing the list of objects, the UI locks for several seconds and I get multiple warnings of skipped frames in logcat.

02-16 02:34:34.539    5730-5730/com.myapp.android D/SyncService﹕ Sync done.
02-16 02:34:34.552    5730-5730/com.myapp.android I/Choreographer﹕ Skipped 123 frames!  The application may be doing too much work on its main thread.
02-16 02:34:41.060    5730-5748/com.myapp.android I/art﹕ Background sticky concurrent mark sweep GC freed 168341(7MB) AllocSpace objects, 1(81KB) LOS objects, 15% free, 39MB/46MB, paused 1.774ms total 108.998ms
02-16 02:34:42.515    5730-5748/com.myapp.android I/art﹕ Background partial concurrent mark sweep GC freed 249676(9MB) AllocSpace objects, 34(3MB) LOS objects, 32% free, 33MB/49MB, paused 10.125ms total 118.693ms
02-16 02:34:45.361    5730-5748/com.myapp.android I/art﹕ Background sticky concurrent mark sweep GC freed 166082(7MB) AllocSpace objects, 1(81KB) LOS objects, 14% free, 41MB/49MB, paused 3.151ms total 115.302ms
02-16 02:34:46.792    5730-5748/com.myapp.android I/art﹕ Background partial concurrent mark sweep GC freed 296675(11MB) AllocSpace objects, 0(0B) LOS objects, 29% free, 38MB/54MB, paused 4.427ms total 122.870ms
02-16 02:34:49.785    5730-5748/com.myapp.android I/art﹕ Background sticky concurrent mark sweep GC freed 172207(7MB) AllocSpace objects, 0(0B) LOS objects, 13% free, 46MB/54MB, paused 3.229ms total 125.006ms
02-16 02:34:51.274    5730-5748/com.myapp.android I/art﹕ Background partial concurrent mark sweep GC freed 302918(11MB) AllocSpace objects, 0(0B) LOS objects, 27% free, 43MB/59MB, paused 5.143ms total 132.996ms
02-16 02:34:54.200    5730-5748/com.myapp.android I/art﹕ Background sticky concurrent mark sweep GC freed 167589(7MB) AllocSpace objects, 6(486KB) LOS objects, 13% free, 51MB/59MB, paused 5.902ms total 136.040ms
02-16 02:34:55.570    5730-5748/com.myapp.android I/art﹕ Background partial concurrent mark sweep GC freed 292352(11MB) AllocSpace objects, 0(0B) LOS objects, 24% free, 48MB/64MB, paused 4.440ms total 157.392ms
02-16 02:34:58.510    5730-5748/com.myapp.android I/art﹕ Background sticky concurrent mark sweep GC freed 173205(7MB) AllocSpace objects, 0(0B) LOS objects, 11% free, 56MB/64MB, paused 5.286ms total 133.044ms
02-16 02:35:00.100    5730-5748/com.myapp.android I/art﹕ Background partial concurrent mark sweep GC freed 303665(11MB) AllocSpace objects, 0(0B) LOS objects, 23% free, 53MB/69MB, paused 10.063ms total 233.137ms
02-16 02:35:03.054    5730-5748/com.myapp.android I/art﹕ Background sticky concurrent mark sweep GC freed 173642(7MB) AllocSpace objects, 0(0B) LOS objects, 10% free, 61MB/69MB, paused 6.528ms total 148.586ms
02-16 02:35:03.784    5730-5730/com.myapp.android I/Choreographer﹕ Skipped 1752 frames!  The application may be doing too much work on its main thread.

I don't understand why this is happening. As far as I know the code is not running in the UI thread, and therefore should not cause this kind of issue.

Here's a sample of my code. Inside performSync(changesOnly, false) is where I iterate over the list and persist data to the database.

public void performSync(final boolean changesOnly, int delay) {
        // Skip sync if it's already running.
        if (!isSyncing()) {
            // Create new thread for responsiveness.
            new ScheduledThreadPoolExecutor(1).schedule(new Runnable() {
                @Override
                public void run() {
                    // Forward call to internal sync method.
                    performSync(changesOnly, false);
                }
            }, delay, TimeUnit.SECONDS);
        }
    }

If I comment out the call to performSync(changesOnly, false) the UI doesn't lock, since no objects are processed. I tried using a regular Thread and even an AsyncTask instead of the ScheduledThreadPoolExecutor, but the problem persisted.

In case you're wondering why are there two performSync methods, the second one (where the processing happens) is recursive, and keeps sending batches of 200 objects to the server until it's done. This is not causing the issue, as when the lock happens I'm only receiving data (not sending), and therefore the method only executes once.

Any ideas of what could be causing this? I'm not performing database operations in the UI thread, and the lock only happens with a large list of objects.

UPDATE: I've managed to fix the freezing by creating another thread before iterating the list and persisting items to the database. So I have the ScheduledThreadPoolExecutor where the sync happens, and a regular Thread where I persist items to the database.

// Create another thread for processing objects.
new Thread(new Runnable() {
    @Override
    public void run() {
        // Start thread with low priority.
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);

        // Process new objects and persist to the database.
        if (response.getObjects() != null) {
            for (MyObject object : response.getObjects()) {
                ...
            }
        }
    }
}).start();

This code is executed inside the aforementioned performSync method. So it's yet another thread created inside the original ScheduledThreadPoolExecutor. I honestly don't understand why I need to do this in order to avoid freezing the UI. Any ideas?

Answer