Stages of my MUNI app, which goes out to the network to request bus arrival times:
App does a blocking network call in the UI thread, causing it to hang if the network is slow. (Implemented while testing, never released.)
App spawns a thread in the background to make the network call. Results/exceptions from the background thread are proxied back to the foreground thread via a Handler message (which, if you're not familiar with Android, basically lets you pass data via the implicit per-thread message loop).
App spawns a Service, conceptually a separate process that can run on without UI present, then serializes its request over AIDL (an Android-specific IDL and serialization library) to the service. The service then does the #2 pattern to run the network request on a background thread, and serializes its response via AIDL back to the IPC thread of the application, which then proxies it back to the foreground again via the Handler.
This design allows the Service to keep running even after the app closes -- allowing my phone to buzz me when my train is about to arrive -- along with allowing the app, when foregrounded, to get in on the same updates that are being computed in the background.
Whenever I take a moment to think about how all this works, I am not surprised computers crash all the time. (Consider in the above: what if the app is terminated due to running out of memory -- what tells the service that it's time to stop polling? What if you switch foreground views while the message is in IPC transit?)