Identifying (and fixing) a bug in Google’s official I/O App

For its flagship developer event (Google I/O 2013), Google released an app (also called Google I/O) to help people navigate the event. Naturally, I ran Little Eye on the Google I/O app to see how it performed, and I managed to stumble upon a serious bug in the app – If you went to the details screen of any session, the app starts to drain power.

Using Little Eye to investigate further, I realized that something is consuming a LOT of CPU, causing the power drain. So Little Eye quickly helped validate that an issue does exist. Next step was to narrow down where the problem was.

To get there, our next release (coming soon!) has a couple of features that help developers go deeper into an App’s context

1. Performance Score
The performance score is a new feature of Little Eye that gives a grade (between “A” and “F”) to an app that benchmarks how it performs against other apps out there. Looking at the breakdown of the Performance Score for the app, it immediately was obvious that the UI thread is consuming most of the CPU within the app.

performance_score_small

2. Threads View
Helps identify which thread is taking up most of the CPU. In this case, the offending thread is id.apps.iosched. This is not the full name unfortunately (we only see the last 16 characters), but two things indicate that this is the UI/main thread – the thread name matches the application name (com.google.android.apps.iosched), and that it’s Process ID matches the thread ID.

bad_thread_small

Also, by looking at the last activity change in the event graph, we know that the last activity to run was ‘SessionDetailActivity’.

activity_name_small

Now armed with all of this context, I decided to try and fix the issue. Luckily, Google has released the source code of the app recently, so I could dig deeper.

First I ran the app in the debugger and came to this page and just paused/suspended the app hoping to find the offending code from the stack. I did this a few times but never saw the apps code in the stack. I did see that each time, ViewRootImpl.performLayout was part of the stack. This all fits in – the layout is constantly being redrawn, which is why the UI thread is so active.

Since I couldn’t catch the thread while it was doing something in the app code, I decided to read the source to see what could be triggering this. I looked at the
Activity and then at the Fragment. After a few minutes of unsuccessfully trying to understand what is causing the redraws (i’m slow at reading new code), I added breakpoints in a few methods. Voila – I noticed that onGlobalLayout was getting called repeatedly.

private ViewTreeObserver.OnGlobalLayoutListener mGlobalLayoutListener
  = new ViewTreeObserver.OnGlobalLayoutListener() {
     @Override
     public void onGlobalLayout() {
        onScrollChanged();
        mAddScheduleButton.setVisibility(View.VISIBLE);
     }
};

On reading the documentation for OnGlobalLayoutListener, it is called on a layout change. And in it’s first line, it calls onScrollChanged()

@Override
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
public void onScrollChanged() {
  float newTop = Math.max(mAddSchedulePlaceholderView.getTop(), mScrollView.getScrollY());
  FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams)
  mAddScheduleButton.getLayoutParams();
  ...
  if (mAddSchedulePlaceholderView.getGlobalVisibleRect(mBufferRect)) {
      lp.leftMargin = mBufferRect.left - parentLeft;
      lp.rightMargin = parentRight - mBufferRect.right;
  }
  mAddScheduleButton.setLayoutParams(lp);
}

But the onScrollChanged is causing a layout change in the last line. Hence creating a loop!!

I changed the code to call onScrollChanged only the first time that onGlobalLayout is invoked. Hopefully I didn’t break something in the process :-) , but this has fixed the CPU problem!

All said, I was able to identify and fix the problem in under an hour with Little Eye. Given that I never worked on this app before – being able to reproduce the issue, identify the root cause, and come up with a (plausible) fix so fast – is pretty cool. If you use Little Eye against your own app, you can probably find and fix issues much faster.

One thought on “Identifying (and fixing) a bug in Google’s official I/O App