How to handle AsyncTask onPostExecute when paused to avoid IllegalStateException - android

I appreciate the numerous postings regarding AsyncTask on a rotation change. I have the following problem when using the compatability lib and trying to dismiss a DialogFragment in onPostExecute.
I have a fragment which fires of an AsyncTask which displays a progress DialogFragment, then in onPostExecute dismisses the dialog and then potentially throws up another DialogFragment.
If when the progress dialog is being displayed I put the application into the background I get the following for my fragment:
1) onPause
2) onSaveInstanceState
3) onPostExecute in which I try to dismiss and invoke a dialog.
I get an IllegalStateException because I'm trying to effectively commit a transaction when the activity has saved its state and I understand this.
On a rotation I've assumed (perhaps incorrectly) that I wouldn't get an onPostExecute until the activity has been recreated. However, when putting the application into the background I assumed (definitely incorrectly) that the onPostExectute wouldn't get called while the fragment/activity was paused.
My question is, is my solution to simply detect in onPostExecute that the fragment/activity is paused and simply perform what I need to do in onResume instead? Seems somewhat ugly to me.
Thanks in advance, peter.
Edit 1
Need to support 2.1 and above
Edit 2
I have considered showing the dialog using FragmentTransaction:add and FragmentTransaction:commitAllowingStateLosshowever this isn't without its problems.

If you need to synchronize your task with the activity lifecycle, I believe that Loaders are exactly what you need. More specifically, you should use AsyncTaskLoader to do the job. So now instead of running an AsyncTask, you launch your loader, then wait for response in a listener. If the activity is paused, you won't get a callback, this part will be managed for you.
There is another way to handle this task: using a fragment which retains its instance. The general idea is that you create a fragment without UI and call setRetainInstance(true). It has a task which is being notified about the activity being available or not. If not, the task's thread suspends until an activity becomes available.

Another way of achieving what you require is to implement the PauseHandler class that I documented in this post.
Then in your onPostExecute method call sendMessage() to post your message into the handler.
When your application resumes the action will be handled.

Rather then using BroadcastReceiver, I prefer using bus libraries like guava, otto or eventbus. Their performance is much better then the broadcast receiver implementation.

I came up with a solution for this problem without any major workaround:
The basic idea how to maintain a progressdialog and a asynctask is described in this blogentry (of course I used the AsyncTaskComplex-Version). All credits go to the author of this blogentry, I only added a tiny thing:
Obviously I'm not using showDialog() anymore. Instead I stick with DialogFragments.
The second tweak is the importent one and also solves the problem with the IllegalStateException:
Instead of only telling the asynctask in onRetainCustomNonConfigurationInstance() that there is no more activity I also do it in onPause(). And instead of only telling the asynctask in onCreate() that there is a new activity I also do it in onResume().
And there you go, your AsyncTask will not try to inform your activity about his finish causing an IllegalStateException when the activity is not visible.
If you would like to see more code instead of words, leave a comment.
/edit:
Sourcecode to show my solution, which I think is a pretty decent one :)
public class MyActivity extends Activity {
private MyTask mTask;
#Override
protected void onCreate(Bundle pSavedInstanceState) {
super.onCreate(pSavedInstanceState);
setContentView(R.layout.editaccount);
Object retained = getLastCustomNonConfigurationInstance();
if ( retained instanceof NewContactFolderIdTask ) {
mTask = (MyTask) retained;
mTask.setActivity(this);
}
}
#Override
protected void onPause() {
if(mTask != null) {
mTask.setActivity(null);
}
super.onPause();
}
#Override
public Object onRetainCustomNonConfigurationInstance() {
if(mTask != null) {
mTask.setActivity(null);
return mTask;
}
return null;
}
#Override
protected void onResume() {
if(mTask != null) {
mTask.setActivity(this);
}
loadValues(); // or refreshListView or whatever you need to do
super.onResume();
}
public void onTaskCompleted() {
loadValues(); // or refreshListView or whatever you need to do
DialogFragment dialogFragment = (DialogFragment) getSupportFragmentManager().findFragmentByTag(PROGRESS_DIALOG_FRAGMENT);
if(dialogFragment != null) {
dialogFragment.dismiss();
}
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater menuInflater = getMenuInflater();
menuInflater.inflate(R.menu.main, menu);
return super.onCreateOptionsMenu(menu);
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
// app icon in Action Bar clicked; go home
Intent intent = new Intent(this, OXClient.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
return true;
case R.id.menu_refresh:
mTask = new MyTask(this);
mTask.execute();
break;
}
return super.onOptionsItemSelected(item);
}
private class NewContactFolderIdTask extends AsyncTask<Boolean, Integer, Bundle> {
private MyActivity mActivity;
private boolean mCompleted;
private NewContactFolderIdTask(MyActivity pActivity) {
this.mActivity = pActivity;
}
public void setActivity(MyActivity pActivity) {
this.mActivity = pActivity;
if(mCompleted) {
notifiyActivityTaskCompleted();
}
}
private void notifiyActivityTaskCompleted() {
if(mActivity != null) {
mActivity.onTaskCompleted();
}
}
#Override
protected Bundle doInBackground(Boolean... pBoolean) {
// Do your stuff, return result
}
#Override
protected void onPreExecute() {
DialogFragment newFragment = ProgressDialogFragment.newInstance();
newFragment.show(getSupportFragmentManager(), PROGRESS_DIALOG_FRAGMENT);
}
#Override
protected void onPostExecute(Bundle pResult) {
mCompleted = true;
notifiyActivityTaskCompleted();
}
}
}

On How to handle Handler messages when activity/fragment is paused I offer another approach using a BroadcastReceiver.
I consider it cleaner more elegant and it offers the advantages that you can invoke code on your base fragment from everywhere within your app and by using sticky broadcasts your invocation can be "remembered" and executed after your fragment resumes.

Related

Call different activity method from AsyncTask

I have a MainActivity, SecondaryActivity and an AsyncTask class.
MainActivity has a method called doSomething()
I call the AsyncTask from MainActivity like this:
new asyncTask(MainActivity.this).execute();
Which means I can reference the MainActivity in my onPostExecute
#Override
protected void onPostExecute(Boolean result){
super.onPostExecute(result);
# activity is defined as this.activity
activity.doSomething();
}
How can I call the AsyncTask from my SecondaryActivity in a similar manner, because I'd need a reference to my MainActivity to access its methods?
EDIT: I would want the doSomething() to be called at all times. So even if it's from SecondActivity, once it finishes its background operation -> doSomething()
The method I'm calling refreshes the screen of MainActivity to show data changes. Secondary activity only calls the AsyncTask when it is being paused/stopped/destroyed but currently the Asynctask finishes after MainActivity has started and so the changes aren't visible.
I think your AsyncTask in nested in the MainActivity currently. Its better you put it in separate class. Whatever parameter is required by it pass it in its constructor. Let both your activity implement the same interface. Something like this
class MainActivity/SecondaryActivity implements DoSomethingListener {
void doSomething() {
}
}
Also pass your activity reference to AsyncTask in the constructor.
Finally onPostExecute since you have reference to either MainActivity or Secondary Activity. Call activity.doSomething.
I guess you want to update something in MainActivity based on the result of the AsyncTask called from SecondaryActivity. In that case, I'd suggest calling SecondaryActivity with startActivityForResult. Then in your onPostExecute, call setResult to set a flag or some data.
Finally, in MainActivity override onActivityResult to call doSomething when the request code corresponds to SecondaryActivity.
So your requirement is to have a single instance of MainActivity. Data in MainActivity may be updated from within MainActivity or from SecondaryActivity. In either case the data to be updated is obtained using an AsyncTask.
My Suggestion
Add the following to the MainActivity in manifest, (More about android:launchMode here.)
android:launchMode="singleInstance"
When you are done interacting with SecondaryActivity, do this,
Intent intent = new Intent(this, MainActivity.class);
intent.putExtra("DATA1", "your_data1");
intent.putExtra("DATA2", "your_data2");
startActivity(intent);
finish();
Then in your MainActivity,
#Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
String data1 = intent.getStringExtra("DATA1");
String data2 = intent.getStringExtra("DATA2");
}
Then call AsyncTask in MainActivity using data1 and data2.
NOTE: This is one way to approach your problem. There are other approaches such as startActivityForResult() depending on your requirement.
UPDATE
If you want to cancel your AsyncTask, call asyncTask.cancel(true);
However, this will not ensure your HttpRequest is aborted, as the cancel will take effect after the request has completed. The work-around for this is a bit hackish. After calling cancel(), contineously check if isCancelled() is true, then do httpRequest.abort() This will only be the fastest way to finish your async task. Need not necessarily mean the request gets aborted.
Try this in your UpsertTask class.
private Context mContext;
public UpsertTask(Context context){
mContext = context;
}
#Override
protected void onPostExecute(Object o) {
super.onPostExecute(o);
if(mContext instanceof MainActivity){
((MainActivity) mContext).doSomething();
}
else if(mContext instanceof SecondActivity){
((SecondActivity) mContext).doSomethingElse();
}
}
#Override
protected void onPostExecute(Boolean result){
super.onPostExecute(result);
# activity is defined as this.activity
if (activity != null) {
if(activity instanceof MainActivity) {
activity.doSomething();
} else if(activity instanceof SecondaryActivity) {
activity.doSomethingElse();
}
}
}
I think that would work. (if you understand your question correctly).
One way this could be done is by using an event bus. This is a way of passing messages/data between activities. You can post to the bus and then activities can listen for the message if they register.
EventBus class (seperate)
public class EventBus extends Bus {
private static final EventBus bus = new EventBus();
public static Bus getInstance() { return bus; }
private EventBus() {}
}
MainActivity class
...
#Override
protected void onResume() {
super.onResume();
EventBus.getInstance().register(this);
}
#Override
protected void onPause() {
super.onPause();
EventBus.getInstance().unregister(this);
}
#Subscribe
public void asyncDone(String message) {
foo(message)
}
AsyncTask class
...
#Override
protected void onPostExecute(Boolean result){
super.onPostExecute(result);
EventBus.getInstance().post("My data")
Thanks to #theheartbreakpug from Reddit for giving me this solution.

The AsyncTask fails when I rotate the device to landscape

I have an Activity in which I have a ProgressBar,an ImageView and a TextView,I update all three from an AsyncTask.All three get updated when the screen is completely in one orientation when the task is running,but the ImageView and TextView are not displayed and the ProgressBar freezes when the screen orientation changes from one orientation to another.
Adding the attach and detach methods to the task and using retainNonConfigurationInstance to return the task when the Activity and using getLastNonConfigurationInstance is destroyed has had no effect.I have also implement three methods for getting the various progress values from the AsyncTask to no effect.
MyActivity looks like this:
static final String TAG="ImageUpdateActivity";
TextView txt_currentOp;
ImageView img_currentOp;
ImageUpdatingTask task;
CustomProgressBar updatebar;
#SuppressWarnings("deprecation")
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_imageupdate);
txt_currentOp=(TextView)findViewById(R.id.txt_currentOp);
img_currentOp=(ImageView)findViewById(R.id.img_updateOp);
updatebar=(CustomProgressBar)findViewById(R.id.progressbar_update);
String filename=getIntent().getStringExtra("pathName");
task=(ImageUpdatingTask)getLastNonConfigurationInstance();
if(task!=null)
{
task.attach(this);
if(task.getStatus()==AsyncTask.Status.RUNNING)
{
Log.d(TAG, "The progress description is: "+task.getProgressDesc());
txt_currentOp.setText(task.getProgressDesc());
img_currentOp.setImageBitmap(task.getProgressBitmap());
updatebar.setProgress(task.getProgress());
}
}
else
{
task=new ImageUpdatingTask(this);
task.execute(filename);
}
}
public Object retainNonConfigurationInstance()
{
task.detach();
return task;
}
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) {
if(task.getStatus()!=AsyncTask.Status.FINISHED)
{
task.cancel(true);
task=null;
}
Intent i=new Intent(this,ImagePreviewActivity.class);
startActivity(i);
}
return super.onKeyDown(keyCode, event);
}
This is how I update the progress from my doInBackground method where
int progress=0;
Bitmap progressBitmap=null;
String progressDesc=null;
are global variables.
mOperation=BITMAP_TO_PIX;
progressDesc=getValueFromOperation(mOperation);
Pix pix=convertBitmapToPix(bitmap);
mOperation=CONVERT_TO_8;
progressDesc=getValueFromOperation(mOperation);
Pix pix2=convertOperation(pix);
temp=pix2.copy();
tempImg=convertPixToBitmap(temp);
progressBitmap=tempImg;
temp=null;
progress+=10;//60
publishProgress(tempImg);
And in my publishProgress I use:
#Override
protected void onProgressUpdate(Bitmap... values) {
// TODO Auto-generated method stub
super.onProgressUpdate(values);
int oldOperation=0,oldProgress=0;
if(mOperation!=oldOperation)
{
String progressText=getValueFromOperation(mOperation);
Log.d(TAG, progressText);
activity.txt_currentOp.setText(progressText);
oldOperation=mOperation;
}
if(oldProgress!=progress)
{
Log.d(TAG,"Update the progress: "+progress);
activity.updatebar.setProgress(progress);
oldProgress=progress;
}
activity.img_currentOp.setImageBitmap(values[0]);
}
And the Activity,is passed to the task using the constructor:
public ImageUpdatingTask(ImageUpdateActivity activity)
{
this.activity=activity;
}
These are the methods that take care of interaction between the AsyncTask and the Activity:
public void attach(ImageUpdateActivity activity)
{
this.activity=activity;
}
public void detach()
{
activity=null;
}
public int getProgress()
{
return progress;
}
public Bitmap getProgressBitmap()
{
return progressBitmap;
}
public String getProgressDesc()
{
return progressDesc;
}
When orientation changes your activity gets is destroyed and recreated. Fragments are hosted by an activity.
By default, Fragments are destroyed and recreated along with their parent Activitys when a configuration change occurs. Calling Fragments setRetainInstance(true) allows us to bypass this destroy-and-recreate cycle, signaling the system to retain the current instance of the fragment when the activity is recreated.
public void setRetainInstance (boolean retain)
Added in API level 11
Control whether a fragment instance is retained across Activity re-creation (such as from a configuration change). This can only be used with fragments not in the back stack. If set, the fragment lifecycle will be slightly different when an activity is recreated:
onDestroy() will not be called (but onDetach() still will be, because the fragment is being detached from its current activity).
onCreate(Bundle) will not be called since the fragment is not being re-created.
onAttach(Activity) and onActivityCreated(Bundle) will still be called.
You can check this blog for a workaround suggested . Uses interface as callback to the activity.
http://www.androiddesignpatterns.com/2013/04/retaining-objects-across-config-changes.html
and the source code for the same is available at
https://github.com/alexjlockwood/worker-fragments
Quoting from the blog
Flow of Events
When the MainActivity starts up for the first time, it instantiates and adds the TaskFragment to the Activity's state. The TaskFragment creates and executes an AsyncTask and proxies progress updates and results back to the MainActivity via the TaskCallbacks interface. When a configuration change occurs, the MainActivity goes through its normal lifecycle events, and once created the new Activity instance is passed to the onAttach(Activity) method, thus ensuring that the TaskFragment will always hold a reference to the currently displayed Activity instance even after the configuration change. The resulting design is both simple and reliable; the application framework will handle re-assigning Activity instances as they are torn down and recreated, and the TaskFragment and its AsyncTask never need to worry about the unpredictable occurrence of a configuration change.

android - How to show dialog after activity finishes

Say we have two activities, Activity1 and Activity2.
In Activity1's onClick() method, we have a call to start Activity 2 if a certain button is pressed:
Intent myIntent = new Intent(Activity1.this, Activity2.class);
Activity1.this.startActivity(myIntent);
After finish() is called in Activity2, and Activity1 is resumed, I need a dialog to show in Activity1, as soon as it is resumed.
Before, I simply called showDialog(id) in the same block of Activity1's onClick() method:
public void onClick(View v) {
if(v == addHole){
//...
Intent myIntent = new Intent(Activity1.this, Activity2.class);
Activity1.this.startActivity(myIntent);
showDialog(END_DIALOG_ID);
}
}
The issue is, after Activity1 resumes, the dialog corresponding to END_DIALOG_ID is not visible, but the screen is darkened and unresponsive (as if the dialog were present), until the back key is pressed.
I have tried putting the showDialog() call in Activity1's onResume() and onRestart() methods, but these both crash the program.
I have also tried creating an AsyncTask method in Activity2, with the showDialog() call in its onPostExecute(), but the dialog is not visible in Activity2.
private class ShowDialogTask extends AsyncTask<Void, Void, Integer> {
/** The system calls this to perform work in a worker thread and
* delivers it the parameters given to AsyncTask.execute() */
protected Integer doInBackground(Void... id) {
//do nothing
return END_DIALOG_ID;
}
/** The system calls this to perform work in the UI thread and delivers
* the result from doInBackground() */
protected void onPostExecute(Integer id) {
super.onPostExecute(id);
showDialog(id);
}
}
I am now trying to implement this by calling
Activity1.this.startActivityForResult(myIntent, END_DIALOG_REQUEST);
with corresponding setResult() and onActivityResult() methods from Activity1, but it seems that there should be a better practice for implementing this. All I need is to have a dialog shown upon Activity2 finishing.
Thanks for any help you can provide.
Like you suggest, call startActivityForResult when starting Activity2. Then, override onActivityResult and check for RESULT_OK, and show your dialog box then. That's a perfectly acceptable practice for doing what you're looking to do.
You can use onResume method (if u'r not looking anything from activity2)
#Override
public void onResume(){
super.onResume();
//do something
}
I have to return to the root activity - MainActivity, potentially closing several activities, and then show dialog. So I have chosen alternative way.
MyDialog {
public static synchronized void planToShowDialog(String info) {
if (info != null) {
saveInfoToPreferences(info);
}
}
public static synchronized void showDialogIfNecessary(Context context) {
String info = readInfoFromPreferences();
if (info != null) {
saveInfoToPreferences(null); // Show dialog once for given info.
new MyDialog(context, info).show();
}
}
private static String readInfoFromPreferences() {
//...
}
private static void saveInfoToPreferences(String info) {
//...
}
}
I call MyDialog.showDialogIfNecessary() from MainActivity.onPostResume() method.

Android Fragments. Retaining an AsyncTask during screen rotation or configuration change

I'm working on a Smartphone / Tablet app, using only one APK, and loading resources as is needed depending on screen size, the best design choice seemed to be using Fragments via the ACL.
This app has been working fine until now being only activity based. This is a mock class of how I handle AsyncTasks and ProgressDialogs in the Activities in order to have them work even when the screen is rotated or a configuration change occurs mid communication.
I will not change the manifest to avoid recreation of the Activity, there are many reasons why I dont want to do it, but mainly because the official docs say it isnt recomended and I've managed without it this far, so please dont recomend that route.
public class Login extends Activity {
static ProgressDialog pd;
AsyncTask<String, Void, Boolean> asyncLoginThread;
#Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.login);
//SETUP UI OBJECTS
restoreAsyncTask();
}
#Override
public Object onRetainNonConfigurationInstance() {
if (pd != null) pd.dismiss();
if (asyncLoginThread != null) return (asyncLoginThread);
return super.onRetainNonConfigurationInstance();
}
private void restoreAsyncTask();() {
pd = new ProgressDialog(Login.this);
if (getLastNonConfigurationInstance() != null) {
asyncLoginThread = (AsyncTask<String, Void, Boolean>) getLastNonConfigurationInstance();
if (asyncLoginThread != null) {
if (!(asyncLoginThread.getStatus()
.equals(AsyncTask.Status.FINISHED))) {
showProgressDialog();
}
}
}
}
public class LoginThread extends AsyncTask<String, Void, Boolean> {
#Override
protected Boolean doInBackground(String... args) {
try {
//Connect to WS, recieve a JSON/XML Response
//Place it somewhere I can use it.
} catch (Exception e) {
return true;
}
return true;
}
protected void onPostExecute(Boolean result) {
if (result) {
pd.dismiss();
//Handle the response. Either deny entry or launch new Login Succesful Activity
}
}
}
}
This code is working fine, I have around 10.000 users without complaint, so it seemed logical to just copy this logic into the new Fragment Based Design, but, of course, it isnt working.
Here is the LoginFragment:
public class LoginFragment extends Fragment {
FragmentActivity parentActivity;
static ProgressDialog pd;
AsyncTask<String, Void, Boolean> asyncLoginThread;
public interface OnLoginSuccessfulListener {
public void onLoginSuccessful(GlobalContainer globalContainer);
}
public void onSaveInstanceState(Bundle outState){
super.onSaveInstanceState(outState);
//Save some stuff for the UI State
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setRetainInstance(true);
//If I setRetainInstance(true), savedInstanceState is always null. Besides that, when loading UI State, a NPE is thrown when looking for UI Objects.
parentActivity = getActivity();
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
loginSuccessfulListener = (OnLoginSuccessfulListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString() + " must implement OnLoginSuccessfulListener");
}
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
RelativeLayout loginLayout = (RelativeLayout) inflater.inflate(R.layout.login, container, false);
return loginLayout;
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
//SETUP UI OBJECTS
if(savedInstanceState != null){
//Reload UI state. Im doing this properly, keeping the content of the UI objects, not the object it self to avoid memory leaks.
}
}
public class LoginThread extends AsyncTask<String, Void, Boolean> {
#Override
protected Boolean doInBackground(String... args) {
try {
//Connect to WS, recieve a JSON/XML Response
//Place it somewhere I can use it.
} catch (Exception e) {
return true;
}
return true;
}
protected void onPostExecute(Boolean result) {
if (result) {
pd.dismiss();
//Handle the response. Either deny entry or launch new Login Succesful Activity
}
}
}
}
}
I cant use onRetainNonConfigurationInstance() since it has to be called from the Activity and not the Fragment, same goes with getLastNonConfigurationInstance(). I've read some similar questions here with no answer.
I understand that it might require some working around to get this stuff organized properly in fragments, that being said, I would like to maintain the same basic design logic.
What would be the proper way to retain the AsyncTask during a configuration change, and if its still runing, show a progressDialog, taking into consideration that the AsyncTask is a inner class to the Fragment and it is the Fragment itself who invokes the AsyncTask.execute()?
Fragments can actually make this a lot easier. Just use the method Fragment.setRetainInstance(boolean) to have your fragment instance retained across configuration changes. Note that this is the recommended replacement for Activity.onRetainnonConfigurationInstance() in the docs.
If for some reason you really don't want to use a retained fragment, there are other approaches you can take. Note that each fragment has a unique identifier returned by Fragment.getId(). You can also find out if a fragment is being torn down for a config change through Fragment.getActivity().isChangingConfigurations(). So, at the point where you would decide to stop your AsyncTask (in onStop() or onDestroy() most likely), you could for example check if the configuration is changing and if so stick it in a static SparseArray under the fragment's identifier, and then in your onCreate() or onStart() look to see if you have an AsyncTask in the sparse array available.
I think you will enjoy my extremely comprehensive and working example detailed below.
Rotation works, and the dialog survives.
You can cancel the task and dialog by pressing the back button (if you want this behaviour).
It uses fragments.
The layout of the fragment underneath the activity changes properly when the device rotates.
There is a complete source code download and a precompiled APK so you can see if the behaviour is what you want.
Edit
As requested by Brad Larson I have reproduced most of the linked solution below. Also since I posted it I have been pointed to AsyncTaskLoader. I'm not sure it is totally applicable to the same problems, but you should check it out anyway.
Using AsyncTask with progress dialogs and device rotation.
A working solution!
I have finally got everything to work. My code has the following features:
A Fragment whose layout changes with orientation.
An AsyncTask in which you can do some work.
A DialogFragment which shows the progress of the task in a progress bar (not just an indeterminate spinner).
Rotation works without interrupting the task or dismissing the dialog.
The back button dismisses the dialog and cancels the task (you can alter this behaviour fairly easily though).
I don't think that combination of workingness can be found anywhere else.
The basic idea is as follows. There is a MainActivity class which contains a single fragment - MainFragment. MainFragment has different layouts for horizontal and vertical orientation, and setRetainInstance() is false so that the layout can change. This means that when the device orientation is changed, both MainActivity and MainFragment are completely destroyed and recreated.
Separately we have MyTask (extended from AsyncTask) which does all the work. We can't store it in MainFragment because that will be destroyed, and Google has deprecated using anything like setRetainNonInstanceConfiguration(). That isn't always available anyway and is an ugly hack at best. Instead we will store MyTask in another fragment, a DialogFragment called TaskFragment. This fragment will have setRetainInstance() set to true, so as the device rotates this fragment isn't destroyed, and MyTask is retained.
Finally we need to tell the TaskFragment who to inform when it is finished, and we do that using setTargetFragment(<the MainFragment>) when we create it. When the device is rotated and the MainFragment is destroyed and a new instance is created, we use the FragmentManager to find the dialog (based on its tag) and do setTargetFragment(<the new MainFragment>). That's pretty much it.
There were two other things I needed to do: first cancel the task when the dialog is dismissed, and second set the dismiss message to null, otherwise the dialog is weirdly dismissed when the device is rotated.
The code
I won't list the layouts, they are pretty obvious and you can find them in the project download below.
MainActivity
This is pretty straightforward. I added a callback into this activity so it knows when the task is finished, but you might not need that. Mainly I just wanted to show the fragment-activity callback mechanism because it's quite neat and you might not have seen it before.
public class MainActivity extends Activity implements MainFragment.Callbacks
{
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
#Override
public void onTaskFinished()
{
// Hooray. A toast to our success.
Toast.makeText(this, "Task finished!", Toast.LENGTH_LONG).show();
// NB: I'm going to blow your mind again: the "int duration" parameter of makeText *isn't*
// the duration in milliseconds. ANDROID Y U NO ENUM?
}
}
MainFragment
It's long but worth it!
public class MainFragment extends Fragment implements OnClickListener
{
// This code up to onDetach() is all to get easy callbacks to the Activity.
private Callbacks mCallbacks = sDummyCallbacks;
public interface Callbacks
{
public void onTaskFinished();
}
private static Callbacks sDummyCallbacks = new Callbacks()
{
public void onTaskFinished() { }
};
#Override
public void onAttach(Activity activity)
{
super.onAttach(activity);
if (!(activity instanceof Callbacks))
{
throw new IllegalStateException("Activity must implement fragment's callbacks.");
}
mCallbacks = (Callbacks) activity;
}
#Override
public void onDetach()
{
super.onDetach();
mCallbacks = sDummyCallbacks;
}
// Save a reference to the fragment manager. This is initialised in onCreate().
private FragmentManager mFM;
// Code to identify the fragment that is calling onActivityResult(). We don't really need
// this since we only have one fragment to deal with.
static final int TASK_FRAGMENT = 0;
// Tag so we can find the task fragment again, in another instance of this fragment after rotation.
static final String TASK_FRAGMENT_TAG = "task";
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
// At this point the fragment may have been recreated due to a rotation,
// and there may be a TaskFragment lying around. So see if we can find it.
mFM = getFragmentManager();
// Check to see if we have retained the worker fragment.
TaskFragment taskFragment = (TaskFragment)mFM.findFragmentByTag(TASK_FRAGMENT_TAG);
if (taskFragment != null)
{
// Update the target fragment so it goes to this fragment instead of the old one.
// This will also allow the GC to reclaim the old MainFragment, which the TaskFragment
// keeps a reference to. Note that I looked in the code and setTargetFragment() doesn't
// use weak references. To be sure you aren't leaking, you may wish to make your own
// setTargetFragment() which does.
taskFragment.setTargetFragment(this, TASK_FRAGMENT);
}
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
{
return inflater.inflate(R.layout.fragment_main, container, false);
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState)
{
super.onViewCreated(view, savedInstanceState);
// Callback for the "start task" button. I originally used the XML onClick()
// but it goes to the Activity instead.
view.findViewById(R.id.taskButton).setOnClickListener(this);
}
#Override
public void onClick(View v)
{
// We only have one click listener so we know it is the "Start Task" button.
// We will create a new TaskFragment.
TaskFragment taskFragment = new TaskFragment();
// And create a task for it to monitor. In this implementation the taskFragment
// executes the task, but you could change it so that it is started here.
taskFragment.setTask(new MyTask());
// And tell it to call onActivityResult() on this fragment.
taskFragment.setTargetFragment(this, TASK_FRAGMENT);
// Show the fragment.
// I'm not sure which of the following two lines is best to use but this one works well.
taskFragment.show(mFM, TASK_FRAGMENT_TAG);
// mFM.beginTransaction().add(taskFragment, TASK_FRAGMENT_TAG).commit();
}
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data)
{
if (requestCode == TASK_FRAGMENT && resultCode == Activity.RESULT_OK)
{
// Inform the activity.
mCallbacks.onTaskFinished();
}
}
TaskFragment
// This and the other inner class can be in separate files if you like.
// There's no reason they need to be inner classes other than keeping everything together.
public static class TaskFragment extends DialogFragment
{
// The task we are running.
MyTask mTask;
ProgressBar mProgressBar;
public void setTask(MyTask task)
{
mTask = task;
// Tell the AsyncTask to call updateProgress() and taskFinished() on this fragment.
mTask.setFragment(this);
}
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
// Retain this instance so it isn't destroyed when MainActivity and
// MainFragment change configuration.
setRetainInstance(true);
// Start the task! You could move this outside this activity if you want.
if (mTask != null)
mTask.execute();
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
{
View view = inflater.inflate(R.layout.fragment_task, container);
mProgressBar = (ProgressBar)view.findViewById(R.id.progressBar);
getDialog().setTitle("Progress Dialog");
// If you're doing a long task, you probably don't want people to cancel
// it just by tapping the screen!
getDialog().setCanceledOnTouchOutside(false);
return view;
}
// This is to work around what is apparently a bug. If you don't have it
// here the dialog will be dismissed on rotation, so tell it not to dismiss.
#Override
public void onDestroyView()
{
if (getDialog() != null && getRetainInstance())
getDialog().setDismissMessage(null);
super.onDestroyView();
}
// Also when we are dismissed we need to cancel the task.
#Override
public void onDismiss(DialogInterface dialog)
{
super.onDismiss(dialog);
// If true, the thread is interrupted immediately, which may do bad things.
// If false, it guarantees a result is never returned (onPostExecute() isn't called)
// but you have to repeatedly call isCancelled() in your doInBackground()
// function to check if it should exit. For some tasks that might not be feasible.
if (mTask != null) {
mTask.cancel(false);
}
// You don't really need this if you don't want.
if (getTargetFragment() != null)
getTargetFragment().onActivityResult(TASK_FRAGMENT, Activity.RESULT_CANCELED, null);
}
#Override
public void onResume()
{
super.onResume();
// This is a little hacky, but we will see if the task has finished while we weren't
// in this activity, and then we can dismiss ourselves.
if (mTask == null)
dismiss();
}
// This is called by the AsyncTask.
public void updateProgress(int percent)
{
mProgressBar.setProgress(percent);
}
// This is also called by the AsyncTask.
public void taskFinished()
{
// Make sure we check if it is resumed because we will crash if trying to dismiss the dialog
// after the user has switched to another app.
if (isResumed())
dismiss();
// If we aren't resumed, setting the task to null will allow us to dimiss ourselves in
// onResume().
mTask = null;
// Tell the fragment that we are done.
if (getTargetFragment() != null)
getTargetFragment().onActivityResult(TASK_FRAGMENT, Activity.RESULT_OK, null);
}
}
MyTask
// This is a fairly standard AsyncTask that does some dummy work.
public static class MyTask extends AsyncTask<Void, Void, Void>
{
TaskFragment mFragment;
int mProgress = 0;
void setFragment(TaskFragment fragment)
{
mFragment = fragment;
}
#Override
protected Void doInBackground(Void... params)
{
// Do some longish task. This should be a task that we don't really
// care about continuing
// if the user exits the app.
// Examples of these things:
// * Logging in to an app.
// * Downloading something for the user to view.
// * Calculating something for the user to view.
// Examples of where you should probably use a service instead:
// * Downloading files for the user to save (like the browser does).
// * Sending messages to people.
// * Uploading data to a server.
for (int i = 0; i < 10; i++)
{
// Check if this has been cancelled, e.g. when the dialog is dismissed.
if (isCancelled())
return null;
SystemClock.sleep(500);
mProgress = i * 10;
publishProgress();
}
return null;
}
#Override
protected void onProgressUpdate(Void... unused)
{
if (mFragment == null)
return;
mFragment.updateProgress(mProgress);
}
#Override
protected void onPostExecute(Void unused)
{
if (mFragment == null)
return;
mFragment.taskFinished();
}
}
}
Download the example project
Here is the source code and the APK. Sorry, the ADT insisted on adding the support library before it would let me make a project. I'm sure you can remove it.
I've recently posted an article describing how to handle configuration changes using retained Fragments. It solves the problem of retaining an AsyncTask across a rotation change nicely.
The TL;DR is to use host your AsyncTask inside a Fragment, call setRetainInstance(true) on the Fragment, and report the AsyncTask's progress/results back to it's Activity (or it's target Fragment, if you choose to use the approach described by #Timmmm) through the retained Fragment.
My first suggestion is to avoid inner AsyncTasks, you can read a question that I asked about this and the answers: Android: AsyncTask recommendations: private class or public class?
After that i started using non-inner and... now i see A LOT of benefits.
The second is, keep a reference of your running AsyncTask in the Application Class - http://developer.android.com/reference/android/app/Application.html
Everytime you start an AsyncTask, set it on the Application and when it finishes it set it to null.
When a fragment/activity starts you can check if any AsyncTask is running (by checking if it's null or not on the Application) and then set the reference inside to whatever you want (activity, fragment etc so you can do callbacks).
This will solve your problem:
If you only have 1 AsyncTask running at any determined time you can add a simple reference:
AsyncTask<?,?,?> asyncTask = null;
Else, have in the Aplication a HashMap with references to them.
The progress dialog can follow the exact same principle.
I came up with a method of using AsyncTaskLoaders for this. It's pretty easy to use and requires less overhead IMO..
Basically you create an AsyncTaskLoader like this:
public class MyAsyncTaskLoader extends AsyncTaskLoader {
Result mResult;
public HttpAsyncTaskLoader(Context context) {
super(context);
}
protected void onStartLoading() {
super.onStartLoading();
if (mResult != null) {
deliverResult(mResult);
}
if (takeContentChanged() || mResult == null) {
forceLoad();
}
}
#Override
public Result loadInBackground() {
SystemClock.sleep(500);
mResult = new Result();
return mResult;
}
}
Then in your activity that uses the above AsyncTaskLoader when a button is clicked:
public class MyActivityWithBackgroundWork extends FragmentActivity implements LoaderManager.LoaderCallbacks<Result> {
private String username,password;
#Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.mylayout);
//this is only used to reconnect to the loader if it already started
//before the orientation changed
Loader loader = getSupportLoaderManager().getLoader(0);
if (loader != null) {
getSupportLoaderManager().initLoader(0, null, this);
}
}
public void doBackgroundWorkOnClick(View button) {
//might want to disable the button while you are doing work
//to prevent user from pressing it again.
//Call resetLoader because calling initLoader will return
//the previous result if there was one and we may want to do new work
//each time
getSupportLoaderManager().resetLoader(0, null, this);
}
#Override
public Loader<Result> onCreateLoader(int i, Bundle bundle) {
//might want to start a progress bar
return new MyAsyncTaskLoader(this);
}
#Override
public void onLoadFinished(Loader<LoginResponse> loginLoader,
LoginResponse loginResponse)
{
//handle result
}
#Override
public void onLoaderReset(Loader<LoginResponse> responseAndJsonHolderLoader)
{
//remove references to previous loader resources
}
}
This seems to handle orientation changes fine and your background task will continue during the rotation.
A few things to note:
If in onCreate you reattach to the asynctaskloader you will get called back in onLoadFinished() with the previous result (even if you had already been told the request was complete). This is actually good behavior most of the time but sometimes it can be tricky to handle. While I imagine there are lots of ways to handle this what I did was I called loader.abandon() in onLoadFinished. Then I added check in onCreate to only reattach to the loader if it wasn't already abandoned. If you need the resulting data again you won't want to do that. In most cases you want the data.
I have more details on using this for http calls here
I created a very tiny open-source background task library which is heavily based on the Marshmallow AsyncTask but with additional functionality such as:
Automatically retaining tasks across configuration changes;
UI callback (listeners);
Doesn't restart or cancel task when the device rotates (like Loaders would do);
The library internally uses a Fragment without any user interface, which is retained accross configuration changes (setRetainInstance(true)).
You can find it on GitHub: https://github.com/NeoTech-Software/Android-Retainable-Tasks
Most basic example (version 0.2.0):
This example fully retains the task, using a very limited amount of code.
Task:
private class ExampleTask extends Task<Integer, String> {
public ExampleTask(String tag){
super(tag);
}
protected String doInBackground() {
for(int i = 0; i < 100; i++) {
if(isCancelled()){
break;
}
SystemClock.sleep(50);
publishProgress(i);
}
return "Result";
}
}
Activity:
public class Main extends TaskActivityCompat implements Task.Callback {
#Override
public void onClick(View view){
ExampleTask task = new ExampleTask("activity-unique-tag");
getTaskManager().execute(task, this);
}
#Override
public Task.Callback onPreAttach(Task<?, ?> task) {
//Restore the user-interface based on the tasks state
return this; //This Activity implements Task.Callback
}
#Override
public void onPreExecute(Task<?, ?> task) {
//Task started
}
#Override
public void onPostExecute(Task<?, ?> task) {
//Task finished
Toast.makeText(this, "Task finished", Toast.LENGTH_SHORT).show();
}
}
My approach is to use delegation design pattern, in general, we can isolate the actual business logic (read data from internet or database or whatsoever) from AsyncTask (the delegator) to BusinessDAO (the delegate), in your AysncTask.doInBackground() method, delegate the actual task to BusinessDAO, then implement a singleton process mechanism in BusinessDAO, so that multiple call to BusinessDAO.doSomething() will just trigger one actual task running each time and waiting for the task result. The idea is retain the delegate (i.e. BusinessDAO) during the configuration change, instead of the delegator (i.e. AsyncTask).
Create/Implement our own Application, the purpose is to create/initialize BusinessDAO here, so that our BusinessDAO's lifecycle is application scoped, not activity scoped, note that you need change AndroidManifest.xml to use MyApplication:
public class MyApplication extends android.app.Application {
private BusinessDAO businessDAO;
#Override
public void onCreate() {
super.onCreate();
businessDAO = new BusinessDAO();
}
pubilc BusinessDAO getBusinessDAO() {
return businessDAO;
}
}
Our existing Activity/Fragement are mostly unchanged, still implement AsyncTask as an inner class and involve AsyncTask.execute() from Activity/Fragement, the difference now is AsyncTask will delegate the actual task to BusinessDAO, so during the configuration change, a second AsyncTask will be initialized and executed, and call BusinessDAO.doSomething() second time, however, second call to BusinessDAO.doSomething() will not trigger a new running task, instead, waiting for current running task to finish:
public class LoginFragment extends Fragment {
... ...
public class LoginAsyncTask extends AsyncTask<String, Void, Boolean> {
// get a reference of BusinessDAO from application scope.
BusinessDAO businessDAO = ((MyApplication) getApplication()).getBusinessDAO();
#Override
protected Boolean doInBackground(String... args) {
businessDAO.doSomething();
return true;
}
protected void onPostExecute(Boolean result) {
//Handle task result and update UI stuff.
}
}
... ...
}
Inside BusinessDAO, implement singleton process mechanism, for example:
public class BusinessDAO {
ExecutorCompletionService<MyTask> completionExecutor = new ExecutorCompletionService<MyTask(Executors.newFixedThreadPool(1));
Future<MyTask> myFutureTask = null;
public void doSomething() {
if (myFutureTask == null) {
// nothing running at the moment, submit a new callable task to run.
MyTask myTask = new MyTask();
myFutureTask = completionExecutor.submit(myTask);
}
// Task already submitted and running, waiting for the running task to finish.
myFutureTask.get();
}
// If you've never used this before, Callable is similar with Runnable, with ability to return result and throw exception.
private class MyTask extends Callable<MyTask> {
public MyAsyncTask call() {
// do your job here.
return this;
}
}
}
I am not 100% sure if this will work, moreover, the sample code snippet should be considered as pseudocode. I am just trying to give you some clue from design level. Any feedback or suggestions are welcome and appreciated.
You could make the AsyncTask a static field. If you need a context, you should ship your application context. This will avoid memory leaks, otherwise you'd keep a reference to your entire activity.
If anyone finds their way to this thread then I found a clean approach was to run the Async task from an app.Service (started with START_STICKY) and then on recreate iterate over the running services to find out whether the service (and hence async task) is still running;
public boolean isServiceRunning(String serviceClassName) {
final ActivityManager activityManager = (ActivityManager) Application.getContext().getSystemService(Context.ACTIVITY_SERVICE);
final List<RunningServiceInfo> services = activityManager.getRunningServices(Integer.MAX_VALUE);
for (RunningServiceInfo runningServiceInfo : services) {
if (runningServiceInfo.service.getClassName().equals(serviceClassName)){
return true;
}
}
return false;
}
If it is, re-add the DialogFragment (or whatever) and if it is not ensure the dialog has been dismissed.
This is particularly pertinent if you are using the v4.support.* libraries since (at the time of writing) they have know issues with the setRetainInstance method and view paging. Furthermore, by not retaining the instance you can recreate your activity using a different set of resources (i.e. a different view layout for the new orientation)
I write samepl code to solve this problem
First step is make Application class:
public class TheApp extends Application {
private static TheApp sTheApp;
private HashMap<String, AsyncTask<?,?,?>> tasks = new HashMap<String, AsyncTask<?,?,?>>();
#Override
public void onCreate() {
super.onCreate();
sTheApp = this;
}
public static TheApp get() {
return sTheApp;
}
public void registerTask(String tag, AsyncTask<?,?,?> task) {
tasks.put(tag, task);
}
public void unregisterTask(String tag) {
tasks.remove(tag);
}
public AsyncTask<?,?,?> getTask(String tag) {
return tasks.get(tag);
}
}
In AndroidManifest.xml
<application
android:allowBackup="true"
android:icon="#drawable/ic_launcher"
android:label="#string/app_name"
android:theme="#style/AppTheme"
android:name="com.example.tasktest.TheApp">
Code in activity:
public class MainActivity extends Activity {
private Task1 mTask1;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTask1 = (Task1)TheApp.get().getTask("task1");
}
/*
* start task is not running jet
*/
public void handletask1(View v) {
if (mTask1 == null) {
mTask1 = new Task1();
TheApp.get().registerTask("task1", mTask1);
mTask1.execute();
} else
Toast.makeText(this, "Task is running...", Toast.LENGTH_SHORT).show();
}
/*
* cancel task if is not finished
*/
public void handelCancel(View v) {
if (mTask1 != null)
mTask1.cancel(false);
}
public class Task1 extends AsyncTask<Void, Void, Void>{
#Override
protected Void doInBackground(Void... params) {
try {
for(int i=0; i<120; i++) {
Thread.sleep(1000);
Log.i("tests", "loop=" + i);
if (this.isCancelled()) {
Log.e("tests", "tssk cancelled");
break;
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
#Override
protected void onCancelled(Void result) {
TheApp.get().unregisterTask("task1");
mTask1 = null;
}
#Override
protected void onPostExecute(Void result) {
TheApp.get().unregisterTask("task1");
mTask1 = null;
}
}
}
When activity orientation changes variable mTask is inited from app context. When task is finished variable is set to null and remove from memory.
For me its enough.
Have a look at below example , how to use retained fragment to retain background task:
public class NetworkRequestFragment extends Fragment {
// Declare some sort of interface that your AsyncTask will use to communicate with the Activity
public interface NetworkRequestListener {
void onRequestStarted();
void onRequestProgressUpdate(int progress);
void onRequestFinished(SomeObject result);
}
private NetworkTask mTask;
private NetworkRequestListener mListener;
private SomeObject mResult;
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
// Try to use the Activity as a listener
if (activity instanceof NetworkRequestListener) {
mListener = (NetworkRequestListener) activity;
} else {
// You can decide if you want to mandate that the Activity implements your callback interface
// in which case you should throw an exception if it doesn't:
throw new IllegalStateException("Parent activity must implement NetworkRequestListener");
// or you could just swallow it and allow a state where nobody is listening
}
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Retain this Fragment so that it will not be destroyed when an orientation
// change happens and we can keep our AsyncTask running
setRetainInstance(true);
}
/**
* The Activity can call this when it wants to start the task
*/
public void startTask(String url) {
mTask = new NetworkTask(url);
mTask.execute();
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// If the AsyncTask finished when we didn't have a listener we can
// deliver the result here
if ((mResult != null) && (mListener != null)) {
mListener.onRequestFinished(mResult);
mResult = null;
}
}
#Override
public void onDestroy() {
super.onDestroy();
// We still have to cancel the task in onDestroy because if the user exits the app or
// finishes the Activity, we don't want the task to keep running
// Since we are retaining the Fragment, onDestroy won't be called for an orientation change
// so this won't affect our ability to keep the task running when the user rotates the device
if ((mTask != null) && (mTask.getStatus == AsyncTask.Status.RUNNING)) {
mTask.cancel(true);
}
}
#Override
public void onDetach() {
super.onDetach();
// This is VERY important to avoid a memory leak (because mListener is really a reference to an Activity)
// When the orientation change occurs, onDetach will be called and since the Activity is being destroyed
// we don't want to keep any references to it
// When the Activity is being re-created, onAttach will be called and we will get our listener back
mListener = null;
}
private class NetworkTask extends AsyncTask<String, Integer, SomeObject> {
#Override
protected void onPreExecute() {
if (mListener != null) {
mListener.onRequestStarted();
}
}
#Override
protected SomeObject doInBackground(String... urls) {
// Make the network request
...
// Whenever we want to update our progress:
publishProgress(progress);
...
return result;
}
#Override
protected void onProgressUpdate(Integer... progress) {
if (mListener != null) {
mListener.onRequestProgressUpdate(progress[0]);
}
}
#Override
protected void onPostExecute(SomeObject result) {
if (mListener != null) {
mListener.onRequestFinished(result);
} else {
// If the task finishes while the orientation change is happening and while
// the Fragment is not attached to an Activity, our mListener might be null
// If you need to make sure that the result eventually gets to the Activity
// you could save the result here, then in onActivityCreated you can pass it back
// to the Activity
mResult = result;
}
}
}
}
Have a look here.
There is a solution based on Timmmm's solution.
But I improved it:
Now the solution is extendable - you only need to extend FragmentAbleToStartTask
You able to keep running several tasks at the same time.
And in my opinion it's as easy as startActivityForResult and receive result
You also can stop a running task and check whether particular task is running
Sorry for my English

How to force main Acivity to wait for subactivity in Android?

I am calling a subactivity from main activity. This subactivity should take few numbers from user (i'm using Edit text control to achieve this), save them to static variable in another class and terminate. I want main activity to wait for subactivity but both are just running simultaneously. Even doing sth like that doesn't help:
Thread t = new Thread(new Runnable(){
public void run(){
Log.v("==================", "run "+new Date());
startActivityForResult(new Intent(ctx,myCustomSubactivity.class),1);
} });
Log.v("==================", "calling run "+new Date());
t.start();
try {
t.join();
} catch (InterruptedException e) {Log.v("==================", "can't join");}
Log.v("==================", "back from activity "+new Date());
do you know how to force main activity to wait? Thread.wait() method is not supported in Android(program throws error).
May be I'm missing something but why don't just use startActivityForResult and onActivityResult mechanism? You could get result from you subactivity from intent it was resulted with.
Edit: BTW as far as I understand, if you will run Object.wait() from Activity code if will hold UI tread whitch can result in Application not responding error.
I agree with Nikolay this is definitely the android way to do this.
Start the subactivity with startActivityForResult in the sub activity use setResult to add an result code and an intent with all the numbers you need in the data bundle.
In your first activity overwrite onActivityResult and retrieve the numbers from the Intent.
If you use the static variable this seems easier in the first moment but it is very insecure and there are some cases this may not work. If your program is send to the background your activities will be saved but if the phone runs low on memory the system will close your program and after the user resumes it everything looks like the moment the user left it but the static variables will be recreated to their initialization value.
Try to get used to the way the android activity lifecycle works. Using this approach will result in fewer used memory and a much better user experience.
Check out the Notepad example, it covers exactly this situation. And as others have said, the Android way is to have your first activity start up your second activity (not sub-activity!) and asynchronously listen for a response (not pause or wait, no need for joining, etc.).
Well... you can do it like this (btw, there's not straight forward way):
Have a singleton class, let's call it Monitor:
public class Singleton
{
private Singleton() { }
private static Singleton instance = new Singleton();
public static Singleton getInstance() {
return instance;
}
}
public class ParentActivity extends Activity
{
private void startAndWait()
{
Intent i = new Intent();
// initialize i
startActivityForResult(i);
Singleton si = Singleton.getInstance();
synchronized(si)
{
si.wait();
}
//do remaining work
}
}
public class ChildActivity extends Activity
{
protected void onCreate(Bundle savedInstance)
{
//do all the work
Singleton si = Singleton.getInstance();
synchronized(si)
{
si.notify();
}
}
}
I'm not here to judge if it's a good pattern or not but if you really need an activity to wait for a sub-activity, you can try this approach:
define an object (lock) over which the two activities get synchronized; this can (should) also work as the object to exchange data between those two activities and thus should be defined as static
in parent activity, start an async task (as the UI main thread cannot be in waiting state)
in the async task, start your sub-activity
the async task waits on the lock till it gets notified
the sub-activity does whatever it needs and notifies the waiting thread when it finishes
I did a similar thing in my app and IMHO had a good reason for this (not to bother a user with login screen upon app start or resume, the app tries to re-use credentials stored in a secured place and only in case it fails, it shows this login screen. So yes, basically any activity in my app can get "paused" and waits till the user provides correct credentials in the login activity upon which the login screen finishes and the app continues exactly where it got paused (in the parent activity).
In the code it would be something like this:
ParentActivity:
public class ParentActivity extends Activity {
private static final String TAG = ParentActivity.class.getSimpleName();
public static class Lock {
private boolean condition;
public boolean conditionMet() {
return condition;
}
public void setCondition(boolean condition) {
this.condition = condition;
}
}
public static final Lock LOCK = new Lock();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.parent_layout);
// do whatever logic you need and anytime you need to stat sub-activity
new ParentAsyncTask().execute(false);
}
private class ParentAsyncTask extends AsyncTask<Boolean, Void, Boolean> {
#Override
protected Boolean doInBackground(Boolean... params) {
// do what you need and if you decide to stop this activity and wait for the sub-activity, do this
Intent i = new Intent(ParentActivity.this, ChildActivity.class);
startActivity(i);
synchronized (LOCK) {
while (!LOCK.conditionMet()) {
try {
LOCK.wait();
} catch (InterruptedException e) {
Log.e(TAG, "Exception when waiting for condition", e);
return false;
}
}
}
return true;
}
}
}
ChildActivity:
public class ChildActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.child_layout);
// do whatever you need in child activity, but once you want to finish, do this and continue in parent activity
synchronized (ParentActivity.LOCK) {
ParentActivity.LOCK.setCondition(true);
ParentActivity.LOCK.notifyAll();
}
finish();
// if you need the stuff to run in background, use AsyncTask again, just please note that you need to
// start the async task using executeOnExecutor method as you need more executors (one is already occupied), like this:
// new ChildAsyncTask().executeOnExecutor(ChildAsyncTask.THREAD_POOL_EXECUTOR, false);
}
}

Categories

Resources