I'm having some trouble with an AsyncTask subclass.
I have a main activity as below that displays a button and a number that counts up on the screen. Clicking the button launches an Edit activity where a number can be entered.
The number displayed on the Main activity should update with the timer which it does but the trouble I'm having is that I can't stop the timer. It should stop when entering the Edit activity and returning from it as well, as well as restart with the a new value too but it doesn't, the timer is always running with the first entered value, it never stops, even when I leave the program and return to the home screen.
I've looked at posts here such as Can't cancel Async task in android but they all just mention checking for isCancelled() which I'm doing. Can anyone see/explain why I can't stop this AsyncTask ?
public class MainActivity extends Activity {
#Override
UpdateTimer ut;
TextView tvn;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
#Override
public void onResume() {
super.onResume();
tvn = (TextView) findViewById(R.id.numDisplay);
if(ut != null )//&& ut.getStatus() == AsyncTask.Status.RUNNING) {
ut.cancel(true);
ut.cancelled = true;
Log.d("-----M_r","called cancel: "+ut.isCancelled()+" "+cancelled);
}
if (updateRequired) {
ut = new UpdateTimer();
ut.execute(number);
updateRequired = false;
}
}
public void onEditButtonPressed(View caller) {
// kill any running timer
if(ut != null )
{
ut.cancel(true);
ut.cancelled = true;
}
// start the edit screen
Intent e_intent = new Intent(this, EditActivity.class);
startActivity(e_intent);
}
private void updateScreen(long number) {
// update screen with current values
tvn.setText("" + number);
}
private class UpdateTimer extends AsyncTask<Long, Long, Integer> {
long number;
public boolean cancelled;
#Override
protected Integer doInBackground(Long... params) {
number = params[0];
cancelled = false;
while(true) {
number += 1;
//sleep for 1 second
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// tell this AsyncTask to update the time on screen
publishProgress(number);
// check if timer needs to stop
if (isCancelled()) break;
if(cancelled) break;
}
return null;
}
protected void onProgressUpdate(Long... progress) {
Log.d("-----M_ut","updated: "+number+" "+this.isCancelled()+" "+cancelled);
updateScreen(progress[0]);
}
protected void onCancelled(Integer result) {
cancelled = true;
Log.d("-----M_ut","-- cancelled called: "+this.isCancelled());
}
}
protected void onStop()
{
super.onStop();
// kill any running timer
if(ut != null) {
ut.cancel(true);
}
}
}
Try this...
remove the variable..cancelled and change to this..
#Override
protected void onCancelled() {
super.onCancelled();
}
call the super.onCancelled instead..
And in the doInBackground check
if (isCancelled()) {
break;
}
Try calling from your activity ut.cancel(true);
Hope it works:)
private YourAsyncTask ut;
declare your asyncTask in your activity.
ut = new YourAsyncTask().execute();
instantiate it like this.
ut.cancel(true);
kill/cancel it like this.
Related
I set a timer on AccountActivity.class to ensure that user does not press home button if not will start the countdown to logout the user or if the user locks his screen.
But now I am facing an issue because of the onPause method. When my user clicks on a button which invokes the getaccounttask() method and it will redirect my user to AccountInformationActivity.class, the onPause method is activated as well and the timer starts to countdown.
Is there any solution to prevent the onPause method from counting down or the timer to be cancelled on my AccountInformationActivity.class?
I tried to do the cancelling of timer before my intent starts but still does not work.
I have tried using handler as well but encountered the same problem, I am still trying to grasp how Android fully works, so your help or solution is deeply appreciated.
public class AccountActivity extends AppCompatActivity {
private Timer timer;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_account);
}
private class getaccounttask extends AsyncTask<String, Void, String>
{
#Override
protected String doInBackground(String... urlaccount)
{
StringBuilder result = new StringBuilder();
try
{
//My Codes
}
catch (Exception e)
{
e.printStackTrace();
}
return result.toString();
}
#Override
protected void onPostExecute(String result)
{
Intent intent = new Intent();
intent.setClass(getApplicationContext(), AccountInformationActivity.class);
startActivity(intent);
}
}
#Override
protected void onPause() {
super.onPause();
timer = new Timer();
Log.i("Main", "Invoking logout timer");
LogOutTimerTask logoutTimeTask = new LogOutTimerTask();
timer.schedule(logoutTimeTask, 300000); //auto logout in 5 minutes
}
#Override
protected void onResume() {
super.onResume();
if (timer != null) {
timer.cancel();
Log.i("Main", "cancel timer");
timer = null;
}
}
private class LogOutTimerTask extends TimerTask {
#Override
public void run() {
//redirect user to login screen
Intent i = new Intent(AccountActivity.this, MainActivity.class);
i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(i);
finish();
}
}
}
Well, the implementation architecture you choose could be improved. I shall come to that later. First apply this quick fix to fix your architecture.
Clearly you want to start a Timer when you go out of Activity using home screen. But user can also go out of Activity by using Intent to switch to AccountActivity. This you can track. So keep a boolean flag like shouldNavigate, initially it should be false. when onResume, it should be set to false, but when getcounttask goes to onPostExecute it should be set to true. So in onPause, if you are going out via getcounttask,shouldNavigate is going to be true and if is true, cancel your Timer else, start your Timer.
Code:
public class AccountActivity extends AppCompatActivity {
private Timer timer;
private volatile boolean shouldNavigate = false;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_account);
}
private class getaccounttask extends AsyncTask<String, Void, String>
{
#Override
protected String doInBackground(String... urlaccount)
{
StringBuilder result = new StringBuilder();
try
{
//My Codes
}
catch (Exception e)
{
e.printStackTrace();
}
return result.toString();
}
#Override
protected void onPostExecute(String result)
{
shouldNavigate = true;
Intent intent = new Intent();
intent.setClass(getApplicationContext(), AccountInformationActivity.class);
startActivity(intent);
}
}
#Override
protected void onPause() {
super.onPause();
if (!shouldNavigate){
timer = new Timer();
Log.i("Main", "Invoking logout timer");
LogOutTimerTask logoutTimeTask = new LogOutTimerTask();
timer.schedule(logoutTimeTask, 300000);
}else{
if (timer != null){
timer.cancel();
timer = null;
}
}
}
#Override
protected void onResume() {
super.onResume();
shouldNavigate = false;
if (timer != null) {
timer.cancel();
Log.i("Main", "cancel timer");
timer = null;
}
}
private class LogOutTimerTask extends TimerTask {
#Override
public void run() {
//redirect user to login screen
shouldNavigate = false;
Intent i = new Intent(AccountActivity.this, MainActivity.class);
i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(i);
finish();
}
}
}
Now a better approach could be keep a Service and a Singleton class.
The Singleton class would be
public Singleton{
private static Singleton instance = null;
private Singleton(){
activityMap = new HashMap<String, Activity>();
}
public static Singleton getInstance(){
if (instance == null) instance = new Singeton();
return instance;
}
public HashMap<String, Activity> activityMap;
}
Now each activity will have a Tag (like its name), so each activity when resumes will do
Singleton.getInstance().activityMap.put(tag, this);
and when goes to onPause will do
Singleton.getInstance().activityMap.remove(tag, this);
So when the service finds that size of Singleton.getInstance().activityMap is zero then clearly no activity is on foreground, so it starts a timer. when the timer expires check again if the count is still zero, if zero then perform your logout.
Make some modifications in onPause() and add this permission
<uses-permission android:name="android.permission.GET_TASKS" />
#Override
protected void onPause() {
if (isApplicationSentToBackground(this)) {
// Do what you want to do on detecting Home Key being Pressed
timer = new Timer();
Log.i("Main", "Invoking logout timer");
LogOutTimerTask logoutTimeTask = new LogOutTimerTask();
timer.schedule(logoutTimeTask, 300000); //auto logout in 5 minutes
Log.i("Main", "Invoking Home Key pressed");
}
super.onPause();
}
public boolean isApplicationSentToBackground(final Context context) {
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningTaskInfo> tasks = am.getRunningTasks(1);
if (!tasks.isEmpty()) {
ComponentName topActivity = tasks.get(0).topActivity;
if (!topActivity.getPackageName().equals(context.getPackageName())) {
return true;
}
}
return false;
}
I'm facing a problem: I created two Activities.
One is the main Activity, which has a Button.
When I click this Button, the second Activity starts.
The second Activity uses an Asynctask in which a number is incremented from 1 to 10 and displays this number in a Textview
What I'm facing is that when I click the back Button while the Asynctask has not completed and then again go to the second Activity the Asynctask is not run from start immediately.
I know because in background when it completed the old task then it again starts a new task. Is there a way to fix this when destroying the Activity it also destroy the Asynctask?
Here is video sample for my problem.
Code for Main Activity:
public class MainActivity extends AppCompatActivity {
Button bt;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bt = (Button) findViewById(R.id.bt);
bt.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Intent i = new Intent(MainActivity.this,SecondAcitivity.class);
startActivity(i);
}
});
}
}
Code of Second Activity:
public class SecondAcitivity extends AppCompatActivity {
TextView t1;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second_acitivity);
t1 = (TextView) findViewById(R.id.t1);
OurWork obj = new OurWork();
obj.execute();
}
class OurWork extends AsyncTask<Void, Integer, String> {
#Override
protected String doInBackground(Void... params) {
int i = 0;
while (i < 11) {
try {
Thread.sleep(700);
publishProgress(i);
i++;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return "Successfully Completed";
}
#Override
protected void onProgressUpdate(Integer... values) {
t1.setText(values[0] + "%");
}
#Override
protected void onPostExecute(String result) {
t1.setText(result);
}
}
}
you need to cancel the task on back pressed, and you need to monitor if the task is canceled while executing the doInbackground().
1- override onbackpressed:
#Override
public void onBackPressed() {
obj.cancel(true); // where obj is the asyncTask refernce object name
super.onBackPressed();
}
2- monitor isCanceled()
#Override
protected String doInBackground(Void... params) {
int i = 0;
while (i < 11 && !isCancelled()) { // added !isCancelled()
try {
Thread.sleep(700);
publishProgress(i);
i++;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return "Successfully Completed";
}
on next iteration of the while loop, after cancel(true); is called,the loop will quit, and doInBackground() will return.
When you press back button , onBackPressed callback is called. so you can basically try this :
#Override
public void onBackPressed() {
if (asyncFetch.getStatus() == AsyncTask.Status.RUNNING) {
asyncFetch.cancel(true);
}
finish();
}
Try to use :
private OurWork task;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second_acitivity);
t1 = (TextView) findViewById(R.id.t1);
task = new OurWork();
task.execute();
}
#Override
public void onBackPressed() {
task.cancel(true);
super.onBackPressed();
}
AsyncTask runs in background of the activity where it was hosted. If OnPause or OnDestroy is called, AsyncTask is destroyed, so to solve this issue, Override OnResume and execute AsyncTask again.
To cancel the asyncTask even when it is running when back is pressed, add this to onBackPressed:
public class SecondAcitivity extends AppCompatActivity {
TextView t1;
static OurWork obj;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second_acitivity);
t1 = (TextView) findViewById(R.id.t1);
obj = new OurWork();
obj.execute();
}
class OurWork extends AsyncTask<Void, Integer, String> {
#Override
protected String doInBackground(Void... params) {
int i = 0;
while (i < 11) {
try {
Thread.sleep(700);
publishProgress(i);
i++;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return "Successfully Completed";
}
#Override
protected void onProgressUpdate(Integer... values) {
t1.setText(values[0] + "%");
}
#Override
protected void onPostExecute(String result) {
t1.setText(result);
}
}
//override onBackPressed and do this
#Override
public void onBackPressed() {
if (obj!=null && (obj.getStatus()== AsyncTask.Status.RUNNING ||
obj.getStatus()== AsyncTask.Status.PENDING ))
obj.cancel(true);
super.onBackPressed();
}
}
EDIT: I've found that what I'm describing below only occurs on my emulated device (Nexus 5, target api 19, 4.4.2 with Intel Atom (x86) cpu), but NOT on my physical device (HTC One)....
EDIT2: Edit1 was due to an IllegalStateException that I didnt catch. Added some code to check if the thread was already running before trying to start it. This combined with the accepted answer resolved my issue.
I have implemented an activty that starts a new thread in the activity's onCreate method, like this:
...
private boolean running;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
running = true;
new Thread(null, work, "myThread").start();
}
Runnable work = new Runnable() {
#Override
public void run() {
while (running) {
//Doing work
}
}
};
I'm "pausing" my thread with my activity's onPause method, like this:
#Override
protected void onPause() {
running = false;
super.onPause();
}
So I thought that resuming it would be just as easy...ยจ
#Override
protected void onResume(){
running = true;
super.onResume();
}
but my thread isn't resuming. Any ideas why? Thankful for any help.
Marcus
All of the answers i think have some issues about your running variable because you can not write and read a variable from two different Threads without synchronized block so i post my own answer:
package com.example.threadandtoast;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.widget.Toast;
public class MainActivity extends ActionBarActivity {
public class MonitorObject{
public boolean running = true;
public String message = "";
public boolean mustBePost = true;
}
Thread t;
int threadNameCounter = 0; // i use this variable to make sure that old thread is deleted
// when i pause, you can see it and track it in DDMS
Runnable work = new Runnable() {
boolean myRunning;
#Override
public void run() {
synchronized(mSync) {
myRunning = mSync.running;
}
while (myRunning) {
runOnUiThread(new Runnable() { // in order to update the UI (create Toast)
#Override // we must switch to main thread
public void run() {
// i want to read the message so i must use synchronized block
synchronized(mSync) {
// i use this variable to post a message just for one time because i am in an infinite loop
// if i do not set a limit on the toast i create it infinite times
if(mSync.mustBePost){
Toast.makeText(MainActivity.this, mSync.message, Toast.LENGTH_SHORT).show();
// the message post so i must set it to false
mSync.mustBePost = false;
// if i am going to pause set mSync.running to false so at the end of infinite loop
//of thread he reads it and leaves the loop
if(mSync.message.equals("Main Activity is going to pause")){
mSync.running=false;
}
}
}
}
});
synchronized(mSync) {
myRunning = mSync.running;
}
}
}
};
final MonitorObject mSync = new MonitorObject();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
#Override
protected void onPause() {
super.onPause();
synchronized(mSync) {
// mSync.running = false; you can not set it here because
// it is possible for the thread to read it and exit the loop before he posts your message
mSync.mustBePost=true;
mSync.message = "Main Activity is going to pause";
}
}
#Override
protected void onResume(){
super.onResume();
threadNameCounter++;
synchronized(mSync) {
mSync.running = true;
mSync.mustBePost=true;
mSync.message = "Main Activity is going to resume";
}
t = new Thread(work,"My Name is " + String.valueOf(threadNameCounter));
t.start();
}
}
Or you can use this code:
public class MainActivity extends ActionBarActivity {
Thread t;
int threadNameCounter = 0; // i use this variable to make sure that old thread is deleted
// when i pause, you can see it in DDMS
String message = "";
boolean isPost = false;
Runnable work = new Runnable() {
#Override
public void run() {
while (true) {
runOnUiThread(new Runnable() {
#Override
public void run() {
if(!isPost){
Toast.makeText(MainActivity.this, message, Toast.LENGTH_SHORT).show();
isPost = true;
if( message.equals("Main Activity is going to pause")){
t.interrupt();
}
}
}
});
if(Thread.currentThread().isInterrupted()){
break;
}
}
}
};
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
#Override
protected void onPause() {
super.onPause();
message = "Main Activity is going to pause";
isPost = false;
}
#Override
protected void onResume(){
super.onResume();
message = "Main Activity is going to resume";
isPost = false;
threadNameCounter++;
t = new Thread(work,"My Name is " + String.valueOf(threadNameCounter));
t.start();
}
}
you can also use semaphore or wait-notify approach.
i put public String message = ""; and public boolean mustBePost = true; in to mSync object but it is
not necessary because only main thread have an access to them.
if you have any problem please ask.
The statement running = false; will stop execution of the Thread, instead of pausing it. Use two variables: One for stopping current Thread, and another for pausing and resuming the Thread, as follow:
boolean isThreadPause=false;
Runnable work = new Runnable() {
#Override
public void run() {
while (running) {
if (!isThreadPause) {
// Doing work
}
}
}
};
In the onPause event of the Activity, set isThreadPause to true, and in the onResume event, set isThreadPause to false.
This is because your Runnable object stops when the while loop stops. You could try this:
Runnable work = new Runnable() {
#Override
public void run() {
while () {
if(running){
//Doing work
}
}
}
};
INTRODUCTION
I have an activity that processes some functions. Inside this activity, the main process is one thread that makes the processing of these functions.When the processing is done, it should call to another activity to start another diferent process.
This is my thread inside the main activity:
CODE
private static void DetectionThread (byte[] data, int width, int height, final Context context) {
mData = data;
mWidth = width;
mHeight = height;
mThread = new Thread() {
#Override
public void run() {
try {
//MAKES THE PROCESSING
//If it's right, continues to next code...
MotionDetectionActivity.gameStarted = true;
gameLaunched = true;
return;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
processing.set(false);
/*HERE MUST INIT THE ACTIVITY WITH INTENT*/
if (MotionDetectionActivity.gameStarted == true && gameLaunched == true) {
gameLaunched = false;
Intent gameIntent = new Intent(context, GameActivity.class);
context.startActivity(gameIntent);
}
processing.set(false);
}
}
};
if (MotionDetectionActivity.gameStarted == false) {
mThread.start();
}
}
QUESTION
Well, the thing is that i'm not getting the desired result. When initializing the GameActivity, it is not showing this activity's layout, and there are some functionalities that are not initialized, f.e. I do this to initialize the TTS:
private static TextToSpeech tts;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.game);
tts = new TextToSpeech(this, this);
//Iniside main method
tts.speak("Initializing...", TextToSpeech.QUEUE_ADD, null);
The thing is that it doesn't talk.
Use AsyncTask instead of Thread, and call the another activity in the onPostExecute method
public class MyAsync extends AsyncTask<Void, Void, Void>
{
#Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
//start the next activity here
}
#Override
protected Void doInBackground(Void... params) {
//your task goes here
return null;
}
}
in my app in android, i need change background image in image view on 10 seconds once. so that i call a Async Task within a run method. when I execute the app it crashes.
It gives the Caused by: java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare() Exception to me.
I know I have to use Thread, but I do not know how to do so properly. Please help me.
This is my code sample:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
.................
new Thread()
{
public void run()
{
while(true){
try
{
Thread.sleep(5000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
count = count + 1;
new ImageChange().execute();
}
}
}.start();
} // OnCreate End
class ImageChange extends AsyncTask<Void, Void, Void>
{
protected void onPreExecute() {
}
protected void onPostExecute(Void unused) {
iv1.setImageBitmap(b1);
iv2.setImageBitmap(b2);
}
protected Void doInBackground(Void... arg0) {
switch(count){
case 1:
b1 = BitmapFactory.decodeFile(f1.getAbsolutePath());
b2 = BitmapFactory.decodeFile(f2.getAbsolutePath());
break;
case 2:
b1 = BitmapFactory.decodeFile(f2.getAbsolutePath());
b2 = BitmapFactory.decodeFile(f1.getAbsolutePath());
break;
default :
count = 0;
b1 = BitmapFactory.decodeFile(f1.getAbsolutePath());
b2 = BitmapFactory.decodeFile(f2.getAbsolutePath());
break;
}
return null;
}
}
You're calling the AsyncTask from a worker Thread. This way it has no access to the UI thread. You probably should consider using a Handler.
Probably, the problem is that you must execute the ImageChange.doInBackground() method in the UI thread. Try to change your code like this:
class ImageChange extends AsyncTask<Void, Void, Void> {
Activity act;
public ImageChange(Activity act) {
this.act = act;
}
protected void onPostExecute(Void unused) {
iv1.setImageBitmap(b1);
iv2.setImageBitmap(b2);
}
protected Void doInBackground(Void... arg0) {
switch(count) {
case 1:
helperMethod(f1.getAbsolutePath(), f2.getAbsolutePath());
break;
case 2:
helperMethod(f2.getAbsolutePath(), f1.getAbsolutePath());
break;
default :
count = 0;
helperMethod(f1.getAbsolutePath(), f2.getAbsolutePath());
break;
}
return null;
}
private void helperMethod(String a, String b) {
act.runOnUIThread(new Runable() {
public void run() {
b1 = BitmapFactory.decodeFile(a);
b2 = BitmapFactory.decodeFile(b);
}
});
}
}
Note that you must pass an Activity to the ImageChange class constructor. It means that you have to call the asyncTask in this way:
new ImageChange(this).execute();
Also consider the possibility of using the class TimerTask
EDIT: Change the Activity part of your code with this:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
.................
new ImageChange().execute();
} // OnCreate End
And add the while(true) to the ImageChange class:
protected Void doInBackground(Void... arg0) {
while(true) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count = count + 1;
switch(count) {
...
}
}
return null;
}
EDIT2: You can solve the problem about onPostExecute inserting the code that must be execute after each iteration inside the while loop:
protected Void doInBackground(Void... arg0) {
while(true) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count = count + 1;
switch(count) {
...
}
act.runOnUIThread(new Runnable() {
public void run() {
iv1.setImageBitmap(b1);
iv2.setImageBitmap(b2);
}
});
}
return null;
}
The code you insert inside the while loop must run in the UI thread; in fact, every onPostExecute method of the AsyncTask class runs on UI thread.
i solved the problem by using Handler Thread.