Android Rotation with IntentService - android

I have an application that uses IntentService to run a background task where I pull data from a website, parse the data out, and create calendar events based on the results. Everything seems to be working create, except I'm running into an issue with rotation.
Using the code below, when I rotate the screen, the ProgressDialog box stays visible, but is never updated with new text when the process is updated, and never goes away once the call is completed. I'm using an IntentService instead of an ASyncTask because the user can also schedule the IntentService to run at other times without having to interface with the app. Any help is appreciated, thanks!
public void onCreate(Bundle savedInstanceState) {
Object retained = getLastNonConfigurationInstance();
if (retained instanceof CalendarHandler) {
// CH is a class level variable defined at the top which references my IntentService, aptly named CalendarHandler
ch = (CalendarHandler) retained;
ch.setActivity(this);
} else {
ch = null;
}
activity = this;
btnLogin.setOnClickListener(OnClickListener(View view) {
ch = new CalendarHandler();
ch.setActivity(MyTlc.this);
// Do other stuff, like run the intent service
}
}
public Handler handler = new Handler() {
public void handleMessage(Message message) {
// We read the information from the message and do something with it
// based on what the result code is
String result = message.getData().getString("status");
if (result.equals("ERROR")) {
activity.removeDialog(PROGRESS_DIALOG);
results.setText(message.getData().getString("error"));
} else if (result.equals("DONE")) {
activity.removeDialog(PROGRESS_DIALOG);
int count = message.getData().getInt("count", 0);
activity.results.setText("Added " + count + " shifts to the calendar");
} else {
activity.pDialog.setMessage(result);
}
super.handleMessage(message);
}
};
From what I understand, this should work, and like I said the ProgressDialog box does stay properly, I just can't seem to pass information to the dialog box after rotating.

Related

Timer not working in app

I have written schedule task by using timer. It is working fine withing single activity.But when i am going to another activity it is not working.My intention is to send data to the server some particular time interval. I am giving the code snippet. I am sorry for the format.
private void login()
{
try {
EditText userNameET = (EditText)findViewById(R.id.userName);
EditText passwordET = (EditText)findViewById(R.id.password);
String userName = userNameET.getText().toString();
String password = passwordET.getText().toString();
boolean isLoginOK = isValidUser(userName, password);
String autoSynchStrVal = "";
String autoSyncFreqStr = "";
long autoSyncFreqInMiliSec = 3600000; // default 1 hrs
if (isLoginOK) {
//added by anirban
CommonUtils.IS_NEW_VERSION_AVAILABLE = isNewVersionAvailable();
CommonUtils.IS_NEW_Notification_AVAILABLE = isNewNotificationAvailable();
autoSynchStrVal = CommonUtils.getPolicyValue(appInstance, "IS_MOBI_AUTO_SYNCH_REQ", 0, 0);
if(autoSynchStrVal != null && !"".equals(autoSynchStrVal) && "1".equals(autoSynchStrVal)){
//boolean isAllTransactionsUploaded = false;
// boolean isAllTransactionsUploaded = VersionCheckingActivity.isAllTransactionsUploaded();
// boolean isMobiEligibleForAutoSync = UploadDownload.isMobiEligibleForAutoSync(appInstance ,isAllTransactionsUploaded);
// if(isMobiEligibleForAutoSync){
autoSyncFreqStr = CommonUtils.getPolicyValue(appInstance, "MOBI_AUTO_SYNCH_FREQUENCY", 0, 0);
if(autoSyncFreqStr != null && !"".equals(autoSyncFreqStr)){
autoSyncFreqInMiliSec = (long) (Double.valueOf(autoSyncFreqStr) * 60 * 60 * 1000); // in millisecond
}
/* boolean isMobiEligibleForAutoSync = false;
try {
isMobiEligibleForAutoSync = UploadDownload.isMobiEligibleForAutoSync(appInstance ,
VersionCheckingActivity.isAllTransactionsUploaded());
if(isMobiEligibleForAutoSync){
_doSynch();
}
} catch (UDBAccessException e) {
e.printStackTrace();
} */
myTimer = new Timer();
myTimer.schedule(new TimerTask() {
#Override
public void run() {
ULoginActivity.this.runOnUiThread(new Runnable() {
#Override
public void run() {
// here we are checking again for eligibility for auto synch
boolean isMobiEligibleForAutoSync = false;
try {
isMobiEligibleForAutoSync = UploadDownload.isMobiEligibleForAutoSync(appInstance ,
VersionCheckingActivity.isAllTransactionsUploaded());
Log.d("inside Run : ", "before Synch");
if(isMobiEligibleForAutoSync){
_doSynch();
Log.d("inside Run : ", "after Synch");
}
} catch (UDBAccessException e) {
e.printStackTrace();
}
}
});
}
}, 1000, autoSyncFreqInMiliSec); //here interval is autoSyncFreqInMiliSec
}
endAction(RESULT_LOGIN_OK, null); // it will finish the activity
} else {
// showing login error
TextView login_msg = (TextView)findViewById(R.id.login_screen_msg);
login_msg.setTextAppearance(this, R.style.error_msg);
//login_msg.setTextColor(Color.RED);
login_msg.setText("Login failed.");
}
} catch (UDBAccessException e) {
UUIHandlers.showErrorMessage(this, e.getMessage());
}catch (Exception e) {
UUIHandlers.showErrorMessage(this, e.getMessage());
}
}
To make it work when you leave the current activity, you have to run that code of snippet on the background service.
As you are executing on the current Activity it runs the code for the first time, but as you leave the activity the code wont be triggered itself unless it is registered to a background service.
Here and here you have examples on how to use them
If you want to execute something after some time, even when your activity is not currently in the foreground, you can use the AlarmManager.
Note: The Alarm Manager is intended for cases where you want to have your application code run at a specific time, even if your application is not currently running. For normal timing operations (ticks, timeouts, etc) it is easier and much more efficient to use Handler.
If you want to periodically send information to a server, I suggest you use a Service or an IntentService.
A Service is an application component representing either an application's desire to perform a longer-running operation while not interacting with the user or to supply functionality for other applications to use.
You can find a good example using a LocalService also here.
Finally got the solution .
Now from Async task( different thread), You are trying to insert data in the database which is locked by UI thread. This will throw an exception because the first write has a lock on the db.
If you hold your timer in a field of your activity (Activity subclass) it will probably go away once you launch another activity. Consider moving your timer to service (Service subclass). This will hold your timer going regardless of your activity flow.
Read this for reference about services:
https://developer.android.com/guide/components/services.html

Android Multiple download pause resume in listview with progress update

I am trying to download multiple files in listview with progressbar. What I achieved is, I can start a particular download, pause/resume it using AsyncTask and progress bar is updated(for single file), this part works well
My problem is I am not able to download multiple files simultaneously and when I leave the listview to another screen although my download is going on in the background but progress is not updated, progress bar shows 0 progress as if it is not downloading but its been downloading in the background.
Finally I found the answer which was much simpler than I thought, here it is as follows
Create a service having Asynctask for downloading and hashtable of values(url, Asynctask)
Pass the value(url, Asynctask) when a list item is clicked and check whether that hashtable contain the value already if yes cancel that Asynctask task if no add it to hashtable and start Asynctask
now for updating progress in my adapter I ran a thread which iterate over hashtable and passes the value using BroadcastListener.
And in activity intercept the broadcast and depending on the ListItem visible update the progress
PS: If anybody needs some code I can provide basic code of the description explained above
public class DownloadingService extends Service {
public static String PROGRESS_UPDATE_ACTION = DownloadingService.class.getName() + ".progress";
private static final long INTERVAL_BROADCAST = 800;
private long mLastUpdate = 0;
private Hashtable<String, DownloadFile> downloadTable;
private LocalBroadcastManager broadcastManager;
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
MessageEntity entityRecieved = (MessageEntity) intent.getSerializableExtra("ENTITY");
queueDownload(entityRecieved);
return super.onStartCommand(intent, flags, startId);
}
private void queueDownload(MessageEntity entityRecieved){
if (downloadTable.containsKey(entityRecieved.getPacketID())) {
DownloadFile downloadFile = downloadTable.get(entityRecieved.getPacketID());
if (downloadFile.isCancelled()) {
downloadFile = new DownloadFile(entityRecieved);
downloadTable.put(entityRecieved.getPacketID(), downloadFile);
startDownloadFileTask(downloadFile);
} else {
downloadFile.cancel(true);
downloadTable.remove(entityRecieved.getPacketID());
}
} else {
DownloadFile downloadFile = new DownloadFile(entityRecieved);
downloadTable.put(entityRecieved.getPacketID(), downloadFile);
startDownloadFileTask(downloadFile);
}
}
#Override
public void onCreate() {
super.onCreate();
downloadTable = new Hashtable<String, DownloadFile>();
broadcastManager = LocalBroadcastManager.getInstance(this);
}
#TargetApi(Build.VERSION_CODES.HONEYCOMB)
void startDownloadFileTask(DownloadFile asyncTask) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
asyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
else
asyncTask.execute();
}
private void publishCurrentProgressOneShot(boolean forced) {
if (forced || System.currentTimeMillis() - mLastUpdate > INTERVAL_BROADCAST) {
mLastUpdate = System.currentTimeMillis();
int[] progresses = new int[downloadTable.size()];
String[] packetIds = new String[downloadTable.size()];
int index = 0;
Enumeration<String> enumKey = downloadTable.keys();
while (enumKey.hasMoreElements()) {
String key = enumKey.nextElement();
int val = downloadTable.get(key).progress;
progresses[index] = val;
packetIds[index++] = key;
}
Intent i = new Intent();
i.setAction(PROGRESS_UPDATE_ACTION);
i.putExtra("packetIds", packetIds);
i.putExtra("progress", progresses);
mBroadcastManager.sendBroadcast(i);
}
class DownloadFile extends AsyncTask<Void, Integer, Void> {
private MessageEntity entity;
private File file;
private int progress;
public DownloadFile(MessageEntity entity) {
this.entity = entity;
}
#Override
protected Void doInBackground(Void... arg0) {
String filename = entity.getMediaURL().substring(entity.getMediaURL().lastIndexOf('/') + 1);
file = new File(FileUtil.getAppStorageDir().getPath(), filename);
downloadFile(entity.getMediaURL(), file);
return null;
}
public String downloadFile(String download_file_path, File file) {
int downloadedSize = 0;
int totalSize = 0;
try {
// download the file here
while ((bufferLength = inputStream.read(buffer)) > 0 && !isCancelled()) {
progress = percentage;
publishCurrentProgressOneShot(true);
}
} catch (final Exception e) {
return null;
}
return file.getPath();
}
}
On big problem with AsynchTask is when you finish its activity, AsynchTask looses it's track with your UI. After that when you return back to that activity the progressBar is not updating even if the download progress still running in background. In fact that AsynchTask is not belong to the new Activity you lunched so the new instance of progress bar in new Activity will not updating.
To fix this problem I suggest you:
1- Run a thread with a timerTask in onResume() which updates ur progressbar with values updating from the AsyncTask running background. Something like this:
private void updateProgressBar(){
Runnable runnable = new updateProgress();
background = new Thread(runnable);
background.start();
}
public class updateProgress implements Runnable {
public void run() {
while(Thread.currentThread()==background)
try {
Thread.sleep(1000);
Message msg = new Message();
progress = getProgressPercentage();
handler.sendMessage(msg);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (Exception e) {
}
}
}
private Handler handler = new Handler(){
#Override
public void handleMessage(Message msg) {
progress.setProgress(msg.what);
}
};
and when your activity is not visible you must destroy the thread:
private void destroyRunningThreads()
{
if(background!=null)
{
background.interrupt();
background=null;
}
}
2- Define a global static boolean variable. Set it true in onPreExecute and in onPostExecute set it to false. It shows that you are downloading or not, so you can check if the variable is equal to true, show the previous progressbar dialog.(you can do something like this with an integer value-or array of integers- in order to show the update percentage for each download progress).
3- The last way I personally used is to show the download progress in Notification Bar and in my list view I just show that it is downloading right now or not.(using 2nd method with a boolean values). In this way even if you finish the activity the notification bar is still updated with download progress.
when you leave your activity, the activity that asynctask shows the progressbar is killed and thus the progressBar dose not show anymore when you come back on new activity because the asynctask dose not aware of your new activity.
General solution that will work in any cases for example when your user closes your app and again opens it and wants to know the progressBar is separating your presentation completely. that means you can create sharedPreferences or database table and put your state of your file in to it while your asynctask is downloading. for example every 500 milisecond update the sharedPreferences or database table with how much downloaded from total file size. then when user come back to your new activity you read from DB or sharedPreferences to show progressBar and update it every for example 1000 milisecond. In this way your user will know the progressBar even if he closes the app and opens it again. I know it takes a bit more work but it surely makes your users be happy.
in order to read and update at fixed rate you can use scheduleAtFixedRate

Detecting if a refresh button has been selected while AsyncTasks are still running

Iv'e got an Android app that is using a list activity to display a list of items pulled from the internet. I First use an AsyncTask to load the list and that Async task finishes it calls a different async task to start loading the thumbnail pictures that go along with the list items. The problem I am having is that the user has access to a refresh button that they can press at any time and when it is pressed, the whole list of items is delete and the loading starts over. The Async task that loads the thumbnails could potentially still be running if this happens and may try to add a thumbnail to a non existing list item then. Iv'e tried synchronizing on the list, using a Boolean which after researching I realized would not work. I have also tried using a static atomic boolean to check if refresh has been hit to cancel the thumbnail loader. Any ideas?
public class LoadItems extends AsyncTask<Void, Void, Boolean> {
private Activity activity;
private static boolean loading = false;
public static final AtomicBoolean refreshing = new AtomicBoolean(false);
private static final String TAG = "LoadItems";
private int start;
private List<ListItem> items;
public LoadItems(Activity activity) {
this.activity = activity;
}
#Override
protected void onPreExecute() {
loading = true;
start = ItemViewer.itemList.size();
}
#Override
protected Boolean doInBackground(Void... arg0) {
items = WebFunctions.getMoreItems(activity);
return (items != null);
}
protected void onPostExecute(Boolean success) {
if (success) {
for (ListItem item: items) {
ItemViewer.itemList.add(item);
Log.d(TAG, "added item " + item.getTitle());
}
LoadThumbnails thumbnailLoader = new LoadThumbnails();
thumbnailLoader.execute(start, ItemViewer.itemList.size());
}
loading = false;
}
public void protectedExecute() {
if (!loading)
execute();
}
public void refresh() {
if (!refreshing.getAndSet(true)) {
WebFunctions.reset();
ItemViewer.itemList.removeAllItems();
execute();
}
}
}
public class LoadThumbnails extends AsyncTask<Integer, Void, Drawable> {
private int position;
private int end;
#Override
protected Drawable doInBackground(Integer... params) {
position = params[0];
end = params[1];
Drawable thumbnail = null;
synchronized(ItemViewer.itemList) {
if (LoadItems.refreshing.get())
cancel(true);
String url = ItemViewer.itemList.get(position).getThumbnailUrl();
if (!url.isEmpty())
thumbnail = WebFunctions.loadDrawableFromUrl(ItemViewer.activity, url);
}
return thumbnail;
}
protected void onPostExecute(Drawable d) {
synchronized (ItemViewer.itemList) {
if (LoadItems.refreshing.get())
cancel(true);
if (d != null)
ItemViewer.itemList.setThumbnail(position, d);
position++;
if (position < end) {
LoadThumbnails lt = new LoadThumbnails();
lt.execute(position, end);
}
}
}
}
This is pretty simple to solve. Whenever the user hits the refresh button, make sure you call cancel() on the last async tasks you have created before you create new tasks. For example,
private void onRefreshClick(View v) {
if(mLastLoadItemTask != null) mLastLoadItemTask.cancel(true);
if(mLastLoadThumbnailTask != null) mLastLoadThumbnailTask.cancel(true);
mLastLoadItemTask = new LoadItems(...);
mLastLoadItemTask.execute();
}
Then, in the onPostExecute of each of your async tasks, first check to see if they were cancelled by calling isCancelled(). If they were cancelled, make sure the onPostExecute method does no work by just returning. For example,
protected void onPostExecute(...) {
if(isCancelled()) return;
//Adding items to list
//Or start load thumbnail task
}
As you can see that should prevent any unintentional or stale updates because the onPostExecute methods and your cancel calls will all happen on the main therad. The last thing I would suggest is to alter your loadThumbs task to be able to stop doing work as soon as possibly by checking isCancelled() whenever it makes sense to do so.
The following steps might help:
cache the results, whatever you have previously pulled from the net should be saved and quickly restored back when your application is launched. this way you avoid long delays and empty screens on application startup, which, in turn, stops the user from pressing 'reload'
make a boolean variable reload_in_progress, set it to true when you start pulling data from the net, and set it to false when all thumbnails are ready. 'reload' click handler should ignore clicks when reload_in_progress is true.
show some king of progress bar to the user, so (s)he knows it's already reloading and does not push reload again.
almost forgot, never update data shown to the user "live", this leads to wonderful situations, when the user clicks on item while it's changing and doing something completely different from what (s)he expected. long updates should keep its data to themselves and quickly swap old data for the new one only when everything is ready.

Android - multithreading issues when changing activity

I have a main menu with an action bar. On create, I run a thread that hits my server for a current status. When Complete, the thread calls a handler which kicks off a constantly running thread that cycles through the items and uses another handler call to change the test in the actionbar. The problem is that when I change views, I either get android.view.WindowLeaked or View not attached to window manager
Here is some sample code
public class MainMenuActivity extends ProtectedWithActionBarActivity{
private int STATUS_COUNTER;
private final int RESULT_STATUS_LOADED = 2000;
private final int RESULT_SHOW_STATUS = 2001;
private CurrentStatusModel currentStatus;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.mainmenu);
ActionBar footerbar = (ActionBar)findViewById(R.id.footerbar);
footerbar.setTitle("Currently connected to " + PreferencesHelper.getCurrentEnvironment().name());
STATUS_COUNTER = 0;
statusLoadThread.start();
}
Thread statusLoadThread = new Thread()
{
#Override
public void run()
{
//set currentStatus with data from server
}
};
Thread statusDisplayThread = new Thread()
{
int sleep = 5000;
boolean threadDone = false;
public void done()
{
threadDone = true;
}
#Override
public void run()
{
while(true)
{
//pick message to send to handler
//increment STATUS_COUNTER or reset to 0 when out of bounds
try
{
sleep(sleep);
}
catch (InterruptedException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
};
private Handler handler = new Handler() {
#Override
public void handleMessage(Message msg) {
switch(msg.what)
{
case RESULT_STATUS_LOADED:
statusDisplayThread.start();
break;
case RESULT_SHOW_STATUS:
ActionBar footerbar = (ActionBar)findViewById(R.id.footerbar);
String message = ((Object[])msg.obj)[0].toString();
OnClickListener listener = (OnClickListener)((Object[])msg.obj)[1];
footerbar.setTitle(message);
footerbar.setOnTitleClickListener(listener);
break;
case ActivityBase.RESULT_ERROR:
break;
}
}
};
}
I'm not sure if what I'm doing is just wrong or if there is something blatantly obvious that I am missing. What needs to happen is the threads need to stop any time I change screens. Should I use Thread.interrupt(); before starting the next activity?
AsyncTasc allows you to implement doInBackground(), where your thread can crank away at its task. This is similar to the functionality you'd get from Thread.
The real magic happens when you override onPreExecute() and onPostExecute(), which are both executed on the UI thread. This should keep you from getting messages about your Activity not being attached.
Edit - this answer contains a small code example for AsyncTask that could get you started.
You are trying to update UI elements after the owning Activity has been detached from the windowing system.
You will make your life a lot simpler if you use AsyncTask instead of vanilla threads (no handler needed, for one thing) and cancel() the background tasks from your Activity.onPause().
Can't you set a flag in onPause that each of your Threads checks for? If the flag is set then the thread drops out of its loop. Thus whenever the Activity is moved to the background each of your Threads will stop. You would need to handle restarting the threads in onResume. You could alternatively use the AsyncTask approach, but this is not guaranteed to actually cancel when you call its cancel() method, it only attempts to cancel the task.

onPostExecute not being called in AsyncTask (Handler runtime exception)

I have an AsyncTask that fetches some data and then updates the UI with this new data. It has been working fine for months, but I recently added a feature that displays a notification when there is new data. Now when my app is launched through the notification, sometimes I get this exception and onPostExecute is not called.
This is what happens when the app is launched:
1) Expand the UI and find views
2) Cancel the alarm (through AlarmManager) that checks for new data and reset the alarm. (This is so that if the user disables the alarm it is cancelled before the next time he/she reboots.)
3) Start the AsyncTask. If the app was launched from the notification, pass in a little bit of the data and then cancel the notification.
I'm stuck on what could be causing this exception. It seems that the exception is from the AsyncTask code, so I'm not sure how I can fix it.
Thanks!
Here is the exception:
I/My App( 501): doInBackground exiting
W/MessageQueue( 501): Handler{442ba140} sending message to a Handler on a dead thread
W/MessageQueue( 501): java.lang.RuntimeException: Handler{442ba140} sending message to a Handler on a dead thread
W/MessageQueue( 501): at android.os.MessageQueue.enqueueMessage(MessageQueue.java:179)
W/MessageQueue( 501): at android.os.Handler.sendMessageAtTime(Handler.java:457)
W/MessageQueue( 501): at android.os.Handler.sendMessageDelayed(Handler.java:430)
W/MessageQueue( 501): at android.os.Handler.sendMessage(Handler.java:367)
W/MessageQueue( 501): at android.os.Message.sendToTarget(Message.java:348)
W/MessageQueue( 501): at android.os.AsyncTask$3.done(AsyncTask.java:214)
W/MessageQueue( 501): at java.util.concurrent.FutureTask$Sync.innerSet(FutureTask.java:252)
W/MessageQueue( 501): at java.util.concurrent.FutureTask.set(FutureTask.java:112)
W/MessageQueue( 501): at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:310)
W/MessageQueue( 501): at java.util.concurrent.FutureTask.run(FutureTask.java:137)
W/MessageQueue( 501): at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1068)
W/MessageQueue( 501): at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:561)
W/MessageQueue( 501): at java.lang.Thread.run(Thread.java:1096)
EDIT: Here is my onCreate method in my main activity (the one opened by the notification). There are some onClickListeners that I omitted to save space. I don't think they should have any effect, since the buttons they are attached to are not being pressed.
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); // Call the parent
setContentView(R.layout.main); // Create the UI from the XML file
// Find the UI elements
controls = (SlidingDrawer) findViewById(R.id.drawer); // Contains the
// buttons
// comic = (ImageView) findViewById(R.id.comic); // Displays the comic
subtitle = (TextView) findViewById(R.id.subtitleTxt); // Textbox for the
// subtitle
prevBtn = (Button) findViewById(R.id.prevBtn); // The previous button
nextBtn = (Button) findViewById(R.id.nextBtn); // The next button
randomBtn = (Button) findViewById(R.id.randomBtn); // The random button
fetchBtn = (Button) findViewById(R.id.comicFetchBtn); // The go to specific id button
mostRecentBtn = (Button) findViewById(R.id.mostRecentBtn); // The button to go to the most recent comic
comicNumberEdtTxt = (EditText) findViewById(R.id.comicNumberEdtTxt); // The text box to Zooming image view setup
zoomControl = new DynamicZoomControl();
zoomListener = new LongPressZoomListener(this);
zoomListener.setZoomControl(zoomControl);
zoomComic = (ImageZoomView) findViewById(R.id.zoomComic);
zoomComic.setZoomState(zoomControl.getZoomState());
zoomComic.setImage(BitmapFactory.decodeResource(getResources(), R.drawable.defaultlogo));
zoomComic.setOnTouchListener(zoomListener);
zoomControl.setAspectQuotient(zoomComic.getAspectQuotient());
resetZoomState();
// enter the new id
imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); // Used to hide the soft keyboard
Log.i(LOG_TAG, "beginning loading of first comic");
int notificationComicNumber = getIntent().getIntExtra("comic", -1);
Log.i(LOG_TAG, "comic number from intent: " + notificationComicNumber);
if (notificationComicNumber == -1) {
fetch = new MyFetcher(this, zoomComic, subtitle, controls, comicNumberEdtTxt, imm, zoomControl);
fetch.execute(MyFetcher.LAST_DISPLAYED_COMIC);
} else {
fetch = new MyFetcher(this, zoomComic, subtitle, controls, comicNumberEdtTxt, imm, zoomControl);
fetch.execute(notificationComicNumber);
((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).cancelAll();
}
Log.i(LOG_TAG, "ending loading of new comic");
Log.i(LOG_TAG, "first run checks beginning");
// Get SharedPreferences
prefs = getSharedPreferences("prefs", Context.MODE_PRIVATE);
// Check if this is the first run of the app for this version
if (prefs.getBoolean("firstRun-" + MAJOR_VERSION_NUMBER, true)) {
prefs.edit().putBoolean("firstRun-" + MAJOR_VERSION_NUMBER, false).commit();
firstRunVersionDialog();
}
// Check if this is the first run of the app
if (prefs.getBoolean("firstRun", true)) {
prefs.edit().putBoolean("firstRun", false).commit();
firstRunDialog();
}
Log.i(LOG_TAG, "First run checks done");
// OnClickListener s for the buttons omitted to save space
EDIT 2: I've been digging through Android source code tracking down where the exception is coming from. This is lines 456 and 457 of sendMessageAtTime in Handler:
msg.target = this;
sent = queue.enqueueMessage(msg, uptimeMillis);
And this is enqueueMessage from MessageQueue:
final boolean enqueueMessage(Message msg, long when) {
if (msg.when != 0) {
throw new AndroidRuntimeException(msg
+ " This message is already in use.");
}
if (msg.target == null && !mQuitAllowed) {
throw new RuntimeException("Main thread not allowed to quit");
}
synchronized (this) {
if (mQuiting) {
RuntimeException e = new RuntimeException(
msg.target + " sending message to a Handler on a dead thread");
Log.w("MessageQueue", e.getMessage(), e);
return false;
} else if (msg.target == null) {
mQuiting = true;
}
msg.when = when;
//Log.d("MessageQueue", "Enqueing: " + msg);
Message p = mMessages;
if (p == null || when == 0 || when < p.when) {
msg.next = p;
mMessages = msg;
this.notify();
} else {
Message prev = null;
while (p != null && p.when <= when) {
prev = p;
p = p.next;
}
msg.next = prev.next;
prev.next = msg;
this.notify();
}
}
return true;
}
I'm a little confused about what mQuiting is, but it looks like the previous time enqueueMessage was called msg.target was null.
This is due to a bug in AsyncTask in the Android framework. AsyncTask.java has the following code:
private static final InternalHandler sHandler = new InternalHandler();
It expects this to be initialized on the main thread, but that is not guaranteed since it will be initialized on whichever thread happens to cause the class to run its static initializers. I reproduced this issue where the Handler references a worker thread.
A common pattern that causes this to happen is using the class IntentService. The C2DM sample code does this.
A simple workaround is to add the following code to the application's onCreate method:
Class.forName("android.os.AsyncTask");
This will force AsyncTask to be initialized in the main thread. I filed a bug on this in the android bug database. See http://code.google.com/p/android/issues/detail?id=20915.
To generalize Jonathan Perlow's solution to the bug he identified specifically, I use the following in any class that uses AsyncTask. The looper/handler/post is how you can run something on the UI thread anywhere in an Android app without passing down a handle to an activity or other context. Add this static initialization block inside the class:
{ // https://stackoverflow.com/questions/4280330/onpostexecute-not-being-called-in-asynctask-handler-runtime-exception
Looper looper = Looper.getMainLooper();
Handler handler = new Handler(looper);
handler.post(new Runnable() {
public void run() {
try {
Class.forName("android.os.AsyncTask");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
});
}
We had run into the problem when trying to get unit tests to run. I found a workaround for that, but hadn't specifically identified the problem. We only knew that trying to use AsyncTask<> in Android JUnit test caused onPostExecute() not to be called. Now we know why.
This post shows how to run multithreaded async code in an Android JUnit test:
Using CountDownLatch in Android AsyncTask-based JUnit tests
For use with non-UI unit tests, I created a simple subclass of android.test.InstrumentationTestCase. It has an "ok" flag and a CountDownLatch. reset() or reset(count) creates a new CountDownLatch({1,count}). good() sets ok=true, count--, and calls.countDown() on the latch. bad() sets ok=false, and counts down all the way. waitForIt(seconds) waits for timeout or the coundown latch to zero. Then it calls assertTrue(ok).
Then tests are like:
someTest() {
reset();
asyncCall(args, new someListener() {
public void success(args) { good(); }
public void fail(args) { bad(); }
});
waitForIt();
}
Because of the AsyncTask static initialization bug, we had to run our actual tests inside a Runnable passed to runTestOnUiThread(). With proper static initialization as above, this shouldn't be necessary, unless the call being tested needs to run on the UI thread.
The other idiom I now use is to test whether the current thread is the UI thread and then run the requested action on the proper thread regardless. Sometimes, it makes sense to allow the caller to request sync vs. async, overriding when necessary. For instance, network requests should always be run on a background thread. In most cases, AsyncTask thread pooling is perfect for this. Just realize that only a certain number will run at once, blocking additional requests. To test whether the current thread is the UI thread:
boolean onUiThread = Looper.getMainLooper().getThread() == Thread.currentThread();
Then use a simple subclass (just doInBackground() and onPostExecute() are needed) of AsyncTask<> to run on a non-UI thread or handler.post() or postDelayed() to run on the UI thread.
Giving the caller the option to run sync or async looks like (getting a locally valid onUiThread value not shown here; add local booleans as above):
void method(final args, sync, listener, callbakOnUi) {
Runnable run = new Runnable() { public void run() {
// method's code... using args or class members.
if (listener != null) listener(results);
// Or, if the calling code expects listener to run on the UI thread:
if (callbackOnUi && !onUiThread)
handler.post(new Runnable() { public void run() {listener()}});
else listener();
};
if (sync) run.run(); else new MyAsync().execute(run);
// Or for networking code:
if (sync && !onUiThread) run.run(); else new MyAsync().execute(run);
// Or, for something that has to be run on the UI thread:
if (sync && onUiThread) run.run() else handler.post(run);
}
Also, using AsyncTask can be made very simple and concise. Use the definition of RunAsyncTask.java below, then write code like this:
RunAsyncTask rat = new RunAsyncTask("");
rat.execute(new Runnable() { public void run() {
doSomethingInBackground();
post(new Runnable() { public void run() { somethingOnUIThread(); }});
postDelayed(new Runnable() { public void run() { somethingOnUIThreadInABit(); }}, 100);
}});
Or simply:new RunAsyncTask("").execute(new Runnable(){public void run(){ doSomethingInBackground(); }});
RunAsyncTask.java:
package st.sdw;
import android.os.AsyncTask;
import android.util.Log;
import android.os.Debug;
public class RunAsyncTask extends AsyncTask<Runnable, String, Long> {
String TAG = "RunAsyncTask";
Object context = null;
boolean isDebug = false;
public RunAsyncTask(Object context, String tag, boolean debug) {
this.context = context;
TAG = tag;
isDebug = debug;
}
protected Long doInBackground(Runnable... runs) {
Long result = 0L;
long start = System.currentTimeMillis();
for (Runnable run : runs) {
run.run();
}
return System.currentTimeMillis() - start;
}
protected void onProgressUpdate(String... values) { }
protected void onPostExecute(Long time) {
if (isDebug && time > 1) Log.d(TAG, "RunAsyncTask ran in:" + time + " ms");
v = null;
}
protected void onPreExecute() { }
/** Walk heap, reliably triggering crash on native heap corruption. Call as needed. */
public static void memoryProbe() {
System.gc();
Runtime runtime = Runtime.getRuntime();
Double allocated = new Double(Debug.getNativeHeapAllocatedSize()) / 1048576.0;
Double available = new Double(Debug.getNativeHeapSize()) / 1048576.0;
Double free = new Double(Debug.getNativeHeapFreeSize()) / 1048576.0;
long maxMemory = runtime.maxMemory();
long totalMemory = runtime.totalMemory();
long freeMemory = runtime.freeMemory();
}
}
I had the same problem on a device with Android 4.0.4 with the IntentService and solved it as sdw said with the Class.forName("android.os.AsyncTask"). The same didn't happen on Android 4.1.2, 4.4.4 or 5.0. I wonder if this Google resolved Martin West issue from 2011.
I added this code on my Application onCreate and it worked:
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN) {
try {
Class.forName("android.os.AsyncTask");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
It would be nice to know if the version of Android need to be changed to something else.
AsyncTask.execute() must be executed on UI thread, i.e. inside Activity.
I have the same problem, it seems to happen when the AsyncTask is running during a suspend/resume.
EDIT:
Yeah, didnt think I had but I used this http://developer.android.com/guide/appendix/faq/commontasks.html#threading
to always start the AsyncTask on the UI thread and the problem has gone.
The problem appeared after I added the licensing function, siggghhhhh
Thanks
Even though this doesn't directly answer the OP's question, I think it will be useful for people searching for the solution of the same problem when running tests.
Overall, Peter Knego's answer sums it up well.
My problem was specifically with running a test on a class outside an Activity that made use of Android's AsyncTask for an API call. The class works in the application, since it is used by an Activity, but I wanted to run a test making an actual API call from the test.
While Jonathan Perlow's answer worked, I didn't like introducing changes to my application due solely to a test.
So, in the case of a test runTestOnUiThread can be used (#UiThreadTest cannot be used, since you cannot wait for a result in a test that uses that annotation).
public void testAPICall() throws Throwable {
this.runTestOnUiThread(new Runnable() {
public void run() {
underTest.thisMethodWillMakeUseOfAnAsyncTaskSomehow();
}
});
// Wait for result here *
// Asserts here
}
Sometimes though, especially in functional tests, Jonathan Perlow's answer seems to be the only one that works.
* Take a look here to see how to pause a test waiting for a result.

Categories

Resources