Reputation: 113
I am writing an Android App that checks for new messages in the background using AlarmManager
and WakefulBroadcastReceiver
. The Receiver starts an IntentService
and that uses an AsyncTask
for the network request (HTTPClient).
After a reboot, the alarm is re-enabled using a BroadcastReceiver listening for android.intent.action.BOOT_COMPLETED
.
The problem occures when the onCreate method of my main Activity loads the full message contents using an AsyncTask, but only under certain circumstances:
java.lang.RuntimeException: Handler (android.os.AsyncTask$InternalHandler) {4139f618} sending message to a Handler on a dead thread
The doInBackground
is executed and returns a String
with valid content, but instead of executing onPostExecute
, the excaption rises.
There are several scenarios:
Question: How can I fix that, without burning in the developer hell for doing network requests on the UI thread?
Searching for a solution, I found onPostExecute not being called in AsyncTask (Handler runtime exception), but neither the solution from "sdw" nor from "Jonathan Perlow" changes a thing.
ServerQuery:
public abstract class ServerQuery extends AsyncTask<Void, Void, String> {
protected Context context;
String user = "ERR";
String pin = "ERR";
String cmd = "ERR";
ServerQuery(Context context, String _user, String _pin, String _cmd) {
this.context = context;
user = _user;
pin = _pin;
cmd = _cmd;
}
private ServerQuery() {
}
@Override
protected String doInBackground(Void... params) {
ArrayList<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>();
nameValuePairs.add(new BasicNameValuePair(context.getString(R.string.post_client), context.getString(R.string.post_client_app)));
nameValuePairs.add(new BasicNameValuePair(context.getString(R.string.post_user), user));
nameValuePairs.add(new BasicNameValuePair(context.getString(R.string.post_pin), pin));
nameValuePairs.add(new BasicNameValuePair(context.getString(R.string.post_cmd), cmd));
HttpClient httpClient = new DefaultHttpClient();
HttpPost httpPost = new HttpPost("http://[my server hostname]/index.php");
try {
httpPost.setEntity(new UrlEncodedFormEntity(nameValuePairs));
} catch (UnsupportedEncodingException e) {
return "ERR";
}
HttpResponse httpResponse = null;
try{
httpResponse = httpClient.execute(httpPost);
}catch (Exception e) {
return "ERR";
}
HttpEntity httpEntity = httpResponse.getEntity();
InputStream inputStream = null;
try {
inputStream = httpEntity.getContent();
} catch (IOException e) {
return "ERR";
}
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
StringBuilder stringBuilder = new StringBuilder();
String s = null;
try {
while ((s = bufferedReader.readLine()) != null) {
stringBuilder.append(s);
}
} catch (IOException e) {
return "ERR";
}
try {
inputStream.close();
} catch (IOException e) {
return "ERR";
}
return stringBuilder.toString();
}
}
lastID:
public class lastID extends ServerQuery {
Integer lastid, savedid;
lastID(Context context, String _user, String _pin, String _cmd) {
super(context, _user, _pin, _cmd);
lastid = -1;
}
@Override
protected void onPostExecute(final String success) {
if(success.equals("ERR") || success.length() < 1) {
Toast.makeText(context, context.getString(R.string.server_no_connection), Toast.LENGTH_LONG).show();
} else {
String content[] = success.split("(;)");
if(content.length == 2) {
lastid = Integer.parseInt(content[0]);
SharedPreferences sharedPreferences = context.getSharedPreferences(context.getString(R.string.settings_filename), 0);
SharedPreferences.Editor editor = sharedPreferences.edit();
savedid = sharedPreferences.getInt(context.getString(R.string.post_lastid), -1);
if (lastid > savedid) { // NEUES ANGEBOT!
editor.putInt(context.getString(R.string.post_lastid), lastid);
editor.commit();
// Benachrichtung
NotificationCompat.Builder notific = new NotificationCompat.Builder(context);
notific.setContentTitle(context.getString(R.string.notify_title));
notific.setContentText(content[1]);
notific.setSmallIcon(R.drawable.ic_notify);
notific.setAutoCancel(false);
notific.setPriority(NotificationCompat.PRIORITY_HIGH);
notific.setTicker(content[1]);
notific.setDefaults(Notification.DEFAULT_SOUND | Notification.DEFAULT_VIBRATE | Notification.DEFAULT_LIGHTS);
Intent resultIntent = new Intent(context, MainActivity.class);
TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
stackBuilder.addParentStack(MainActivity.class);
stackBuilder.addNextIntent(resultIntent);
PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_CANCEL_CURRENT);
notific.setContentIntent(resultPendingIntent);
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(lastid, notific.build());
}
}
}
}
}
MainActivity onCreate:
[ loading shared preferences, setting onclicklisteners ]
angebot = new Angebot(this, getApplicationContext(), user, pin, getString(R.string.post_cmd_viewAngebot));
angebot.execute((Void) null);
Angebot
is like lastID, but it uses the String
to fill the TextView
s.
EDIT 1:
Angebot:
public class Angebot extends ServerQuery {
protected Activity activity;
protected TextView datumView;
// more TextView s
protected Button hungerButton;
Angebot(Activity activity, Context context, String _user, String _pin, String _cmd) {
super(context, _user, _pin, _cmd);
this.activity = activity;
datumView = (TextView) this.activity.findViewById(R.id.datum);
// finding all other TextView s
}
@Override
protected void onPreExecute() {
showProgress(true);
}
@Override
protected void onPostExecute(final String success) {
Log.v(C.TAG_ALARMESSEN, "Angebot.onPostExecute");
showProgress(false);
if(success.equals("ERR") || success.length() < 1) {
Toast.makeText(activity.getApplicationContext(), context.getString(R.string.server_no_connection), Toast.LENGTH_LONG).show();
} else {
String content[] = success.split("(;)");
// put each content string in a TextView
} else {
Toast.makeText(context, context.getString(R.string.err02s), Toast.LENGTH_LONG).show(); // nicht angemeldet
}
}
}
@Override
protected void onCancelled() {
showProgress(false);
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2)
public void showProgress(final boolean show) {
final View progressView = activity.findViewById(R.id.login_progress);
progressView.setVisibility(show ? View.VISIBLE : View.GONE);
}
}
EDIT 2: stack trace:
java.lang.RuntimeException: Handler (android.os.AsyncTask$InternalHandler) {4139f618} sending message to a Handler on a dead thread
at android.os.MessageQueue.enqueueMessage(MessageQueue.java:196)
at android.os.Handler.sendMessageAtTime(Handler.java:473)
at android.os.Handler.sendMessageDelayed(Handler.java:446)
at android.os.Handler.sendMessage(Handler.java:383)
at android.os.Message.sendToTarget(Message.java:363)
at android.os.AsyncTask.postResult(AsyncTask.java:300)
at android.os.AsyncTask.access$400(AsyncTask.java:156)
at android.os.AsyncTask$2.call(AsyncTask.java:264)
at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:305)
at java.util.concurrent.FutureTask.run(FutureTask.java:137)
at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:208)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1076)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:569)
at java.lang.Thread.run(Thread.java:856)
Upvotes: 1
Views: 379
Reputation: 28866
I would assume that the workarounds for the AsyncTask bug mentioned here: onPostExecute not being called in AsyncTask (Handler runtime exception) would work in this case too but according to the OP they don't.
Instead of fixing the issue I would suggest to use an AsyncTaskLoader instead. There's plenty of articles about the difference (mainly the advantages) of AsyncTaskLoader compared to AsyncTask. Here are just two (good) examples): http://www.javacodegeeks.com/2013/01/android-loaders-versus-asynctask.html and http://www.androiddesignpatterns.com/2012/07/loaders-and-loadermanager-background.html
The main advantages of an AsyncTaskLoader is that they are synced with the life cycle of the Activity or Fragment that started it and that they can deal with configuration changes gracefully (unlike AsyncTasks).
Here's how you'd implement it. First you need the AsyncLoaderClass:
class ServerQuery extends AsyncTaskLoader<String> {
String mResult;
public ServerQuery(Context context) {
super(context);
}
@Override
protected void onStartLoading() {
if (mResult == null) {
forceLoad(); // no data yet -> force load it
}
else {
deliverResult(mResult); // already got the data -> deliver it
}
}
@Override
public String loadInBackground() {
// load your data...
return mResult; // return the loaded data
}
@Override
public void cancelLoadInBackground() {
// do whatever it takes to stop loading
}
}
Replace the "// load your data..." part with whatever you have in your doInBackground method. That's all there is to the actual AsyncTaskLoader. As you can see there's no equivalent to onPostExecute and that's the beauty of the AsyncTaskLoader, it's there to load data, nothing else. All the issues that occurred because AsyncTask tried to update the ui on the onPostExecute are gone.
The logic in onPostExecute now resides completely in the Activity/Fragment that starts the loader. Here's how you do it:
LoaderManager loaderMgr = getLoaderManager();
loaderMgr.initLoader(0, null, this);
this stands for LoaderManager.LoaderCallbacks meaning you have to implement that interface in your fragment/activity like so:
@Override
public Loader<String> onCreateLoader(int id, Bundle args) {
return new ServerQuery(this);
}
@Override public void onLoaderReset(Loader<String> loader) {}
@Override
public void onLoadFinished(Loader<String> loader, String data) {
// here goes your code to update the ui (previously onPostExecute);
}
onCreateLoader simply creates your loader (ServerQuery in this case) which will be managed by the LoaderManager henceforth. onLoadFinished will be called when the data is delivered to your fragment/activity allowing you to update the ui. You can check out one of my other answers to get some more information about Loaders that might help you understand how they work: https://stackoverflow.com/a/20916507/534471. Especially the part about configuration changes might be helpful.
Upvotes: 1