Wednesday, March 1, 2017

Getting Santa Tracker Into Shape



Posted by Sam Stern, Developer Programs Engineer



Santa Tracker is a holiday tradition at Google.  In addition to bringing seasonal joy to millions of users around the world, it's a yearly testing ground for the latest APIs and techniques in app development.  That's why the full source of the app is released on Github every year.




In 2016, the Santa team challenged itself to introduce new content to the app while also making it smaller and more efficient than ever before.  In this post, you can read about the road to a more slimmer, faster Santa Tracker.

APK Bloat

Santa Tracker has grown over the years to include the visual and audio assets for over a dozen games and interactive scenes.  In 2015, the Santa Tracker APK size was 66.1 MB.




The Android Studio APK analyzer is a great tool to investigate what made the 2015 app so large.



Screenshot from 2017-02-01 11:59:09.png




First, while the APK size is 66.1 MB, we see that the download size is 59.5MB! The majority of that size is in the resources folder, but assets and native libraries contribute a sizable piece.




The 2016 app contains everything that was in the 2015 app while adding four completely new games.  At first, we assumed that making the app smaller while adding all of that would be impossible, but (spoiler alert!) here are the final results for 2016:



Screenshot from 2017-02-01 12:06:23.png




The download size for the app is now nearly 10MB smaller despite the addition of four new games and a visual refresh. The rest of this section will explore how we got there.

Multiple APK Support on Google Play with APK Splits

The 2015 app added the "Snowdown" game by Google's Fun Propulsion Labs team.  This game is written in C++, so it's included in Santa Tracker as a native library.  The team gave us compiled libraries for armv5, armv7, and x86 architectures.  Each version was about 3.5MB, which adds up to the 10.5MB you see in the lib entry for the 2015 APK.




Since each device is only using one of these architectures, two thirds of the native libraries could be removed to save space - the tradeoff here is that we�ll publish multiple APKs.  The Android gradle build system has native support for building an APK for each architecture (ABI) with only a few lines of configuration in the app's build.gradle file:





ext.abiList = ['armeabi', 'armeabi-v7a', 'x86']
android {
   
   // ...
   splits {
       abi {
           // Enable ABI splits
           enable true
           // Include the three architectures that we support for snowdown
           reset()
           include(*abiList)
           // Also build a "universal" APK that will run on any device
           universalApk true
       }
   }
}




Once splits are enabled, each split needs to be given a unique version code so that they can co-exist in the Play Store:





// Generate unique versionCodes for each APK variant: ZXYYSSSSS
//   Z is the Major version number
//   X is the Minor version number
//   YY is the Patch version number
//   SSSS is information about the split (default to 0000)
// Any new variations get added to the front
import com.android.build.OutputFile;
android.applicationVariants.all { variant ->
   variant.outputs.each { output ->
       // Shift abi over by 8 digits
       def abiFilter = output.getFilter(OutputFile.ABI)
       int abiVersionCode = (abiList.indexOf(abiFilter) + 1)
       // Merge all version codes
       output.versionCodeOverride = variant.mergedFlavor.versionCode + abiVersionCode
   }
}




In the most recent version of Santa Tracker, we published versions for armv5, armv7, and x86 respectively.  With this change in place, 10.5MB of native libraries was reduced to about 4MB per variant without losing any functionality.

Optimize Images

The majority of the Santa Tracker APK is image resources. Each game has hundreds of images, and each image comes in multiple sizes for different screen densities. Almost all of these images are PNGs, so in past years we ran PNGCrush on all of the files and figured our job was done.  We learned in 2016 that there have been advancements in lossless PNG compression, and Google's zopfli tool is currently the state of the art.




By running zopflipng on all PNG assets we losslessly reduced the size of most images by 10% and some by as much as 30%. This resulted in almost a 5MB size reduction across the app without sacrificing any quality. For instance this image of Santa was losslessly reduced from 10KB to only 7KB.  Don't bother trying to spot the differences, there are none!







Before (10.2KB)
After (7.4KB)
santa-before.png
santa-before-zopfli.png




Unused Resources

When working on Santa Tracker engineers are constantly refactoring the app, adding and removing pieces of logic and UI from previous years. While code review and linting help to find unused code, unused resources are much more likely to slip by unnoticed.  Plus there is no ProGuard for resources, so we can't be saved by our toolchain and unused images and other resources often sneak into the app.




Android Studio can help to find resources that are not being used and are therefore bloating the APK.  By clicking Analyze > Run Inspection by Name > Unused Resources Android Studio will identify resources that are not used by any known codepaths.  It's important to first eliminate all unused code, as resources that are "used" by dead code will not be detected as unused.




After a few cycles of analysis with Android Studio's helpful tools, we were able to find dozens of unused files and eliminate a few more MB of resources from the app.

Memory Usage

Santa Tracker is popular all around the world and has users on thousands of unique Android devices.  Many of these devices are a few years old and have 512MB RAM or less, so we have historically run into OutOfMemoryErrors in our games.




While the optimizations above made our PNGs smaller on disk, when loaded into a Bitmap their memory footprint is unchanged.  Since each game in Santa Tracker loads dozens of images, we quickly get into dangerous memory territory.




In 2015 six of our top ten crashes were memory related. Due to the optimizations below (and others) we moved memory crashes out of the top ten altogether.

Image Loading Backoff

When initializing a game in Santa Tracker we often load all of the Bitmaps needed for the first scene into memory so that the game can run smoothly.  The naive approach looks like this:






private LruCache<Integer, Drawable> mMemoryCache;
private BitmapFactory.Options mOptions;
public void init() {
 // Initialize the cache
 mMemoryCache = new LruCache<Integer, Drawable>(240);
 // Start with no Bitmap sampling
 mOptions = new BitmapFactory.Options();
 mOptions.inSampleSize = 1;
}
public void loadBitmap(@DrawableRes int id) {
   // Load bitmap
   Bitmap bmp = BitmapFactory.decodeResource(getResources(), id, mOptions);
   BitmapDrawable bitmapDrawable = new BitmapDrawable(getResources(), bmp);
   
   // Add to cache
   mMemoryCache.put(id, bitmapDrawable);
}




However the decodeResource function will throw an OutOfMemoryError if we don't have enough RAM to load the Bitmap into memory.  To combat this, we catch these errors and then try to reload all of the images with a higher sampling ratio (scaling by a factor of 2 each time):





private static final int MAX_DOWNSAMPLING_ATTEMPTS = 3;
private int mDownsamplingAttempts = 0;
private Bitmap tryLoadBitmap(@DrawableRes int id) throws Exception {
   try {
       return BitmapFactory.decodeResource(getResources(), id, mOptions);
   } catch (OutOfMemoryError oom) {
       if (mDownSamplingAttempts < MAX_DOWNSAMPLING_ATTEMPTS) {
           // Increase our sampling by a factor of 2
           mOptions.inSampleSize *= 2;
           mDownSamplingAttempts++;
       }
   }
   throw new Exception("Failed to load resource ID: " + resourceId);
}




With this technique low-memory devices will now see more pixelated graphics, but by making this tradeoff we almost completely eliminated memory errors from Bitmap loading.

Transparent Pixels

As mentioned above, an image's size on disk is not a good indicator of how much memory it will use. One glaring example is images with large transparent regions.  PNG can compress these regions to near-zero disk size but each transparent pixel still demands the same RAM.




For example in the "Dasher Dancer" game, animations were represented by a series of 1280x720 PNG frames.  Many of these frames were dominated by transparency as the animated object left the screen.  We wrote a script to trim all of the transparent space away and record an "offset" for displaying each frame so that it would still appear to be 1280x720 overall. In one test this reduced runtime RAM usage of the game by 60MB! And now that we were not wasting memory on transparent pixels, we needed less downscaling and could use higher-resolution images on low-memory devices.

Additional Explorations

In addition to the major optimizations described above, we explored a few other avenues for making the app smaller and faster with varying degrees of success.

Splash Screens

The 2015 app moved to a Material Design aesthetic where games were launched from a central list of 'cards'. We noticed that half of the games would cause the card 'ripple' effect to be janky on launch, but we couldn't find the root cause and were unable to fix the issue.




When work on the 2016 version of the app we were determined to fix the game jank launch. After hours of investigation, we realized it was only the games fixed to the landscape orientation that caused jank when launched.  The dropped frames were due to the forced orientation change. To create a smooth user experience, we introduced splash screens in between the launcher Activity and game Activities.  The splash screen would detect the current device orientation and the orientation needed to play the game being loaded and rotate itself at runtime to match.  This immediately removed any noticeable jank from game launches and made the whole app feel smoother.

SVG

When we originally took on the task of reducing the size of our resources, using SVG images seemed like an obvious optimization.  Vector images are dramatically smaller and only need to be included once to support multiple densities.  Due to the 'flat' aesthetic in Santa Tracker, we were even able to convert many of our PNGs to tiny SVGs without much quality loss. However loading these SVGs was completely impractical on slower devices, where they would be tens or hundreds of times slower than a PNG depending on the path complexity.




In the end we decided to follow the recommendation limiting vector image sizes at 200x200 dp and only used SVG for small icons in the app rather than large graphics or game assets.

Conclusions

When we started building Santa Tracker 2016 we were faced with a daunting problem: how can we make the app smaller and faster while adding exciting new content? The optimizations above were discovered by constantly challenging each other to do more with less and considering resource constraints with every change we made. In the end we were able to incrementally make the Santa Tracker app as healthy as it has ever been ... our next job will be helping Mr. Claus work off all that extra cookie weight.

Monday, February 27, 2017

Welcome to Google Developer Day at Game Developer Conference 2017

Posted by Paul Bankhead, Director, Product Management, Google Play 



Mobile gaming is more popular than ever. Over the past year, we saw breakout hits, including Pokemon GO, Star Wars: Galaxy of Heroes, Clash Royale and Reigns introduce new, high quality gaming experiences on Google Play. Gamers around the world were also able to access Google Play more easily than ever before, helping developers reach a larger audience and grow their businesses. In 2016, nearly 300 million new (30 day active) users adopted Android devices from emerging markets such as India, Brazil, and Indonesia. And last year, more than 100 million new users were able to access locally relevant forms of payments (such as direct carrier billing or gift cards) helping more people globally access and buy their favorite apps and games on Google Play.



We've also focused heavily on polishing our software and hardware offerings to improve the overall gaming experience on Android. The release of Nougat delivered high-performance realtime 3D graphics with the Vulkan API and the launch of Pixel phones provided the first Daydream-ready devices tailored for immersive mobile VR. Elsewhere, the expansion of Firebase provided the tools and infrastructure to support developers throughout the lifecycle of their game with features like real-time analytics, push notifications, storage, and ads. To streamline the integration, Firebase is now completely available for C++ and Unity developers.

NEW FEATURES TO HELP YOU SUCCEED ON GOOGLE PLAY

Today, during our annual Developer Day at the Game Developers Conference, we introduced new tools to improve the overall discovery on Google Play, especially supporting developers who build high quality and engaging games.
  • Promoting high quality experiences based on engagement, not just installs: With the enormous variety of games available on Google Play, there are many instances when great games don't get the visibility and attention they deserve. Recently, we've begun tuning our algorithms to optimize for user engagement, not just downloads. This is one of our ways to reward quality, which for games means promoting titles with stickiness (strong engagement and retention metrics) as well as a more traditional measure like a high star rating.



  • Offer sales and increase purchases of premium games with strikethrough pricing: Available in the Google Play Developer Console starting today, strikethrough pricing allows developers to run their own price promotions on paid apps and games leading to greater awareness and conversion. During our pilot phase, developers not only saw a 3x�20x lift in installs during their promotions, they also maintained a nice lift once the sales ended.





  • More curation of high quality games through editorial pages: One more way we'll highlight quality games is through new editorial pages on the Play store launching later this month. These pages allow our editors to hand-select games exemplifying optimal gaming experiences on Android. They allow users to explore different game styles and genres with editorial reviews on themes such as epic RPGs and top racing games.



UPCOMING GAMES ON GOOGLE PLAY

At our Developer Day, we also gave attendees a sneak peek at some high-fidelity games coming to Google Play later this year. Including titles from major studios to indies, and even two new VR titles, there's something for every gamer!


Available for pre-registration on Google Play

  • TRANSFORMERS: Forged to Fight by Kabam is a new high-definition, action-fighting mobile game set in an immersive world. The game will feature authentic Transformers "more than meets the eye" action, allowing players to engage with Optimus Prime, Megatron and many other popular Autobots and Decepticons in a stunning 3D environment. The game will be available worldwide on April 5, 2017.

  • Battle Breakers is a new frenetic tactical role-playing game from Epic Games, powered by Unreal Engine 4. A vibrant fantasy sci-fi cartoon adventure, Battle Breakers lets you recruit and build a dream team from hundreds of unique heroes to battle monsters as you take back the Kingdom, one break at a time!

  • Injustice 2 lets you guide your stable of Super Heroes and Villains to victory. Expanding on the hit game Injustice: Gods Among Us, Injustice 2 delivers brand new characters, tons of exciting new modes and the look and fighting style that NetherRealm Studios is known for. Injustice 2 will be available on Google Play for Android devices in May.

Coming soon

  • Virtual Rabbids is the first VR Rabbids experience on mobile developed by Ubisoft Montpellier in collaboration with Bucharest. Available this spring on Daydream, players will find themselves in some of the most precarious situations as they race to save the planet.

  • Beartopia is a cooperative multiplayer village game by Spry Fox. Make friends, work together and grow a thriving community.

Later this afternoon, we'll host a series of lightning talks to share what it takes to launch successful VR and AR games, build with Firebase, implement machine learning in your game, and so much more. Visit our site for more info and the Google Developer Day schedule. For those who can't make it in person, watch the live stream!



This is just the start of what we have planned for 2017. We hope you can make use of these tools to improve your games, engage your audience, and grow your business and revenue.





How useful did you find this blogpost?
? ? ? ? ?


Thursday, February 23, 2017

Keeping up to Date with the Support Library



Posted by Agustin Fonts, Product Manager, Android Support Library



It's important to keep current when you're dealing with technology. That�s why we're constantly working to improve the quality of our software, particularly libraries that are linked into your apps, such as the Support Library.  The Support Library is a suite of libraries that provides backward compatibility along with additional features across many Android releases.






We have just released version 25.2 of the Support Library.  If you're making use of the android.support.v7.media.MediaRouter class in revision 25.1.1 or 25.1.0, we strongly recommend that you update due to a known issue.  If you haven't updated recently, you've missed out on some great bug fixes such as these:






25.2:



  • Corrected a severe mediarouter issue in which using an A2DP Bluetooth device and media routing APIs could cause the device to become unresponsive, requiring a reboot



  • Showing a slide presentation with screen mirroring no longer causes the device to disconnect from Wi-Fi



  • Media button now properly handles media apps that did not register themselves with setMediaButtonReceiver()



  • TextInputLayout correctly overlays hint and text if text is set by XML (AOSP issue 230171)



  • Corrected a memory leak in MediaControllerCompat (AOSP issue 231441)



  • RecyclerView no longer crashes when recycling view holders (AOSP issue 225762)




Reporting (and fixing) a Bug





The Support Library is developed by the Android Framework and Developer Relations teams, and, just like the Android platform, you can file bugs using the AOSP issue tracker, or submit fixes to our Git repository. Your feedback is critical in helping us to make the Support Library the most productive environment to use for developing Android applications.


Wednesday, February 22, 2017

Publish your app with confidence from the Google Play Developer Console


Posted by Kobi Glick, Product Manager, Google Play



Publishing a new app, or app update, is an important and exciting milestone for
every developer. In order to make the process smoother and more trackable, we're
announcing the launch of a new way to publish apps on Google Play with some new
features. The changes will give you the ability to manage your app releases with
more confidence via a new manage releases page in the Google Play Developer Console.














Manage your app updates with clarity and control




The new manage releases page is where you upload alpha, beta, and production
releases of your app. From here, you can see important information and the
status of all your releases across tracks.








The new manage releases page.


Easier access to existing and new publishing
features





Publishing an app or update is a big step, and one that every developer wants to
have confidence in taking. To help, we've added two new features.


First, we've added a validation step that highlights potential issues before you
publish. The new "review and rollout" page will appear before you confirm the
roll out of a new app and flag if there are validation errors or warnings. This
new flow will make the app release process easier, especially for apps using
multi-APK. It also provides new information; for example, in cases where you
added new permissions to your app, the system will highlight it.











Second, it's now simpler to perform and track staged roll-outs during the
publishing flow. With staged rollouts, you can release your update to a growing
% of users, giving you a chance to catch and address any issues before affecting
your whole audience.




If you want to review the history of your releases, it is now possible to track
them granularly and download previous APKs.




Finally we've added a new artifacts library under manage releases where you can
find all the files that help you manage a release.



Start using the new manage releases page today


You can access the new manage releases page in the Developer Console. Visit the
Google
Play Developer Help Center
for more information. With these changes, we're
helping you to publish, track and manage your app with confidence on Google
Play.








How useful did you find this blogpost?


? ? ? ? ?



                                                                              





Tuesday, February 21, 2017

Build flexible layouts with FlexboxLayout


Posted by Takeshi Hagikura, Developer Programs Engineer



At Google I/O last year we announced ConstraintLayout,
which enables you to build complex layouts while maintaining a flat view
hierarchy. It is also fully supported in Android Studio's Visual
Layout Editor
.




At the same time, we open sourced FlexboxLayout to bring the
same functionalities of the CSS
Flexible Layout module
to Android. Here are some cases where
FlexboxLayout is particularly effective.



FlexboxLayout can be interpreted as an advanced LinearLayout
because both layouts align their child views sequentially. The significant
difference between LinearLayout and FlexboxLayout is that
FlexboxLayout has a feature for wrapping.




That means if you add the flexWrap="wrap" attribute,
FlexboxLayout puts a view to a new line if there is not enough
space left in the current line as shown in the picture below.











One layout for various screen sizes


With that characteristic in mind, let's take a case where you want to put views
sequentially but have them move to new lines if the available space changes (due
to a device factor, orientation changes or the window resizing in the
multi-window mode).










Nexus5X portrait













Nexus5X landscape











Pixel C with multi window mode enabled, divider line on the left.












Pixel C with multi window mode enabled, divider line on the middle.













Pixel C with multi window mode enabled, divider line on the right.







You would need to define multiple DP-bucket layouts (such as layout-600dp,
layout-720dp, layout-1020dp) to handle various screen sizes with traditional
layouts such as LinearLayout or RelativeLayout. But
the dialog above is built with a single FlexboxLayout.




The technique used in the example is setting the flexWrap="wrap" as
explained above,




<com .google.android.flexbox.flexboxlayout 
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:flexwrap="wrap">

then you can get the following layout where child views are aligned to a new
line instead of overflowing its parent.

















Another technique I'd like to highlight is setting the
layout_flexGrow attribute to an individual child. This helps
improve the look of the final layout when free space is left over. The
layout_flexGrow attribute works similar to the
layout_weight attribute in LinearLayout. That means
FlexboxLayout will distribute the remaining space according to the
layout_flexGrow value set to each child in the same line.




In the example below, it assumes each child has the layout_flexGrow
attribute set to 1, so free space will be evenly distributed to
each of them.


 <android .support.design.widget.TextInputLayout
android:layout_width="100dp"
android:layout_height="wrap_content"
app:layout_flexgrow="1">












You can check out the complete layout
xml
file in the GitHub repository.





RecyclerView integration 


Another advantage of FlexboxLayout is that it can be integrated
with RecyclerView.
With the latest release of the alpha
version
the new FlexboxLayoutManager extends
RecyclerView.LayoutManager, now you can make use of the Flexbox
functionalities in a scrollable container in much more memory-efficient way.




Note that you can still achieve a scrollable Flexbox container with
FlexboxLayout wrapped with ScrollView. But, you will
be likely to experience jankiness or even an OutOfMemoryError if the number of
items contained in the layout is large, as FlexboxLayout doesn't
take view recycling into account for the views that go off the screen as the
user scrolls.




(If you would like to learn more about the RecyclerView in details, you can
check out the videos from the Android UI toolkit team such as 1, 2)




A real world example where the RecyclerView integration is useful
is for apps like the Google Photos app or News apps, both expect large number of
items while needing to handle various width of items.




One example is found in the demo
application
in the FlexboxLayout repository. As you can see in
the repository, each image shown in RecyclerView has a different
width. But by setting the flexWrap setting to wrap,




FlexboxLayoutManager layoutManager = new FlexboxLayoutManager();
layoutManager.setFlexWrap(FlexWrap.WRAP);

and setting the flexGrow (as you can see, you can configure the
attributes through FlexboxLayoutManager and
FlexboxLayoutManager.LayoutParams for child attributes instead of
configuring it from xml) attribute to a positive value for each child,


void bindTo(Drawable drawable) {
mImageView.setImageDrawable(drawable);
ViewGroup.LayoutParams lp = mImageView.getLayoutParams();
if (lp instanceof FlexboxLayoutManager.LayoutParams) {
FlexboxLayoutManager.LayoutParams flexboxLp =
(FlexboxLayoutManager.LayoutParams) mImageView.getLayoutParams();
flexboxLp.setFlexGrow(1.0f);
}
}

you can see every image fits within the layout nicely regardless of the screen
orientation.












If you would like to see complete FlexboxLayout example, you can check:







What's next?


Check out the full documentation for other
attributes to build flexible layouts tailored for your needs. We're very open to
hear your feedback, if you find any issues or feature requests, please file an
issue on the GitHub
repository
.