I'm trying to pass the callback action of a button from the main activity to an AsyncTask class. For now i just pass the Button in the constructor of the AsyncTask class and i do it directly in there:
public AsyncTaskClass(Button btnOk){
this.btnOk = btnOk;
}
#Override
protected void onPreExecute() {
btnOk.setOnClickListener(btnOkListener);
Log.i("AsyncTask", "onPreExecute");
}
private View.OnClickListener btnOkListener = new View.OnClickListener() {
#Override
public void onClick(View v) {
//things to do
}
};
#Override
protected Boolean doInBackground(Void... params) { //etc
Is this good practice? Is there a better way of doing it, perhaps by using an external CallBack Interface? If so, how it can be made? Thank you for your time!
You never, ever pass views, fragments, activities, etc as parameters to threads. The lifecycle of Android can make you nasty surprises like, instances of views, that are not related to any UI container, since it has been replaced by other from the stack. Anyway a callback from click event will always arrive in the UI thread. So either you start the asynch task from onClick() which is simple. Or if in your case, the task is already alive you can call it from the onClick(). But the Asynch task should never know that there is a button!
MyTask myTask;
class MyTask extends AsyncTask{
private boolean shouldDoTheStuff = false;
public synchronized void doSomeStuff(){
shouldDoTheStuff = true;
}
protected synchronized Boolean doInBackground(Void... params) {
while(true){
Thread.sleep(200);
if(shouldDoTheStuff){
//react to the click
shouldDoTheStuff = false;
}
}
}
}
private View.OnClickListener btnOkListener = new View.OnClickListener() {
#Override
public void onClick(View v) {
myTask.doSomeStuff();
}
};
Instead of synchronized you can use AtomicBoolen, which is more clean way.
Related
I've been using AsyncTasks for a while however, I've recently encountered a scenario where I'm unsure of how to handle correctly. Since I thought it would be a somewhat common scenario I decided to ask the question here.
So, I'm trying to use an AsyncTask to make a simple call to sign a user in to the app. After the call completes, if it succeeds, the user should be taken to another activity. This logic is simple. The problem arrises when the user navigates away from the app before the sign in call returns. In such a case, what should I do in onPostExecute()?
What I've seen some apps do is they continue with the call anyways, as long as the activity is still around, and will launch the next activity. However this creates a weird experience where the user navigates away from the app, then several seconds later, the app just pops back up in their face. Of course, I would like to avoid doing this.
Update
Example code:
public class ExampleActivity extends Activity {
private boolean mIsPaused;
...
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
Button btnSignIn = (Button) findViewById(R.id.btn_sign_in);
btnSignIn.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
new SignInTask(ExampleActivity.this).execute();
}
});
...
}
#Override
protected void onPause() {
super.onPause();
mIsPaused = true;
}
#Override
protected void onResume() {
super.onResume();
mIsPaused = false;
}
private boolean isPaused() {
return mIsPaused;
}
...
private static class SignInTask extends AsyncTask<Void, Void, SomeResult> {
private final WeakReference<ExampleActivity> mAct;
public SignInTask(ExampleActivity act) {
mAct = new WeakReference<ExampleActivity>(act);
}
#Override
protected SomeResult doInBackground(Void... params) {
return mApi.signIn(creds);
}
#Override
protected void onPostExecute(SomeResult result) {
if (result.getCode() == OK) {
ExampleActivity act = mAct.get();
if (act != null) {
if (act.isPaused()) {
// do something
} else {
startActivity(new Intent(act, NextActivity.class));
}
} else {
// do something
}
}
}
}
}
made your AsyncTask class as static inner class.
Pretty interesting problem... Going with what you've started by using booleans, you could save the response the Activity receives to the SharedPreferences in the event it is paused, or continue processing normally if it is not. If the Activity later resumes (or is recreated), check whether or not there is a saved response and handle accordingly. I was thinking something along the lines of:
import org.json.JSONObject;
import android.app.Activity;
import android.os.Bundle;
public class TaskActivity extends Activity {
private static final String KEY_RESPONSE_JSON = "returned_response";
private boolean paused = false;
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
// don't setup here, wait for onPostResume() to figure out what to do
}
#Override
public void onPostResume(){
super.onPostResume();
paused = false;
if(isSavedResponseAvailable()) processResponse(getSavedResponse());
else setup();
}
#Override
public void onPause(){
paused = true;
super.onPause();
}
private void setup(){
// normal setup
}
public void onReceiveResponse(JSONObject response){
if(paused) setSavedResponse(response);
else processResponse(response);
}
private void processResponse(JSONObject response){
// Continue with processing as if they never left
getSharedPreferences(this.getClass().getName(), 0).edit().clear().commit(); // Clear everything so re-entering won't parse old data
}
private boolean isSavedResponseAvailable(){
return getSavedResponse() != null;
}
private JSONObject getSavedResponse(){
try{
return new JSONObject(getSharedPreferences(this.getClass().getName(), 0).getString(KEY_RESPONSE_JSON, ""));
}
catch(Exception e){ }
return null;
}
private void setSavedResponse(JSONObject response){
getSharedPreferences(this.getClass().getName(), 0).edit().putString(KEY_RESPONSE_JSON, response.toString()).commit();
}
}
Clearly that's assuming your response from the task is JSON, but there's no reason you couldn't extend that to save the data individually and rebuild the necessary response object from the saved preference data.
As far as clean approaches go, though... I give this about a 3/10, but I can't think of anything better (well, other than making the TaskActivity abstract and forcing implementations to override setup(), processResponse(), isResponseAvailable(), getSavedResponse(), and setSavedResponse(), but that would only be mildly better for like a 4/10)
I would suggest putting a try/catch statement in the post execute - as far as I know what would happen in this situation is that you would get some kind of Window Manager exception.
What I would STRONGLY recommend, however, is stopping any async tasks (with the cancel method) on the onPause method, meaning that you won't interrupt them.
http://developer.android.com/reference/android/os/AsyncTask.html#cancel(boolean)
public final boolean cancel (boolean mayInterruptIfRunning)
Added in API level 3
Attempts to cancel execution of this task. This attempt will fail if the task has already completed, already been cancelled, or could not be cancelled for some other reason. If successful, and this task has not started when cancel is called, this task should never run. If the task has already started, then the mayInterruptIfRunning parameter determines whether the thread executing this task should be interrupted in an attempt to stop the task.
Calling this method will result in onCancelled(Object) being invoked on the UI thread after doInBackground(Object[]) returns. Calling this method guarantees that onPostExecute(Object) is never invoked. After invoking this method, you should check the value returned by isCancelled() periodically from doInBackground(Object[]) to finish the task as early as possible.
Parameters
mayInterruptIfRunning true if the thread executing this task should be interrupted; otherwise, in-progress tasks are allowed to complete.
Returns
false if the task could not be cancelled, typically because it has already completed normally; true otherwise
See Also
isCancelled()
onCancelled(Object)
boolean isRunning; //set it to true in onResume, and false in onStop
boolean isWaiting; // set it to true in onPostExecute, if "isRunning" is false
check in onResume whether isWaiting is true, if yes, take user to another screen.
Use the cancel() of AsynchTask class onBackPress() of Activty class
public class ExampleActivity extends Activity {
private boolean mIsPaused;
SignInTask singleTaskObj;
...
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
Button btnSignIn = (Button) findViewById(R.id.btn_sign_in);
btnSignIn.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
singleTaskObj = new SignInTask(ExampleActivity.this).execute();
}
});
...
}
#Override
protected void onPause() {
super.onPause();
mIsPaused = true;
}
#Override
protected void onResume() {
super.onResume();
mIsPaused = false;
}
protected void onBackPressed()
{
singleTaskObj.cancel();
}
private boolean isPaused() {
return mIsPaused;
}
...
private static class SignInTask extends AsyncTask<Void, Void, SomeResult> {
private final WeakReference<ExampleActivity> mAct;
public SignInTask(ExampleActivity act) {
mAct = new WeakReference<ExampleActivity>(act);
}
#Override
protected SomeResult doInBackground(Void... params) {
return mApi.signIn(creds);
}
#Override
protected void onPostExecute(SomeResult result) {
if (result.getCode() == OK) {
ExampleActivity act = mAct.get();
if (act != null) {
if (act.isPaused()) {
// do something
} else {
startActivity(new Intent(act, NextActivity.class));
}
} else {
// do something
}
}
}
}
}
My application has a refresh button on the main activity. When the user presses that button, a new thread is created which starts updating the SQLite database. When this thread started, user could possibly get into another activies of the application.
The problem is these other activities(ListActivity) should be updated according to the DB when that background thread is completed. How could I provide that. I tried getting current task with ActivityManager but It requires extra permission which I dont want.
Edit:
Sorry seems I misunderstood you. Please take a look at the following code, it is similar to Chinaski's (you just use an interface for the callback methods) but I added a bit more to ensure you know how to use it in a way that will avoid memory leaks.
Note how the activity detaches during onDestroy -- alternatively you could use a WeakReference, however these days you'd use a Fragment with setRetainInstance(true) and completely avoid the detaching/attaching as the fragment would be retained.
MyAsyncTask
public class MyAsyncTask extends AsyncTask<Void, Void, Void> {
private Callback mCallback;
private boolean mIsComplete = false;
private boolean mHasCallbackBeenCalled = false;
public MyBackgroundTask(Callback callback) {
mCallback = callback;
}
/** Only safe to call this from the UI thread */
public void attach(Callback callback) {
mCallback = callback;
if (mIsComplete && !mHasCallbackBeenCalled) {
fireCallback();
}
}
/** Only safe to call this from the UI thread */
public void detach() {
mCallback = callback;
}
#Override
public void doInBackground() {
// do the heavy stuff here
return null;
}
#Override
public void onPostExecute(Void result) {
mIsComplete = true;
fireCallback();
}
private void fireCallback() {
if (mCallback != null) {
mCallback.callbackMethod();
mHasCallbackBeenCalled = true;
}
}
public static interface Callback {
public void callbackMethod();
}
}
MyActivity
public class MyActivity extends Activity implements MyAsyncTask.Callback {
private MyAsyncTask mTask;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Check for a retained task after a configuration change
// e.g. a rotation
if (getLastNonConfigurationInstance() != null) {
mTask = (MyAsyncTask) getLastNonConfigurationInstance();
// Re-attach the task
mTask.attach(this);
}
}
#Override
public void onDestroy() {
// Detach from task to avoid memory leak
if (mTask != null) {
mTask.detach();
}
super.onDestroy();
}
#Override
public Object onRetainNonConfigurationInstance() {
// Retain the async task duration a rotation
return mTask;
}
/** Callback method */
#Override
public void callbackMethod() {
// Do something here
}
}
You could make a singleton in which you will have your thread and a queue of "tasks". When a task is finished, you check / launch the next task, and when you add a task, you launch it, or add it in the queue if a task is already running.
I don't say this is the best solution, but it's one.
My main application does this: It retrievs data from the internet and has 3 button, when OnClicked, i am going to 3 other screens. because the data loading may be a little slow, I want to use an async Task. This is my sample code for asynctask.
class LoginProgressTask extends AsyncTask {
#Override
protected Boolean doInBackground(String... params) {
try {
Thread.sleep(4000); // Do your real work here
} catch (InterruptedException e) {
e.printStackTrace();
}
return Boolean.TRUE; // Return your real result here
}
#Override
protected void onPreExecute() {
showDialog(AUTHORIZING_DIALOG);
}
#Override
protected void onPostExecute(Boolean result) {
// result is the value returned from doInBackground
removeDialog(AUTHORIZING_DIALOG);
}
}
and this is my sample of my main activity:
public class MainScreen extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MainTheme();
}
public void MainTheme(){
retrieve_data(); //function for getting the data
... action with the buttons, onClicks Listener
}
}
My question is how can I mix those codes in One activity to make it work, becuase I haven't understood AsyncTask. Or what I should return in the doInBackground?
I learned it from >> AsynTask link. may be it will helpful to you too, first read the whole example then start applying it on your code.
For example, if you have a Button to login, you should do something like this:
Button button; //Here button to go other sreen
public void onCreate(){
//some business code here
//notice: you have declare you button to your layout. I don't post it, but maybe you know how to
button.setOnClickListener(new OnClickListener){
#Override
public void onClick(View view){
LoginProcessTask loginTask = new LoginProcessTask(this);
login.excute(data of param1);
}
}
}
And you should notice that, in your LoginProcessTask, you have wrongly extended it. It must be (just for example):
class LoginProgressTask extends AsyncTask<String, Integer, Integer>{ ......}
You can use a Service instead of AsyncTask. This will help you.
friends,
i am using following code to fill android listview asynchronously
now problem is while running this thread i press back button or home button and comes back to activity
multiple threads are started....
can i avoid these multiple threads
actually i want if thread is running then it should not get called again and again.
it should be called when previous thread is complete.
private static String[] items={.......};
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
setListAdapter(
new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1,
new ArrayList()));
AddStringTask test= new AddStringTask();
test.execute();
}
class AddStringTask extends AsyncTask<Void, String, Void> {
#Override
protected Void doInBackground(Void... unused) {
for (String item : items) {
publishProgress(item);
SystemClock.sleep(1000);
}
return(null);
}
#Override
protected void onProgressUpdate(String... item) {
((ArrayAdapter)getListAdapter()).add(item[0]);
}
#Override
protected void onPostExecute(Void unused) {
Toast
.makeText(AsyncThread.this,
"Done - Finished updating Java Book List!",
Toast.LENGTH_SHORT)
.show();
}
}
}
any useful solution?
thanks in advance.
You can intercept the Activity Closing by overriding onStop() or onPause() or even onBackPressed() and terminate the update when it does...
If you don't wish to terminate it and instead just want NOT to initiate it again, just use static variables (or any other method) such that the test variable is created only once...
To terminate a AsyncTask, i believe there's a cancel() method...Use true as its parameter.
From API Reference
final boolean cancel(boolean mayInterruptIfRunning)
//Attempts to cancel execution of this task.
When an Activity terminates, e.g. after screen orientation changing, is that possible to change an AsyncTask activity context? Else it will create an error because when the activity terminates AsyncTask's activity context is gone too.
My homework done is the following:
public void onSaveInstanceState(Bundle savedInstanceState) <- doesn't solve
public Object onRetainNonConfigurationInstance() <- doesn't solve
android:configChanges="keyboardHidden|orientation"
<- solved but doesn't handle well relative layouts
What do you pass on your onRetainNonConfigurationInstance()? What I do is pass an object to it containing the AsyncTask, and then I try to retrieve the value in getLastNonConfigurationInstance().
EDIT: On second thought, it would depend on what you want to do after a configuration change. If you want to terminate the AsyncTask, and then call cancel() on it. If you want to continue its processing even after an orientation change, then you have to hold on to the task.
You can do that by saving the Activity in the AsyncTask like this:
private MyAsyncTask searchTask;
#Override
public void onCreate(Bundle savedInstance){
super.onCreate(savedInstance);
if (getLastNonConfigurationInstance()!=null) {
SavedObject savedObj = (SavedObject)getLastNonConfigurationInstance();
searchTask = savedObj.getAsyncTask();
searchTask.attach(this);
} else {
searchTask = new MyAsyncTask(this);
searchTask.execute();
}
}
#Override
public Object onRetainNonConfigurationInstance(){
searchTask.detach();
final SavedObject savedObj = new SavedObject();
savedObj.setAsyncTask(searchTask);
return savedObj;
}
private class MyAsyncTask extends AsyncTask<Void, Void, Void> {
MyActivity parentActivity = null;
MyAsyncTask (MyActivity activity) {
attach(activity);
}
void attach(MyActivity activity) {
this.parentActivity=activity;
}
void detach() {
parentActivity=null;
}
// Do your thread processing here
}
private class SavedObject {
private MyAsyncTask asyncTask;
public void setAsyncTask(MyAsyncTask asyncTask){
this.asyncTask = asyncTask;
}
public MyAsyncTask getAsyncTask() {
return asyncTask;
}
}
in the OnCancel method of your asynch task put finish();
public void onCancel(DialogInterface dialog) {
cancel(true);
dialog.dismiss();
finish();
}