I have a doubt about destruction of activities and objects.
While I attach & detach the activity from the AsyncTask I do not change the ArrayAdapter from the asynctask (see code). So, what I get is multiple activities being attached & detached (ought to orientation changes) and just one task running and modifying ONE adapter, which in turn is the one from the first activity that created the task. So, when I attach the task in the onCreate() I just set the adapter with the one which holds the task, which in turn has all the values processed (in the example just a dummie list of numbers).
How can this be possible? I thought that onDestroy() would erase the activity itself and its attributes, and therefore I would get a null pointer exception or something like that while trying to access the ArrayAdapter of the original activity from the AsynkTask, but the code below works!
private static class TestingTask extends AsyncTask<Void, Integer, Void> {
private TestingActivity mActivity; // extends ListActivity
private ArrayAdapter<String> mAdapter;
private boolean mIsFinished;
private TestingTask(Context activity) {
attach(activity);
mAdapter = (ArrayAdapter<String>)mActivity.getListAdapter();
mIsFinished = false;
}
private void attach(Context activity) {
mActivity = (TestingActivity)activity;
}
private void detach() {
mActivity = null;
}
protected Void doInBackground(Void... params) {
for (int i = 0; i < 100000; i++) {
publishProgress(i);
}
return null;
}
protected void onProgressUpdate(Integer... values) {
if (!isCancelled()) {
mAdapter.add(values[0].toString());
}
}
// ...
}
Is this because the task keeps an active reference to the ArrayAdapter object, and therefore it is not deleted? Or is it something else?
I also experienced another "similar case" in which I returned an Activity's attribute from onRetainNonConfigurationInstance() let's say A a, that had visibility over B b (which is another attribute of the Activity). Then, when trying to access b instance through a, there is no problem and I thought I would need a wrapper to hold the two instances (a and b), or else I would get an exception when trying to access b (which I do not actually save). I do not know if it is related width the previous case in which the objects that I supposed not to be available actually are there, maybe because of the active reference to them that causes no deletion?
Thank you!
I think I have found the answer to these questions and as I was wondering... it is related to the Garbage Collector and the use of strong references.
In Understanding weak references article it is said that:
if an object is reachable via a chain of strong references (strongly reachable), it is not eligible for garbage collection. As you don't want the garbage collector destroying objects you're working on, this is normally exactly what you want
In another article How Gargabe Collection works it is explained that:
if an object holds reference of another object and when you set container object's reference null, child or contained object automatically becomes eligible for garbage collection.
So, my conclusion is that:
In the first case: As I am setting activity to null in detach() there is no memory leak and all objects can be garbage collected unless the adapter, which has a strong reference. So, I understand that the activity and all other objects contained by it are deleted unless the adapter, this is what I actually want.
In the second case: As I am returning the container object (A a) in onRetainNonConfigurationInstance() and it has a strong reference to (B b), b instance is accessible too, because it can be reachable via a chain of strong references.
Hope this will be helpful. If anyone else wants to give his/her opinion it will be welcome!
Related
I have 5 fragments in ViewPager used to fill business object with several fields step by step, in each step some of those fields will be set. I've read many articles about communication between fragments but I'm not feeling comfortable the way others preferred, so after thinking about HOW should I do this in my case, finally I start thinking to use singleton model object which all fragments can easily access to its fields and fill them in specific steps.
As I'm new to android I want to hear from experts about using singleton instead of passing data between fragments such as implemented interface(It seems its so complicated and hard to maintenance). Any advice will be helpful.
While singleton approach seems easy to implement and understand it is way not to best way to achieve what you need. One reason is that your model object or as you call it business object lives outside of your activity's context which can create hard to find bugs. E.g. in case when more than one instance of your activity class is created by system and both keep reference to your singleton. See how you lose track of your objects?
What I would do is
Make my model object to implement Parcelable you will hate it at the beginning but once you get use to it it will become your model's best friend
Since your model is parcelable now you can easily pass it between fragments, activities, and even save it in shared preferences. One important thing to note here when you pass your parcelable between fragment or activity it is like pass by value, i.e. every time new instance is created.
Set your fragment's argument or if it is already instantiated then get arguments and add your model. here is an example:
if a fragment is not active yet:
Bundle args = new Bundle();
args.putParcable("businessObject", yourBusinessObjectThatIsParcable);
yourFragment.setArguments(args);
Otherwise:
yourFragment.getArguments().putParcelable("businessObject", yourBusinessObjectThatIsParcable);
In your fragment perhaps in onCreateView method get your model object like this MyParcableObject mpo = (MyParcableObject)getArguments().getParcelable("businessObject") and use it set whatever data you want.
When you finish editing your object on button click or in onPause method updated your fragment's arguments same way getArguments().putParcelable("businessObject", mpo);
in your last page or last fragment you can pass your object to your activity, here is how to do it
Even though it looks cumbersome but it is a practice that you need to get used to as an android developer. You get lot more control when your model implements parcelable.
Another way to do what you need is thru Delegation Pattern but it is mostly used for callbacks even though you can pass objects as well.
I wouldn't recommend a global singleton. There are two main reasons:
By definition, a singleton limits your app to a single instance of the main business object. If you (or a designer, or your boss's boss's boss) ever decide to have multiple of these ViewPagers at a time, you will have to change your architecture anyways.
The "Android way of thinking" is to expect that your user may put your app in the background and use other apps before returning to your app. If the system decides to kill your app in the background, then your singleton memory object will be destroyed, and your user will have lost all of their progress. The correct Android way to save state is by keeping the state in an Activity or Fragment, saving it appropriately in onSaveInstanceState(), and restoring it in onCreate().
All of the Fragments in the ViewPager can get a reference to the parent Activity via a call to getActivity(). Or if your ViewPager is within a Fragment, then all of the Fragments can access the parent Fragment via a call to getParentFragment(). You can then cast the result to the appropriate class (or better yet, interface) and make method calls to pass data back and forth. Keep track of your business data in the parent Activity/Fragment. This way, you don't need a global singleton
For example,
public class MyParentFragment extends Fragment {
private String mPageOneData;
private int mPageTwoData;
private List<Date> mPageThreeData;
public void setPageOneData(String data) {
mPageOneData = data;
}
...
}
public class PageOneFragment extends Fragment {
private void sendDataToParent(String data) {
Fragment f = getParentFragment();
if (f != null && f instanceof MyParentFragment) {
MyParentFragment parent = (MyParentFragment) f;
f.setPageOneData(data);
}
}
}
you can save your data in onSaveInstanceState() event of the activity in case your process will go into the background.
you can restore your data in onCreate() event by using Bundle and getExtras().
you can save your data in application class and the data will still be there in case your process will go into the background.
i prefer the first option because you don't want to make a mess in the application class with all the data from different activities and fragments.
I hope i could help :)
Have you checkout EventBus?
I'm not sure if it is the best approach, specially when your question is too broad, however it will be cool with just 5 fragments.
Hope it helps
I suppose in your MainActivity there is a ViewPager, and FragmentOne will be one of the fragments inside the view pager. Here the MainActivity is communicating to the FragmentOne to refreshhis adapter. Hope is clear.
In your MainActivity add this interface:
public interface Updateable {
public void update();
}
Implement this interface in a fragment that needs to be updated, and write the code to notify the adapter inside the update method:
public class FragmentOne extends Fragment implements MainActivity.Updateable {
...
#Override
public void update() {
// YOUR CODE TO UPDATE HERE, FOR EXAMPLE, HERE I'M UPDATING THE ADAPTER
if ( adapter != null ) {
adapter.notifyDataSetChanged();
} else {
Log.d("LOG_TAG", "null");
}
}
...
}
Call the update method from the MainActivity when the fragment loads first. You can do this overriding the getItemPosition method in your PagerAdapter, like this:
#Override
public int getItemPosition(Object object) {
if ( object != null && object instanceof FragmentOne ) {
FragmentOne f = (FragmentOne) object;
f.update();
}
return super.getItemPosition(object);
}
Finally, you have to call notifyDataSetChanged() of your viewPager adapter. This will force the adapter of your viewpager to call the getItemPosition method.
mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
int previousState;
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
#Override
public void onPageSelected(int position) {
}
#Override
public void onPageScrollStateChanged(int state) {
if (previousState == ViewPager.SCROLL_STATE_SETTLING && state == ViewPager.SCROLL_STATE_IDLE) {
if ( viewPagerAdapter.getItem(viewpager.getCurrentItem()) instanceof Pictures ) {
Log.d("LOG_TAG", "New Position=" + viewpager.getCurrentItem());
viewPagerAdapter.notifyDataSetChanged();
}
}
previousState = state;
}
});
Before choosing any option, keep in mind user can navigate or open any other app(s) so you lost your data.
You can use onSaveInstanceState but it will somehow difficult to maintain (as you said you are new in android). You can go with with singleton by using
Database - Use when you want to store maintain multiple records but you have to create a database getter/setter or use any ORM like RushOrm etc.
SharefPreference(preferably) - If you want to use single values.
In both cases you will create a singleton object and access its properties in your fragments.
make your objects parcelable and then pass it to other fragments using bundle. i.e bundle.putParcelable(obj) parcelable is very efficient and fast.
it should motivate you
http://www.developerphil.com/parcelable-vs-serializable/
I want to customize the process of obtaining the authentication token from AccountManager.
AccountManager has getAuthToken() and getAuthTokenByFeatures() methods, but I want to implement a customized flow, which includes switching between activities, etc...
I wanted to implement it the following way:
public AccountManagerFuture<Bundle> getAuthTokenForActiveAccount() {
GetAuthTokenForActiveAccountFuture future =
new GetAuthTokenForActiveAccountFuture(MyActivity.this);
future.start();
return future;
}
Using the following nested class in my activity:
private static class GetAuthTokenForActiveAccountFuture extends Thread implements
AccountManagerFuture<Bundle> {
private final Activity mActivity;
public GetAuthTokenForActiveAccountFuture(Activity activity) {
mActivity = activity;
// TODO: write this method
}
#Override
public void run() {
// TODO: write this method
}
#Override
public boolean cancel(boolean b) {
// TODO: write this method
return false;
}
#Override
public boolean isCancelled() {
// TODO: write this method
return false;
}
#Override
public boolean isDone() {
// TODO: write this method
return false;
}
#Override
public Bundle getResult() throws
OperationCanceledException, IOException, AuthenticatorException {
return internalGetResult(null, null);
}
#Override
public Bundle getResult(long timeout, TimeUnit timeUnit) throws
OperationCanceledException, IOException, AuthenticatorException {
return internalGetResult(timeout, timeUnit);
}
private Bundle internalGetResult(Long timeout, TimeUnit timeUnit) throws
OperationCanceledException, IOException, AuthenticatorException {
// TODO: write this method
return null;
}
}
My idea was that I could create my own AccountManagerFuture object and "unblock" its getResult() method only after all the required steps were done (some of them include activity switching).
I got two issues here:
I need Activity context for switching to other activities when necessary, but the Activity I pass into constructor should be destroyed when I switch to other activity, but it won't because my Thread holds a reference to it... So I create a memory leak here. It seems that making the inner class non-static won't resolve this issue - the reference returned from getAuthTokenForActiveAccount() will still prevent from the outer Activity to be garbage collected. Is there any way I could achieve what I try to do without leaking the context?
Thread is eligible for garbage collection once its run() method returns, right? But in my case I want this thread to stick around because it also functions as AccountManagerFuture - it should be kept in memory until all references to it are gone. My question is this: is it enough to keep a (strong) reference to Thread for preventing it from being garbage collected? If not, how could I force this Thread to stick around until all references are gone?
At first. Making your Future non-static would make it having an implicit reference to its outer class - the Activity.
You should used some form of indirect communication between your future and your Activities..You should probably move it into Service anyway - did you think about any configuration change? Where do you hold the reference for your Future?
I would advice you to either move your flow into fragments - then you wouldn't have to switch Activities - and place your future into a retained Fragment (to make it survive orientation change) or move it into a background service and communicate with your activities (or any sort of UI) through broadcastreceivers or event bus.
Thread won't be garbage collected as long as you keep some reference to it. No matter if its finished or not. I think that you are confusing this with the fact that a running Thread won't be garbage collected even without keeping references to it. (I guess tha JVM does so, but I have to admit I'm not sure about this)
issue 1 solution:
use private WeakReference mContextHolder. when you need context - call mContextHolder.get() and check on null;
issue 2 solution:
Use Service which will host your threads.
Problem:
I'm saving some data in a singleton class... Sometimes it happens, that this singleton returns null data, from which I derive, that it was destroyed...
My idea/thoughts:
Actually, I thought, the singleton will live as long as the application lives and as long as the application remembers anything else like fragments state for example, my singleton will exist with it's last data too. Isn't this correct?
concrete problem:
My case is following: I go from my main fragment to a sub fragment and save an selected object in my singleton... I stop using my app and come back after some time. My app remembers it's state and recreates the fragments, my fragment want to get the selected object from my singleton and get's null.
I thought, a singleton should exist as long as the application exists and therefore needs no saving... Whenever the application is ended, nothing will be restored anyway and my app won't remember anything, so that's ok anyway. Is that a wrong assumption?
I need an answer to this question, because if I'm sure, that above thoughts are correct, I at least know, that I have to search for the problem somewhere else...
Here is a short summury of what I've found out (or have had forgotten)
Activitys can be recreated, although the application was destroyed
Singletons can be garbage collected if not referenzed from somewhere
So you HAVE TO SAVE your singletons! Otherwise, whenever your phone is on low memory, it may kill the application and create a NEW application, but RECREATE the activities...
For me, as I'm actually always use a single activity with fragments, it is easy to solve the problem:
when I create an activity, I call a static restore function (BEFORE calling get!!!)
in the onSaveInstanceState of the activity a always save the singleton to the bundle
=> so my singleton looks like following (base structure)
public class DataCache implements Parcelable
{
private static final String TAG = DataCache.class.getName();
private static DataCache mCache = null;
public static synchronized final DataCache get()
{
if (mCache == null)
mCache = new DataCache();
return mCache;
}
private DataCache()
{
// init...
}
public void save(Bundle outState)
{
outState.putParcelable(TAG, this);
}
public static void restore(Bundle savedInstanceState)
{
if (savedInstanceState != null && savedInstanceState.containsKey(TAG))
{
// only restore, if necessary, i.e. if application was destroyed but activity saved our last cache
if (mCache == null)
mCache = savedInstanceState.getParcelable(TAG);
}
}
}
I've been puzzled very much by this lifecycle thing, so I did a little experiement. Long story short: The result shows that when a process is created after having been destroyed, UI objects allocated in the last session were all gone and need be re-created (which is expected). But other memory space allocated in the last session are still available for this session.
The surprise to me is: system's UI objects (like ListView) and memory space allocated by me are not destroyed at the same time. Why they don't die (or stay alive) at the same time???
See the experiement here:
public class PracticeActivity extends ListActivity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// If there is left-over value in G.count[99], then do not populate
// the ListView.
if (G.count[99] == 0) {
ListView lv = getListView();
lv.setAdapter(new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1, m_Starbucks));
}
Log.d("TAG", MessageFormat.format("Created, count = {0,number}", G.count[99]));
Log.d("TAG", MessageFormat.format("Starbucks = {0}", m_Starbucks[0]));
G.count[99]++; // increment the count
m_Starbucks[0] = "Coffee Frappuccino"; // and change the menu
}
#Override public void onRestart() { super.onRestart(); Log.d("TAG", "Restarted"); }
#Override public void onStart() { super.onStart(); Log.d("TAG", "Started"); }
#Override public void onResume() { super.onResume(); Log.d("TAG", "Resumed"); }
#Override public void onPause() { super.onPause(); Log.d("TAG", "Paused"); }
#Override public void onStop() { super.onStop(); Log.d("TAG", "Stopped"); }
#Override public void onDestroy() {
super.onDestroy();
if (isFinishing())
Log.d("TAG", "Destroyed -- someone finished me");
else
Log.d("TAG", "Destroyed -- system needs resources");
}
private static final String[] m_Starbucks = {
"Latte", "Cappuccino", "Caramel Macchiato", "Americano", "Mocha",
"White Mocha", "Mocha Valencia", "Cinnamon Spice Mocha",
"Toffee Nut Latte", "Espresso", "Espresso Macchiato",
"Espresso Con Panna"
};
}
Here is the class G, defined in G.java file:
public class G {
public static int[] count = new int[100];
}
Running this test produced the following results:
Created, count = 0
Starbucks = Latte
Started
Resumed
Paused
Stopped
Destroyed -- someone finished me
Created, count = 1
Starbucks = Coffee Frappuccino
Started
Resumed
In the first session, count[99]'s value was 0, so the program went to populate the ListView, so everything was fine.
In the 2nd session, count[99] still holds the value left over from the first session, so the program did not populate the ListView, in hope that the ListView would also be available too. But it's not, the result is a black screen. This means G.count[] was retained (and so is m_Starbucks[]) from last session, but the ListView didn't survive.
It's apparent that there's only one instance of PracticeActivity in the system, when this instance dies, both PracticeActivity and G classes should die too. But they didn't, they still retain values from the last session.
QUESTIONS:
If count[] and m_Starbucks[] are
still available, then this means
PracticeActivity and G are both
alive too. Then why the ListView is
gone? Shouldn't all of them die or
live at the same time?
When I see some of my classes'
members hold their old values from
last session, can I trust that
all of my classes' members are also valid??? I.e., does Android
kill my resources in an all-or-none
fashion? Or, it can delete some and
leave some others? (This question
shouldn't have existed in the first
place, but seeing the result of the
experiement, one starts to wonder.)
Can anybody shed some light on this? Much appreciated.
Static class members live as long as JVM (DVM) lives - which may be (and certainly is) longer than
your activity lifecycle. Your activity could be destroyed, but static fields survive it.
If count[] and m_Starbucks[] are still
available, then this means
PracticeActivity and G are both alive
too.
No. count and m_Starbucks are both declared static. Per Java documentation:
"Class Variables (Static Fields) A
class variable is any field declared
with the static modifier; this tells
the compiler that there is exactly one
copy of this variable in existence,
regardless of how many times the class
has been instantiated"
So say you do the following: (pretend this isn't an activity and you can conveniently just construct it)...
PracticeActivity example1 = new PracticeActivity();
PracticeActivity example2 = new PracticeActivity();
Then you do not have example1.m_Starbucks[0] and example2.m_Starbucks[0] as distinct variables. Instead, you just have PracticeActivity.m_Starbucks[0] and any specific instance of that class has the same variable. Therefore it is unaffected by (unrelated to!) the destruction of the actual instance of your Activity. And in fact, they exist, even if you have never constructed an instance of the class that contains them.
Also, if you change example1.m_Starbucks[0], you will find that example2.m_Starbucks[0] also has changed -- because, again, there's only one array.
The simple answer here is that you shouldn't be using static variables for this type of storage. It's safe to use static for constants and some other special cases, but never as member variables that you expect to hold attributes of a given instance of a class, that make that instance uniquely different from other classes.
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.