I have a Service which runs in the background when my app starts, and continues to run after the app is closed. It implements a Loader.OnLoadCompleteListener<Cursor> for a registered Uri from a Content Provider. When a new Cursor object is delivered on the callback method onLoadComplete(Loader<Cursor> loader, Cursor data) I then run an AsyncTask using the Cursor to create an ArrayList of custom Objects that implement the Parcelable interface. Once processed, if the app is open it sends back to the App getting marshalled/demarshalled using the IPC framework using a Handler with Messenger,Messages and aBundle. If the App is not open then the ArrayList is ready to be sent back to the App once opened, which calls to the Service when opened.
Problem:
The ArrayList can get relatively large (it doesn't contain Bitmaps, only Primitives and several short String objects) and the sheer amount means it hits the FAILED BINDER TRANSACTION when the ArrayList gets to about 700 objects.
Current Solution (that feels a bit hacky)
I am splitting the ArrayList into chunks and sending several Messages back to the Handler where it is then reconstructed into one ArrayList and then used in the App (updating RecyclerViews etc..).
I use this approach as the performance is significantly improved - only need to do initial query when App/Service is started first time, rather than always querying the Content Provider every time the App is opened.
The reason I say 'hacky' is because this feels like a workaround for a limitation of the Binder Framework rather than a solution.
Any advise on approaching this differently would be welcomed.
Two alternatives that come in my mind is:
Having a static ArrayList inside your service and sending a broadcast when the cursor received to the Activity to copy the contents of the static Arraylist which is inside the service in the local arraylist inside the activity. With this way Activity only has reference of the static aarraylist only the time it copies the contents.
I would save the contents in sql database asynchronously and then send broadcast to activity to retrieve asyncrhonously again the cursor from the database. On the UI thread then I would call notifydatasetChanged on the adapter.
Related
I'm currently working on an app that is composed of a background IntentService running all the time (even when activity lost focus) and a Activity (a section pager with fragments).
The background service is checking time very often and storing a large array of objects (up to 300) parsed from an xml file.
Each object represent an event with an associated startTime and when this startTime matches the current time the service has to notify the activity (if available) to display that the event is happening.
On the activity side I have fragments (tabs of the section pager) with a custom arrayAdapter to list the events into a ListView.
Right now what I'm doing is sending my object from my service with a LocalBroadcastManager. Fragments are the listening and when they receive the object they simply update their local Arraylist by replacing it and notifying the ArrayAdapter that the data set changed.
Since each fragment has to keep an arrayList to update the ArrayAdapter and then the ListView, I end up with the same object stored both inside the service's array and in one of the fragment' array. I'm wondering if there is a better way.
The question : What would be the most efficient (on memory/cpu usage and speed) way to share a large array of objects between my service and my activity ?
You could use greenrobot's EventBus. I don't think you can avoid duplicating the data. If you will use the same object instance in the Service and in the Fragments, you'll have to take care to notify the adapters each and every time, otherwise you'll get errors.
You would be better off storing the downloaded objects in a local SQLite database and only loading the minimum amount of information needed in the Fragments. This would minimize the time it takes to transfer the data from the IntentService thread to the Activity's main thread (because you wouldn't even transfer the data, just send a signal to the current Fragment that the data has been updated). A ContentObservable may also help you.
Also, you shouldn't use an IntentService for tasks that run all the time. The IntentService is intended to run a single task and finish, being started again the next time you need it to run another task. You should use a plain Service with a Thread or HandlerThread inside it. You will have better control this way, and you will also be able to run multiple tasks in parallel if you need to.
I am trying to understand some finer points of AsyncTaskLoaders. This may be obvious, to others but I can't find an unambiguous example or definition that demonstrates and exmplains what happens when you override the deliverResult() method. What actually gets delivered ? How does this interact with the calling object ? I can see use of super.deliverResult, which passes a private object from the class. So, does the loader automatically know what to associate with the "delivered result". I am totally confused.
Seems I'm a bit late to the party, but anyway...
One of the main advantages of this intermediary step between the background loading and the UI thread's callback onLoadFinished() getting called
loadInBackground()
deliverResult() and
the callback onLoadFinished()
is that it gives us a means of shortcutting the whole loading process from within the AsyncTaskLoader class.
And this can be put to good use for caching the loading result within your AsyncTaskLoader and preventing the background loading from happening if there is cached data.
And why would we want to do this? Isn't the whole point of loaders dealing with those dreaded activity lifecycle issues (e.g. rotating the device), maintaining state (like, caching data) and having a means to get updated when underlying data changes (CursorLoader)?
Well, yes, but this isn't the whole story.
Consider this use case:
You've got your app (the one with the AsynTaskLoader) up-and-running and it already has loaded data into your UI.
Then, you switch over to your Twitter app to check on some news and return to you app.
Without caching, upon returning to your app, the loader would do its reloading.
This behavior is different from the one after configuration changes, e.g. rotating your device, in which case no reloading would take place.
So, how would we then prevent the loader from re-fetching data in case we're just sending our app to the background and, later, return to it again?
Solution
Create a cache member variable in your AsyncTaskLoader implementation.
Override deliverResult() so that you save your fetched data in your cache first, before you call the superclass's implementation of deliverResult().
In onStartLoading() check if there's cached data, and if so, let your AsyncTaskLoader just deliver that. Otherwise, start loading.
Here's a link to a sample app which implements this behaviour.
It's just a "Toy app" and as such part of Udacity's current version of the "Developing Android Apps" fundamentals course. And here is the link to the respective video within that course that deals with this issue. (The course is free, but you'll still have to sign-up w/ Udacity).
In short, what this app demonstrates, is a UI in which the user can input a search query for searching GitHub's repos (via the GitHub API), showing the resulting search URL in a TextView and also the raw JSON fetched from GitHub in another TextView.
The whole action happens in just MainActivity.java and the relevant part here is within the AsyncTaskLoader that's implemented as an anonymous inner class:
For step 1, just introduce a member variable in your AsyncTaskLoader implementation that's meant to serve as your data cache.
/* This String will contain the raw JSON
from the results of our Github search */
String mGithubJson;
For step 2, override deliverResult() as to cache the loading result.
When loadInBackground() has finished, it passes its return value to deliverResult().
It does so anyway, but now that we've overridden deliverResult() we can step right in and store our fetched data into the cache member variable which we've created with just so good foresight.
And finally, we chain up to the super class implementation of deliverResult() with super.deliverResult() which will pass-on the result to the callback method onLoadFinished(), running on the UI thread.
#Override
public void deliverResult(String githubJson) {
mGithubJson = githubJson;
super.deliverResult(githubJson);
}
For step 3, check in onStartLoading() whether or not we've got cached data.
If we don't have cached data (yet), just force the loading to begin with a call to forceLoad().
But if we do have cached data, just call deliverResult(yourCachedDataGoesHere) and pass-in the cached data as argument.
if (mGithubJson != null) {
deliverResult(mGithubJson);
} else {
forceLoad();
}
So, if you now switch back and forth between your app and some other app(s), you'll notice that no reloading takes place, as the loader will just use your cached data.
suppose when data are loading in the background, at this time, user press HOME button and exist the app, when user comes back to the app, loading has been finished. So we have already have the data, then AsyncTaskLoader will call the deliverResult() method, deliver the data to the onLoadFinished() method for displaying.
When the user come back to app, onStartLoading() is being called before loadInBackground(). In this method, we could check if our data if empty or not, if not empty, we call deliverResult() and send the result to onLoaderFinished(), so it could prevent to reload data.
When we press HOME exist the app and then come back, it will not create a new Loader, instead the old loader will try to load data.
The only answer I can find that makes any sense is based on a decription in this link.
"A registered listener to receive the Loader's results when it
completes a load. For each of its Loaders, the LoaderManager
registers an OnLoadCompleteListener which will forward the Loader’s
delivered results to the client with a call to
onLoadFinished(Loader loader, D result). Loaders should deliver
results to these registered listeners with a call to
Loader#deliverResult(D result)."
deliverResult appears to be used when you have listeners to the AsyncTask and want to send the results back to them. I would say it's uncommon. The Android documentation is even less descriptive:
"Sends the result of the load to the registered listener. Should only
be called by subclasses. Must be called from the process's main
thread.
Parameters
data : the result of the load"
deliverResult works after doInbackground completes. It sends the result D (returned by doInBackground) to the calling thread. You may wish to override it for cleaning data, but you can do clean-up in doInBackground instead without overriding deliverResult.
I would like to pass data from activity to service. Currently I am trying to use an ArrayList<HashMap<String, String>>. What options do I have for passing this to the service? I was thinking about putting it as extra data in the intent but I'm not sure how to do this. Or is it possible to have this set up as some sort of global?
At the moment it is set up as a static of the activity, and the service is calling back to the activity to be provided with the next item from the ArrayList. I'm not happy with how that is working, so would like to move the list into the service.
EDIT
In response to the questions raised below:
I am trying to pass a playlist (yep, another media app) to the service. The playlist ArrayList<HashMap<String, String>> contrains paired song info, such as "songTitle" , "this is a title".
I think however that i am getting confused with the lifecycle of the application. When a user clicks play, the service is created. If I used a static for the list, when the service is created it would see that list. So the service will happily continue to play songs from this list. However, if the user changes folder etc, how do I get the app to recognise the change in playlist? The way the app is designed, the user can untick tracks in the list at any time, or can browse to a different folder. When the song completes the new list should be loaded. What is the best way of doing that? Or is the way that I am doing it now the best way? When the song completes send a broadcast back to the main activity requesting the next track. Should I destroy the service at this point and allow the "next song" function recreate it?
Lifecycle confusion!
Have you considered storing the data in a database and simple sending the intent that data has changed? Or use a ContentProvider that signals "by itself" that data has changed and needs to be reloaded? That way you won't use state even if the app closes unexpectectly.
Both ArrayList and HashMap implement Serializable, so you should just be able to stuff your structure in an Intent, and retrieve it using Intent.getSerializableExtra().
From your Activity, all you need to do is call the Intent.putExtra(String name, Serializable value) overload.
As a rule of thumb, try to avoid having static variables hanging out in your application if possible. They get tricky when your app is shut down by the OS and then recreated (i.e. from the recent apps list).
I would like to write an app to follow the price of lists of stocks.
There would be three activities containing lists of stocks :
myStocksActivity containing the stocks I'm interested in
searchedStocksActivity containing a list of stocks I could add in the list contained in myStocksActivity
winnersLoosersStocksActivity containing the list of stocks with the best or worst performance of the day.
I would like to persist the list of stocks in myStocksActivity, to be able to remove or add stocks from that list and to add the stocks displayed in searchedStocksActivity or winnersLoosersStocksActivity to myStocksActivity.
Another challenge : the price of the stocks should be updated every X minutes depending on the settings of app. The prices should only be updated when the app is running and should only update the prices of the list I'm currently looking at.
For the moment, my architecture isn't great : almost all the logic is contained in myStocksActivity and I know it's not a good thing.
I was thinking about moving the update of the stock list to a service, but I don't want this service to run when the application is not running.
What do you think ? Is my problem clear ? Thank you !
If I was you I would try and design(and build) a domain model first. This should end up being a set of classes which allows you to do everything you want with your stocks, independently of the a UI. You should also build in data persistence directly into these classes (i suggest using SQLite for this bit).
Then once you have a working model, build the UI on top of that. The MVP design pattern works pretty well with android.
Implement your activities as Views, these should both present data, and captures UI events and delegate these events to to Presenter instances, which then communicate/manipulate the model, and updates the Views accordingly. For example,
MyStocksView could present the user with a list of stocks, and the latest movements of stock price (or whatever). The MyStocksView contains the actual widgets that make up the user interface, and also acts as event listener and handler for various UI events (like when the user click a button). It should also contain a instance of a MyStocksPresenter class.
Once the user clicks the button, lets say, "remove stock", the event handler of MyStocksView then fires a method in the presenter instance for example, presenter.removeStock(id) , this method then updates the model (removes it from the in-memory data structures and database) and finally, if everything was successful, updates the View. It basically works as a middle man between the presentation layer, and the data-model.
In regards to the automatic updates every X minutes, I would handle this with a AsyncTask, there's not really much point in using a service if you only want this to happen while you app is running.
Whenever you main activity gets paused (or destroyed), call StopService(blabla);.
That way you can keep it as a service, and it won't run in the background.
Create a class for a stock, and store the update logic in there
I would put the handler - what holds instances of the stock-class and loops over a set to tell them to update - either in its own class purely with static methods and variables, or also in the stock class with static methods/etc.
The service then contains timing information, and calls the static update() method
The activity that displays stocks starts/stops the service with onCreate/onDestroy or onUpdate/onPause (I think it's update; the on that happens each time the Activity is brought to the forefront)
Same activity tells the handler class which ones to load, and uses its list for the display.
One other thing: I'd suggest against using a service. Use an AsyncTask. Services actually do run on the UI thread, so if there's a long-running loop during the update, the UI will freeze. AsyncTask starts a thread and will not block the UI.
I'm trying to write an Activity that spawns off an AsyncTask for a long-running operation, and periodically publishes status updates back to the Activity. I'd like to make sure the AsyncTask survives screen rotation or other destruction conditions like OOM correctly.
I've read this thread on the android-developers list, which in turn led me to this implementation, which is almost what I need. However, the last step of my AsyncTask is to delete some data from a content resolver. It is extremely important that this step is not skipped, because the data is sensitive and should not be left on the phone under normal conditions. However, ContentResolvers come from Contexts, in this case the Activity, and with this scheme the Activity may be null by the time the AsyncTask is ready to do the deletion.
Is it safe to get a ContentResolver from the Activity at AsyncTask construction and hold onto that past the lifetime of the Activity? If not, what can I do to handle this case?
Why don't you get the ContentResolver from the Application ? You need to understand the different concepts behind an Activity and Context - just because an Activity is a Context, doesn't mean that you need to use an Activity for all tasks that it does. Use an Activity only for UI stuff