It's a simple scenario:
You start some background, let's say, network operation in a separate thread.
Set your ProgressBar visible.
Go away from your app before network operation completed.
While your app is in background the network operation completes but you never receive a callback (or fired event) because you should unsubscribe your callbacks/event subscriptions to prevent undesired exceptions (you can only modify UI views from a main thread).
You resume the app and see ProgressBar on the screen despite your background operation has already been finished.
I'm curious what is the best way to handle this scenario.
A possible option could be also using an event bus (for example https://github.com/greenrobot/EventBus) and holding a sticky event when the network operation is completed and checking this in onResume of your activity.
My answer is a Service or an IntentService. While your app is in background I assume you hide the progress bar. When the app is in foreground You can bind to the Service's instance, having maybe a method that returns the current progress. If the progress is grater or equal to the max you show the progress again, otherwise you undertake another action
The general pattern for those this type of scenario (can also include data processing, view updates, etc, etc) is:
/*
`controller` is the object with reference to the task
currently being executed.
It can be anything:
a network operation,
a file copy,
an image processing,
an asset loading, etc, etc...
*/
public void onStart() {
super.onStart();
myView.setSomeProperty(controller.getCurrentValue());
controller.subscribe(this);
}
public void onStop() {
controller.unsubscribe(this);
super.onStop();
}
#Override
public void onControllerSubscriptionUpdate(int newValue){
myView.setSomeProperty(newValue);
}
that way, every time the Activity or Fragment is coming to the foreground, your view gets updated with the latest parameter.
Related
I have some fragments loaded in a ViewPager, where each "page" is loaded from a row in a cursor. Each fragment shows an image (JPEG) on the device. When the user dismisses the fragment (i.e swipe/page change, hits back/up, or just closes the app entirely) I want to invoke a method which opens the JPEG file for writing and does an update of its metadata. The actual work is eventually handled by the Apache Commons Imaging library.
I've implemented this by invoking my saveToFile() method from each fragment's life cycle onStop() handler. Does this mean the entire file operation ends up running on the UI thread? Should I definitely set up an AsyncTask for this?
Say the file write for some reason suddenly (for some jpeg) should take a long time, eg 2 minutes. What would then happen? Would the UI just wait (freeze) at this page/fragment before resuming? Or would the process (write to file) carry on "in the background" somehow? Or would the process just be killed, stopped short mid-process?
The way I have this wired up currently (onStop invoking saveToFile(), which calls up the imaging library and then updates the file) seems to work as it should. Even if I end the app, I still see my Toast text popping up, saying "Writing to file..." Seemingly, the process is never disturbed, and I can't say I'm experiencing any UI lag.
onStop() handler. Does this mean the entire file operation ends up
running on the UI thread? Should I definitely set up an AsyncTask for
this?
YES
An AsyncTask has several parts: a doInBackground method that does, in fact, run on a separate thread and the onPostExecute method that runs on the UI thread.
You can also use some sort of observer pattern such as EventBus to run async and post results to the UI.
Say the file write for some reason suddenly (for some jpeg) should
take a long time, eg 2 minutes. What would then happen? Would the UI
just wait (freeze)
The application will crash because Android will forcefully close it due to ANR (Application Not Responding).
Refer to the official documentation for details on this: https://developer.android.com/training/articles/perf-anr.html
Android applications normally run entirely on a single thread by
default the "UI thread" or "main thread"). This means anything your
application is doing in the UI thread that takes a long time to
complete can trigger the ANR dialog because your application is not
giving itself a chance to handle the input event or intent broadcasts.
Therefore, any method that runs in the UI thread should do as little
work as possible on that thread. In particular, activities should do
as little as possible to set up in key life-cycle methods such as
onCreate() and onResume(). Potentially long running operations such as
network or database operations, or computationally expensive
calculations such as resizing bitmaps should be done in a worker
thread (or in the case of databases operations, via an asynchronous
request).
The most effective way to create a worker thread for longer operations
is with the AsyncTask class.
Here is what I recommend though. Use the above mentioned, EventBus and create a BaseActivity which will automatically save the data for you onClose() by firing an event that runs Async. You then extend that base activity in all the places where you need autosave capabilities.
Here's what I mean with an example that uses EventBus.
public abstract class BaseActivity extends Activity{
#Override
protected void onResume(){
if(!EventBus.getDefault().isRegistered(this))
EventBus.getDefault().register(this);
super.onResume();
}
#Override
protected void onDestroy() {
if(EventBus.getDefault().isRegistered(this))
EventBus.getDefault().unregister(this);
super.onDestroy();
}
#Override
protected void onStop() {
super.onStop();
//We fire event and pass the current parent class that inherited this base.
EventBus.getDefault().post(new EventBusProcessMySaveData(this.getClass()));
}
}
//Your model class to use with EventBus
public final class EventBusProcessMySaveData{
private final Class className;
public EventBusProcessMySaveData(final Class className){
this.className = className;
}
public Class getClassName(){
return this.className;
}
}
public class MyMainActivity extends BaseActivity{
//Do you standard setup here onCreate() and such...
//Handle Event for Saving Operation, async.
//This will fire everytime theres an onClose() IN ANY activity that
//extends BaseActivity, but will only process if the class names match.
#Subscribe(threadMode = ThreadMode.ASYNC)
public void methodNameDoesNotReallyMatterHere(final EventBusProcessMySaveData model){
//We make sure this is the intended receiving end by comparing current class name
//with received class name.
if(model.getClassName().equals(this.getClass())){
//Do whatever you need to do that's CPUintensive here.
}
}
}
On Android basically, I am trying to upload/download data from a web service and have a progress bar that should be rendered showing the progress percentage.
Following is the approach that I implemented along with the reason for considering them:
Chose IntentService to run in background:
As per the link I could not use bound service as it will destroy when the binded Activity/Fragment is destroyed. Hence the selection of IntentService over Service. Move over it would be one time and intent service works on a worker thread so it is much better.
Broadcast to receive updates:
Implementation of LocalBroadcastManager to update the progress bar UI by registering it to listen to the broadcast received from the IntentService described above.
However with this approach, the following issue needs to be addressed:
Sequence of broadcasts is not maintained. The broadcast carries the percentage of progress of the upload/download. But since the sequence is not maintained, there are chances that I receive stale updates (eg: I may receive 30% after 40%). I also tried using sendBroadcastSync method of the LocalBroadcastManager but it doesn't work consistently (I am not very sure on this). Hence I tried to implement Messenger via Handler however I got to know that the approach will not be able to reconnect to the on going upload/download once the UI is re-created (Activitiy/Fragment is destroyed and created). Basically the reference to the Handler is lost along with the previous activity/fragment and hence the messages are not delivered to the UI for update.
I have been trying to get my way about since a while but I am not able to get it done. Your words of wisdom will be very helpful.
How about using an interface. UI: get instance and setCallback() when activity/fragment created, Service: get instance and call update(use a handler if not called from the main thread, handler.post(runnable {... update ...})).
If you want to notify multiple UI instances, keep an ArrayList in the updater class, and modify the 'setCallback' method to 'addCallback', and update every list item in 'update'.
public class ProgressUpdater {
private static ProgressUpdater sUpdater;
private UpdateCallback mCallback;
public interface UpdateCallback {
public void update(long progress);
}
public void setCallback(UpdateCallback callback) {
mCallback = callback;
}
public void update(long progress) {
if (mCallback != null) {
mCallback.update(progress);
}
}
public static ProgressUpdater getInstance() {
if (null == sUpdater) {
sUpdater = new ProgressUpdater();
}
return sUpdater;
}
private ProgressUpdater() {
}
}
I would go about the problem a bit differently, as ordering of messages may be a problem with a variety of message solutions.
The fact that you get the 40% event after the 30% event is not really a problem. What is a problem is if you update the UI to reflect 30%.
Have each event (each Intent when using LocalBroadcastManager as your event bus) contain a timestamp along with the percentage-complete. Track in your UI the last-seen event timestamp. If you get an event that is older than your last-seen event timestamp, ignore that event.
If you are sure that your percentage is monotonically increasing — in other words, that there is no legitimate scenario in which you might drop from 40% to 30% complete — you could skip the timestamp and just only apply new percentages that are higher than your currently-showing percentage.
If you want to constantly update your ui then we can start a local broadcast manager from a service and register your broadcast receiver in your activity.
How do I properly do that?
I have a stopwatch and I'm saving it's state in onSaveInstance and restoring it's state in onRestoreInstance...
Now I've following problem: if I stop the thread in onSaveInstance and the screen get's locked or turned off, onRestoreInstance is not called and the stopwatch is not continuing...
If I don't stop it, the stopwatch is running in background on and on even when the screen is off or the activity is not active anymore...
So what's the usual way to handle such a thing?
PS:
I even have a working solution, a local variable to save the running state in the onStop event and restarting the thread in the onStart event... But I still want to know if there's a "default" solution using the android system itself....
Ok. I better now understand what you're doing. I thought you were using the thread to count. Right now it sounds like you're using it to update the UI.
Instead, what you probably should be doing is using a self-calling Handler. Handlers are nifty little classes that can run asynchronously. They're used all over the place in Android because of their diversity.
static final int UPDATE_INTERVAL = 1000; // in milliseconds. Will update every 1 second
Handler clockHander = new Handler();
Runnable UpdateClock extends Runnable {
View clock;
public UpdateClock(View clock) {
// Do what you need to update the clock
clock.invalidate(); // tell the clock to redraw.
clockHandler.postDelayed(this, UPDATE_INTERVAL); // call the handler again
}
}
UpdateClock runnableInstance;
public void start() {
// start the countdown
clockHandler.post(this); // tell the handler to update
}
#Override
public void onCreate(Bundle icicle) {
// create your UI including the clock view
View myClockView = getClockView(); // custom method. Just need to get the view and pass it to the runnable.
runnableInstance = new UpdateClock(myClockView);
}
#Override
public void onPause() {
clockHandler.removeCallbacksAndMessages(null); // removes all messages from the handler. I.E. stops it
}
What this will do is post messages to the Handler which will run. It posts every 1 second in this case. There is a slight delay because Handlers are message queues that run when available. They also run on the thread that they're created on, so if you create it on the UI thread you will be able to update the UI without any fancy tricks. You remove the messages in the onPause() to stop updating the UI. The clock can continue to run in the background, but you won't be showing it to the user anymore.
I just got into Android programming, but I don't think onRestoreInstance will be called in that situation because you're not switching from one activity to another. I think your best bet is to call onPause which will then call onSaveInstance if you need it to, but use onResume which might or might not call onRestoreInstance.
in my app, i'm uploading some files that can take up to several minutes. i'm thinking of a way to notify the user about activity going on passively by adding a progress bar in my custom title bar. what i want to do is have every activity, each which uses the custom titles, appear with the progress bar until the thread finishes and does a callback which would make invisible the progress bar. can something like this be accomplished?
what seems to make this impossible is that if the user is in an activity with the view loaded, the thread finishing callback would have to manipulate the loaded view resources to disable the progess bar which doesn't seem feasible. are there any suggestions to accomplish this or alternative solutions in keeping a global and passive indication of something going in the background?
You can use a service to achieve this. Services
Basically how it would work, is you bind to the service in each activity when you create the activity. You use this service to start your upload method.
When you bind to the service you pass a handler, which is then used to update your UI in that specific activity. The service will never directly affect the UI (it will be running on a separate thread) instead the handler passes a message back to the UI thread with data in a Bundle, such as upload progress, or a bool to say it's finished.
I have two static tables with about 500 records each which provide lookup material for some ListViews in my app. When the app first starts and the tables are created I run a process to populate the tables which takes less than a minute but I've been looking to run the process in background using Async Task with a progress dialog letting the user know what is happening.
My concern is that while the process is running and the data is being added and then the user tilts the phone the process will cancel. What would be the state of my database then? Is Async Task the best solution for this or should I use Threading?
So when you rotate or change the orientation of the phone, the activity is the only thing destroyed. You don't necessarily have to get rid of the async task. In fact it will live on. Just don't let another task come in and work on it ad-hocly.
So if you want to have your activity act as if upon rotating that you can start right back up, where you left off, there is a method called onRetainNonConfigurationInstance(). It's basically the method that stashes objects which can't be parceled like in saveInstanceState()
So the idea being:
public void onCreate(Bundle a) {
...
AsyncTask myTask = getNonConfigurationInstance();
if (myTask == null) {
myTask = new AsyncTask();
myTask.execute();
}
...
}
public Object onRetainNonConfigurationInstance() {
return myTask;
}
This will keep the async task running, and when you get your onCreate called after the rotation you just pick it back up and do what needs to be done.
One thing to be conscious of is the progressView. It will have to be destroyed and reinitialized to the new state. Also the overall dismissing of it and showing it in the first place should be done outside the AsyncTask. But nothing is to say that the AsyncTask can't call some callback that you always set in your onCreate() so that it will notify to tell to update the UI or play a sound of completion, etc.
You could also decide to handle the configuration changes on your own through the use of the android:configChanges in your manifest.
You then implement the onConfigurationChanged method and perform any actions inside.
See the developer doc.
When a configuration change occurs at
runtime, the activity is shut down and
restarted by default, but declaring a
configuration with this attribute will
prevent the activity from being
restarted. Instead, the activity
remains running and its
onConfigurationChanged() method is
called.