Is SQL or general file access appropriate in the Android main UI thread?
Asked Answered
S

1

6

I'm trying to follow Android best practices, so in debug mode I turn all the following on:

StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build()); //detect and log all thread violations
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectAll().penaltyLog().build()); //detect and log all virtual machine violations

Android now yells at me when I try to use any sort of file access or SQL in the main (UI) thread. But I see so many recommendations to use file access and/or SQL in the main thread. For example, the main activity should load default preference values inside onCreate() in case they haven't been set yet:

PreferenceManager.setDefaultValues(context, resId, readAgain);

Oops---that results in a file access on the first application execution, because onCreate() is called on the UI thread. The only way around it I can see is to start a separate thread---which introduces a race condition with other UI code that might read the preferences and expect the default values to already be set.

Think also of services such as the DownloadManager. (Actually, it's so buggy that it's useless in real life, but let's pretend it works for a second.) If you queue up a download, you get an event (on the main thread) telling you a download has finished. To actually get information about that download (it only gives you a download ID), you have to query the DownloadManager---which involves a cursor, giving you an error if you have a strict policy turned on.

So what's the story---is it fine to access cursors in the main thread? Or is it a bad thing, and half the Android development team and Android book authors forgot about that?

Snowbound answered 30/11, 2011 at 0:8 Comment(4)
PreferenceManager.setDefaultValues doesn't involve SQL. SharedPreferences are all flat XML files.Luce
Yeah, it's been a week---I didn't remember. I've edited the question to point out that this is a file access---which is the same point. Thanks for noting this.Snowbound
Now on the Android 4.2.2 emulator, using PreferenceManager.setDefaultValues(context, resId, readAgain) upon starting from a spawned thread that is not the main thread will prevent the activity from inflating with a long stack trace including "java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()". So it seems that, not only is it OK to access files on the main thread, it is now mandatory as far as setting default preference values goes.Snowbound
I ran across this post because I am currently refactoring my code to a cleaner design. And I too share the sentiment voiced by @GarretWilson! Instead of loading preference values in the onCreate of my activity, i wanted to do this in my repository class, which I was hoping would do all preferences related work in addition to the current SQLite DB work. From a maintainability perspective, wanting to read/write into preferences makes things so much easier. But if I do this, Runtime throws an exception at me!Adversative
G
8

The only way around it I can see is to start a separate thread---which introduces a race condition with other UI code that might read the preferences and expect the default values to already be set.

Then use an AsyncTask, putting the setDefaultValues() call in doInBackground() and the "other UI code that might read the preferences" in onPostExecute().

To actually get information about that download (it only gives you a download ID), you have to query the DownloadManager---which involves a cursor, giving you an error if you have a strict policy turned on.

So query the DownloadManager in a background thread.

So what's the story---is it fine to access cursors in the main thread?

That depends on your definition of "fine".

On Android 1.x and most 2.x devices, the filesystem used is YAFFS2, which basically serializes all disk access across all processes. The net effect is that while your code may appear sufficiently performant in isolation, it appears sluggish at times in production because of other things going on in the background (e.g., downloading new email).

While this is a bit less of an issue in Android 3.x and above (they switched to ext4), there's no question that flash I/O is still relatively slow -- it will just be a bit more predictably slow.

StrictMode is designed to point out where sluggishness may occur. It is up to you to determine which are benign and which are not. In an ideal world, you'd clean up them all; in an ideal world, I'd have hair.

Or is it a bad thing, and half the Android development team and Android book authors forgot about that?

It's always been a "bad thing".

I cannot speak for "half the Android development team". I presume that, early on, they expected developers to apply their existing development expertise to detect sluggish behavior -- this is not significantly different than performance issues in any other platform. Over time, they have been offering more patterns to steer developers in a positive path (e.g., the Loader framework), in addition to system-level changes (e.g., YAFFS2->ext4) to make this less of a problem. In part, they are trying to address places where Android introduces distinct performance-related challenges, such as the single-threaded UI.

Similarly, I cannot speak for all Android book authors. I certainly didn't focus on performance issues in early editions of my books, as I was focusing on Android features and functions. Over time, I have added more advice in these areas. I have also contributed open source code related to these topics. In 2012, I'll be making massive revisions to my books, and creating more open source projects, to continue addressing these issues. I suspect, given your tone, that I (and probably others) are complete failures in your eyes in this regard, and you are certainly welcome to your opinion.

Graner answered 30/11, 2011 at 0:39 Comment(5)
Thanks for the detailed response, although I still feel a little unsettled---I'm not sure if it's in your answer or just the general situation. But I'm not sure you got my point as to setting the preference values. What if the user his the "Do Something" button, which action depends on preference settings---I suppose I have to check a flag set in the InitPreferencesAsyncTask.OnPostExecute(), and if it isn't set show a dialog, "Can't do that yet---preferences haven't loaded." Hence the impetus of my question---is Android designed with crappy anti-patterns, or am I missing something?Snowbound
@GarretWilson: It's fairly clear that your objective is to attack people. You can do that by yourself; I'm not going to play along.Graner
For a second I had no idea whom you thought I was "attacking"---after all I have no idea who designed the Android architecture. Then I realized that, because you suggested the use of AsyncTask, that you must be assuming I was attacking you. This was not the case, although I wasn't sure you understood my point. You suggested a workaround to a problem, and if I was attacking anything, it was the design of Android that would require such a work-around. It still seems to me that my response is merely bemoaning the Android design and asking if I'm missing something. Sorry if you took it otherwise.Snowbound
@GarretWilson The goal is to avoid running operations that could potentially take a long time on the UI thread. However, if it doesn't make sense to display a user interface until some sort of initialization has happened, then do that first. If that's loading SharedPreferences, so be it. There's a difference between local file access and a network request. What you don't want is the user waiting 5 seconds before your app loads or the UI freezing when they refresh some remote content. You are being pedantic, which is good in theory, but at the end of the day development is engineering.Cecum
@DavidCowden, regarding being pedantic, I personally didn't mind loading preferences in the main thread. You may have missed that when I first opened this issue I had turned on Android warnings for thread violations, and it was Android which informed me that this was a bad practice by logging a warning when it happened. Apparently in later versions of Android it now throws an error if, on startup, you load preferences in a separate thread. So it seems the original warning was Android being inappropriately pedantic.Snowbound

© 2022 - 2024 — McMap. All rights reserved.