0

I have an Android Activity and user starts some task (for example by a button click) which may take some time - say something between between 1 ms and 1 min. It can be some network operation or computing or anything other. I can use a Thread or AsyncTask or some other tool. I want to change something on the Activity when the task finishes, for example show the downloaded image or best chess move etc. I must use Handler or some equivalent tool to run the main thread but it is still clear and quite simple. And here is my problem:

What is the recommended way to get the current Activity object (to call some its method)? Please note that user may not do anything during the task life (I want to get the original Activity object), he may also close the Activity by back key (I want to get null in this case - even if he reopens the Activity for the second time), he may change the configuration - for example switch from portrait to landscape mode (I want to get the new - current instance of the Activity).

Cœur
  • 37,241
  • 25
  • 195
  • 267
Jan Němec
  • 309
  • 6
  • 14

4 Answers4

2

It sounds like a job for an invisible Fragment with setRetainInstance(true) called upon it.

Basically you should move all your task logic to a special Fragment that doesn't create any view hierarchy (its onCreateView should return null). The fragment should call setRetainInstance(true) upon itself early, e.g. in constructor. When you need to run task, you just add new fragment. This fragment spawns thread/async task and receives the result. Then you can use getActivity in the Fragment to get most recent activity instance. If the activity is destroyed because of user leaving it with back key, the fragment's onDestroy will be called, so you can distinguish if activity is destroyed or if it is recreating now.

Just be carefull not to store Activity instance somewhere in fragment to prevent leaks and not to add the fragment twice accidentally.

See the article on developer.android.com for details about that approach.

Darth Beleg
  • 2,657
  • 24
  • 26
  • This link seems to be right, but I need more conservative approach (Fragments are to new) because I want to be old Android versions compatible. I am sure my problem had some solution even in Android 1.5. It is probably possible to save some link to my Thread in onSaveInstanceState() and restore it in onRestoreInstanceState() and keep the reference to the current Activity in the Thread object. But I am looking for a simpler solution for such simple and common problem. (And I also do not want to hold the reference to the Activity object - this is a common cause of memory leaks.) – Jan Němec Oct 02 '14 at 09:49
  • Android 1.5? There's no real reason a developer should have to worry about it. You probably can count on the fingers which 1.5 devices still exist in the world that are not broken, and certainly none of those access the Google Play Store anymore, so it's likely they won't have the chance to download it. Also, Android back on those days lacked a lot of dev tools to accomplish those tasks you described. Also, Fragments been around since Honeycomb, which was released Feb/2011 (that's more than 3.5 years), not so new. – Budius Oct 02 '14 at 12:28
  • 1
    Finally, you can use `onRetainNonConfigurationInstance` and `getLastNonConfigurationInstance` to "save" a thread that is currently executing, but those methods were deprecated on API13 (Honeycomb) and the docs directly advise you NOT to use them. ref: https://developer.android.com/reference/android/app/Activity.html – Budius Oct 02 '14 at 12:29
  • Yes, onRetainNonConfigurationInstance and getLastNonConfigurationInstance seems to be exactly what I was looking for. (It still looks quite complex for such a basic and very common task - informing GUI about a result of a thread - but probably there is really no simple solution.) – Jan Němec Oct 02 '14 at 14:11
  • @JanNěmec, you can get fragments from API levels 4+ (i.e. 1.6+) with the [support library](https://developer.android.com/tools/support-library/index.html). This fragment approach has the benefit that it manages activity reference inside the Fragment for you - attaching/detaching/destroing it. – Darth Beleg Oct 02 '14 at 15:55
  • seriously onRetainNonConfigurationInstance and getLastNonConfigurationInstance is a bad idea. It's an API that's been deprecated 3 years ago. For just "notify" there much better ways, check my answer about the `LocalBroadcastManager` also there's very nice 3rd party libraries such as Otto http://square.github.io/otto/ – Budius Oct 02 '14 at 16:01
2

there're quite a few different ways of doing it.

  • as suggested by @DarthBeleg, you can use Fragment with setRetainInstance(true);, all the processing logic goes inside this fragment, activity can findFragmentByTag and the fragment can find the activity by getActivity()

  • Service. Services are great, they have their own life cycle, can keep running while the activity is gone. The activity start the service passing the task requirements on the Intent and then there're two options

    1) the activity binds to the service and use some public methods to register callbacks when the tasks is complete, results is available

    2) the activity onStart/onStop register a BroadcastReceiver with the LocalBroadcastManager and the Service send broadcasts to notify or pass information to the activity any time it wants to.

  • if all you're doing is downloading/chaching images to be shown on your activity UI, just forget all those threading and caching complexity and use Picasso library you make one line of code Picasso.with(context).load(url).into(imageView); and the library automagically takes care of everything you need.

  • there's also the Loaders that your activity can connect to by implementing LoaderCallbacks and calling getLoaderManager().initLoader(0, null, this), and on rotation the activity re-connects to the same loader again. And the result get pushed to the current running activity instance.

so as you can see, it really depends on the use case to see which is the best option.

hope it helps... happy coding.

Budius
  • 39,391
  • 16
  • 102
  • 144
0

If there is only one Activity in your app then:

  1. I suppose if you are using any fragments, then inside a fragment you can retrieve your activity object using "getActivity()".

  2. If you are inside your activity then you can simply access your activity object using "this" object.

Hope this helps.

Sheraz Nadeem
  • 311
  • 2
  • 7
-1

Use startActivityForResult, which will start your task, and return the result to your original activiy's onActivityResult.

Then you can check for the ResultCode and intent to establish what happened exactly (including the case where the user cancelled the operation).

See the developer docs for more information on how to get the result from an activity.

Robin Eisenberg
  • 1,836
  • 18
  • 26
  • Well, as far I know it is only usefull for notyfying from child Activity to parent Activity. Not for notifying from Thread or AsyncTask to the Activity. There is 1 Activity only in my case. – Jan Němec Oct 02 '14 at 08:57