Speaker 1: Never picked up your phone, open an app you use
Speaker 1: all the time, and then just nothing, just a frozen screen,
Speaker 1: maybe a little spinner, or you get that absolutely dreaded
Speaker 1: application not responding message asking if you want to force quit?
Speaker 2: Oh, I know that feeling. It's instantly frustrating, isn't It
Speaker 2: basically screams this app is broken at the user. And
Speaker 2: almost every single time that feeling, it's because the app's
Speaker 2: main thread got completely tied up.
Speaker 1: The main thread that's like the nerve center of the
Speaker 1: app on Android gap, the bit doing everything you see
Speaker 1: and interact with exactly.
Speaker 2: It's often called the UI thread, and for good reason,
Speaker 2: it draws everything on the screen, handles your taps, your swipes,
Speaker 2: runs crucial activity callbacks. Think of it like, well, a
Speaker 2: very busy air traffic controller for the UI.
Speaker 1: And if that controller gets bogged down trying to I
Speaker 1: don't know, calculate pie to a million places instead of
Speaker 1: landing planes.
Speaker 2: Everything grinds to a halt. The UI freezes solid.
Speaker 1: So blocking that main thread, that's the real villain behind
Speaker 1: the unresponsiveness, that jerky animations, the jank and yeah, ultimately
Speaker 1: that anr dialogue precisely Okay, so our source material for
Speaker 1: this deep dive asynchronous Android by Steve Lyles. It goes
Speaker 1: right into this problem. It's all about using the power
Speaker 1: of modern phones, you know, with their multiple process or
Speaker 1: core is just sitting.
Speaker 2: There right, leveraging that power to keep.
Speaker 1: Apps smooth and responsive by doing work off that critical
Speaker 1: main thread.
Speaker 2: Yeah, and our mission today is basically to unpack the
Speaker 2: key ideas and tools Android gives us for handling this
Speaker 2: background work properly. We want to give you, the listener,
Speaker 2: a kind of shortcut to understanding how to build apps
Speaker 2: that actually feel responsive, drawing straight from the book.
Speaker 1: Right, mission accepted, Let's dive in. So why is this
Speaker 1: such a big deal on mobile specifically compared to say
Speaker 1: a desktop app.
Speaker 2: Well, mobile devices, even though they're super powerful now, they
Speaker 2: still operate under tighter constraints than a desktop or a server.
Speaker 2: You know, limited battery, often less ram efficiency.
Speaker 1: Is key, right, And like you said, each Android app
Speaker 1: process usually just gets that one main thread and all
Speaker 1: the critical UI stuff happens there.
Speaker 2: And this is where it gets really painful if you're
Speaker 2: not careful, If you try to do anything that takes
Speaker 2: a noticeable amount of time reading a big file, doing
Speaker 2: heavy math, or the classic one making a network request.
Speaker 2: If you do that directly on them a main thread.
Speaker 1: That's when the trouble starts. How quickly does it become noticeable?
Speaker 1: Like how much time is too much?
Speaker 2: Oh, a user will feel a delay of even just
Speaker 2: a couple hundred milliseconds. But for smooth UI for animations
Speaker 2: and scrolling, you're aiming for sixty frames per second. That
Speaker 2: means you have about sixteen point sixty seven milliseconds to
Speaker 2: draw each frame.
Speaker 1: Wow, it's not much time at all, not at all.
Speaker 2: And if your main thread is busy doing something else
Speaker 2: for longer than that sixteen milliseconds, you drop a frame.
Speaker 2: That's the jank. Your smooth scrolling suddenly becomes a stuttering mess.
Speaker 1: And the absolute worst case the ANR.
Speaker 2: The ANR, Yeah, block that main thread for five whole seconds,
Speaker 2: and Android basically throws its hands up. This app is
Speaker 2: stuck and shows that dialogue offering to kill it. The
Speaker 2: system takes unresponsiveness very seriously.
Speaker 1: Even built in protections at the platform level, didn't they.
Speaker 2: They definitely did. You've got developer tools like strict mode,
Speaker 2: which can flash the screen red if you do slow
Speaker 2: operations on the main thread during development handy and crucially,
Speaker 2: starting back in Honeycomb API eleven, they introduced the network
Speaker 2: on main thread exception. If you try to make a
Speaker 2: network call on the main thread in a modern app,
Speaker 2: it just crashes boom.
Speaker 1: So they're literally stopping you from making that mistake.
Speaker 2: They are actively pushing you away from it, which leads
Speaker 2: to the fundamental solution the source hammers home. You have
Speaker 2: to offload long running work, heavy computation file io network calls,
Speaker 2: get them onto background threads, let the main thread focus
Speaker 2: on the UI.
Speaker 1: Simple idea, but I guess managing multiple threads isn't always straightforward.
Speaker 2: Not at all. That's why you run into the classic
Speaker 2: headaches of concurrent programming. You get correctness issues like race.
Speaker 1: Condition Race conditions was a simple example of that.
Speaker 2: The book uses a great one. Imagine an integer variable
Speaker 2: mind starting at zero, Thread A reads it cee zero
Speaker 2: before A can update it. Thread B also reads it
Speaker 2: ce zero. Then thread A adds one and writes one back.
Speaker 2: Then thread B adds one to the zero at red
Speaker 2: and writes one back.
Speaker 1: Ah. So even though two threads incremented, the result is
Speaker 1: one not two. Because they race to read the old
Speaker 1: value exactly.
Speaker 2: They tripped over each other and the result is wrong.
Speaker 2: You can also get liveness issues like deadlock. That's where
Speaker 2: thread A is waiting for a lock held by thread B,
Speaker 2: and thread B is waiting for a lock held by.
Speaker 1: Thread A like a gridlock.
Speaker 2: Nobody can move precisely, stuck forever.
Speaker 1: And Android adds its own specific wrinkles to these concurrency problems.
Speaker 2: It sure does. The activity life cycle is probably the
Speaker 2: biggest one. So you spin up a background thread and
Speaker 2: it holds a direct reference to your activity or maybe
Speaker 2: one of its views. Okay, now the user rotates to
Speaker 2: the phone, The system destroys the old activity instance and
Speaker 2: creates a new one. But your background thread might still
Speaker 2: be chugging along holding onto that reference to the old
Speaker 2: now dead activity, and that.
Speaker 1: Means the old activity can't be garbage collected exactly.
Speaker 2: Instant memory leak because something still has a grip on it.
Speaker 1: Ooh, memory leaks the bane of mobile development, they really are.
Speaker 2: And then there's the whole synchronization challenge. Your background thread
Speaker 2: does its work, it's a result, great, but how do
Speaker 2: you get that result back to the UI. You must
Speaker 2: update UI elements on the main thread, right, So getting
Speaker 2: data safely from the background thread to the main thread
Speaker 2: is a fundamental problem that the Android toolkit is designed
Speaker 2: to help solve.
Speaker 1: Okay, so this is why Android provides these specific asynchronous
Speaker 1: tools to give us safer managed ways to deal with threads,
Speaker 1: life cycle issues, and UI updates.
Speaker 2: That's the core idea. Instead of just dealing with raw
Speaker 2: JABA threads and hoping for the best, you use these
Speaker 2: higher level building blocks, which brings us neatly to the
Speaker 2: toolkit itself.
Speaker 1: Let's walk through that toolkit then, following the source. First
Speaker 1: up is the one everyone seems to know. Maybe the
Speaker 1: post child acinc tasks.
Speaker 2: Ah, Yes, good old acinc task introduce way back in
Speaker 2: API three, specifically to make it easier to do short
Speaker 2: background tasks and get results back to the UI. It's
Speaker 2: really a helper class for that common pattern, and.
Speaker 1: It uses that specific structure of callback methods right, each
Speaker 1: running on a particular thread.
Speaker 2: Correct, you've got on pre execute, which runs first on
Speaker 2: the main thread. Good for setting things up like showing
Speaker 2: a progress spinner. Then the main work course do in background.
Speaker 2: This runs on a background thread. This is where your
Speaker 2: slow operation goes, and critically, you cannot touch the UI
Speaker 2: directly from here.
Speaker 1: Right, But what if you need to show progress, like
Speaker 1: for a download.
Speaker 2: That's what published progress is for. You call it from
Speaker 2: inside do in background and that triggers it triggers on
Speaker 2: progress update, which runs back on the main thread, so
Speaker 2: you can safely update your progress bar or.
Speaker 1: A text view makes sense. And when do in background.
Speaker 2: Finishes, it returns a result which gets passed to on
Speaker 2: post execute, and on post execute also runs on the
Speaker 2: main thread, perfect for hiding the spinner showing the final data,
Speaker 2: updating the UI.
Speaker 1: And there's non canceled too if the task gets canceled
Speaker 1: before finishing.
Speaker 2: Yep, and a really key thing to remember. Each acing
Speaker 2: task object is single use. You create it, you execute
Speaker 2: it once, and that's it. You can't reuse it.
Speaker 1: Now. You mentioned earlier that how acing task runs tasks
Speaker 1: concurrently or one after another has changed over time. That
Speaker 1: sounds potentially confusing.
Speaker 2: Slight chuckle. Yeah, it's got a bit of a history.
Speaker 2: Originally API three it executed tasks serially, one at a time.
Speaker 2: Then in API four they switched it to run concurrently
Speaker 2: using a thread pool that could handle like up to
Speaker 2: one hundred and twenty eight tasks at once.
Speaker 1: Oh, that's a big change.
Speaker 2: Huge, and it caused problems because developers often implicitly relied
Speaker 2: on the old serial behavior. So from API eleven onwards
Speaker 2: they switched it back. The default execute method now runs
Speaker 2: tasks serially again.
Speaker 1: Okay, so the tricky period is really API four through ten,
Speaker 1: where just calling execute might give you unexpected concurrency.
Speaker 2: Exactly. That's the danger zone. If you need explicit control,
Speaker 2: you should use execute on executor. You can pass it
Speaker 2: acing task dop threadpool executor to get the concurrent behavior,
Speaker 2: or acing test serial executor to force serial execution regardless
Speaker 2: of the API level.
Speaker 1: So the advice is target API eleven or higher if
Speaker 1: you can test carefully on older versions, or maybe even
Speaker 1: reimplement it yourself if you need absolute consistency.
Speaker 2: Those are the main options. The source lays out.
Speaker 1: Yeah, okay, beyond the concurrency history, what are the other
Speaker 1: common traps with acing task? I feel like I've run
Speaker 1: into issues using them inside activities before.
Speaker 2: Ah. Yes, the activity life cycle strikes again. This is
Speaker 2: probably the most common pitfall. If you declare your acing
Speaker 2: task as a regular inner class or an anonymous inner
Speaker 2: class inside your activity.
Speaker 1: It automatically gets a reference to the activity instance.
Speaker 2: Right, precisely an implicit strong reference because it often needs
Speaker 2: to call activity methods or update views.
Speaker 1: But if the user rotates, the screen.
Speaker 2: System destroys the old activity and creates a new one.
Speaker 2: But your acing task might still be running in the background,
Speaker 2: holding onto that reference to the old destroyed activity.
Speaker 1: Preventing it from being garbage collected memory. Leak city population.
Speaker 2: You exactly leak city. So how do you fight that? Well?
Speaker 1: Can't you just call cancel on the acing task and
Speaker 1: the activities on destroy.
Speaker 2: Or something you definitely should call cancel, maybe an on
Speaker 2: pause or on destroy. It signals the task that it
Speaker 2: should stop on canceled gets called instead of on post execute.
Speaker 2: But cancel is just a request.
Speaker 1: It doesn't magically stop the doing background method.
Speaker 2: No, your code inside doing background needs to periodically check
Speaker 2: that is canceled flag and actually stop doing work if
Speaker 2: it's true. So cancelation helps, but it might not prevent
Speaker 2: the leak if the task takes a while to notice
Speaker 2: it's been canceled and the activity is already gone.
Speaker 1: Okay, So cancelation is good hygiene, but not a perfect
Speaker 1: leak prevention. What's the more robust solution? The book talks about.
Speaker 2: Using what's called a retained headless fragment.
Speaker 1: Okay, that sounds complicated.
Speaker 2: It sounds worse than it is. The idea is pretty neat.
Speaker 2: You create a fragment, but it doesn't have any UI,
Speaker 2: it's headless, and you call set retain instance true on it.
Speaker 1: What is set retain instance true.
Speaker 2: It tells the system when the activity is destroyed and
Speaker 2: recreated due to a configuration change like rotation, keep this
Speaker 2: fragment instance alive. Don't destroy and recreate it.
Speaker 1: Ah, so the fragment survives the rotation unlike the activity exactly.
Speaker 2: You host your assing task inside this retain fragment. The
Speaker 2: fragment can then hold onto the task safely. When the
Speaker 2: new activity instance is created, the fragment reattaches to it.
Speaker 1: And how does the fragment talk to the activity? Then?
Speaker 2: Typically using an interface? The activity implements the interface like
Speaker 2: task callbacks. The fragment gets a reference to the activity
Speaker 2: and it's on attached method checks if it implements the interface,
Speaker 2: and stores it, often as a weak reference just to
Speaker 2: be extra safe, though less critical here than with handlers.
Speaker 2: It detaches an on detach. The task running inside the
Speaker 2: fragment can then call back to the currently attached activity
Speaker 2: via the fragment.
Speaker 1: So the fragment acts like a stable, surviving container for
Speaker 1: the task, decoupling it from the activity's messy life cycle.
Speaker 2: Precisely. It's a much safer pattern for managing acing tasks
Speaker 2: tied to activities, and it's available in the support library,
Speaker 2: so it works way back.
Speaker 1: That's really clever. So bottom line for acing task. Good
Speaker 1: for simple short background jobs closely tied to the UI,
Speaker 1: especially needing progress updates. But watch out for leaks and
Speaker 1: use that retained fragment pattern if needed.
Speaker 2: Yep, that's a good summary. Now, moving deeper into the toolkit,
Speaker 2: the source introduces handler and handler thread. These are more
Speaker 2: like the fundamental building blocks. In fact, acinc task uses
Speaker 2: handlers under the hood.
Speaker 1: Okay, handlers, This involves the looper right, What exactly is
Speaker 1: a looper?
Speaker 2: A looper is basically a message loop for a thread.
Speaker 2: A thread associated with a leaper runs a loop that
Speaker 2: continuously checks a message queue. If there's a message or
Speaker 2: a task like a runnable in the quy you, the
Speaker 2: looper pulls it out and processes it. And the key
Speaker 2: thing is key thing is the main thread and Android
Speaker 2: already has a looper running on it. That's how it
Speaker 2: processes UI events, drawing updates and everything else. It's constantly
Speaker 2: checking its message queue.
Speaker 1: Okay, so the main thread is just a special thread
Speaker 1: that happens to have this built in message processing loop.
Speaker 2: You got it. And a handler is an object you
Speaker 2: create that's bound to a specific looper and therefore to
Speaker 2: that looper's thread.
Speaker 1: Bound to the looper, so bound to the thread essentially.
Speaker 2: Yes, The handler gives you a way to post tasks
Speaker 2: onto that specific threads message queue. You can call handler
Speaker 2: dot post runnable or handler dot send message message.
Speaker 1: Ah. So if I create a handler on the main
Speaker 1: thread which automatically binds it to the main threads looper,
Speaker 1: I can then pass that handler object to a background
Speaker 1: thread exactly, and the background thread can use it, say
Speaker 1: main thread handler dot post some runnable and some runnable
Speaker 1: will get added to the main threads queue and eventually
Speaker 1: run on the main thread.
Speaker 2: That's the core mechanism. It's the stamp way to get
Speaker 2: results or UI updates from a background thread back to
Speaker 2: the main thread safely. Your background thread does its calculation,
Speaker 2: then posts the runnable via the main thread handler to
Speaker 2: update a text view or.
Speaker 1: Whatever, and that explains methods like post delayed too just
Speaker 1: adds it to the queue to be run later right.
Speaker 2: Post delayed, post out time, post out front of queue.
Speaker 2: They all just manipulate how and when the task gets
Speaker 2: added to the target threads message queue and activity dot
Speaker 2: run on UI thread. Runnable is basically just a shortcut
Speaker 2: for getting a handler for the main thread and posting
Speaker 2: your runnable to it.
Speaker 1: What about sending message objects instead of runables?
Speaker 2: Messages are an alternative way to send work. They're often
Speaker 2: better if you need to pass structured data along with
Speaker 2: the request. A message object has fields like what, an
Speaker 2: integer code for the message type arge one, arch two,
Speaker 2: and objection for arbitrary data.
Speaker 1: So you define different message types.
Speaker 2: With what yeah, and then in your handler instead of
Speaker 2: just posting runnables, you override the handle message message msg method.
Speaker 2: Inside that method, you typically have a switch statement based
Speaker 2: on msg doob what to decide how to process different
Speaker 2: kinds of messages. The source also points out using message
Speaker 2: dot obtain to reuse message objects from a system pool,
Speaker 2: which is more efficient than creating new message every time.
Speaker 1: Okay, and handler thread? What's that for?
Speaker 2: Handler thread? Is just a convenience class provided by the SDK.
Speaker 2: It's basically a standard Java thread subclass that automatically sets
Speaker 2: up a looper on itself when it starts.
Speaker 1: Ah, so it gives you a ready made background thread
Speaker 1: that already has its own message Q and looper.
Speaker 2: Exactly. You start the handler thread, get its looper, create
Speaker 2: a handler bound to that looper, and now you have
Speaker 2: an easy way to post tasks to run sequentially on
Speaker 2: that specific background thread. It's really useful if you need
Speaker 2: a dedicated worker thread for a series of tasks like
Speaker 2: processing sensor data or something.
Speaker 1: Do handlers have the same memory leak problems as async
Speaker 1: tasks if you're not careful like using them as inner
Speaker 1: classes in inactivity?
Speaker 2: Oh? Absolutely, same exact problem. A non static inner handler
Speaker 2: class holds an implicit reference to the outer activity. If
Speaker 2: you post a delayed message or runnable to that handler
Speaker 2: and the activity gets destroyed before the message is processed.
Speaker 1: The handler and its pending message keep the old activity
Speaker 1: alive leak yep.
Speaker 2: So the solution is identical. Make your handler subclass static
Speaker 2: if it needs to talk back to the activity or
Speaker 2: its views, pass the activity context to it, and store
Speaker 2: it in a weak reference.
Speaker 1: Use weak reference so the garbage collector can still reclaim
Speaker 1: the activity if nothing else holds.
Speaker 2: It strongly exactly, and typically you'd have methods like attach
Speaker 2: activity and detach in your static handler class to set
Speaker 2: and clear that weak reference as the activity comes and
Speaker 2: goes standard safe pattern.
Speaker 1: Okay, So handlers and handler threads offer more fundamental control
Speaker 1: over threading and message passing. Good for when async task
Speaker 1: is too simple where you need fine grain control or
Speaker 1: communication between arbitrary threads.
Speaker 2: That's a great way to put it. More control, more flexibility,
Speaker 2: but also requires a bit more care with life cycle management.
Speaker 1: All right, Moving on in the toolkit. The next major
Speaker 1: piece the source covers is the loader framework. This sounds
Speaker 1: specifically data oriented.
Speaker 2: It absolutely is. Loaders are designed primarily for asynchronously loading
Speaker 2: data that's going to be displayed in UI components, especially
Speaker 2: things like list views or recycler views.
Speaker 1: Asynchronously loading data. Okay, well, what's their killer feature? Why
Speaker 1: use them over, say, an async task?
Speaker 2: Their main advantage is how they integrate with the activity
Speaker 2: and fragment life cycle. They automatically handle things like configuration changes,
Speaker 2: screen rotations, and can efficiently cache data and deliver updates
Speaker 2: without you having to manually manage all the state restoration.
Speaker 1: Ah. So they solve that reload data on rotation problem.
Speaker 2: That they do it beautifully. The core component is the
Speaker 2: loader manager. You get an instance of it from your
Speaker 2: activity or fragment. The loader manager is responsible for managing
Speaker 2: the life cycle of your loader.
Speaker 1: Instances and how do you interact with it.
Speaker 2: Your activity or fragment implements an interface called loadermanager dot
Speaker 2: loader callbacks. This has three methods you need to implement.
Speaker 1: Okay, what are they?
Speaker 2: First is on create loader. This is called by the
Speaker 2: loader manager when you initialize a loader using in it
Speaker 2: loader or restart loader. Your job here is just to
Speaker 2: create and return the specific loader instance you.
Speaker 1: Need, like a loader for database queries or maybe a
Speaker 1: custom one exactly.
Speaker 2: The second callback is onload finish. This is the important one.
Speaker 2: It gets called on the main thread when the loader
Speaker 2: has successfully finished loading its data. You receive that a
Speaker 2: here and update your UI like populating an adapter. And
Speaker 2: a third onloader reset This gets called when the loader
Speaker 2: is being destroyed or reset. Your job here is to
Speaker 2: clear out any references you might be holding to the
Speaker 2: loader's data, so it can be garbage collected, for example
Speaker 2: telling your adapter that the data is no longer valid.
Speaker 1: So the loader manager orchestrates the loading, caching, and life
Speaker 1: cycle awareness, and your activity fragment just plugs into these
Speaker 1: specific life cycle points.
Speaker 2: That's the idea. It abstracts away a lot of the
Speaker 2: tricky life cycle management. If you need to create your
Speaker 2: own custom loader. Acing taskloader is a common base.
Speaker 1: Class to use ACM taskloader, so it uses acing tasks
Speaker 1: in charge does.
Speaker 2: It provides the basic structure you implement the loading background method.
Speaker 2: That's where your actual data loading happens off the main thread.
Speaker 2: The framework handles delivering the result via onload finished. It
Speaker 2: also manages things like caching the result and only reloading
Speaker 2: if the underlying data has changed.
Speaker 1: The book gives an example. Right a thumbnail loader.
Speaker 2: Yes, loading a bitmap thumbnail Loading background does the slow
Speaker 2: work of decoding the image. Onload finished gets the bitmap
Speaker 2: back on the main thread to display it. On start
Speaker 2: loading checks if a cached version exists before starting the
Speaker 2: load on reset would be where you might recycle the
Speaker 2: bitmap if needed, and.
Speaker 1: You kick things off using loadermanager dot in it loader
Speaker 1: with some unique ID for each loader correct.
Speaker 2: That ID links the netloader call to your specific implementation
Speaker 2: of the loader callbacks.
Speaker 1: What about cursor loader That seems like a really common
Speaker 1: one one.
Speaker 2: It's a specialized subclass of acyink taskloader designed specifically for
Speaker 2: querying content providers, thin contacts, calendar data, or very commonly
Speaker 2: the Media Store for images, music, et cetera.
Speaker 1: So it handles queerying a database via a content provider.
Speaker 2: Yes, And crucially it manages the cursor object that comes
Speaker 2: back from the query. It handles opening it, closing it correctly,
Speaker 2: and even automatically re querying if the underlying data in
Speaker 2: the content provider changes.
Speaker 1: Oh wow, So it watches for data changes too?
Speaker 2: It can, Yes, and it works perfectly with adapters like
Speaker 2: cursor adapter for displaying data and lists.
Speaker 1: The Media store example in the book loading IMAX thumbnails
Speaker 1: into a list really highlights the life cycle benefit, doesn't it.
Speaker 1: You rotate the screen and boom, the data reappears almost
Speaker 1: instantly because the cursor loader managed by the loader manager
Speaker 1: survived the activity recreation. It just reconnects to the new
Speaker 1: activity instance and delivers the already loaded cursor data straight
Speaker 1: away in on load finished. No need to hit the
Speaker 1: database again just because the screen rotator.
Speaker 2: That is a massive user experience win and the book
Speaker 2: mentioned combining loaders, Yeah.
Speaker 1: A really powerful pattern. Imagine you use a cursor loader
Speaker 1: to get a list of say image IDs from the
Speaker 1: media store that populates your main list adapter. Then inside
Speaker 1: your adapter's buy and view method where you set up
Speaker 1: each list item view. As items become visible, you can
Speaker 1: use the same loader manager to initialize separate individual thumbnail
Speaker 1: loader instances using the image id is part of the
Speaker 1: unique loader id for each specific thumbnail. Ah So one
Speaker 1: main loader for the list structure, and then potentially many
Speaker 1: smaller on demand loaders for the images within the.
Speaker 2: Visible items exactly loading just what's needed asynchronously efficiently and
Speaker 2: handling all the life cycle stuff automatically. It's a very
Speaker 2: elegant solution for complex data displays.
Speaker 1: So loaders are definitely the way to go for async
Speaker 1: data loading tied to the UI, especially lists or grids
Speaker 1: where surviving configuration changes and getting automatic updates is important.
Speaker 2: Absolutely, especially when dealing with content providers via cursor loader.
Speaker 1: All right, next tool intense service. The name suggests it's
Speaker 1: related to services and intents it is.
Speaker 2: It's a specific subclass of service designed for a particular
Speaker 2: use case, running simple background tasks sequentially off the main
Speaker 2: thread in a way that's decoupled from the UI and
Speaker 2: guaranteed to complete.
Speaker 1: Guaranteed to complete, Yeah, even if the user navigates away
Speaker 1: from the app.
Speaker 2: Yes, that's the key benefit. It's designed for operations that
Speaker 2: need to finish reliably, like uploading a file or processing
Speaker 2: some data in the background, regardless of what the user
Speaker 2: is currently doing.
Speaker 1: How does it work.
Speaker 2: It uses a single background worker thread, a handworth thread
Speaker 2: actually internally. When you want it to do some work,
Speaker 2: you call start service with an intent containing any data
Speaker 2: the task needs.
Speaker 1: Okay, Sending an intent triggers the work right.
Speaker 2: The intent service receives these intents in its on handle
Speaker 2: intent intent method. This method runs on that background worker thread.
Speaker 2: You put your actual background processing code inside on handle
Speaker 2: intent and.
Speaker 1: It processes them one by one like a queue exactly.
Speaker 2: It maintains an internal queue of the incoming intents. It
Speaker 2: processes one intent completely in on handle intent, then automatically
Speaker 2: picks up the next one from the queue. When the
Speaker 2: queue is empty, the intent service automatically stops itself.
Speaker 1: Oh self stopping.
Speaker 2: That's convenience very you don't need to manually manage its
Speaker 2: life cycle as much. The source uses calculating the nth
Speaker 2: prime number as a simple example task you'd put in
Speaker 2: on handle intent.
Speaker 1: Since it's decoupled from the activity that started it, how
Speaker 1: do you get results back? You can't just return a
Speaker 1: value from.
Speaker 2: On handle intent, No, you need a different mechanism. One
Speaker 2: common pattern described in the source is using a pending
Speaker 2: intent the component starting the service. Say, an activity creates
Speaker 2: a pending intent that wraps an intent targeted back at itself.
Speaker 2: It puts this pending intent as an extra in the
Speaker 2: intent used to start the service.
Speaker 1: So you send a reply to address.
Speaker 2: With the worker request kind of yeah, inside on handle intent.
Speaker 2: Once the work is done, the intent service retrieves that
Speaker 2: pending intent from the incoming intent and calls its send method,
Speaker 2: passing back any result data in a result intent.
Speaker 1: And where does that result intent arrive?
Speaker 2: If the pending intent was created using activity dot create
Speaker 2: pending result, the result arise back in the original activities
Speaker 2: on activity result method. And this mechanism works even if
Speaker 2: the activity was recreated, for example, due to rotation while
Speaker 2: the service was running.
Speaker 1: That's pretty neat. What are other ways to communicate results.
Speaker 2: System notifications are very common, especially for longer tasks or
Speaker 2: work that finishes when the app might not be in
Speaker 2: the foreground. You just use notification compact, dot builder from
Speaker 2: within on handle intent to post or updata. Notification makes
Speaker 2: sense for things like upload complete exactly. You can also
Speaker 2: use broadcasts, which we'll talk more about. With regular services,
Speaker 2: the Intent service could broadcast an intent with the result
Speaker 2: and any interested component with a registered broadcast receiver could
Speaker 2: pick it up. For showing progress, notifications are again a
Speaker 2: good fit. You can use notification Compact, dot builder dot
Speaker 2: set progress to show a progress bar right in the notification.
Speaker 1: Okay, so Intent service use it for reliable, sequential background
Speaker 1: work that comes in as discrete requests. Intents needs to
Speaker 1: finish even if the app isn't visible and doesn't require
Speaker 1: complex concurrency like uploads, downloads, simple data processing jobs.
Speaker 2: That's the sweet spot for Intent service, simple reliable, queued work.
Speaker 1: But what if you need more flexibility, more control over threads,
Speaker 1: or different ways to communicate. That brings us to the
Speaker 1: base class the regular service.
Speaker 2: Right a plane service gives you a component that can
Speaker 2: run in the background independent of the UI, but it
Speaker 2: doesn't provide that built in worker thread and queuing mechanism.
Speaker 2: Like Intent service, you manage the threading yourself.
Speaker 1: So if I need multiple background threads working in parallel
Speaker 1: within my service.
Speaker 2: Then a regular service is what you'd use. You would
Speaker 2: typically create and manage your own thread pool inside the service,
Speaker 2: maybe using Java's executor service framework like executors dot nufix
Speaker 2: third pool.
Speaker 1: And where would you start the work?
Speaker 2: Usually in the on start command intent intent in flags
Speaker 2: in started method. This gets called every time start service
Speaker 2: is invoked. You'd take the task described by the intent
Speaker 2: and submit it to your executor service. The source shows
Speaker 2: a pattern where the service keeps track of active tasks
Speaker 2: and calls stop self started only when all tasks associated
Speaker 2: with a particular start command are complete.
Speaker 1: So much more manual control over concurrency in life cycle exactly.
Speaker 2: And because you have this flexibility, services also offer more
Speaker 2: ways to communicate back to other components beyond the pending
Speaker 2: intent notification options typical for Intent.
Speaker 1: Service like what what are the alternatives?
Speaker 2: One mechanism is using a messenger. This is based on handlers.
Speaker 2: The client, like an activity, creates a handler, usually on
Speaker 2: its main thread, to receive messages back from the service.
Speaker 2: It wraps this handle in a messenger object and passes
Speaker 2: the messenger to the service, maybe in the intent used
Speaker 2: to start it or through so.
Speaker 1: The activity sends the service away to message it back precisely.
Speaker 2: The service can then use this client provided messenger to
Speaker 2: send message objects back to the activities handler. These messages
Speaker 2: are processed on the activity's main thread via its handler's
Speaker 2: handle message method. Again, you need the static handler plus
Speaker 2: week reference pattern in the activity.
Speaker 1: For safety okay messenger for message based communication? What about
Speaker 1: direct calls? Binding?
Speaker 2: Binding is another major communication pattern for services. An activity
Speaker 2: or other component can call bind service instead of start
Speaker 2: service if the service allows binding. The system establishes a connection.
Speaker 2: The activity receives a callback on service connected, which provides
Speaker 2: it with a direct communication channel, usually an eyebinder interface
Speaker 2: to the service, so.
Speaker 1: The activity can then just call methods directly on the
Speaker 1: service object.
Speaker 2: Often yes, the ibinder returned by the service's on bind
Speaker 2: method typically provides a way to get a direct reference
Speaker 2: to the service instance itself, allowing the activity to call
Speaker 2: public methods on it.
Speaker 1: Sounds super convenient for tightly coupled interactions.
Speaker 2: It is very efficient for local ongoing communication but big
Speaker 2: warning sign memory leaks. If the service holds a strong
Speaker 2: reference back to the bound activity etexample, to send callbacks,
Speaker 2: you can easily create a leak if the activity is
Speaker 2: destroyed while still bound.
Speaker 1: So how do you do callbacks safely with a bound service?
Speaker 2: The recommended pattern, again from the source, involves interfaces and
Speaker 2: weak references. The activity implements a callback interface. When binding,
Speaker 2: the activity passes itself as the callback listener to the service.
Speaker 2: The service stores this listener only as a weak reference.
Speaker 2: When the service needs to notify the client, it retrieves
Speaker 2: the listener from the week reference, checking it's not in
Speaker 2: all and calls the interface method. Often the service uses
Speaker 2: an ACYNC task internally to do work and makes the
Speaker 2: callback from on post execute to ensure it lands on
Speaker 2: the main thread.
Speaker 1: Okay, so weak reference is key again for safe binding callbacks.
Speaker 1: Are there other options? Broadcasting?
Speaker 2: Yes, Broadcasting is always an option. The service can simply
Speaker 2: construct an intent with the results and broadcast it. Any
Speaker 2: component interested can register a broadcast receiver to listen for
Speaker 2: that intent.
Speaker 1: And using local broadcast manager is better for broadcasts just
Speaker 1: within your own app.
Speaker 2: Definitely, it's more efficient and more secure as the broadcasts
Speaker 2: don't leave your application process.
Speaker 1: The source mentioned something about checking if anyone received a broadcast.
Speaker 2: Ah yeah, using sen broadcast sync. Unlike the regular asynchronous
Speaker 2: end broadcast, this one sends the broadcast and waits for
Speaker 2: all receivers to process it before returning. Receivers can potentially
Speaker 2: modify the result data or code. The service can check
Speaker 2: if the broadcast was handled, for example, if the result
Speaker 2: code changed. If not, maybe nobody was listening, so the
Speaker 2: service could fall back to posting a notification instead. One
Speaker 2: tricky point. Send broadcast sync runs on the calling thread,
Speaker 2: so if your receiver needs the main thread, you might
Speaker 2: need to call send broadcast sync from the main thread
Speaker 2: within your service, or e g. From that acing tasks
Speaker 2: on post execute again.
Speaker 1: Lots of unication options with services, so use a regular
Speaker 1: service when you need fine grained control over background threads, concurrency,
Speaker 1: or when you need more complex ongoing interaction patterns like
Speaker 1: binding or messengers. Things like music players or concurrent downloads.
Speaker 1: Seem like prime candidate exactly.
Speaker 2: More power, more flexibility, more responsibility.
Speaker 1: Okay. Last tool in the main toolkit Alarm Manager. This
Speaker 1: sounds like it's for scheduling things way out in the
Speaker 1: future precisely.
Speaker 2: Alarm manager is a system service that lets your applications
Speaker 2: schedule code to be run at some point in future,
Speaker 2: even if your application isn't currently running, or even if
Speaker 2: the device is asleep.
Speaker 1: Even if the device is asleep. How does that work?
Speaker 2: Well, there are different types of alarms you can set.
Speaker 2: They vary based on two main things, the clock they
Speaker 2: use and whether they should wake the device up.
Speaker 1: Okay, what are the clock types?
Speaker 2: You have elapsic real time and RTC. Elapse Real time
Speaker 2: is based on the time since the device lasts booted.
Speaker 2: It ignores wall clock time and time zones, just counts
Speaker 2: milliseconds since boot. RTC real time clock is based on
Speaker 2: standard UTC wall clock time.
Speaker 1: So lapse of drill time is good for run this
Speaker 1: five minutes from now, and RTC is for run this
Speaker 1: at two pm tomorrow.
Speaker 2: Generally yes. And then for each of those clock tikes
Speaker 2: you have a regular version and a wake up version.
Speaker 2: What's the difference the regular versions? A lapse to real
Speaker 2: time in RTC will only fire the alarm when the
Speaker 2: device happens to be awake anyway for some other reason,
Speaker 2: after the schedule time is passed. If the device is
Speaker 2: asleep at the schedule time, the alarm is delayed until
Speaker 2: it wakes up ah.
Speaker 1: So they don't guarantee exact timing if the device is asleep.
Speaker 2: Correct the wake up versions elapse A drill time wake
Speaker 2: up and RTC awake up, however, will actively wake the
Speaker 2: device's CPU up if necessary to deliver the alarm at
Speaker 2: or near the scheduled time.
Speaker 1: Got it wake up alarms use more battery.
Speaker 2: Presumably significantly more yes, so you should only use them
Speaker 2: when timely execution is critical. The source also mentions that
Speaker 2: since API nineteen Kitcat, regular alarms set with set are
Speaker 2: inexact by default.
Speaker 1: Inexact. That mean the.
Speaker 2: System might batch alarms from different apps together and shift
Speaker 2: their delivery times slightly to save battery. If you absolutely
Speaker 2: need an alarm to fire at a precise time. On
Speaker 2: API nineteen plus Nature, you have to use set exact.
Speaker 2: For repeating alarms, they're set in exact repeating, which is
Speaker 2: preferred for battery life, and the older set repeating.
Speaker 1: Okay, how do you cancel an alarm you've set?
Speaker 2: You use the cancel method on the alarm manager, passing
Speaker 2: in the exact same pending intent you use when you
Speaker 2: originally set the alarm.
Speaker 1: The pending intent has to match exactly.
Speaker 2: Yes, the matching is done based on the intent inside
Speaker 2: the pend intent. Using intent dot filter equals that checks
Speaker 2: the action data type, category is, and component name, but
Speaker 2: it notably ignores any extras in the intent. Also, if
Speaker 2: you set a new alarm using a pending intent that
Speaker 2: matches an existing one, the old one is automatically canceled.
Speaker 1: Okay, so the alarm fires and it triggers the pending intent.
Speaker 1: What can that pending intent actually do?
Speaker 2: It can trigger one of three things, depending on how
Speaker 2: you created it. Pending intent. Dot get activity launches an
Speaker 2: ex activity, pending intent, dot get broadcast sends a broadcast,
Speaker 2: and pending intent. Dot get service starts a service.
Speaker 1: Launching an activity from an alarm sounds disruptive.
Speaker 2: It usually is taking over the user screen unexpectedly isn't
Speaker 2: great ux, So pending intent dot get activity for alarms
Speaker 2: is pretty rare.
Speaker 1: So it's usually a broadcast or a service.
Speaker 2: Most commonly it's pending intent dot get broadcast. You create
Speaker 2: a broadcast receiver to handle the alarm trigger. A big
Speaker 2: advantage is that if your receiver is registered in the manifest,
Speaker 2: statically the system can launch your app process just to
Speaker 2: deliver the alarm broadcast, even if your app wasn't running.
Speaker 1: Okay, but what can the broadcast receiver actually do? Did
Speaker 1: you say they have time limits?
Speaker 2: They do. The on receive method runs on the main
Speaker 2: thread and typically has only about ten seconds before the
Speaker 2: system might kill the process. So it's good for very
Speaker 2: quick tasks like scheduling the next alarm or maybe posting
Speaker 2: a notification.
Speaker 1: Can you do background work? You mentioned go ACYNC before.
Speaker 2: Yes. On API eleven plus APA A receive seiver can
Speaker 2: call go async within on receive. This tells the system, hey,
Speaker 2: I need a bit more time for background work. It
Speaker 2: gives you maybe up to ten seconds off the main thread,
Speaker 2: but you must call finish on the pending result object
Speaker 2: returned by go a sink when your background work completes.
Speaker 2: It extends the receiver's life slightly, but it's still not
Speaker 2: suitable for long running tasks.
Speaker 1: Okay, so what about penningintent dot get service? Can't the
Speaker 1: alarm just start a service directly to do the long work.
Speaker 2: You can do that, but the source points out it's
Speaker 2: often unreliable, especially for wake up alarms. Why because starting
Speaker 2: a service is a synchronous the alarm might fire. Start
Speaker 2: service gets called, but the device might go back to
Speaker 2: sleep before the service has fully started and had a
Speaker 2: chance to acquire its own wakelock.
Speaker 1: Ah, So the service might get started and then immediately
Speaker 1: killed as the device sleeps again.
Speaker 2: Exactly the work might never actually happen. So for reliable,
Speaker 2: long running background work triggered by a wake up alarm,
Speaker 2: there's a standard, more robust pattern who alarm manager triggers
Speaker 2: a penning intent get broadcast for a broadcast receiver. Because
Speaker 2: it's a broadcast triggered by a wake up alarm, the
Speaker 2: system guarantees the device is held awake long enough for
Speaker 2: on receive to start.
Speaker 1: Okay, receiver starts.
Speaker 2: Inside on receive. The very first thing the receiver does
Speaker 2: is acquire a partial wakelock. This tells the system keep
Speaker 2: the CPU running, I have work to do.
Speaker 1: Got the lock then what Then?
Speaker 2: While holding the lock, the receiver calls start service to
Speaker 2: start your actual worker service like an intent service or
Speaker 2: a custom service. It typically passes a reference to the
Speaker 2: wakelock or just lets the service acquire its own to
Speaker 2: the service.
Speaker 1: So the receiver's job is just get woken up, grab
Speaker 1: the lock, start the service and that's it pretty much.
Speaker 2: The service then starts up guaranteed to happen because the
Speaker 2: wakelock is held, does its potentially long running work, and critically,
Speaker 2: when the work is finished, the service is responsible for
Speaker 2: releasing the wekelock that.
Speaker 1: Ensures the device stays awake exactly as long as needed
Speaker 1: for the work, but no longer.
Speaker 2: Precisely, it's a handoff. The source mentions the wakeful broadcast
Speaker 2: receiver helper class which simplifies managing this weightlock handoff between
Speaker 2: the receiver and the service.
Speaker 1: Okay, that makes sense. So alarm manager for future scheduling,
Speaker 1: especially across app restarts or device sleep using the broadcast
Speaker 1: receiver plus wakelock plus service pattern for reliable, long running
Speaker 1: wakeful tasks.
Speaker 2: That's a reliable way to do it. Use it for
Speaker 2: scheduling periodic sinks, triggering reminders, running maintenance tasks at specific times.
Speaker 1: Wow, that's a comprehensive toolkit. So wrapping this up, the
Speaker 1: core message from Asynchronous Android seems crystal clear. Keeping that
Speaker 1: main UI thread free is paramount for a response of
Speaker 1: that you have to offload work.
Speaker 2: Absolutely, and we've walked through the spectrum of tools andre it. It
Speaker 2: gives you to achieve that each has its place, from
Speaker 2: the relatively simple UI focused async task to.
Speaker 1: The fundamental building blocks of handler looper and handler thread
Speaker 1: for more general purpose thread communication and scheduling.
Speaker 2: Then loaders, which are specialized for efficient life cycle aware
Speaker 2: data loading into the UI.
Speaker 1: Intense service for simple, reliable cue background work.
Speaker 2: The more powerful base service for when you need custom
Speaker 2: concurrency or more complex communication like binding.
Speaker 1: And finally, alarm manager for scheduling tasks well into the
Speaker 1: future even when the device is asleep using that wayclock
Speaker 1: pattern for reliability.
Speaker 2: It really forces you to think about concurrency and resource
Speaker 2: management right from the start.
Speaker 1: It really does. It highlights how designing for the sometimes
Speaker 1: harsh constraints of mobile limited battery, the absolute need for
Speaker 1: responsiveness managing life cycles really shapes how you approach software development.
Speaker 2: It definitely does, and it leaves us with a final thought,
Speaker 2: maybe something for you the listener too them all over,
Speaker 2: How does this deep dive into Android's asynchronous patterns, this
Speaker 2: necessity of keeping critical threads free and managing resources carefully
Speaker 2: on mobile? How does that inform or maybe change, how
Speaker 2: you think about write a software, even on platforms with
Speaker 2: seemingly abundant resources. Do these principles of responsive no news
Speaker 2: and careful concurrency management translate more broadly,