0

I have a splash page on my app, and what I want to do is initiate a background task to start downloading things so they're ready when the user needs them.

So for example a picture of the week or something.

Splash screen starts (start background task)
Splash screen finishes (background task still working)
Home screen starts (temporary "loading" place holder)
User navigates to another activity
Download finishes
User returns to home screen which is updated with picture of the week

Now I'm aware of Async Tasks, but I've heard that async tasks get canceled on finish() and I've also heard they don't get canceled.

Is this sort of task, background loading best handled in a service? Can anyone provide me with a tutorial on loading things like this in the background?

EDIT:
People are mentioning that UI work should be in the UI and non-UI in the non-UI. I've considered handlers, but does the listener work when the current activity isn't active?

Lets say I start a handler in the main and then switch pages. When I come back I'm pretty sure the main UI won't be updated because the listener didn't trigger while I was viewing another page.

I'm assuming I'll have to create a class just for storing variables or something. Once the service finishes, it'll set a "pic_of_week_loaded" to true and when the main reloads it checks this var. How would I go about this?

Rawr
  • 2,206
  • 3
  • 25
  • 53
  • Considered using the [DownloadManager](http://developer.android.com/reference/android/app/DownloadManager.html)? – Jens Aug 30 '12 at 17:24
  • DownloadManager is API 9 which may be a problem – Marcin Orlowski Aug 30 '12 at 17:27
  • 1
    [Dashboard](http://developer.android.com/about/dashboards/index.html) is always a good tool to check when prioritizing what versions to support. – Jens Aug 30 '12 at 17:39

6 Answers6

2

You can extend from Application class, create a thread (or AsyncTask) within it that will basically download stuff from Internet. After it finishes, you can notify the home screen activity to show up the contents into the place holder.

Eng.Fouad
  • 115,165
  • 71
  • 313
  • 417
  • 1
    No, but once created, activity could get handle to ApplicationObject and simply do something like `myApp.isDataDownloaded()` to find out the current state. – Marcin Orlowski Aug 30 '12 at 17:40
  • @Rawr Yes. You can declare a flag in the application class that indicates whether the home screen activity is shown or not. You may need to set the flag to true at `onStart()` of home screen activity and set it to false at `onStop()`. Once the background downloading finishes, check for the flag, if it's true notify the home screen activity, otherwise you may need to load the contents from `onStart()` of the home screen activity (you can have a flag on application class that tells whether the downloading is done or not yet). – Eng.Fouad Aug 30 '12 at 17:40
  • @Eng.Fouad okay yea, so this way might work. I've heard a lot of people contradict each other about whether or not an AsyncTask gets canceled on `finish()` can you provide some reliable documentation to suggest Async persists even after `finish()`. The only time the background task should stop is if someone goes to settings and literally Force Stops the program. – Rawr Aug 30 '12 at 17:43
  • See [this tutorial](http://www.devahead.com/blog/2011/06/extending-the-android-application-class-and-dealing-with-singleton/) to know more about how to implement `Application` class. – Eng.Fouad Aug 30 '12 at 17:43
  • @Eng.Fouad Thanks I literally think links are crack xD – Rawr Aug 30 '12 at 17:44
  • @Eng.Fouad Never mind my Async question. I realized you were talking about an AsyncTask inside the Application class, not the Main activity. make sense now. I think I like this method the best. I can use this Application class to store global variables and run background downloads, correct? – Rawr Aug 30 '12 at 17:58
  • Accepted because the answer had a full understanding of the question and the different aspects that needed to be addressed. Also provided additional information for clarification. Thanks! – Rawr Aug 30 '12 at 18:07
1

Consider using IntentService to do your background job, so you will not be bond to activity life cycle.

EDIT: as per comments.

Using own application object is quite simply. Just create it like this:

final public class MyApplication extends Application {
 ....
}

then update your Manifest to look like this:

<application
    android:name=".MyApplication"
    ...

and theoretically that's it. You can now add your own methods there, incl. async task operations. And you can get handle to your application object with just MyApplication app = (MyApplication)getApplicationContext(); and then do app.XXX(); Some important notes though: if you'd like to use AsyncTask in your ApplicationObject, be aware of Android Bug #20915. The workaround, as per discussin there is to do this in onCreate():

public void onCreate() {
  // Workaround for android bug #20915
  // http://code.google.com/p/android/issues/detail?id=20915
  try {
    Class.forName("android.os.AsyncTask");
  } catch (Exception e) {
    e.printStackTrace();
  }

  super.onCreate();
  ...
Marcin Orlowski
  • 72,056
  • 11
  • 123
  • 141
  • yea I'm thinking this may be the safest way to go. Any idea how I would store a variable that the main would check to see if the image is finished downloading? – Rawr Aug 30 '12 at 17:35
  • 1
    In my apps I usually got own `ApplicationObject`, so I can put such "global" variables and logic just there, but it is not really necessary (however I see many benefits, but it complicates your code a bit). the simplest approach I can think of now would simply be to use shared preferences for this. – Marcin Orlowski Aug 30 '12 at 17:37
  • Yea I was thinking it might have to be a shared preference, but I'm starting to get a need for a place to store variables that any activity can access. I might look into this Application Object. Got any links? – Rawr Aug 30 '12 at 17:39
  • I accepted the other answer because it is the path I'm going to take rather than an IntentService but I upvoted a lot of your comments because you provided a lot of information on the Application class concept. You were very helpful sir. – Rawr Aug 30 '12 at 18:05
  • 1
    No problem. See the update anyway, as you may be facing the issue described as well. – Marcin Orlowski Aug 30 '12 at 18:10
1

Another choice is to use the Android Asynchronous Http Client

I would just do it in the activity but make sure to check that the activity is still active when you go to display it (make sure onDestroy hasn't been called on it yet)..

Then I would cache the result so you don't have to load it again from the web (store the date in the filename so you know which date the picture is for so you know if the cache holds the latest one already).

Matt Wolfe
  • 8,924
  • 8
  • 60
  • 77
  • very nice idea. I was thinking the main would check the SDcard to see if there was an image in a certain location and the app would check for the most current image and delete the one stored in the SDcard if there is a newer image. Then the background task would download and save it to the location. The main would just check to see if there is an image present in this "said location". I didn't even think about caching and that date in the file name is pretty smart too. – Rawr Aug 30 '12 at 17:48
1

I will try to explain why (1) keeping global state in Application sublcass and (2) using AsyncTask are both bad approaches for this case.

(1) OS may kill your app process if the app is not in the foreground and there is no running services in the app at the moment. More details on this in this post: How to declare global variables in Android?

(2) AsyncTask is tricker than it looks at first. :) For instance, if ((OS < 1.6) || (OS >= 3.0)) all tasks are run on the same background worker thread by default. I mean ALL tasks for the current Java process are executed on the only background thread. So the next possibly started tasks in other activities will have to wait untill that first/init one is done. I think this is not what you'd like to get (if user navigates from Home activity to some other activity and that another activity will need to load some data using AsyncTask, then make sure your first/init task is done quickly, because it will block any subsequent tasks). Of course, if the amount of work for the first/init task is not big, then you can don't worry about this point. More details on how AsyncTask works in this post: Running multiple AsyncTasks at the same time -- not possible?

So my advice would be to use IntentService to download the image/date you need on startup. Once it downloads the data it sets the flag in SharedPreferences and sends a broadcast. By this time the Home activity may be in 3 states:

  • active (passed onResume(), but not yet onPause())
  • paused (passed onPause(), but not yet returned to onResume)
  • destroyed

While it is active it uses broadcast receiver to listen to the service. When it is paused it unregisters the broadcast receiver, but when it comes to active the first thing it does is to check the flag from SharedPreferences. If flag is still not set, then registers broadcast receiver.

Community
  • 1
  • 1
Vit Khudenko
  • 28,288
  • 10
  • 63
  • 91
  • Well it's good to know the downsides. The thing is I only want it to run in the background if the app is in the foreground, and by coincidence I will have a service running in the background so it shouldn't be a problem :) I just think the application way will be more useful overall to the type of app I'm making :O – Rawr Aug 30 '12 at 23:00
0

1. Well AsyncTask was introduced into android to give what its know for Painless Threading, but i don't think thats the case here....

2. Now Its always good to keep the UI work on the UI thread, and Non-UI work on the Non-UI thread, and that became a rule from the arrival of HoneyComb version of Android.

3. Now as you want to download few things at background, which DOES NOT INCLUDE reflecting the output on the UI. Then i think you look into CountDownLatch Class in the java.util.concurrent.

4. Else you can always run a Non-UI thread at background.

Kumar Vivek Mitra
  • 33,294
  • 6
  • 48
  • 75
  • So the download doesn't directly affect the UI, but when it finishes it will probably send out a broadcast as well as setting a global variable. If the main is currently open, it receives the broadcast and updates. If the main is returned to at a later time, the onResume will check for the variable. Any ideas how to store a variable that persists through activities? and not passing between activities, a variable set in the background? almost like a preference. – Rawr Aug 30 '12 at 17:36
  • When i want to do what you mentioned, i use Singleton Pattern, i know some people don't like this idea, but it works really well when i am doing Android.... Create a class with Singleton Pattern, having those variable which you want to set, and its getter and setter methods... VOILA !!!! you are done........... Access it anytime, anywhere from any place in you application....but it will be available till your app is running.... (It wont be persisted after the application is closed), use a database for storing these value from this class, if you intend to use it later – Kumar Vivek Mitra Aug 30 '12 at 17:41
0

I prefer to use Threads and Handlers for background tasks. The thread will execute some operation in the background, then when the task is complete, update the handler to update the UI. Here is some sample code below:

Handler handler = new Handler() { //Declare this as a global variable to the class
      @Override
      public void handleMessage(Message msg) {
    //display each item in a single line
          if(msg.obj.equals("update subcat")){
              subcategoryAdapter.notifyDataSetChanged();
          }else if (msg.obj.equals("update products")){
              productsAdapter.notifyDataSetChanged();
          }else if (msg.obj.equals("update cat")){
              categoryAdapter.notifyDataSetChanged();
          }
     }
 };

Here is your background task:

Thread background = new Thread(new Runnable() {

    @Override
    public void run() {
        getSubCategories(((Categories)searchResults.get(pos)).ID);
        handler.sendMessage(handler.obtainMessage(pos, "update subcat"));
        }
    });
    background.start();
coder
  • 10,460
  • 17
  • 72
  • 125
  • 1
    i'm pretty sure `notifyDataSetChanged()` doesn't trigger if the activity has already `finish()`ed – Rawr Aug 30 '12 at 17:37