OnSaveInstanceState/ RestCalls - android

I am relatively new to Android development, and I have a question about onSaveInstanceState(). I am currently working on a login Activity for an app. To check to see if the user can login to their account, I perform a rest call to a server and, based on the response-code, see if I should grant access to the user. The root of my question is based on the fact that I am trying to avoid passing the Activity's Context to my rest-call class. To do this, I create a boolean field in my login Activity representing whether or not the rest-call was successful and a runnable that updates said boolean that I pass to the rest-call class. I know this goes against the idea of an AsyncTask, but I can't find any alternative to simply putting up a dialog box telling the user to wait while this happens. My questions are below.
1) If I use savedInstanceState() in the onCreate method, how do I instantiate this boolean field for the first time barring null checking an Object boolean? What I mean by this is that after the Activity is destroyed for whatever reason (such as orientation change, etc...) I will use the boolean value stored in my overriden onSaveInstanceState method; however, when it is created for the first time, it has no reference to a boolean value so it has to create one.
2) Does this Runnable even help? I did it so that I wouldn't have to pass the context, but if the Activity is going to be deleted before the RestCall(AsyncTask) is complete, does it really matter whether you pass the context or a Runnable affecting a field of the Activity? The more I think about this, the more I believe it is not going to make much of a difference as it will still result in it pointing to a non-existent object. I am trying to avoid using a Singleton design as I have gathered it is not optimal, but because of the potential lag in time with an AsyncTask, I am beginning to think that it may not be avoidable.
I know onSaveInstanceState() is a topic that has been brought up a lot on StackOverflow, however, I could not find an answer to these questions. I apologize if there has already been a thread for this, but any help or guidance on this would be greatly appreciated! Thank You!
Login Activities' setup:
public class LoginActivity extends Activity implements View.OnClickListener {
private EditText username_et;
private EditText password_et;
private Button login_b;
private boolean login_success = true;
private Runnable run;
/**
* Instances created when app starts
*/
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.view_login);
// login_success = false;
login_success = savedInstanceState.getBoolean("login_success");
username_et = (EditText) findViewById(R.id.username_text);
username_et.setOnClickListener(LoginActivity.this);
password_et = (EditText) findViewById(R.id.password_text);
password_et.setOnClickListener(LoginActivity.this);
login_b = (Button) findViewById(R.id.login_button);
login_b.setOnClickListener(LoginActivity.this);
run = new Runnable() {
#Override
public void run() {
login_success = true;
}
};
}
#Override
public void onSaveInstanceState(Bundle savedInstanceState){
super.onSaveInstanceState(savedInstanceState);
savedInstanceState.putBoolean("login_success", login_success);
}

Congratulations. You just discovered Android's dirty little secret.
AsyncTask has an inherent design flaw. It doesn't deal well with configuration changes that happen during background task execution because of exactly the problem you mentioned. It needs to hold a reference to the activity, but there's no guarantee that the reference will still be valid by the time the background task completes.
Here are two ways to overcome this problem:
I refer you to Alex Lockwood's excellent blog post on using hidden fragments with setRetainInstance(true) to span activity destruction and recreation. This is a more involved solution than the next one, but this solution has the advantage that you can still report progress with callbacks. If you were intending to call publishProgress() in your AsyncTask, then this is the method you should use.
Use a Loader. Loaders were designed around database data retrieval in the background, but the fact is that they can also be used to handle remote server access in the background as well. I use a Loader for the majority of my remote server tasks.
Here's an example:
public static class ResetPasswordLoader extends AsyncTaskLoader<Pair<CharSequence, Exception>> {
private static final String TAG = "ResetPasswordLoader ";
private String mEmail;
public ResetPasswordLoader(Context context, String email) {
super(context);
mEmail = email;
// set the content-changed flag
onContentChanged();
}
#Override
protected void onStartLoading() {
// only start the load if the content-changed flag is set
// takeContentChanged() returns the value of the flag before it is cleared
if (takeContentChanged()) {
forceLoad();
}
}
#Override
public Pair<CharSequence, Exception> loadInBackground() {
CharSequence result = null;
Exception exc = null;
try {
result = Service.getInstance().resetPassword(mEmail);
} catch (RemoteServiceException e) {
exc = e;
Log.e(TAG, "loadInBackground(), email = " + mEmail, e);
}
return new Pair<>(result, exc);
}
}
Also, in my onLoadFinished() override I make sure to call loaderManager.destroyLoader() on the loader's id.
Again, Alex Lockwood's blog has some great articles on loaders as well.
For the UI, something I do frequently is put up a indeterminate progress bar over the UI upon calling loaderManager.initLoader(). I also set a boolean like mProgressShown. This boolean gets saved in onSaveInstanceState, so when the activity/fragment is created again, I restore the boolean value which tells me to show the progress bar immediately. Some time later onLoadFinished will be called and I clear mProgressShown and hide the progress bar.

Related

What process is the best way to send on API and save SQLiteDatabase?

I'm new on Android and working an big app which has sending data to API and saving it on SQlite. All of this process is on one class file . But it leaves me on an error. Sometimes the device hanged. other scenario is the data is incomplete . I have read about Intent Service and Services and I want to learn about the two, but I'm wondering how to get all of my data from UI and put it on services. May I know How?
It depends on the nature of the application. If this should happen in response to a user input...you could well use an AsyncTask. Otherwise, a background service could also do the job.
What you should NEVER do is run a network operation and/or database access on the main UI thread.
Services can receive data via intents. The way to send these intents depend on the type of service (Started, Bound or both). There are plenty of resources out there you can read...here's one from Android documentation...
https://developer.android.com/guide/components/services
An Example of an AsyncTask
The example below shows an implementation of AsyncTask that fetches a user's details from a network resource...
public class FetchUserTask extends AsyncTask<String,Void, UserDTO> {
private FetchUserTaskListener listener;
#Override
protected UserDTO doInBackground(String...params){
if(params == null || params.length == 0)
return null;
String userID = params[0];
UserDataProvider provider = new UserDataProvider(userID);
try {
return provider.get(userID);
}
catch(Exception ex){
//log the error
return null;
}
}
#Override
protected void onPostExecute(UserDTO user){
if(listener != null)
listener.onCompleted(user);
}
public void setListener(FetchUserTaskListener listener){
this.listener = listener;
}
public interface FetchUserTaskListener{
void onCompleted(boolean success);
}
}
How'd you use this AsyncTask?
For example, in an Activity, you would use it as below...
public class UserDetailsActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
//instantiate activity...
super.onCreate(savedInstanceState);
setContentView(R.layout.whatever_layout);
fetchUser(userId);
}
private void fetchUser(String userID){
FetchUserTask task = new FetchUserTask();
task.setListener(new FetchUserTaskListener<UserDTO>() {
#Override
public void onCompleted(UserDTO user) {
//CAUTION: make sure the activity hasn't been stopped before
//accessing any UI elements and/or context
}
}
task.execute(userID);
}
}
Note
You can (and will need to) make the example(s) above a bit more sophisticated. For example you can have the FetchUserTaskListener's onCompleted method return also an error message if an error occurred.
You will also need to check whether the activity has been paused or stopped before you access any context-bound data otherwise you might get an ILlegalStateException.
Make use of SQLiteOpenHelper class and it has methods to be overridden in your own class by extending SQLiteOpenHelper. Create Add, Update, Delete, Get methods as per your requirement in this class and keep this class as Singleton pattern. User Asynctasks to call those methids and you are done.
Hope that helps you visualise things in better way.

How do I handle AsyncTask incompletion in fragments due to screen rotation

I have an App which opens few fragments (actually 3) through a Fragment container (on Tab) and the first fragment uses AsyncTask to download feeds into the app.
The problem occurs when there is screen rotation - the application crashes.
I temporarily handle this problem by loading the data from PostExecute into private static variable but this problem can still occur when the user first enters the app.
This seems a very common or rampant problem but I've not been able to find outright solution here.
I do understand that this is because of configuration changes due to the screen rotation as the AsyncTask is running on a parallel thread to the UI thread.
I do refrain from using the Java threads/executor/executor service at this stage since there seem a ready-made tool in AsyncTask
I have been unable to interrupt the process in order to restart the activity or the fragment successfully and discard the initial subsequent AsyncTask calls..
In other words, how do I destroy the AsyncTask within a Fragment when there has been continuous configuration changes like Screen Rotation. Your help would be greatly appreciated.
public class MolyListFragment extends Fragment {
public final static String MOLY_ARTICLE_DATA = "No Details";
private static final String TAG = "MOLY";
private static ArrayList<MolyPg> mMolyPgs = null;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (mMolyPgs == null) {
new MolyLoadTask().execute(getResources().getString(R.string.mly_feed));
}
else
displayMlicles (mMolyPgs); //this displays the arrayList out of static mMolyPgs
}
private class MolyLoadTask extends AsyncTask<String, Void, ArrayList<MolyPg>> {
#Override
protected ArrayList<MolyPg> doInBackground(String... urls) {
String myUrl = urls[0];
ArrayList<MolyPg> myMlPgs = null;
try {
myMlPgs = loadXmlFromNetwork(myUrl);
} catch (MalformedURLException e) {
Log.d(TAG, "MalformedURLException", e);
} catch (IOException e) {
getResources().getString(R.string.connection_error);
} catch (XmlPullParserException e) {
getResources().getString(R.string.xml_error);
}
return myMlPgs;
}
#Override
protected void onPostExecute(ArrayList<MolyPg> result) {
super.onPostExecute(result);
Do some acrobatics here
}
........
}
}
There are several solutions you could go with, but they all have one common theme...you shouldn't have long-running operations like that so tightly bound directly to your user interface classes.
If I were to make a suggestion, there are two possibilites that often work well:
Implement your AsyncTask inside of a custom AsyncTaskLoader that you can connect to each time your Fragment is started up again to retrieve the data or wait for it to come back. There are a number of callback methods you need to implement, so you'll want to reference the documentation (link). You may also want to look at the source for CursorLoader to get another example of an AsyncTaskLoader implementation.
Create a "data" Fragment in your application whose sole purpose is to manage your background tasks and provide the information when available. This fragment will have no UI component (i.e. don't override onCreateView()) and you will call setRetainInstance() so the FragmentManager keeps only one around. When you add the fragment with a constant tag value, all other components in your UI can easily find it via the FragmentManager to retrieve the data. For more information about this, see the docs section Adding a fragment without a UI.
If you're using fragments you should be using Loaders instead. Check the docs:
http://developer.android.com/reference/android/app/LoaderManager.html
The fragment will find any previously created loaders and "reconnect", so you can get the results from it.
There are ways to "reconnect" AsyncTasks as well, but you're better off just using Loaders.
You can use setRetainInstance(), however as docs say this can only be used with fragments not in the back stack

Should accessing SharedPreferences be done off the UI Thread?

With the release of Gingerbread, I have been experimenting with some of the new API's, one of them being StrictMode.
I noticed that one of the warnings is for getSharedPreferences().
This is the warning:
StrictMode policy violation; ~duration=1949 ms: android.os.StrictMode$StrictModeDiskReadViolation: policy=23 violation=2
and it's being given for a getSharedPreferences() call being made on the UI thread.
Should SharedPreferences access and changes really be made off the UI thread?
I'm glad you're already playing with it!
Some things to note: (in lazy bullet form)
if this is the worst of your problems, your app's probably in a good spot. :) Writes are generally slower than reads, though, so be sure you're using SharedPreferenced$Editor.apply() instead of commit(). apply() is new in GB and async (but always safe, careful of lifecycle transitions). You can use reflection to conditionally call apply() on GB+ and commit() on Froyo or below. I'll be doing a blogpost with sample code of how to do this.
Regarding loading, though...
once loaded, SharedPreferences are singletons and cached process-wide. so you want to get it loaded as early as possible so you have it in memory before you need it. (assuming it's small, as it should be if you're using SharedPreferences, a simple XML file...) You don't want to fault it in the future time some user clicks a button.
but whenever you call context.getSharedPreferences(...), the backing XML file is stat'd to see if it's changed, so you'll want to avoid those stats during UI events anyway. A stat should normally be fast (and often cached), but yaffs doesn't have much in the way of concurrency (and a lot of Android devices run on yaffs... Droid, Nexus One, etc.) so if you avoid disk, you avoid getting stuck behind other in-flight or pending disk operations.
so you'll probably want to load the SharedPreferences during your onCreate() and re-use the same instance, avoiding the stat.
but if you don't need your preferences anyway during onCreate(), that loading time is stalling your app's start-up unnecessarily, so it's generally better to have something like a FutureTask<SharedPreferences> subclass that kicks off a new thread to .set() the FutureTask subclasses's value. Then just lookup your FutureTask<SharedPreferences>'s member whenever you need it and .get() it. I plan to make this free behind the scenes in Honeycomb, transparently. I'll try to release some sample code which
shows best practices in this area.
Check the Android Developers blog for upcoming posts on StrictMode-related subjects in the coming week(s).
Accessing the shared preferences can take quite some time because they are read from flash storage. Do you read a lot? Maybe you could use a different format then, e.g. a SQLite database.
But don't fix everything you find using StrictMode. Or to quote the documentation:
But don't feel compelled to fix everything that StrictMode finds. In particular, many cases of disk access are often necessary during the normal activity lifecycle. Use StrictMode to find things you did by accident. Network requests on the UI thread are almost always a problem, though.
One subtlety about Brad's answer: even if you load the SharedPreferences in onCreate(), you should probably still read values on the background thread because getString() etc. block until reading the shared file preference in finishes (on a background thread):
public String getString(String key, String defValue) {
synchronized (this) {
awaitLoadedLocked();
String v = (String)mMap.get(key);
return v != null ? v : defValue;
}
}
edit() also blocks in the same way, although apply() appears to be safe on the foreground thread.
(BTW sorry to put this down here. I would have put this as a comment to Brad's answer, but I just joined and don't have enough reputation to do so.)
I know this is an old question but I want to share my approach. I had long reading times and used a combination of shared preferences and the global application class:
ApplicationClass:
public class ApplicationClass extends Application {
private LocalPreference.Filter filter;
public LocalPreference.Filter getFilter() {
return filter;
}
public void setFilter(LocalPreference.Filter filter) {
this.filter = filter;
}
}
LocalPreference:
public class LocalPreference {
public static void saveLocalPreferences(Activity activity, int maxDistance, int minAge,
int maxAge, boolean showMale, boolean showFemale) {
Filter filter = new Filter();
filter.setMaxDistance(maxDistance);
filter.setMinAge(minAge);
filter.setMaxAge(maxAge);
filter.setShowMale(showMale);
filter.setShowFemale(showFemale);
BabysitApplication babysitApplication = (BabysitApplication) activity.getApplication();
babysitApplication.setFilter(filter);
SecurePreferences securePreferences = new SecurePreferences(activity.getApplicationContext());
securePreferences.edit().putInt(Preference.FILER_MAX_DISTANCE.toString(), maxDistance).apply();
securePreferences.edit().putInt(Preference.FILER_MIN_AGE.toString(), minAge).apply();
securePreferences.edit().putInt(Preference.FILER_MAX_AGE.toString(), maxAge).apply();
securePreferences.edit().putBoolean(Preference.FILER_SHOW_MALE.toString(), showMale).apply();
securePreferences.edit().putBoolean(Preference.FILER_SHOW_FEMALE.toString(), showFemale).apply();
}
public static Filter getLocalPreferences(Activity activity) {
BabysitApplication babysitApplication = (BabysitApplication) activity.getApplication();
Filter applicationFilter = babysitApplication.getFilter();
if (applicationFilter != null) {
return applicationFilter;
} else {
Filter filter = new Filter();
SecurePreferences securePreferences = new SecurePreferences(activity.getApplicationContext());
filter.setMaxDistance(securePreferences.getInt(Preference.FILER_MAX_DISTANCE.toString(), 20));
filter.setMinAge(securePreferences.getInt(Preference.FILER_MIN_AGE.toString(), 15));
filter.setMaxAge(securePreferences.getInt(Preference.FILER_MAX_AGE.toString(), 50));
filter.setShowMale(securePreferences.getBoolean(Preference.FILER_SHOW_MALE.toString(), true));
filter.setShowFemale(securePreferences.getBoolean(Preference.FILER_SHOW_FEMALE.toString(), true));
babysitApplication.setFilter(filter);
return filter;
}
}
public static class Filter {
private int maxDistance;
private int minAge;
private int maxAge;
private boolean showMale;
private boolean showFemale;
public int getMaxDistance() {
return maxDistance;
}
public void setMaxDistance(int maxDistance) {
this.maxDistance = maxDistance;
}
public int getMinAge() {
return minAge;
}
public void setMinAge(int minAge) {
this.minAge = minAge;
}
public int getMaxAge() {
return maxAge;
}
public void setMaxAge(int maxAge) {
this.maxAge = maxAge;
}
public boolean isShowMale() {
return showMale;
}
public void setShowMale(boolean showMale) {
this.showMale = showMale;
}
public boolean isShowFemale() {
return showFemale;
}
public void setShowFemale(boolean showFemale) {
this.showFemale = showFemale;
}
}
}
MainActivity (activity that get called first in your application):
LocalPreference.getLocalPreferences(this);
Steps explained:
The main activity calls getLocalPreferences(this) -> this will read your preferences, set the filter object in your application class and returns it.
When you call the getLocalPreferences() function again somewhere else in the application it first checks if it's not available in the application class which is a lot faster.
NOTE: ALWAYS check if an application wide variable is different from NULL, reason -> http://www.developerphil.com/dont-store-data-in-the-application-object/
The application object will not stay in memory forever, it will get killed. Contrary to popular belief, the app won’t be restarted from scratch. Android will create a new Application object and start the activity where the user was before to give the illusion that the application was never killed in the first place.
If I didn't check on null I would allow a nullpointer to be thrown when calling for example getMaxDistance() on the filter object (if the application object was swiped from the memory by Android)
SharedPreferences class does some reads & writes within XML files on disk, so just like any other IO operation it could be blocking. The amount of data currently stored in SharedPreferences affects the time and resource consumed by the API calls. For minimal amounts of data it's a matter of a few milliseconds (sometimes even less than a millisecond) to get/put data. But from the point of view of an expert it could be important to improve the performance by doing the API calls in background. For an asynchronous SharedPreferences I suggest checking out the Datum library.
i do not see any reason to read them from a background thread. but to write it i would. at startup time the shared preference file is loaded into memory so its fast to access, but to write things can take a bit of time so we can use apply the write async. that should be the difference between commit and apply methods of shared prefs.

Background task, progress dialog, orientation change - is there any 100% working solution?

I download some data from internet in background thread (I use AsyncTask) and display a progress dialog while downloading. Orientation changes, Activity is restarted and then my AsyncTask is completed - I want to dismiss the progess dialog and start a new Activity. But calling dismissDialog sometimes throws an exception (probably because the Activity was destroyed and new Activity hasn't been started yet).
What is the best way to handle this kind of problem (updating UI from background thread that works even if user changes orientation)? Did someone from Google provide some "official solution"?
Step #1: Make your AsyncTask a static nested class, or an entirely separate class, just not an inner (non-static nested) class.
Step #2: Have the AsyncTask hold onto the Activity via a data member, set via the constructor and a setter.
Step #3: When creating the AsyncTask, supply the current Activity to the constructor.
Step #4: In onRetainNonConfigurationInstance(), return the AsyncTask, after detaching it from the original, now-going-away activity.
Step #5: In onCreate(), if getLastNonConfigurationInstance() is not null, cast it to your AsyncTask class and call your setter to associate your new activity with the task.
Step #6: Do not refer to the activity data member from doInBackground().
If you follow the above recipe, it will all work. onProgressUpdate() and onPostExecute() are suspended between the start of onRetainNonConfigurationInstance() and the end of the subsequent onCreate().
Here is a sample project demonstrating the technique.
Another approach is to ditch the AsyncTask and move your work into an IntentService. This is particularly useful if the work to be done may be long and should go on regardless of what the user does in terms of activities (e.g., downloading a large file). You can use an ordered broadcast Intent to either have the activity respond to the work being done (if it is still in the foreground) or raise a Notification to let the user know if the work has been done. Here is a blog post with more on this pattern.
The accepted answer was very helpful, but it doesn't have a progress dialog.
Fortunately for you, reader, I have created an extremely comprehensive and working example of an AsyncTask with a progress dialog!
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.
I've toiled for a week to find a solution to this dilemma without resorting to editing the manifest file. The assumptions for this solution are:
You always need to use a progress dialog
Only one task is performed at a time
You need the task to persist when the phone is rotated and the progress dialog to be automatically dismisses.
Implementation
You will need to copy the two files found at the bottom of this post into your workspace. Just make sure that:
All your Activitys should extend BaseActivity
In onCreate(), super.onCreate() should be called after you initialize any members that need to be accessed by your ASyncTasks. Also, override getContentViewId() to provide the form layout id.
Override onCreateDialog() like usual to create dialogs managed by the activity.
See code below for a sample static inner class to make your AsyncTasks. You can store your result in mResult to access later.
final static class MyTask extends SuperAsyncTask<Void, Void, Void> {
public OpenDatabaseTask(BaseActivity activity) {
super(activity, MY_DIALOG_ID); // change your dialog ID here...
// and your dialog will be managed automatically!
}
#Override
protected Void doInBackground(Void... params) {
// your task code
return null;
}
#Override
public boolean onAfterExecute() {
// your after execute code
}
}
And finally, to launch your new task:
mCurrentTask = new MyTask(this);
((MyTask) mCurrentTask).execute();
That's it! I hope this robust solution will help someone.
BaseActivity.java (organize imports yourself)
protected abstract int getContentViewId();
public abstract class BaseActivity extends Activity {
protected SuperAsyncTask<?, ?, ?> mCurrentTask;
public HashMap<Integer, Boolean> mDialogMap = new HashMap<Integer, Boolean>();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(getContentViewId());
mCurrentTask = (SuperAsyncTask<?, ?, ?>) getLastNonConfigurationInstance();
if (mCurrentTask != null) {
mCurrentTask.attach(this);
if (mDialogMap.get((Integer) mCurrentTask.dialogId) != null
&& mDialogMap.get((Integer) mCurrentTask.dialogId)) {
mCurrentTask.postExecution();
}
}
}
#Override
protected void onPrepareDialog(int id, Dialog dialog) {
super.onPrepareDialog(id, dialog);
mDialogMap.put(id, true);
}
#Override
public Object onRetainNonConfigurationInstance() {
if (mCurrentTask != null) {
mCurrentTask.detach();
if (mDialogMap.get((Integer) mCurrentTask.dialogId) != null
&& mDialogMap.get((Integer) mCurrentTask.dialogId)) {
return mCurrentTask;
}
}
return super.onRetainNonConfigurationInstance();
}
public void cleanupTask() {
if (mCurrentTask != null) {
mCurrentTask = null;
System.gc();
}
}
}
SuperAsyncTask.java
public abstract class SuperAsyncTask<Params, Progress, Result> extends AsyncTask<Params, Progress, Result> {
protected BaseActivity mActivity = null;
protected Result mResult;
public int dialogId = -1;
protected abstract void onAfterExecute();
public SuperAsyncTask(BaseActivity activity, int dialogId) {
super();
this.dialogId = dialogId;
attach(activity);
}
#Override
protected void onPreExecute() {
super.onPreExecute();
mActivity.showDialog(dialogId); // go polymorphism!
}
protected void onPostExecute(Result result) {
super.onPostExecute(result);
mResult = result;
if (mActivity != null &&
mActivity.mDialogMap.get((Integer) dialogId) != null
&& mActivity.mDialogMap.get((Integer) dialogId)) {
postExecution();
}
};
public void attach(BaseActivity activity) {
this.mActivity = activity;
}
public void detach() {
this.mActivity = null;
}
public synchronized boolean postExecution() {
Boolean dialogExists = mActivity.mDialogMap.get((Integer) dialogId);
if (dialogExists != null || dialogExists) {
onAfterExecute();
cleanUp();
}
public boolean cleanUp() {
mActivity.removeDialog(dialogId);
mActivity.mDialogMap.remove((Integer) dialogId);
mActivity.cleanupTask();
detach();
return true;
}
}
Did someone from Google provide some "official solution"?
Yes.
The solution is more of an application architecture proposal rather that just some code.
They proposed 3 design patterns that allows an application to work in-sync with a server, regardless of the application state (it will work even if the user finishes the app, the user changes screen, the app gets terminated, every other possible state where a background data operation could be interrumpted, this covers it)
The proposal is explained in the Android REST client applications speech during Google I/O 2010 by Virgil Dobjanschi. It is 1 hour long, but it is extremely worth watching.
The basis of it is abstracting network operations to a Service that works independently to any Activity in the application. If you're working with databases, the use of ContentResolver and Cursor would give you an out-of-the-box Observer pattern that is convenient to update UI without any aditional logic, once you updated your local database with the fetched remote data. Any other after-operation code would be run via a callback passed to the Service (I use a ResultReceiver subclass for this).
Anyway, my explanation is actually pretty vague, you should definititely watch the speech.
While Mark's (CommonsWare) answer does indeed work for orientation changes, it fails if the Activity is destroyed directly (like in the case of a phone call).
You can handle the orientation changes AND the rare destroyed Activity events by using an Application object to reference your ASyncTask.
There's an excellent explanation of the problem and the solution here:
Credit goes completely to Ryan for figuring this one out.
After 4 years Google solved the problem just calling setRetainInstance(true) in Activity onCreate. It will preserve your activity instance during device rotation. I have also a simple solution for older Android.
you should call all activity actions using activity handler. So if you are in some thread you should create a Runnable and posted using Activitie's Handler. Otherwise your app will crash sometimes with fatal exception.
This is my solution: https://github.com/Gotchamoh/Android-AsyncTask-ProgressDialog
Basically the steps are:
I use onSaveInstanceState to save the task if it is still
processing.
In onCreate I get the task if it was saved.
In onPause I discard the ProgressDialog if it is shown.
In onResume I show the ProgressDialog if the task is still
processing.

Is AsyncTask really conceptually flawed or am I just missing something?

I have investigated this problem for months now, came up with different solutions to it, which I am not happy with since they are all massive hacks. I still cannot believe that a class that flawed in design made it into the framework and no-one is talking about it, so I guess I just must be missing something.
The problem is with AsyncTask. According to the documentation it
"allows to perform background
operations and publish results on the
UI thread without having to manipulate
threads and/or handlers."
The example then continues to show how some exemplary showDialog() method is called in onPostExecute(). This, however, seems entirely contrived to me, because showing a dialog always needs a reference to a valid Context, and an AsyncTask must never hold a strong reference to a context object.
The reason is obvious: what if the activity gets destroyed which triggered the task? This can happen all the time, e.g. because you flipped the screen. If the task would hold a reference to the context that created it, you're not only holding on to a useless context object (the window will have been destroyed and any UI interaction will fail with an exception!), you even risk creating a memory leak.
Unless my logic is flawed here, this translates to: onPostExecute() is entirely useless, because what good is it for this method to run on the UI thread if you don't have access to any context? You can't do anything meaningful here.
One workaround would be to not pass context instances to an AsyncTask, but a Handler instance. That works: since a Handler loosely binds the context and the task, you can exchange messages between them without risking a leak (right?). But that would mean that the premise of AsyncTask, namely that you don't need to bother with handlers, is wrong. It also seems like abusing Handler, since you are sending and receiving messages on the same thread (you create it on the UI thread and send through it in onPostExecute() which is also executed on the UI thread).
To top it all off, even with that workaround, you still have the problem that when the context gets destroyed, you have no record of the tasks it fired. That means that you have to re-start any tasks when re-creating the context, e.g. after a screen orientation change. This is slow and wasteful.
My solution to this (as implemented in the Droid-Fu library) is to maintain a mapping of WeakReferences from component names to their current instances on the unique application object. Whenever an AsyncTask is started, it records the calling context in that map, and on every callback, it will fetch the current context instance from that mapping. This ensures that you will never reference a stale context instance and you always have access to a valid context in the callbacks so you can do meaningful UI work there. It also doesn't leak, because the references are weak and are cleared when no instance of a given component exists anymore.
Still, it is a complex workaround and requires to sub-class some of the Droid-Fu library classes, making this a pretty intrusive approach.
Now I simply want to know: Am I just massively missing something or is AsyncTask really entirely flawed? How are your experiences working with it? How did you solve these problem?
Thanks for your input.
How about something like this:
class MyActivity extends Activity {
Worker mWorker;
static class Worker extends AsyncTask<URL, Integer, Long> {
MyActivity mActivity;
Worker(MyActivity activity) {
mActivity = activity;
}
#Override
protected Long doInBackground(URL... urls) {
int count = urls.length;
long totalSize = 0;
for (int i = 0; i < count; i++) {
totalSize += Downloader.downloadFile(urls[i]);
publishProgress((int) ((i / (float) count) * 100));
}
return totalSize;
}
#Override
protected void onProgressUpdate(Integer... progress) {
if (mActivity != null) {
mActivity.setProgressPercent(progress[0]);
}
}
#Override
protected void onPostExecute(Long result) {
if (mActivity != null) {
mActivity.showDialog("Downloaded " + result + " bytes");
}
}
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mWorker = (Worker)getLastNonConfigurationInstance();
if (mWorker != null) {
mWorker.mActivity = this;
}
...
}
#Override
public Object onRetainNonConfigurationInstance() {
return mWorker;
}
#Override
protected void onDestroy() {
super.onDestroy();
if (mWorker != null) {
mWorker.mActivity = null;
}
}
void startWork() {
mWorker = new Worker(this);
mWorker.execute(...);
}
}
The reason is obvious: what if the
activity gets destroyed which
triggered the task?
Manually disassociate the activity from the AsyncTask in onDestroy(). Manually re-associate the new activity to the AsyncTask in onCreate(). This requires either a static inner class or a standard Java class, plus perhaps 10 lines of code.
It looks like AsyncTask is a bit more than just conceptually flawed. It is also unusable by compatibility issues. The Android docs read:
When first introduced, AsyncTasks were executed serially on a single background thread. Starting with DONUT, this was changed to a pool of threads allowing multiple tasks to operate in parallel. Starting HONEYCOMB, tasks are back to being executed on a single thread to avoid common application errors caused by parallel execution. If you truly want parallel execution, you can use the executeOnExecutor(Executor, Params...) version of this method with THREAD_POOL_EXECUTOR; however, see commentary there for warnings on its use.
Both executeOnExecutor() and THREAD_POOL_EXECUTOR are Added in API level 11 (Android 3.0.x, HONEYCOMB).
This means that if you create two AsyncTasks to download two files, the 2nd download will not start until the first one finishes. If you chat via two servers, and the first server is down, you will not connect to the second one before the connection to the first one times out. (Unless you use the new API11 features, of course, but this will make your code incompatible with 2.x).
And if you want to target both 2.x and 3.0+, the stuff becomes really tricky.
In addition, the docs say:
Caution: Another problem you might encounter when using a worker thread is unexpected restarts in your activity due to a runtime configuration change (such as when the user changes the screen orientation), which may destroy your worker thread. To see how you can persist your task during one of these restarts and how to properly cancel the task when the activity is destroyed, see the source code for the Shelves sample application.
Probably we all, including Google, are misusing AsyncTask from the MVC point of view.
An Activity is a Controller, and the controller should not start operations that may outlive the View. That is, AsyncTasks should be used from Model, from a class that is not bound to the Activity life cycle -- remember that Activities are destroyed on rotation. (As to the View, you don't usually program classes derived from e.g. android.widget.Button, but you can. Usually, the only thing you do about the View is the xml.)
In other words, it is wrong to place AsyncTask derivatives in the methods of Activities. OTOH, if we must not use AsyncTasks in Activities, AsyncTask loses its attractiveness: it used to be advertised as a quick and easy fix.
I'm not sure it's true that you risk a memory leak with a reference to a context from an AsyncTask.
The usual way of implementing them is to create a new AsyncTask instance within the scope of one of the Activity's methods. So if the activity is destroyed, then once the AsyncTask completes won't it be unreachable and then eligible for garbage collection? So the reference to the activity won't matter because the AsyncTask itself won't hang around.
It would be more robust to keep a WeekReference on your activity :
public class WeakReferenceAsyncTaskTestActivity extends Activity {
private static final int MAX_COUNT = 100;
private ProgressBar progressBar;
private AsyncTaskCounter mWorker;
#SuppressWarnings("deprecation")
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_async_task_test);
mWorker = (AsyncTaskCounter) getLastNonConfigurationInstance();
if (mWorker != null) {
mWorker.mActivity = new WeakReference<WeakReferenceAsyncTaskTestActivity>(this);
}
progressBar = (ProgressBar) findViewById(R.id.progressBar1);
progressBar.setMax(MAX_COUNT);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_async_task_test, menu);
return true;
}
public void onStartButtonClick(View v) {
startWork();
}
#Override
public Object onRetainNonConfigurationInstance() {
return mWorker;
}
#Override
protected void onDestroy() {
super.onDestroy();
if (mWorker != null) {
mWorker.mActivity = null;
}
}
void startWork() {
mWorker = new AsyncTaskCounter(this);
mWorker.execute();
}
static class AsyncTaskCounter extends AsyncTask<Void, Integer, Void> {
WeakReference<WeakReferenceAsyncTaskTestActivity> mActivity;
AsyncTaskCounter(WeakReferenceAsyncTaskTestActivity activity) {
mActivity = new WeakReference<WeakReferenceAsyncTaskTestActivity>(activity);
}
private static final int SLEEP_TIME = 200;
#Override
protected Void doInBackground(Void... params) {
for (int i = 0; i < MAX_COUNT; i++) {
try {
Thread.sleep(SLEEP_TIME);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.d(getClass().getSimpleName(), "Progress value is " + i);
Log.d(getClass().getSimpleName(), "getActivity is " + mActivity);
Log.d(getClass().getSimpleName(), "this is " + this);
publishProgress(i);
}
return null;
}
#Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
if (mActivity != null) {
mActivity.get().progressBar.setProgress(values[0]);
}
}
}
}
Why not just override the onPause() method in the owning Activity and cancel the AsyncTask from there?
You are absolutely right - that is why a movement away from using async tasks/loaders in the activities to fetch data is gaining momentum. One of the new ways is to use a Volley framework that essentially provides a callback once the data is ready - much more consistent with MVC model. Volley was populised in the Google I/O 2013. Not sure why more people aren't aware of this.
Personally, I just extend Thread and use a callback interface to update the UI. I could never get AsyncTask to work right without FC issues. I also use a non blocking queue to manage the execution pool.
I thought cancel works but it doesn't.
here they RTFMing about it:
""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."
That does not imply, however, that the thread is interruptible. That's a
Java thing, not an AsyncTask thing."
http://groups.google.com/group/android-developers/browse_thread/thread/dcadb1bc7705f1bb/add136eb4949359d?show_docid=add136eb4949359d
You would be better off thinking of AsyncTask as something that is more tightly coupled with an Activity, Context, ContextWrapper, etc. It's more of a convenience when its scope is fully understood.
Ensure that you have a cancellation policy in your lifecycle so that it will eventually be garbage collected and no longer keeps a reference to your activity and it too can be garbage collected.
Without canceling your AsyncTask while traversing away from your Context you will run into memory leaks and NullPointerExceptions, if you simply need to provide feedback like a Toast a simple dialog then a singleton of your Application Context would help avoid the NPE issue.
AsyncTask isn't all bad but there's definitely a lot of magic going on that can lead to some unforeseen pitfalls.
As to "experiences working with it": it is possible to kill the process along with all AsyncTasks, Android will re-create the activity stack so that the user will not mention anything.

Categories

Resources