Activity lifecycle observer - how to know when an activity instance is destroyed? - android

Imagine I have a class MyLibrary, that requires to be passed an Activity as a parameter to it's constructor: public MyLibrary(Activity someActivity). Is there a way to attach an observer to someActivity within the MyLibrary constructor, so that MyLibrary would know when someActivity gets destroyed?
I'm using Bumptech's Glide for async image loading and in several places in their issues it is mentioned that after passing a context, activity or fragment to it's Glide.with() function, Glide will automatically pause images that are currently loading if the passed object gets destroyed. (Example) I'm wondering how Glide figures out when to stop loading once the given context, activity or fragment is destroyed, how does it observe it?
Ideally I'd like to do something along the following lines (note this is a made up example):
public MyLibrary(Activity someActivity){
someActivity.addLifecycleObserver(new LifecycleObserver(){
#Override
public void onStart() {}
#Override
public void onStop() {}
#Override
public void onDestroy() {}
});
}

Google just released an architectural library which handles your use case. Check out the guide

Related

When does a LifecycleRegistry instance start listening to LifecycleOwner's lifecycle changes?

I've started learning architecture components, but can't find one thing.
LifecycleFragment just creates a new LifecycleRegistry object, which does not start observing the fragment's lifecycle.
I guess the LifecycleRegistry object starts listening to the fragment's lifecycle when we, for example, put it into LiveData.observe() as first param, but I haven't found any proof of this in source code.
Question: When and how does a LifecycleRegistry object start to observe a fragment's lifecycle and refresh LifecycleRegistry.mState?
There is a ContentProvider called LifecycleRuntimeTrojanProvider that is merged into the app's AndroidManifest.xml. In its onCreate method it initializes a singleton called LifecycleDispatcher, which is responsible for updating all LifecycleRegistry instances.
LifecycleDispatcher uses the Application.registerActivityLifecycleCallbacks method that has been around since API 14 to get notified when a new activity is created. At this point it injects an instance of ReportFragment into the activity. The ReportFragment uses the Fragment lifecycle callbacks to update the activity's LifecycleRegistry if necessary, like this:
#Override
public void onStop() { // Showing onStop as example
super.onStop();
dispatch(Lifecycle.Event.ON_STOP);
}
private void dispatch(Lifecycle.Event event) {
if (getActivity() instanceof LifecycleRegistryOwner) {
((LifecycleRegistryOwner) getActivity()).getLifecycle().handleLifecycleEvent(event);
}
}
If the new activity is a FragmentActivity, the LifecycleDispatcher calls FragmentManager.registerFragmentLifecycleCallbacks to get notified of the activity's fragments lifecycle events. It relays the onFragmentCreated, onFragmentStarted and onFragmentResumed callbacks to the LifecycleRegistry in case the fragment is a LifecycleRegistryOwner, in the same way as before.
The onFragmentPaused, onFragmentStopped, and onFragmentDestroyed callbacks are called after the corresponding callbacks are called on the fragment, but the LifecycleObserver callbacks must be called before. So whenever a fragment is created, the LifecycleDispatcher injects an instance of LifecycleDispatcher.DestructionReportFragment into it. The DestructionReportFragment's lifecycle callbacks are used to update the registry for the pause, stop and destroy events.
I can't link to the code because it hasn't been released yet, but you can browse it in Android Studio after you add the library to your project.
As Mordag said, as of now, both the LifecycleActivity and LifecycleFragment are not yet implemented. In their documentation Google says:
Any custom fragment or activity can be turned into a LifecycleOwner by implementing the built-in LifecycleRegistryOwner interface (instead of extending LifecycleFragment or LifecycleActivity).
However, that is only half the story, because naturally you are using these Lifecycle Aware components to be able to react to your Activity/Fragment lifecycles and with their code snippet it just doesn't work, because initialising a LifecycleRegistry with the Activity/Fragment like this
LifecycleRegistry lifecycleRegistry = new LifecycleRegistry(this);
only gets you a Lifecycle in the INITIALIZED state.
So, long story short, in order for this to work right now (BEFORE their 1.0-release) it is you who have to implement the Lifecycle of the Activity/Fragment that implements the LifecycleRegistry. So, for each callback of the Activity/Fragment you need to do this:
public class ScoreMasterFragment extends Fragment
implements LifecycleRegistryOwner {
private LifecycleRegistry lifecycle;
#Override
public LifecycleRegistry getLifecycle() {
return lifecycle;
}
public ScoreMasterFragment(){
lifecycle = new LifecycleRegistry(this);
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//more code here
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
}
#Override
public void onStart() {
super.onStart();
//more code here
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_START);
}
#Override
public void onResume() {
super.onResume();
//more code here
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_RESUME);
}
#Override
public void onPause() {
super.onPause();
//more code here
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE);
}
#Override
public void onStop() {
super.onStop();
//more code here
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_STOP);
}
#Override
public void onDestroy() {
super.onDestroy();
//more code here
_lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY);
}
This will likely be in the code of the future LifecycleActivity and LifecycleFragment, but until then, if you put your Activities/Fragments observing some LifecycleAware object (like LiveData) you will have to do this.
In the case of LiveData, because it will not notify its observers unless they are at least in the STARTED state and in other cases because other LifecycleAware components cannot react to a Lifecycle if its only state is INITIALIZED.
The LifecycleFragment and LifecycleActivity are currently not fully implemented. Those classes will be implemented when the lib is reaching 1.0-release. Currently you can use those LifecycleRegistry to observe LiveData objects. Those objects are based on a future result which could e.g. be an object from your database.
The official documentation can be found here: https://developer.android.com/topic/libraries/architecture/index.html
Official statement regarding the two classes you mentioned:
Lifecycle Fragment and ActivityCompat in the Support Library do not
yet implement LifecycleOwner interface. They will when Architecture
Components reaches 1.0.0 version.
LifecycleActivity ,LifecycleFragment and LifecycleRegistryOwner interface are deprecated in API level 1.0.0. Use android.support.v7.app.AppCompatActivity and android.support.v4.app.Fragment instead of it.
Official documentation here LifecycleActivity LifeCycleFragment

When should I unsubscribe from observables in activities and fragments to avoid negative consequences?

I have subscribers that interact with UI components of an activity/fragment. I'm not sure where and when should I unsubscribe them. I see two ways: the former is to unsubscribe them in the onPause() method, the latter is to unsubscribe them in the onStop() method. Which one is correct and why? Thanks
You should use onPause as you don't need the listener running while the activity or fragment is not. onPause gets called everytime the item is no longer activity running. onStop is called on the way to destruction.
"But what if my observable downloads some content after the activity starts"
For long time operations or operations that must be independent from Activity Lifecycle you shuold use Service component
Please take a look at this https://github.com/trello/RxLifecycle. It will prevent your app from throwing NPE on views and context memory leaks.
Read about MVP pattern. Below I made exemplary implementation
Presenter class
public class SamplePresenter {
#NonNull
private final Observable<SomeData> someDataObservable;
public SamplePresenter(#NonNull ApiService apiService) {
someDataObservable = apiService.apiRequest()
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io());
}
#NonNull
public Observable<SomeData> getSomeDataObservable() {
return someDataObservable;
}
}
MainActivity class
#Inject
SamplePresenter samplePresenter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
samplePresenter.getSomeDataObservable()
.compose(this.<SomeData>bindToLifecycle())
.subscribe(new Action1<SomeData>() {
#Override
public void call(SomeData someData) {
}
});

Android communication between activity and fragment

I have an activity which contains a ViewPager inside it.The ViewPager's Adapter is FragmentStatePagerAdapter. Each page is Fragment.The Fragment contains a number of Threads. My problem is, I have to stop all the threads inside the fragment when ViewPager's page is changed.How can I do this ?
you have asked about communication between activity and fragment that you achieve using interface:
Your Fragment:
public class YourFragment
extends Fragment{
private OnListener listener;
public interface OnListener
{
void onChange();
}
void initialize( OnListener listener)
{
this.listener = listener;
}
//onview pager change call your interface method that will go to the activity as it has the listener for interface.
listener.onChange();
}
Your Activity:
public class yourActivity
extends Activity
implements yourFragment.OnListener
{
// intialize the method of fragment to set listener for interface where you define fragment.
yourFragment.initialize( this );
// implement what you want to do in interface method.
#Override
public void onChange()
{
// implement what you want to do
}
}
hope it will help.
Android's philosophy with applications is to kill processes, so maybe following the same idea you could kill your Threads. Be aware though that this can lead to deadlocks if your Threads own locks, or monitors.
A more serious approach to me seems to use Thread.interrupt() from your Activity. Then your Threads in your Fragment have to check Thread.interrupted for interruption, and finish gracefully if they've been interrupted.
You can use Thread.join() if you want some synchronous behavior
In addition, you can wait for a certain amount of time for your Thread to finish gracefully using a Timer, then kill them on timeout.
Please have a look at java.lang.Thread
To let this be more easy to implement, you could use a ThreadPoolExecutor or some other helper of java.util.concurrent package.

How to use Context to access/manipulate another class/activity

I want to create a generic AsynceTask class that all my activities use/share for downloading content from a url. for this reason, I don't want the OnPostExecute to do anything other than to send the content back to some method in the activity that invoked the AsyncTask class.
I know that I need to create a constructor that sets the context of the Activity that invoked the AsyncTask, but then what, how do I use the context to send something back the the activity corresponding to that context. I've seen no tutorials that show how to use context in this manner.
Lets say I have:
public class LoginActivity {
public int ActivityMember;
public void HandleButtonClick(void){
DownloadFromURL task = new DownloadFromURL(this);
task.execute(url);
}
public void HandleLoginResult(int x){
ActivityMember = x;
}
}
now in a separate java file I have:
private class DownloadFromURL extends AsyncTask<List<NameValuePair>, Long, JSONObject> {
Context context;
public void DownloadFromURL (Context context){
this.context = context;
}
#Override
protected void onPostExecute(JSONObject json) {
context.(<- *my question involves this part of code)
}
}
I'm pretty sure I cant call context.ActivityMember, or context.HandleLoginResult(y) inside onPostExecute, because context is not of the type LoginActivity, its a Context.
So how can I access members or methods belonging to LoginActivity, using it's context?
you can use ((ActivityName)contextName).methodName()
But it is not a good solution. You can try something like this
pass your activity name along with the context to the async class.
protected void onPostExecute(SoapObject result)
{
if(classname.equals("LoginActivity"))
{
((LoginActivity) object).method();
}
else if(classname.equals("MainActivity"))
{
((MainActivity) object).method();
}
}
Easy to do, you just create a method in your activity and call it from the instance you pass through AsyncTask parent class constructor.
Assume you have in your activity some like this:
public void Foo(ArrayList<DataType> data)
{
//Do some with data
}
You then call this method from onPostExecute like this:
#Override
protected void onPostExecute(ArrayList<DataType> data)
{
activity.Foo(data);
}
Where activity is the instance passed through the constructor.
Cheers
This involves basic Object Oriented Programming knowledge.
If you look closely at http://developer.android.com/reference/android/app/Activity.html you can see Activity extends Context, that's why you can get away with passing this to your AsyncTask constructor and having the compiler do the required object slicings.
We can use that to your advantage: Create an abstract class extending Activity (Let's say: DataActivity, just an example though, name it whatever you want) and write a method named onDataDownloadComplete(JSONObject json) (A callback) on it, that you would of call on your AsyncTask's onPostExecute. Make all your activities extend from DataActivity and implement that method. Change the AsyncTask context from Context to DataActivity so you can call the onDataDownloadComplete callback and you are done. Again, as DataActivity would of extend Activity and Activity extends Context, DataActivity or anyhting extending it would be a valid context for the AsyncTask.
Hope you find this useful.
I realized there is another way to achieve what I was trying to do. This takes away the Asyncrony, but for the case of login, I actually do want the UI to be inactive while the app trys to log in.
After I call
asyncTask.execute()
I can call
asyncTask.get()
and that will retrieve the result of doInBackground and allow you to run it on the spot. Alternatively I can use a timeout so I dont block forever:
asynceTask.get(5000, TimeUnit.MILLISECONDS)

Google Analytics in Android app - dealing with multiple activities

I was pretty excited to see how easy it is to set up Google Analytics with my app, but the lack of documentation has me sitting with a few questions. The only information that I can find is right from the documentation here, which only looks at reporting PageViews and Events from one Activity. I want to report PageViews and Events across multiple Activities in my app.
Right now in the onCreate() of all of my activities, I am calling:
tracker = GoogleAnalyticsTracker.getInstance();
tracker.start("UA-xxxxxxxxx", this);
And in the onDestroy() of all of my activities:
tracker.stop();
I then track PageViews and Events as needed, and Dispatch them along with another HTTP request I am performing. But I'm not so sure this is the best way. Should I be calling start() and stop() in each activity, or should I only call start() and stop() in my main launcher activity?
The problem with calling start()/stop() in every activity (as suggested by Christian) is that it results in a new "visit" for every activity your user navigates to. If this is okay for your usage, then that's fine, however, it's not the way most people expect visits to work. For example, this would make comparing android numbers to web or iphone numbers very difficult, since a "visit" on the web and iphone maps to a session, not a page/activity.
The problem with calling start()/stop() in your Application is that it results in unexpectedly long visits, since Android makes no guarantees to terminate the application after your last activity closes. In addition, if your app does anything with notifications or services, these background tasks can start up your app and result in "phantom" visits. UPDATE: stefano properly points out that onTerminate() is never called on a real device, so there's no obvious place to put the call to stop().
The problem with calling start()/stop() in a single "main" activity (as suggested by Aurora) is that there's no guarantee that the activity will stick around for the duration that your user is using your app. If the "main" activity is destroyed (say to free up memory), your subsequent attempts to write events to GA in other activities will fail because the session has been stopped.
In addition, there's a bug in Google Analytics up through at least version 1.2 that causes it to keep a strong reference to the context you pass in to start(), preventing it from ever getting garbage collected after its destroyed. Depending on the size of your context, this can be a sizable memory leak.
The memory leak is easy enough to fix, it can be solved by calling start() using the Application instead of the activity instance itself. The docs should probably be updated to reflect this.
eg. from inside your Activity:
// Start the tracker in manual dispatch mode...
tracker.start("UA-YOUR-ACCOUNT-HERE", getApplication() );
instead of
// Start the tracker in manual dispatch mode...
tracker.start("UA-YOUR-ACCOUNT-HERE", this ); // BAD
Regarding when to call start()/stop(), you can implement a sort of manual reference counting, incrementing a count for each call to Activity.onCreate() and decrementing for each onDestroy(), then calling GoogleAnalyticsTracker.stop() when the count reaches zero.
The new EasyTracker library from Google will take care of this for you.
Alternately, if you can't subclass the EasyTracker activities, you can implement this manually yourself in your own activity base class:
public abstract class GoogleAnalyticsActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Need to do this for every activity that uses google analytics
GoogleAnalyticsSessionManager.getInstance(getApplication()).incrementActivityCount();
}
#Override
protected void onResume() {
super.onResume();
// Example of how to track a pageview event
GoogleAnalyticsTracker.getInstance().trackPageView(getClass().getSimpleName());
}
#Override
protected void onDestroy() {
super.onDestroy();
// Purge analytics so they don't hold references to this activity
GoogleAnalyticsTracker.getInstance().dispatch();
// Need to do this for every activity that uses google analytics
GoogleAnalyticsSessionManager.getInstance().decrementActivityCount();
}
}
public class GoogleAnalyticsSessionManager {
protected static GoogleAnalyticsSessionManager INSTANCE;
protected int activityCount = 0;
protected Integer dispatchIntervalSecs;
protected String apiKey;
protected Context context;
/**
* NOTE: you should use your Application context, not your Activity context, in order to avoid memory leaks.
*/
protected GoogleAnalyticsSessionManager( String apiKey, Application context ) {
this.apiKey = apiKey;
this.context = context;
}
/**
* NOTE: you should use your Application context, not your Activity context, in order to avoid memory leaks.
*/
protected GoogleAnalyticsSessionManager( String apiKey, int dispatchIntervalSecs, Application context ) {
this.apiKey = apiKey;
this.dispatchIntervalSecs = dispatchIntervalSecs;
this.context = context;
}
/**
* This should be called once in onCreate() for each of your activities that use GoogleAnalytics.
* These methods are not synchronized and don't generally need to be, so if you want to do anything
* unusual you should synchronize them yourself.
*/
public void incrementActivityCount() {
if( activityCount==0 )
if( dispatchIntervalSecs==null )
GoogleAnalyticsTracker.getInstance().start(apiKey,context);
else
GoogleAnalyticsTracker.getInstance().start(apiKey,dispatchIntervalSecs,context);
++activityCount;
}
/**
* This should be called once in onDestrkg() for each of your activities that use GoogleAnalytics.
* These methods are not synchronized and don't generally need to be, so if you want to do anything
* unusual you should synchronize them yourself.
*/
public void decrementActivityCount() {
activityCount = Math.max(activityCount-1, 0);
if( activityCount==0 )
GoogleAnalyticsTracker.getInstance().stop();
}
/**
* Get or create an instance of GoogleAnalyticsSessionManager
*/
public static GoogleAnalyticsSessionManager getInstance( Application application ) {
if( INSTANCE == null )
INSTANCE = new GoogleAnalyticsSessionManager( ... ,application);
return INSTANCE;
}
/**
* Only call this if you're sure an instance has been previously created using #getInstance(Application)
*/
public static GoogleAnalyticsSessionManager getInstance() {
return INSTANCE;
}
}
The SDK now has a external library which takes care of all of this. Its called EasyTracker. You can just import it and extend the provided Activity or ListActivity, create a string resource with your code and you are done.
The tracker will only track the activity where it's executed. So, why don't you subclass an Activity which start it every time on onCreate:
public class GAnalyticsActivity extends Activity{
public void onCreate(Bundle icicle){
super.onCreate(icile);
tracker = GoogleAnalyticsTracker.getInstance();
tracker.start("UA-xxxxxxxxx", this);
}
// same for on destroy
}
Then, you extends that class for every activity you use:
public class YourActivity extends GAnalyticsActivity{
public void onCreate(Bundle icicle){
super.onCreate(icile);
// whatever you do here you can be sure
// that the tracker has already been started
}
}
The approach I am using is to use a Bound Service (I happen to be using one already so was spared the creation of extra boiler plate code.)
A Bound Service will only last as long as there are Activities bound to it. All the activities in my app bind to this service, so it lasts only as long as the user is actively using my application - therefore very much a real 'session'.
I start the tracker with a singleton instance of Application which I have extended and added a static getInstance() method to retrieve the instance:
// Non-relevant code removed
public IBinder onBind(Intent intent) {
tracker = GoogleAnalyticsTracker.getInstance();
tracker.startNewSession(PROPERTY_ID, MyApp.getInstance());
}
public boolean onUnbind(Intent intent) {
tracker.stopSession();
}
See: http://developer.android.com/guide/topics/fundamentals/bound-services.html
I did a time based split between visits in my app, working like this:
I've build a wrapper singleton Tracker object for the GoogleAnalyticsTracker where i keep the last time something got tracked. If that time's more then x seconds i treat it as a new visit.
Of course this is only useful if you track everything in your app, and may not be the best solution in every situation, works well for my app though.
It only supports trackPageView, but setCustomVar and trackEvent should be easily implemented..
Anywhere you need to track something just add the line:
Tracker.getInstance(getApplicationContext()).trackPageView("/HelloPage");
I usually do it in the onResume of an activity
Tracker gist
You will need something like this: http://mufumbo.wordpress.com/2011/06/13/google-analytics-lags-on-android-how-to-make-it-responsive/
That's on the previous version and used to work very well. Now I'm in the same struggle as you, as V2 doesn't seems to be very consistent.
I wonder if this is something that could be done using AOP.
Android can only use compile-time AOP methods so maybe something like AspectJ?
There's a little more info on using AspectJ in Android in this thread. The main issue being that you would still need to declare on classes you own.

Categories

Resources