I am working on an app which listens for View events like scrolling, layout drawn using ViewTreeObserver. ViewTreeObserver has a method to check if it's alive before doing anything eg. adding listeners.
I have to reproduce the issue of dead / not alive ViewTreeObserver to see If my code works well in production. I don't see anything in android documentation to reproduce it.
I appreciate any help / pointers.
Thanks
In fact, if you check the source code of class ViewTreeObserver, there is a "kill" function to set mAlive to false, also only here, but it is never invoked.
/**
* Marks this ViewTreeObserver as not alive. After invoking this method, invoking
* any other method but {#link #isAlive()} and {#link #kill()} will throw an Exception.
*
* #hide
*/
private void kill() {
mAlive = false;
}
In my opinion, this observer will become unavailable (but is not un-alive, you couldn't use isAlive() to determine current observer's state) after:
You removed listener(s), such as
view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
The current activity containing the view is destroyed
If getViewTreeObserver is called before the view is attached, a variable mFloatingTreeObserver is returned. When the view is attached the first observer's listeners will be merged with the parent's listeners and the first getViewTreeObserver that you obtained will no longer be alive, as kill() is called in that merge method
Related
So in my understanding listener is usually used to trigger some class when some thing happened on particular place. (for example when we click the button we want to do some action).
On the other hand I see some places where callbacks are used to execute/enqueue some code in different place. (retrofit uses this to enqueue API calls)
What is the main difference between these 2?
In short, they are the same.
Really?
Yes. But there are some "theoretical" (and in some cases practical differences).
Concepts
Listener is a word that in Android, it's commonly associated with a View ClickListener or similar. Where there are methods like addxxxListener(...) etc.
A Callback is often heard in the context of "for this particular event" I supply a "callback" and I will be called back when something happens.
In practice, they are often just instances of some interface passed along.
In my limited experience (only about 10 years of Java/Android -now Kotlin as well-), the term is used interchangeably.
I normally think of a callback when I am expecting something to "call me back" when something happens, and a listener as something where I listen to an event but as you can see by reading this... they could be the same. :)
It's often mentioned that one could have multiple listeners, but one callback (but there's nothing enforcing this and I have seen all use cases you can think of where either term is used) "You can add multiple callbacks" is not uncommon, even though if there are "multiple" then it "must be a listener". In fact, if you go as far as Android's View, the method is view.setOnClickListener { }
So it's a Listener, but you can only set ONE. So the above rule about 1 callback, N listeners is already broken since the early days of Android.
In some cases though, the convention is more tied to the method name, rather than the class' name:
setXXXyyy(...) allows you to set "one" and only one yyy listener/callback.
addXXXyyy(...) allows you to add one or more (though not always, so read the documentation) yyy listener/callback.
An add is often (...but not always) accompanied by a removeXXXyyy(...) implying that if you keep/have a reference to a callback/listener you could remove it (and no longer be listening or be called back).
In the case of set, it's often expected that if needed you call set...(null) and pass that null to "remove" the sole listener/callback. (Views do this for you, you don't need to call it, but you can).
Anyway, don't quote me on this, but I'd argue they are the same. This is also more complicated when you involve other languages/frameworks/tools, where there's an even blurrier line.
In many instances, you will see callback used, but the method contains the word "Listener", and vice-verse, so don't get too crazy about it.
If you can, where you can, however, don't use either, just use Coroutines :)
For a concrete example of how both are used as the same thing...
Look at the Android View.setOnClickListener method:
/**
* Register a callback to be invoked when this view is clicked. If this view is not
* clickable, it becomes clickable.
*
* #param l The callback that will run
*
* #see #setClickable(boolean)
*/
public void setOnClickListener(#Nullable OnClickListener l) {
Notice something strange?
The method is called setOnClickListener yet the Javadocs say: "a callback..."
They are the same! Two ways of naming an interface. In general, Listener is also a callback and VICE VERSA!
for a type of event you can have many listeners , but one callback !
A callback is a procedure where you pass as an argument to another procedure. The procedure receiving the parameter can call it, or share it so some other procedures in the system can call it.
A listener watches for an event to be fired. For example, KeyListener waits for KeyEvents, a MessageListener waits for messages to arrive on a queue, and so on.
I have this code:
ViewThreeObserver observer = my_view.getViewTreeObserver();
observer.addOnScrollChangedListener(new OnScrollChangedListener() {
#Override
public void onScrollChanged() {
if(condition) {
//do something
}
}
});
Now I would remove listener on observer if condition is verified.
I've try with:
observer.addOnScrollChangedListener(null);
But I get an error that claim "ViewThreeObserver is not alive". What does it mean, and how I could remove listener correctly?
observer is a long-lived reference which has no guarantee to be valid for the lifetime of the view. Instead you can just call getViewTreeObserver on your view again and remove the listener (use removeOnScrollChangedListener as Ahmad mentioned).
my_view.getViewTreeObserver().removeOnScrollChangedListener(this);
Although this is a short-lived call, there is a potential of it being not alive so you could check isAlive on it beforehand (never experienced this myself).
You can also use isAlive on observer if you wanted to (most likely will not be alive) and use that to remove the listener. If observer is not alive you will need to call getViewTreeObserver anyway.
Quote for getViewTreeObserver
Returns the ViewTreeObserver for this view's hierarchy. The view tree
observer can be used to get notifications when global events, like
layout, happen. The returned ViewTreeObserver observer is not
guaranteed to remain valid for the lifetime of this View. If the
caller of this method keeps a long-lived reference to
ViewTreeObserver, it should always check for the return value of
isAlive().
I've seen many different variations of this here are a few:
Without checking isAlive
Checking isAlive on short-lived call
Use ViewTreeObserver#removeOnScrollChangedListener(...) instead of setting the listener to null.
I get an error that claim "ViewThreeObserver is not alive"
It's recommended that you check with ViewTreeObserver#isAlive() if the ViewTreeObserver is alive or not before removing the listener.
I need a way to run some code at the exact moment in which the activity is fully loaded, laid out, drawn and ready for the user's touch controls. Which method/listener does that?
Commonsware is right, without explaining what your are trying to do and why, it's not possible to answer your question and I suspect, with detail, you are probably thinking about it the wrong way.
However, I do have some code where I needed to do some very funky layout stuff after everything had been measured.
I could have extended each of the view classes in the layout and overriden onMeasure() but that would have been a lot of work. So, I ended up doing this. Not great, but it works.
mainMenuLayout is the layout I needed to get funky with. The onGlobalLayout callback is called when the layout has completed drawing. Utils.setTitleText() is where the funkiness takes place and as I pass mainMenuLayout to it, it has access to the position and size of all of the child views.
mainMenuLayout.getViewTreeObserver().addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
// only want to do this once
mainMenuLayout.getViewTreeObserver().removeGlobalOnLayoutListener(this);
// set the menu title, the empty string check prevents sub-classes
// from blanking out the title - which they shouldn't but belt and braces!
if (!titleText.equals("")){
Utils.setTitleText(_context,mainMenuLayout,titleText);
}
}
});
I've found that if I post a Runnable to the message queue, it will run after the content for the activity has been drawn. For example, if I want the width and height of a View, I would do this:
view.post( new Runnable() {
#Override
public void run() {
int width = view.getWidth(); // will be non-zero
int height = view.getHeight(); // will be non-zero
}
} );
I've found success with this anytime after I call setContentView().
onRestoreInstanceState method is the one called to restore UI state which is called after onResume .I think you can use this onRestoreInstanceState method.. and put your code after restoring UI state from the savedInstanceState...
Try onPostResume() called after onResume() at this moment the Activity instance should be visible and all underlying Views are rendered. In many situations this is true when onResume() is called as well.
Maybe it little helps:
#Override
public void onAttachedToWindow(){}
Using android-support-v4.jar and FragmentActivity (no fragments at this point)
I have an AsyncTaskLoader which I start loading and then change the orientation while the background thread is still running. In my logs I see the responses come through to the background requests. The responses complete and I expect onLoadFinished() to be called, but it never is.
As a means of troubleshooting, in the Manifest, if I set android:configChanges="orientation" onLoadFinished() gets called as expected.
My Activity implements the loader callbacks. In the source for LoaderManager.initLoader() I see that if the loader already exists, the new callback is set to the LoaderInfo inner object class but I don't see where Loader.registerListener() is called again. registerListener only seems to be called when LoaderManagerImpl.createAndInstallLoader() is called.
I suspect that since the activity is destroyed and recreated on orientation change and since it is the listener for callbacks, the new activity is not registered to be notified.
Can anyone confirm my understanding and what the solution so that onLoadFinished is called after orientation change?
Nikolay identified the issue - Thank you.
I was calling initLoader fron onResume(). The Android documentation states:
"You typically initialize a Loader within the activity's onCreate()
method, or within the fragment's onActivityCreated() method."
Read "typically" as a bit more emphatic than I did when it comes to dealing with configuration change life cycle.
I moved my initLoader call to onCreate() and that solved my problem.
I think the reason is that in FragmentActivity.onCreate() a collection of LoaderManagers is pulled from LastNonConfigurationInstance and in FragmentActivity.onStart() there is some start up work regarding Loaders and LoaderManagers. Things are already in process by the time onResume() is called. When the Loader needs instantiated for the first time, calling initLoader from outside onCreate() still works.
It's actually not the call to initLoader() in onCreate() that's fixing it. It's the call to getLoaderManager(). In summary, what happens is that when an activity is restarted, it already knows about the loaders. It tries to restart them when your activity hits onStart(), but then it hits this code in FragmentHostCallback.doLoaderStart()*:
void doLoaderStart() {
if (mLoadersStarted) {
return;
}
mLoadersStarted = true;
if (mLoaderManager != null) {
mLoaderManager.doStart();
} else if (!mCheckedForLoaderManager) {
mLoaderManager = getLoaderManager("(root)", mLoadersStarted, false);
// WTF: Why aren't we calling doStart() here?!
}
mCheckedForLoaderManager = true;
}
Since getLoaderManager() wasn't called yet, mLoaderManager is null. It therefore skips the first condition and the call to mLoaderManager.doStart().
You can test this by simply putting a call to getLoaderManager() in onCreate(). You don't need to call init / restart loaders there.
This really seems like a bug to me.
* This is the code path even if you aren't using fragments, so don't get confused by that.
I have a LocalService that exposes a Binder with some APIs. I create a Service Listener, just like this:
if (dataServiceListener == null) {
dataServiceListener = new DataServiceListener();
mainActivity.getApplicationContext().bindService
(new Intent(mainActivity, LocalService.class),
dataServiceListener.svcConn, mainActivity.BIND_AUTO_CREATE);
}
After I call the method that the Binder in dataServiceListener exposes, I get the response in the dataServiceListener onResult() method. Up to this point, no kind of issues, everything is working.
Some sort of problem occurs when I close the Activity that is waiting for the Service Listener callback and immediately reopen it. Even though I re-instantiate the dataServiceListener in onCreate(), I get two callbacks instead of one, the old one from the destroyed Activity and the latter (right) one; this way the results mix up on the UI.
Is there a way to tell the Service or the Service Listener that when the activity finishes, the callbacks must be avoided. Or maybe even destroy the ServiceListener objects.
I think this is the issue that Mark L. Murphy (Commonsware) described in "The Busy Coder's Guide to Android Development":
The biggest catch is to make sure that the activity retracts the listeners when it is done.
How can I do this? Is there a way to get rid of the useless listeners when the activity finishes?
Thank you!
I had the same issue. I was working in a remote sevice using AIDL. I got this problem when i am trying do unregister my listeners using the remove method from ArrayList Collection inside a foreach loop, because I was not using asBinder in the comparision. Searching fora solution, I find out the RemoteCallbackList class in Android API. This class does exactly what i needed, and what i think you should do, on a easy way, taken all reponsabilites for the hard work that involves this task.
From the Android API:
To use this class, simply create a single instance along with your service, and call its register(E) and unregister(E) methods as client register and unregister with your service. To call back on to the registered clients, use beginBroadcast(), getBroadcastItem(int), and finishBroadcast().
Broadcast sample:
int i = callbacks.beginBroadcast();
while (i > 0) {
i--;
try {
callbacks.getBroadcastItem(i).somethingHappened();
} catch (RemoteException e) {
// The RemoteCallbackList will take care of removing
// the dead object for us.
}
}
callbacks.finishBroadcast();
The code you show is for binding to a service. You do not show where you are registering a listener with that service. You apparently are, based upon your question and your reference to an onResult() method. Given the nature of your problem, I am going to guess that what you're doing is:
Binding to the service in onCreate()
In onServiceConnected(), you are calling some sort of setListener() method on the Binder
In that case, if we ignore configuration changes, the proper way to unwind matters would be to, in onDestroy(), call some removeListener() method on the Binder, then call unbindService().
Configuration changes, particularly in a pre-fragment world, make this complicated. It's the reason why this sample project (and the accompanying material in the book) is so icky. Binding is twitchy -- if you unbind from the old activity, and nothing else is keeping the service around, the service will shut down before the new activity gets a chance to bind. Binding is also state -- you cannot simply fail to unbind, lest you leak stuff.
So, the recipe becomes:
Bind to the service in onCreate() using the Application Context
In onServiceConnected(), call sort of setListener() method on the Binder
In onRetainNonConfigurationInstance(), make note of the fact that you're undergoing a configuration change, and return some Object that has your Binder, your Listener, and all the rest of your state
In onCreate(), use getLastNonConfigurationInstance() -- if it is null, proceed as normal, but if it is not null, hold onto that Binder and Listener and don't re-bind and re-register the listener
In onDestroy(), if the flag from Step #3 above is false (i.e., we are not undergoing a configuration change), call some removeListener() method on the Binder, then call unbindService().
Using fragments with setRetainInstance(true) can probably simplify this some, though I have not worked through a sample for that yet.
I had this issue too. You need to release all the resources,listeners,threads from the service when it finishes.
Your activity has to register/unregister itself as the listener. You need to use the proper lifecycle callback methods, not onBackPressed(). Register onStart(), unregister onStop(). One way to do it is to make the listener a static member of your service, and provide static register/unregister methods. Then call those from your activity as appropriate.
I finally solved the issue (and no, I haven't been working on it for so long :D).
The callback to the listener was made before the Fragment's onDestroy was called. So the boolean "dontupdate" value was never set to false. Overriding onBackPressed in the main activity solved the problem, as I invoked a destroy() method for each fragment that takes care of setting the boolean value to false.