If you haven’t heard of the 2048 game, this puzzle game got insanely popular after a hacker news article. Today, if we search 2048 on Google Play we get a listing of more than 250 apps!
Searching 2048 in Estar shows that the second most popular version of 2048, Estoty Entertainment Lab app, with more than 10 million downloads and average rating of 4.4, has only 3 energy stars compared to 4 or 5 of its counterparts.
In this blog post, I show how I used Eagle to study the energy consumption behavior of this app and reduced its idle energy by 87%. I fire up Eagle, connect the phone, and start energy profiling 2048 app. I play the game for a short while and look at the results. In addition to fine-grained energy breakdown among program entities such as threads and methods, Eagle also accurately reenacts the power draw over time of every major power-draining phone component. Browsing through these power lines, one thing that instantly caught my attention was that while playing the game, even though I had been idle most of the time, thinking next move and not interacting with app, the CPU and GPU power consumption of the app never dropped — the app continues to drain high power while I was idle.
So, next I collect another energy profile where I do not interact with the app at all. The app is just sitting idle on the screen not animating, not doing anything. As earlier, I saw that CPU and GPU again continue to drain excessive power.
Going back to the method energy breakdown by Eagle quite clearly shows that the app called library method nativeRender 1617 times even when the screen had no new content to show.
Thus Eagle has exactly pinpointed that I need to look at calls to Cocos2dxRenderer.nativeRender from Cocos2dxRenderer.onDrawFrame method for reducing the energy of this app. To fix this problem and generate a new app apk, I use apktool to decompile the app and edit dalvik bytecode files, in particular Cocos2dxRenderer.smali. Looking at Cocos2dxRenderer.smali file, we see that onDrawFrame directly calls nativeRender method without checking if the app has any new content to draw. Right after the frame is rendered, onDrawFrame is called again, creating a loop of repeated drawing.
The fix is simply that whenever there is a user action, such as pressing a key, increment a counter by a value corresponding to the number of times nativeRender should be called. Then, onDrawFrame decrements the counter and only calls nativeRender iff counter > 0. Even though bytecode is more verbose than Java, I just had to add 44 lines of code in Cocos2dxRenderer.smali file. The full patch can be found here.
Time for testing! I recompile the app using apktool, install it on the phone and collect idling energy profile. Now, I find that the CPU and GPU are indeed idle when the use is idle, and whether the user actively playing the game or being idle, the app gameplay has no noticeable difference.