3

I am building a fitness app which continually logs activity on the device. I need to log quite often, but I also don't want to unnecessarily drain the battery of my users which is why I am thinking about batching network calls together and transmitting them all at once as soon as the radio is active, the device is connected to a WiFi or it is charging.

I am using a filesystem based approach to implement that. I persist the data first to a File - eventually I might use Tape from Square to do that - but here is where I encounter the first issues.

I am continually writing new log data to the File, but I also need to periodically send all the logged data to my backend. When that happens I delete the contents of the File. The problem now is how can I prevent both of those operations from happening at the same time? Of course it will cause problems if I try to write log data to the File at the same time as some other process is reading from the File and trying to delete its contents.

I am thinking about using an IntentService essentially act as a queue for all those operations. And since - at least I have read as much - an IntentServices handles Intents sequentially in single worker Thread it shouldn't be possible for two of those operations to happen at the same time, right?

Currently I want to schedule a PeriodicTask with the GcmNetworkManager which would take care of sending the data to the server. Is there any better way to do all this?

Xaver Kapeller
  • 49,491
  • 11
  • 98
  • 86
Mattias Petter Johansson
  • 1,064
  • 1
  • 15
  • 32
  • `GCMNetworkManager` already automatically batches calls and persits the data. As long as you use that you don't have to take care of anything. – Xaver Kapeller Dec 18 '15 at 10:55
  • I took the liberty of formatting, shortening and rephrasing your question to better fit on Stack Overflow. I hope you don't mind. If I misinterpreted some part of your question and changed the meaning somewhere feel free to edit your question again and fix my mistake. – Xaver Kapeller Dec 18 '15 at 20:02

3 Answers3

5

1) You are overthinking this whole thing!

Your approach is way more complicated than it has to be! And for some reason none of the other answers point this out, but GcmNetworkManager already does everything you are trying to implement! You don't need to implement anything yourself.


2) Optimal way to implement what you are trying to do.

You don't seem to be aware that GcmNetworkManager already batches calls in the most battery efficient way with automatic retries etc and it also persists the tasks across device boots and can ensure their execution as soon as is battery efficient and required by your app.

Just whenever you have data to save schedule a OneOffTask like this:

final OneoffTask task = new OneoffTask.Builder()

        // The Service which executes the task.
        .setService(MyTaskService.class) 

        // A tag which identifies the task
        .setTag(TASK_TAG) 

        // Sets a time frame for the execution of this task in seconds.
        // This specifically means that the task can either be
        // executed right now, or must have executed at the lastest in one hour.
        .setExecutionWindow(0L, 3600L)  

        // Task is persisted on the disk, even across boots                          
        .setPersisted(true) 

        // Unmetered connection required for task
        .setRequiredNetwork(Task.NETWORK_STATE_UNMETERED) 

        // Attach data to the task in the form of a Bundle
        .setExtras(dataBundle)

        // If you set this to true and this task already exists
        // (just depends on the tag set above) then the old task
        // will be overwritten with this one.
        .setUpdateCurrent(true)

        // Sets if this task should only be executed when the device is charging
        .setRequiresCharging(false)

        .build();
mGcmNetworkManager.schedule(task);

This will do everything you want:

  • The Task will be persisted on the disk
  • The Task will be executed in a batched and battery efficient way, preferably over Wifi
  • You will have configurable automatic retries with a battery efficient backoff pattern
  • The Task will be executed within a time window you can specify.

I suggest for starters you read this to learn more about the GcmNetworkManager.


So to summarize:

All you really need to do is implement your network calls in a Service extending GcmTaskService and later whenever you need to perform such a network call you schedule a OneOffTask and everything else will be taken care of for you!

Of course you don't need to call each and every setter of the OneOffTask.Builder like I do above - I just did that to show you all the options you have. In most cases scheduling a task would just look like this:

mGcmNetworkManager.schedule(new OneoffTask.Builder()
                .setService(MyTaskService.class)
                .setTag(TASK_TAG)
                .setExecutionWindow(0L, 300L)
                .setPersisted(true)
                .setExtras(bundle)
                .build());

And if you put that in a helper method or even better create factory methods for all the different tasks you need to do than everything you were trying to do should just boil down to a few lines of code!


And by the way: Yes, an IntentService handles every Intent one after another sequentially in a single worker Thread. You can look at the relevant implementation here. It's actually very simple and quite straight forward.

Xaver Kapeller
  • 49,491
  • 11
  • 98
  • 86
0

All UI and Service methods are by default invoked on the same main thread. Unless you explicitly create threads or use AsyncTask there is no concurrency in an Android application per se.

This means that all intents, alarms, broad-casts are by default handled on the main thread.

Also note that doing I/O and/or network requests may be forbidden on the main thread (depending on Android version, see e.g. How to fix android.os.NetworkOnMainThreadException?).

Using AsyncTask or creating your own threads will bring you to concurrency problems but they are the same as with any multi-threaded programming, there is nothing special to Android there.

One more point to consider when doing concurrency is that background threads need to hold a WakeLock or the CPU may go to sleep.

Community
  • 1
  • 1
Martin C.
  • 12,140
  • 7
  • 40
  • 52
-2

Just some idea. You may try to make use of serial executor for your file, therefore, only one thread can be execute at a time. http://developer.android.com/reference/android/os/AsyncTask.html#SERIAL_EXECUTOR

Frank Fung
  • 165
  • 1
  • 9