Apr 12 | 3:55 PM |
Mark M. | has entered the room |
Mark M. | turned on guest access |
Apr 12 | 4:15 PM |
Michaelidle | has entered the room |
Mark M. |
hello, Michaelidle!
|
Mark M. |
how can I help you today?
|
Michaelidle |
Hello! I'm new around here, but have seen some of your talks. Good to meet you.
|
Apr 12 | 4:20 PM |
Mark M. |
good to (e-)meet you too!
|
Michaelidle |
I'm a fairly experienced with Java, but still shaky when it comes to threading, especially on Android.
|
Michaelidle |
So please excuse the ignorance of my next few questions =)
|
Mark M. |
no worries
|
Michaelidle |
I have an IntentService, and inside of the onHandleIntent() method I open up a websocket, which has multiple callbacks onOpen, onFailure() etc. Am I digging myself a hole? I haven't pushed this into the app store yet, but I feel like it's wrong to have callbacks in an Android component (Service) that doesn't "exist" anymore. I'm not sure if I am making much sense.
|
Mark M. |
you are making complete sense
|
Mark M. |
and yes, off the cuff, this sounds bad
|
Mark M. |
IntentService is a simple solution for synchronous background work
|
Mark M. |
it is not designed to support asynchronous work, though
|
Mark M. |
for that case, use a regular Service, with your own background thread, and calling stopSelf() when you know that you are done with the service
|
Apr 12 | 4:25 PM |
Michaelidle |
Yes, so essentially I do a synchronous background operation, and at the end, my last requirement is to connect to a socket.
|
Michaelidle |
I didn't necessarily "want" to connect to the socket in the IntentService, but it was convenient because the IntentService work that is done, ends up returning with an Object that I need to connect to the web socket.
|
Mark M. |
¯\_(ツ)_/¯
|
Mark M. |
either convert the whole shooting match to a regular Service with your own thread, or break the work into two services
|
Michaelidle |
Another "requirement" is to essentially stay connected, until the process dies. If so, the web socket falls back onto GCM, so it's not a big deal.
|
Mark M. |
um, well, the point behind a service is to try to keep the process rolling for a bit longer
|
Michaelidle |
Okay, I may send it into another service entirely.
|
Michaelidle |
Yeah, I don't need to keep the process going too much longer. I'd be okay with this happening with the lifecycle of my activities.
|
Michaelidle |
So, what's the "bad" part of having callbacks in something like IntentService. onHandleIntent seems to finish, and my callbacks get called. Am I creating a leak?
|
Apr 12 | 4:30 PM |
Mark M. |
well, your use case is, um, odd
|
Mark M. |
the "and my callbacks get called" part will be unreliable
|
Michaelidle |
Unreliable? How so?
|
Mark M. |
once onHandleIntent() returns, if there is no more work to be done, the IntentService calls stopSelf() and destroys itself
|
Mark M. |
if, at this point, there are no more running services in your process, your process importance falls
|
Michaelidle |
Correct.
|
Mark M. |
hence, sometime after onHandleIntent() returns, your process will be terminated
|
Mark M. |
that could be milliseconds, seconds, or minutes later
|
Michaelidle |
My web socket service detects if I have disconnected and will fallback to sending me a push through GCM.
|
Mark M. |
not if your process is terminated, it won't
|
Mark M. |
if your process is terminated, everything is gone
|
Mark M. |
::poof::
|
Michaelidle |
Well, I think the server side of the web socket equation sends a message, sees that it couldn't sent it, and will send a GCM instead.
|
Mark M. |
if the other party disconnects, while your process is still running, *then* you will get a callback
|
Mark M. |
true, but this begs the question of why you're bothering with the WebSocket in the first place
|
Mark M. |
personally, I don't trust code where I expect the process to killed in the middle of my doing some work
|
Michaelidle |
Correct. I understand that I won't get a callback if my process dies. I'm okay with that, as the web socket server will detect that I didn't get the message and fallback onto GCM.
|
Michaelidle |
It's not that it's expected, it's just handling the case of the socket not being connected.
|
Mark M. |
if your service is not running, you have to expect your process to terminate at any moment
|
Apr 12 | 4:35 PM |
Mark M. |
including in the middle of whatever I/O you are doing (WebSocket, disk, etc.)
|
Michaelidle |
For example, if I reboot my phone and don't open my app, my server will send me a push notification. When I log back into the application, it will register for the socket, and use that socket for realtime communication.
|
Michaelidle |
Yeah, I see your point.
|
Mark M. |
for some number of milliseconds, yes
|
Mark M. |
now, that number of milliseconds may be "thousands" or "tens of thousands", or it may not be
|
Mark M. |
if the work you are doing in response to the message is disposable, I suppose what you're doing might work
|
Michaelidle |
So, onHandleIntent() returns, stopSelf gets called, and the IntentService destroys itself... but it does NOT destroy the callback...? correct?
|
Mark M. |
well, the IntentService itself does not know anything about your WebSocket stuff
|
Mark M. |
you may be at risk of garbage collection, so you'll need some static reference to that WebSocket (direct or indirect)
|
Mark M. |
that might come "for free" with whatever thread is involved in your WebSocket code (yours or inside some library)
|
Mark M. |
but otherwise, your WebSocket stuff should be oblivious to the service being destroyed... up until the process is terminated
|
Michaelidle |
So the IntentService can be destroyed even though there is a listener/callback inside of it? How is that possible? (I know you just said that the IntentService doesn't know about my web socket stuff). But again, what if we just took it to a more generic level and forget that it's a web socket, and just a callback.
|
Apr 12 | 4:40 PM |
Mark M. |
"How is that possible?" -- again, IntentService knows nothing about your code
|
Apr 12 | 4:40 PM |
Mark M. |
bear in mind that "destroyed" refers to a state in Android terminology
|
Mark M. |
it does not mean "garbage collected" or "has its bytes set to all 0's" or something
|
Mark M. |
"destroyed" means "Android's gonna ignore it and let go of any references to it"
|
Mark M. |
eventually, it will get garbage collected
|
Mark M. |
but none of that has anything to do with your code
|
Mark M. |
by the same token, it's not like, just because you registered an OnClickListener on a Button, that an activity cannot be destroyed
|
Michaelidle |
Wouldn't it be impossible to garbage collect the service though if there's a callback registered?
|
Mark M. |
that depends on who is holding what
|
Mark M. |
and that gets back to your earlier point about a memory leak, which I failed to address
|
Mark M. |
if the callback object has a reference to the IntentService, the IntentService can still be destroyed, but it cannot be garbage collected
|
Mark M. |
this would represent a memory leak
|
Mark M. |
your callback object might explicitly have a reference to the IntentService (e.g., you needed a Context and held onto the IntentService, since that's a Context)
|
Mark M. |
or it might have an implicit reference (e.g., your callback is an inner class of the IntentService)
|
Mark M. |
either would be a memory leak
|
Michaelidle |
I think I understand. So in an Activity, just because I register a callback on a button, doesn't mean that I leak the activity because I never call an unregister on the button. But the button is effectively destroyed, so the callback would never be called.
|
Mark M. |
right
|
Mark M. |
and, if nothing else has a reference to that callback, that callback will be garbage collected
|
Mark M. |
in your case, with the WebSocket, if the WebSocket code is calling back into your code, it must be managing some thread on your behalf
|
Apr 12 | 4:45 PM |
Mark M. |
so your callback probably cannot be garbage-collected, so long as all of that is outstanding
|
Michaelidle |
Aha. That's what I'm missing here, the callback is actually an object, right... so if no one is holding onto that callback, it could be GC'd?
|
Mark M. |
(I say "probably" because I do not know much about the WebSocket library in question, let alone its implementation details, beyond educated guesswork)
|
Michaelidle |
I think this is making more sense, and I think if I just wasn't using anonymous inner classes, this would be a little bit clearer.
|
Mark M. |
yeah, anonymous inner classes are handy but make this sort of work icky
|
Mark M. |
so, once your service is destroyed, you are counting on the WebSocket library for keeping itself around (e.g., by the running thread referencing it), and thereby keeping your callback around
|
Mark M. |
that might work
|
Mark M. |
that might not work
|
Mark M. |
it would depend upon the library implementation
|
Mark M. |
you could "force it" by having a static reference to your callback somewhere, which amounts to an intentional memory leak
|
Michaelidle |
Okay, so I think my solution, may be acceptable for right now. As you said before, the message I get is disposable. I may try to work out something better where I connect a socket at activity onStart and close the socket onStop, but traversing through activities seems like I may miss something... or make a ton of connections/disconnections to my server.
|
Apr 12 | 4:50 PM |
Michaelidle |
I really don't like to introduce statics, but maybe making a web socket helper/wrapper class singleton could work
|
Mark M. |
you could collect a heap dump, shortly after your IntentService is destroyed
|
Mark M. |
and see whether something is holding onto your callback
|
Apr 12 | 4:55 PM |
Michaelidle |
Great idea.
|
Michaelidle |
Shortly after the IntentService is destroyed or after the IntentService is GC'd?
|
Mark M. |
after it is destroyed should be OK
|
Mark M. |
the act of creating a heap dump basically does a full-process GC
|
Michaelidle |
Really? Didn't know that. Thanks for the heads up. I usually do a force gc via the ADM.
|
Mark M. |
well, the heap dump does not contain any garbage
|
Mark M. |
from the logs, it appears they do this by doing a full-process GC, then generating the dump
|
Mark M. |
it's possible that they just filter out garbage when writing the dump file, but leave the garbage in the process
|
Mark M. |
regardless, for your analysis, after the IntentService is destroyed (and give a few milliseconds), the IntentService should no longer be tied to a GC root, and so it will not appear in the heap dump
|
Apr 12 | 5:00 PM |
Mark M. |
that's a wrap for today's chat
|
Mark M. |
the transcript will be posted to https://commonsware.com/office-hours/ shortly
|
Mark M. |
the next office hours are Thursday at 9am US Eastern
|
Mark M. |
BTW, thanks for subscribing!
|
Mark M. |
and have a pleasant day!
|
Michaelidle | has left the room |
Mark M. | turned off guest access |