Nexus 7 (2012) Tablet Lag/Slowness Issue (aka Slollipop)

An Android tablet in a light green being charged by a portable battery on top of a desk.

I have a 2012 Google Nexus 7 tablet (nicknamed “Moses”) which developed ridiculous lag issues shortly after updating to Android 5.1.1 (“Lollipop”). This is not news– there are pages of Google search results with people complaining about similar issues with this combination of Nexus 7 tablets and Android 5.1.1. In my particular case, it took several tries over the course of nearly three minutes to even unlock the screen, and even after it was finally unlocked, the lag time was so bad it made the experience of using the tablet entirely frustrating.

I’d tried the usual suggestions one finds in those support forum threads– clearing the cache partition, turning off gesture tracing on the keyboard, etc.– but although the performance improved enough to make the tablet actually somewhat usable again, I’d still experience intermittent lags until my task killer app would shut down apps running in the background (i.e. Feedly, Buffer, etc.). I wound up removing several apps I considered no longer essential, and that seemed to resolve whatever was binding things up– but what’s the point of having a quad-core multimedia-friendly tablet if you are limited to only “essential” apps?

In the end, I finally wound up unlocking, rooting, wiping and re-installing the stock Android 5.1.1 kernel. So far, the ridiculously difficult screen unlocks, lag times, etc. have not returned, but I also haven’t re-installed any of the third-party apps which I’d previously had on the tablet– so I guess the jury’s still out for now. If those symptoms should return, at least I’m in a position to replace the stock Android kernel with something else.

The Situationally Aware Algorithm

Mountain lion lounging in a cottonwood tree during the heat of the day.

Developers often pick an algorithm for its speed, but sometimes it makes more sense to dynamically pick an algorithm based upon the resources available at a particular moment. For example– the “Update All” feature for installing available updates for applications on Android Lollipop devices. “Update All” will download and unpack available updates one after the other, without waiting for the first update it unpacked to be installed. This parallel work queue approach makes sense from a time-saving perspective . . . until you try doing it on a phone with only 400-500 MB of storage space.

When storage space is limited, the processes of downloading the next update and unpacking the last downloaded update rapidly fill up the storage, usually faster than the installing process can use and dispatch the files. Suddenly, you have multiple update error messages in your notifications due to “insufficient space.”

Now, our annoyed Android user has a choice:

  1. Clear their cached data in “Settings -> Storage” and hope this frees up enough space for the “Update All” feature to work properly.
  2. Update each Android app one at a time.

But what if the “Update All” process checked the available storage space before it started downloading updates, and based upon a particular threshold, decided between downloading all the available updates in rapid succession or downloading the first available update and waiting for it to be updated and purged before proceeding with the next download?

The idea of a “situationally aware algorithm” is not new. We can dynamically apply different sorting algorithms to data by determining the size of the dataset beforehand. We can determine how much free space remains in storage on an Android device by using the getFreeSpace() method. We have the means to inspect the resources available to us before we decide to tackle a particular problem, and yet more often than not we plunge ahead with a predetermined “one size fits all” approach.

I blame satisficing. I’m also guilty of it.

When I walk in the woods in my neighborhood, I never look up to see if a mountain lion might be perched on a limb I’m about to pass under. Mountain lions are a non-issue for me, and walking beneath tree branches is an experience which I take for granted will be uneventful.

Until, of course, it isn’t. 😉

Similarly, if we apply an algorithm for sorting large datasets to a smaller dataset, it will still give you results. It’s only when we come across a dataset which completely breaks the algorithm and get no results back (e.g. the dataset is so large it consumes all available RAM) we begin to question if the currently implemented approach to the problem is the correct one.

On the bright side, I think the adoption of practices like User Stories and Test Driven Development– particularly in team approaches where the stories, tests and code are written by different people– is gradually pushing development beyond the satisficing “just make it work” mentality into a more nuanced, situationally aware paradigm.

In the meantime, the best we can do is keep learning our craft, be patient with the status quo, and . . . start checking tree branches for mountain lions before we walk under them.