AsyncTask doInBackground returns too soon - android

I have an AsyncTask connecting to a websocket.
protected Void doInBackground() {
client.connect();
return null;
}
When it's finished attempting the connection, I want the following to happen (currently inside onPostExecute):
protected void onPostExecute() {
if (socketConnected) {
doOtherThings();
} else {
log("Failed to connect.");
}
I've also tossed in the following, as another probe of sorts (in the WebSocketClient implementation):
public void onOpen() {
log("Opened successfully!");
socketConnected = true;
}
The onPostExecute method prints the failure message , followed by the success message from onOpen. This suggests that doInBackground is returning too soon. Is there any common reason this might happen?

Yes, probably the method client.connect() creates a ASyncTask and this leaves your task to continue executation, ending the method and going to onPostExecute.
If thats the case, the API you are using does should have some methods to listening async messages.
The API should have some Listener or Callback that will be fired at your thread.
EDIT: I searched WebViewClient on internet and the method connect() does indeed creates a new Thread, you should look how to use the calls properly.

Related

Android - RxJava vs AsyncTask to prevent getActivity() memory leak

How does using RxJava (or RxAndroid,etc) instead of AsyncTask in Android help prevent a context leak? In AsyncTask, if you execute it and user leaves the app, then the activity context can be null and the app can crash. I have heard that RxJava can help prevent this type of crash when doing threading. I also heard that it can do better error handling then the doInBackground method of AsyncTask (which handles errors badly). Most of the time I just return null (for example) in doInBackground if anything fails, but I've read that RxJava can return the exact error and not leak. Can anyone give an example?
Here is a small demo of a crash in AsyncTask if the user leaves the app while it's trying to report results to UI:
#SuppressWarnings("unused")
private class GetTask extends AsyncTask<Void, Void, Void> {
#Override
protected void onPostExecute(String result) {
pd = new ProgressDialog(getActivity().getApplicationContext());//can crash right here
pd.setTitle("Grabbing Track!");
pd.setMessage("Please wait...");
pd.setCancelable(false);
pd.setIndeterminate(true);
pd.show();
}}
And here is a doInBackground method call that does not send out errors that are useful:
#Override
protected String doInBackground(String... params) {
String myIntAsString = 1/0 + ""; //this should give an error (how do we report it to the caller??
//or if we are parsing json and it fails, how do we report it to the caller cleanly. Can RxJava help?
}
I think the good thing about RxJava is if you have a bunch of tasks you can put them in a sequence such that you know when one finishes and the next is about to start. in a AsyncTask if you have more then one running, you have NO guarantee which task will complete first and then you have to do alot of error checking if you care about order. So RxJava allows you to sequence calls.
In regards to memory leaks we can have a AsyncTask as an inner class of an activity. Now since its tied to the activity when the activity is destroyed that context is still hanging around and wont be garbage collected, thats the memory leak part.
Here is where RxJava can help. if any errors occur at all then we can call the subscribers onError method. The subcriber can look like this:
public Observable<JsonObject> get_A_NetworkCall() {
// Do your network call...but return an observable when done
}
Subscription subscription = get_A_NetworkCall()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<jsonResponse>() {
#Override
public void onCompleted() {
// Update UI
}
#Override
public void onError() {
// show error on UI
}
#Override
public void onNext(JsonObject response) {
// Handle result of jsonResponse
}
});
or something similar - this is psuedocode . The point is you can report the errors more cleanly and switch threads in one line. Here we are reporting on androids main thread but doing the work on a new thread. After we are done in our activities onDestroy method we can simply unsubscribe to the observable and it kills it and prevents any memory leaks that we encounter with AsyncTask. This to me should be the replacement for any asyncTasks.
I use a combination of two things. First, RxAndroid is really helpful:
https://github.com/ReactiveX/RxAndroid
You can use AppObservable.bindActivity on it to bind an observable such that its output is observed on the main thread, and if the activity is scheduled to be destroyed, messages will not be forwarded. You still have to manage the pause/resume lifecycle though. For that, I use a composite subscription like this (pseudojava coming up):
public class MyActivity extends Activity {
private final CompositeSubscription subscriptions = new CompositeSubscription();
#Override
public void onResume() {
super.onResume();
subscriptions.add(AppObservable.bindActivity(this, myObservable)
.subscribe());
subscriptions.add(AppObservable.bindActivity(this, myOtherObservable)
.subscribe());
}
#Override
public void onPause() {
subscriptions.clear();
super.onPause();
}
}
Obviously you'd want to do more in subscribe if you want to do something with the data, but the important thing is to collect the returned Subscription instances and add them to the CompositeSubscription. When it clears them out, it will unsubscribe them as well.
Using these 'two weird tricks' should keep things from coming back to the Activity when it is an inoperative state.

How to stop the thread on Android?

I have a function parseData which recieves Vector of urls and gives them to DataParser. DataParser gets data from urls and parses it. The problem is that user might request new urls to parse before previous parsingis finished. In that case previous data becomes irrelivant but thread continues to work. Since there might be a lot of urls in one request and parsing each of them takes time, after 5-6 sequential requests phone starts work very slowly.
Here is the code snippet.
public void parseData(final String key, final Vector<String> data)
{
this.key = key;
Thread thread = new Thread(new Runnable() {
#Override
public void run() {
DataParser dp = new DataParser(key);
dp.setData(data);
dp.startParse();
}
});
thread.start();
}
I think the solution might be to keep extra flag in DataParser. Since it requesting urls in cycle, I can check flag and break cycle, but it seems to me rude.
Are there other ways to solve this issue?
You can use interrupt() method:
thread.interrupt();
BTW, checking some kinds of flags isn't so rude and bad style. But don't forget to declare such flag as volatile.
You need to check periodically for a flag in worker thread. Set that flag if worker thread is to be stopped.
This kind of thing is done well in an Async Task instead of straight thread. There is a cancel method to them and an is canceled function that can tell you to stop.
You could constantly check on a boolean flag every time you perform a parsing operation, and stop parsing if this flag becomes true.
From another thread, you can establish the value of this flag to "cancel" the parsing.
This is the technique AsyncTasks use to cancel the work done in doInBackground().
class DataParser {
private boolean volatile mIsCancelled = false;
public void startParsingAsync() {
new Thread(new Runnable(
public void run() {
parse();
}
)).start();
}
private void parse() {
while(!isCancelled()) {
parseNextNode();
}
}
private synchronized void isCancelled() {
return mIsCancelled();
}
public synchronized void cancel() {
mIsCancelled = true;
}
private void parseNextNode() {
.....
}
From another thread, you can invoke the method cancel() once the data has become irrelevant.
Note the you have to synchronize the access to the flag, as it will be accessed from different threads.
This code is not tested, so it may not even compile...
That's the theory, but for practical uses, you should use an AsyncTask, which gives the cancelling for you.

Do something if AsyncTask is cancelled?

I'm using AsyncTask to populate SQLite database. I'm downloading data from a certain webpage and putting it in SQLite tables. The thing is, I want to either download 100% of the data or none. So in case the AsyncTask is for some reason interrupted, I want to delete all the data that has been downloaded so far.
This is how I tried to do it:
#Override
protected void onCancelled() {
super.onCancelled();
dbHandler.deleteFromDatabase(razred);
Log.i("TAG", "AsyncTask cancelled");
}
I thought that "onCancelled" will execute if AsyncTask is interrupted in any way but it doesn't. What could I do to erase data that was made with AsyncTask in case it is cancelled in any way? (ex. activity paused, activity destroyed, internet connection interrupted etc.)
You're on the right track, but in your doInBackground() you also need to specifically call isCancelled() to check if it's cancelled and then return from doInBackground(). Then your code will work properly.
Refer to the AsyncTask documentation for "Cancelling a task"
Here's the quote from the documentation for easy reference:
A task can be cancelled at any time by invoking cancel(boolean). Invoking this method will cause subsequent calls to isCancelled() to return true. After invoking this method, onCancelled(Object), instead of onPostExecute(Object) will be invoked after doInBackground(Object[]) returns. To ensure that a task is cancelled as quickly as possible, you should always check the return value of isCancelled() periodically from doInBackground(Object[]), if possible (inside a loop for instance.)
EDIT: Per request, some sample code:
private class MyAsyncTask extends AsyncTask<Void,Void,Void> {
private SQLiteDatabase db;
#Override
protected void onPreExecute() {
// any kind of initialization or setup needed before the
// background thread kicks off. remember: this is still on
// on the main (UI) thread
// since youre doing DB I/O, Ill make believe Im initializing the DB here
db = DatabaseHelper.getInstance(MainActvity.this).getWritableDatabase();
}
/*
* The background thread to do your disk and network I/O. If you need
* to pass in any parameters, this is the first Void in the template
*/
#Override
protected Void doInBackground(Void... params) {
// other stuff you need to do in the background. Since you want an
// all-or-nothing type thing, we will use a transaction to manually
// control the db
db.beginTransaction();
try {
// do network I/O to retrieve what you need and then write to DB.
...
... // if theres a loop in here somewhere when reading the data, check !isCancelled() as part of the condition or as one of the first statements and then break
...
db.setTransactionSuccessful(); // assuming everything works, need to set
// this successful here at the end of the try
} catch (InterruptedException ie) { // or some other exception
cancel(true); // heres where you can call cancel() if youve been interrupted
} catch (IOException ioe) { // if your network connection has problems
cancel(true);
} finally {
db.endTransaction();
// other cleanup, like closing the HTTP connection...
// no need to close the DB if you implement it properly
}
return null; // if there was some return value, that would go here
}
#Override
protected void onCancelled(Void result) {
// depending on how you implement doInBackground(), you may not even need this,
// unless you have a lot of other "state" you need to reset aside from the DB transaction
}
#Override
protected void onPostExecute(Void result) {
// any other items to do on main (UI) thread after doInBackground() finishes
// remember, this only gets called if cancel() is not called!
}
}
Hope that helps!
I know this is not exactly what you've asked for, but I have to say you are doing it all wrong by using the AsyncTask.
There are many cases where your async task will be terminated without you being able to do anything. For such critical tasks as this one, use a Service.
With a Service you can till the system to restart your service in case it is terminated prematurely. You then can continue what you started, or start all over again (deleting all previous downloads...etc).
With an AsyncTask, if the system decided to terminate your async task prematurely, you are not notified nor the AsyncTask is restarted. It just dies in complete silence.
I think in the onpostexecute you could handle anything you wanted to.
private class ParseDownload extends AsyncTask<Summary, Void, Boolean> {
#Override
protected Boolean doInBackground(Summary... urls) {
for (Summary url : urls) {
url.dosomething();
if (isCanceled();) { return false;}
}
return true;
}
#Override
protected void onPostExecute(Boolean result) {
if (!result) {
// delete * from yourtable here...
// and mark the download incomplete etc.
}
}
}
Good Luck

Stange onPause / onResume behaviour

I am seeing some strange behaviour with onPause / onResume in my app and cannot work out what is happening.
I perform a database query (simple subclass of AsyncTask) in onResume and cancel it in onPause if it is still executing. I received a crash report that made me wonder if the task cancel was working or not so added an analytics event to record onPostExecute getting called after onPause had cancelled the task.
Over the last month I have seen 140 of these events for 4,100 page views.
#Override
protected void onResume() {
super.onResume();
mIsResumed = true;
if (mReverseCardsTask == null) {
mReverseCardsTask = new TcgCursorTask(this) {
#Override
protected Cursor doInBackground(Void... params) {
return mDb.reverseFetchCards();
}
#Override
protected void onPostExecute(Cursor cursor) {
if (mIsResumed) {
onReverseCardsCursor(cursor);
} else {
EasyTracker.getTracker().sendEvent("error", "on-post-execute", "called after paused", 1L);
}
}
};
mReverseCardsTask.execute();
}
}
#Override
protected void onPause() {
super.onPause();
mIsResumed = false;
if (mReverseCardsTask != null) {
mReverseCardsTask.cancel(false);
mReverseCardsTask = null;
}
}
I have a feeling I am missing something very simple here, but can't see it.
I just noticed I am not clearing mReverseCardsTask in onPostExecute, but that should not matter.
Just calling cancel() doesn't do anything. You actually have to put checks in the process to determine if it is to be canceled and do the job of canceling it.
The OS doesn't know what you may need to do to clean things up (like closing files or open network connections) before stopping.
OK. I have worked it out. I am not sure which API version it was fixed in, but if you look at the code for Gingerbread there is a clear race condition in the cancel() handling. The GUI thread code which processes the MESSAGE_POST_RESULT message from the background calls onPostExecute() regardless of whether or not the task was cancelled.
It turns out that the fix is quite simple. All I need to do is add my own check of isCancelled() before executing my onPostExecute() logic.
The Gingerbread code receives MESSAGE_POST_RESULT and calls finish(). Then finish() calls onPostExecute().

onCancelled() seems differnet in Ice Cream Sandwich

Just wondered what's happened with IceCream Sandwich...
I had a simple AsyncTask reading data from a server. When the Disconnect button was clicked, then lServerTask.cancel(true); was called which was successfully calling onCancelled(), setting m_Running = false; thus breaking out of the while loop in protected void doInBackground(Void...params).
With my Galaxy Nexus, I click the disconnect button, but the task does not quit. And is still connected to the server. Does anyone have any ideas?
Many Thanks
Mark
Code FYI -
// automatically done on worker thread (separate from UI thread)
protected Void doInBackground(Void...params)
{
while(m_Running)
{
try
{
lRDS.readSocket();
}
catch (IOException e)
{
lRDS.disconnectFromServer();
publishProgress(e.toString());
return null;
}
lDataBuffer = lRDS.getDataBuffer();
publishProgress(lDataBuffer);
}
return null;
}
#Override
protected void onCancelled()
{
m_Running = false;
connectRDSButton.setEnabled(true);
disconnectRDSButton.setEnabled(false);
}
and from the UI side:
private class disconnectRDSButtonHandler implements View.OnClickListener
{
public void onClick(View v)
{
editRobotData.setText("Disconnect...");
if (lRobotDataServerTask.cancel(true) == true)
{
editRobotData.setText("Disconnected...");
}
}
}
Okay it seems I've found the issue. I needed to check isCancelled() in the while loop of the doInBackground() method and then call the onCancelled() method from there. Not sure why I didn't see this as a problem on android 2.2 on HTC desire.
protected Void doInBackground(Void...params)
{
while(m_Running)
{
if (isCancelled () == true)
{
onCancelled();
}
Hope this helps someone.
Mark
There are two approaches to cancel an AsyncTask:
Checking isCancelled inside your doInBackground method to try to terminate as soon as possible.
Calling cancel(true), then AsyncTask's background thread will be interrupted. For this to work, you'll need to strategically place Thread.sleep calls in your doInBackground method, and when you catch the InterruptedException, you return.
Of these two approaches, the latter is the most unreliable by far, since any call to a method catching an InterruptedException other than yours will screw the cancellation. For instance, if you call a method such as SystemClock.sleep that swallows the exception, you'll never hit your catch clause.
In both cases, long operations in between isCancelled checks or Thread.sleep calls will run to completion.
Now about your question, you were using the second approach, but there were not any Thread.sleep call, so it wont work unless some of the methods you are calling catched the Interrupted exception and throwed the IOException. Probably changes in implementation from a version to another make the difference.

Categories

Resources