tech blog

AppNexus is today’s most powerful, open, and customizable ad tech platform. Advertising’s largest and most innovative companies build their businesses on AppNexus.

Weird Android bug

| Comments

The AppNexus Mobile Advertising SDK provides developers a fast and convenient way to monetize their apps. It’s a well documented and throughly tested open-source code with direct engineering support. While implementing the Android native ad solution, I ran across a puzzling issue that I’d like to share my investigation and hopefully save other Android developers some time in the future. For those who are not familiar with native advertising, the IAB has a very clear video.

The issue was that a registration call to bond a native ad response with an Android native view would fail silently, even though the debugging tool showed that the code was executed correctly.

To simplify it, let’s pretend you’re building an app that turns on/off a flashlight, in which there’re two runnables turnOnTheLight and turnOffTheLight. You would assume that if we call view.post(turnOffTheLight) first and then call view.post(turnOnTheLight), the light should be on. However, in the test run, the light is actually off after execution. I put break points and stepped through the code, turnOffTheLight was indeed posted first. Then what happened?

I downloaded the source code of Android SDK, stepped into the method post() and found this:

Android SDK source code from View.javaSource
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
 * <p>Causes the Runnable to be added to the message queue.
 * The runnable will be run on the user interface thread.</p>
 *
 * @param action The Runnable that will be executed.
 *
 * @return Returns true if the Runnable was successfully placed in to the
 *         message queue.  Returns false on failure, usually because the
 *         looper processing the message queue is exiting.
 *
 * @see #postDelayed
 * @see #removeCallbacks
 */
public boolean post(Runnable action) {
    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null) {
        return attachInfo.mHandler.post(action);
    }
    // Assume that post will succeed later
    ViewRootImpl.getRunQueue().post(action);
    return true;
}

It turns out that, if the view is not attached to the window, the runnable will be put in the RunQueue of the view hierachy - The run queue is used to enqueue pending work from Views when no Handler is attached. The work is executed during the next call to performTraversals on the thread.

Go back to the scenario above, when posting turnOffTheLight the view was not attached but was attached when posting turnOnTheLight. Thus, turnOnTheLight is posted to UI thread to be executed immediately and turnOffTheLight is not executed till the next performTraversals is called. The solution is very simple, post both runnables to the UI thread directly using the following method:

AppNexus SDK source codeSource
1
2
3
4
5
6
7
 Handler handler = new Handler(Looper.getMainLooper());
 handler.post(new Runnable() {
        @Override
        public void run() {
            // code
        }
});

In the end, this is not actually a bug in the app’s code, it’s more of a rare use case exposed a slient inconvenient behavior of an Android convenience method, that the call to APIs must be done in the proper sequence to get the correct result. Sharing here with Android developers who might run into this weird situation too.

Comments