Question

I'm having some understanding-issues when it comes to Background Services on Android. I have a feeling that, for the ones of you with a ton of experience, my questions are easily answered and that I just need to be pointed in the right direction :-)

Let me first explain what I'm trying to achieve: I want to download pictures that come with a set of data (download url, title, an ID, an owner ID and a file name). The user enters that data through text-fields. The user can enter several entities of that data-set so everything is stored in ArrayLists. There is an ArrayList for the URLs and an ArrayList for ID, for example. In the long run this info is not supposed to come from a user but from a server. The user interface is basically just for debugging. After the download is completed, the data-sets are put into a SQLite DB.

My goal is to have a separate background service that takes care of the download and inserts the data into the DB as soon as they're done. So from the Activity where the user enters the data I create an Intent and add the user data as extras. Here is the first problem: There is no ArrayList extra. That leads to my first question:

How do I pass an ArrayList between Services/Activities? I've read something about Parcels, is that the way to go although long is a primitive data-type?

My second problem is that I can't be sure the download is completed before the Service is called again. So when the service is called again the method onStartCommand() is called again. Right now my Service Class looks like this (the data in ArrayList is not implemented yet):

public class DownloadService extends Service implements DownloadAlbumPictures.DownloadCompletedListener {

    public final static String EXTRA_ALBUMPICTURE_URLS = "ALBUM_PICTURE_URLS";
    public final static String EXTRA_ALBUMPICTURE_NAMES = "ALBUM_PICTURE_NAMES";
    public final static String EXTRA_ALBUMPICTURE_TITLES = "ALBUM_PICTURE_TITLES";

    private ArrayList<String> picUrls;
    private ArrayList<String> namesForFiles;
    private ArrayList<Long> ownerIds;
    private ArrayList<Long> pictureIds;
    private ArrayList<Long> albumIds;
    private ArrayList<Integer> positions;
    private ArrayList<String> titles;
    private String pathToFiles;

    // download interface
     public void albumPicDownloadCompleted(Context context, Uri uri, int indexOfCompletedFile) {
         // init
          AlbumPicture newAlbumPic;

         // data from download
          long ownerId = 0 //ownerIds.get(indexOfCompletedFile);
          long pictureId = 0 //pictureIds.get(indexOfCompletedFile);
          int positionInAlbum = 0 //positions.get(indexOfCompletedFile);
          String title = titles.get(indexOfCompletedFile);
          String pathToPic = uri.toString();
          long albumId = 0 //albumIds.get(indexOfCompletedFile);

          newAlbumPic = new AlbumPicture(ownerId, pictureId, positionInAlbum, title, pathToPic, albumId);

         // put picture into DB
          newAlbumPic.putIntoDb(this);
     }

    @Override
     public int onStartCommand(Intent intent, int flags, int startId) {
         picUrls = intent.getStringArrayListExtra(EXTRA_ALBUMPICTURE_URLS);
         namesForFiles = intent.getStringArrayListExtra(EXTRA_ALBUMPICTURE_NAMES);
         titles = intent.getStringArrayListExtra(EXTRA_ALBUMPICTURE_TITLES);

          DownloadAlbumPictures dwnldAlbumPics = new DownloadAlbumPictures(newPicUrls, newNamesForFiles);
          dwnldAlbumPics.execDownload(this);
          return Service.START_REDELIVER_INTENT;
     }
}

DownloadAlbumPictures.execDownload calls Androids Download manager. It also registers a broadcast receiver for the download completed event which then calls the interface function albumPicDownloadCompleted():

public class DownloadAlbumPictures {

public interface DownloadCompletedListener {
    public void albumPicDownloadCompleted(Context context, Uri uri, int indexOfCompletedFile);
}

private ArrayList<String> picUrls;
private ArrayList<String> namesForFiles;
private ArrayList<Long> downloadReferences; 
private String pathToFiles;

private DownloadCompletedListener dwnldCompletedListener;

public DownloadAlbumPictures(ArrayList<String> _picUrls, ArrayList<String> _namesForFiles) {
    picUrls = _picUrls;
    namesForFiles = _namesForFiles;
}

public DownloadAlbumPictures() {

}

public boolean execDownload(Context context) {
    // check if source info is available
    if (picUrls == null || namesForFiles == null)
        return false;

    fetchPics(context);
    return true;
}

public boolean execDownload(Context context, ArrayList<String> _picUrls, ArrayList<String> _namesForFiles) {
    picUrls = _picUrls;
    namesForFiles = _namesForFiles;

    return execDownload(context);
}

// example use to download pictures
private void fetchPics(Context context) {
    // path
    pathToFiles = createPathForAlbumPictures();
    // listener interface
    try {
        dwnldCompletedListener = (DownloadCompletedListener) context;
    } catch (ClassCastException e) {
        throw new ClassCastException(context.toString() + " must implement DownloadCompletedListener for AlbumPicture download.");
    }

    // sending files to download manager and saving reference
    // TODO file type check missing => security issue
    DownloadFiles downloadFiles = new DownloadFiles(picUrls, pathToFiles, namesForFiles);
    if (downloadFiles.startDownload(context)) {
        downloadReferences = downloadFiles.getDownloadReferences();
    }
    else {
        downloadReferences = null;
    }
    // add listeners for when file download is completed
    IntentFilter broadcastFilter = new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE);
    BroadcastReceiver receiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            long reference = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);    // get reference of completed download (if no completed download -1)
            // check all downloaded files
            for (int i=0; i<downloadReferences.size(); i++) {
                if (reference == downloadReferences.get(i)) {
                    // actual path to file needs to be updated
                    DownloadManager dwnldmngr = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
                    Uri uriOfFile = dwnldmngr.getUriForDownloadedFile(reference);
                    // hand over uri to interface
                    dwnldCompletedListener.albumPicDownloadCompleted(context, uriOfFile, i);    // interface
                }
            }
        }
    };
    context.registerReceiver(receiver, broadcastFilter);
}
}

Now to my question: If I call the Service a second time before the first "ArrayList of downloads" is done, my data will be overwritten. The solution I came up with is, that I need to implement a queue within the service. New data doesn't overwrite the old data but is added in a queue. As soon as a download is done that item is removed from the queue. It seems complicated. Am I missing another option? What is best practice here?

I hope I can get some best-practice info on my issue. Maybe I'm doing it way too complicated :-)

I apologize if my explanation is not very clear. Since I don't have a solution for the problem my thoughts on this topic are a little fuzzy.

Thanks for your help! Dan.

Was it helpful?

Solution

There is no ArrayList extra

Yes, there is, for ArrayList<Integer>, ArrayList<String>, and ArrayList<Parcelable>).

How do I pass an ArrayList between Services/Activities?

In your case, apparently use putStringArrayListExtra().

DownloadAlbumPictures.execDownload calls Androids Download manager.

There is no point in having a service do that. Instead, have the activity start the download, and have a manifest-registered BroadcastReceiver pass control to an IntentService for the database insert once the download is complete.

Or, do the download yourself.

Or, do the database insert yourself first (IntentService or simple thread spawned by the activity), but with a "in progress" status flag in some column. Then, just flip the flag when the download is complete.

The solution I came up with is, that I need to implement a queue within the service.

The second and third options I outline above should avoid that requirement.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top