Async Task is Back – A Summary of Colt McAnlis’ Android Threads Talk @ Austin Droids


A few weeks ago we had Colt McAnlis visited us at the Austin Droids Meetup and discussed Concurrency. Below are my notes from the meetup.

Basic Threading Background

Most basic concurrency primitive: Thread. Thread begins, it does some work and it ends:

Screen Shot 2016-03-20 at 5.49.23 PM


If you want something longer lasting (E.g. a thread that sticks around for a long period time and grabs blocks of work from a queue and performs work for that block until it’s done, and moves onto the next) that entails more complexity. And Colt is Bald because of having to do this in C++.

Screen Shot 2016-03-20 at 6.01.23 PM

Fortunately there are threading primitives that make our lives easier:

Looper

Is essentially 2 things a long running thread, and a queue of tasks that it executes 1 at a time.

Handler

Interface for you to add work into a work queue. It hides all of the thread safety(balding causing) problems and allows us to do complex things like inserting the work at arbitrary places in the queue or even delay insertion into the queue.

Unit of Work

On Android there lots of ways to do concurrency. So keep in mind when you see things like intents, runnable and messages that they all represent the same thing. That is a unit of work that needs to be performed on some thread.

Screen Shot 2016-03-20 at 6.09.24 PM

Handler Thread

Looper and the Handler combined. This takes all the complexity that is involved and makes this nice uniformed interface for us to use.

Screen Shot 2016-03-20 at 6.18.04 PM

When your app is launched the OS creates a Handler Thread which is your main thread or UI thread. UI Drawing tries to happen every 16ms. As a result that is a bulk of what the main thread does. That is why it is also known as the UI thread. All the code you write runs on the main thread so you are competing with drawing updates. Our job as Android Developers is to make sure that we don’t interfere with the UI updates.

Hitchy Hitchy Bang Bang

When the smoothness of the UI is impacted from other work on the main thread(e.g. Clunky scrolling). This was called Jank apparently Colt hates this word :). And prefers Hitching as it’s the new hip word.

Systrace is good for getting a high level view of what is going on.

Concurrency Primitive Roundup

So the main concurrency primitives on Android are:
Async Task, Handler Thread, Thread Pool, Intent Services

Async Task

Screen Shot 2016-03-20 at 6.28.03 PM

This primitive is the worst in that it “allows you to shoot yourself in the foot” in so many ways.

Async Task should only be used for work that is 2ms in duration or less
All AsyncTasks by default operate synchronously as they share the same worker thread

Screen Shot 2016-03-20 at 6.31.24 PM
Async Task can be made to use a thread pool, but strange things still happen as the do before portion and do after portion still happen in a synchronous portion since they are running on the same thread. If you are trying to do this you should most likely be using a ThreadPool instead(Colt said it’s worse than Enums).

Implicit References of Async Task cause memory leaks.

As Colt points out this isn’t an Android Specific pitfall but Java. You may be asking yourself why Java Async Task exists in Android not in the Java framework. The reason is because of implicit references. When you have a nested inner class they have access to the outer class. E.g.

Class A{
    
     Void doSomeAMethod(){

      }    
 
    Class B{
     
     Public B(){
           doSomeAMethod(); // The fact that this can work means there is a reference to class A
          A.this.doSomeAMethod();// Explicitly using the implicit reference
     }           

     }
}
 
Now on Android let’s replace those with this:
Class A extends Activity{
  Class B Extends AsyncTask{
  ...
  }
}

We see that we just added concurrency to having an implicit reference which is where a leak can and will happen. If you are updating a UI element on post execute in an AsyncTask you are holding a reference to a View, and Views hold a reference to Activity(Context). If a user rotates their phone or scrolls the view off screen then those views and their attached activity is now leaked. When you see these seemingly random popups for an app that stopped(Random because you haven’t recently been using the app or maybe it was in the background from a few days ago) it’s because of this an Activity Leak like this.

Handler Thread

Anytime you have long running block of code that has nothing to do with the UI Thread use it.

When you have tons of work that needs to be done, and you don’t care about when it gets done, or the order that it gets done. Bonus in that it also decides when to create and terminate threads.

“Memory will always screw you” – Colt McAnlis

Each thread requires 64K of Memory Per Thread Minimum
Every app already has a ton of threads:

Screen Shot 2016-03-20 at 5.48.02 PM

Then think of all the other apps that are running on your device as well. Think if they have just as many threads running. That quickly eats up memory. Colt gave an interesting example where he was helping solve someone with their app where it kept crashing. They had a few threads, turned out that the other libraries they were using also had threads being used and all those threads combined were causing the out of memory issues. Final point Memory isn’t thread safe so always keep that in mind. E.g. if you try to update the a View from a thread that isn’t the UI thread it can and likely will blow up.

Screen Shot 2016-03-20 at 6.25.38 PM

Render Script

Thread pool executor is great, but most of the apps in the google play store are doing some sort of multimedia processing. And Render Script is designed for that, bonus cause the GPU can then be used to help do the work.

Ultimate Goal

Create work for the UI that doesn’t reference the UI
One possible solution is to associate work with id’s and let the fragment/activity keep track of what the completion of those ids mean. E.g. an id could designate an update to an ImageView. This is ideal as no references are kept to the Activity or Fragment.

Useful Work

In the event that the view for which work was done isn’t around, but we may want the results of the work for later we should be using a loader.

You can watch the full video here.

Leave a Reply

Your email address will not be published. Required fields are marked *