3d view, android, drawable, droid inspector, facebook, hack, inspector, mozilla, optimization, optimize, performance, reverse engineering, shader, view hierarchy, views
Having worked on Firefox for Android since the native re-write, and constantly improving its performance, I know the entire UI architecture of the app. While most optimizations had gone in before I developed Droid Inspector, the tool was still useful with the new about:home that we’ve been working on lately. On the other hand, I’ve always wanted to know if I was doing things the right way, and how the top applications in the Android market were developed. Though the UiAutomator have a tree hierarchy of any app, it wasn’t as good as Droid Inspector. What if I can look at other good apps using Droid Inspector? What about Facebook through Droid Inspector’s eyes? And I did it! 😀
Disclaimer: The following investigation is out of my own interest. The views and recommendations are my own and not my employer’s! Also, I never got a special copy of Facebook with my droidinpector-server.jar packed in it. When you can build AOSP and run it on a device, you can really investigate any app with Droid Inspector! Can’t you? 😉
My initial impression when the tree structure loaded was: “OHHMG! Why does it have sooooo maaaany vieeeews??” The hierarchy had more than 250 views for what was seen on screen. Before we delve into details, as far as the good parts:
- They use Fragments. And properly remove the unused fragments. For e.g., the left menu is removed when not needed.
- Each feed item has gone threw an overdraw pass already. The light blue background of the app and the white background of the feed item (though feel like two different entries) are drawn by a single
Drawable. This removes the potential overdraw (of white area blocking the blue background). Kudos!
- They have used compound drawables in couple of places.
- Each entry in the list of feed is called a RecycleViewWrapper. In their first native re-write blog, they mentioned about a better recycler for the list view items. This view probably recycles the bitmaps when not used. Nice!
- The list is gesture aware. So, if an user is scrolling the list, the images loaded in the background are not added to the rows until the scrolling stopped. This avoids a re-layout pass that might have happened while the user is still scrolling. Also, one might not need to unnecessarily cause a re-layout pass when the view is going to be moved to scrap heap in few milliseconds. This is a really good approach. If you want to use a custom library, you could try using Lucas Rocha‘s Smoothie, that does this job for free.
Now the parts that could be optimized easily. As I mentioned earlier, each feed item has item own “compressed” background. That way one layer of overdraw is removed. But the page still shows a blue background. This isn’t actually needed as the feed items cover the entire screen. Also, there is a black screen at the far back (pretty close to the root of the hierarchy). This is never seen (or probably shown as a part of touching the image). This could be hidden until needed. There is also a blue background to the top of the entire phone screen area. As per the hierarchy, this is called “RefreshableListViewOverflowItem”. May be the new entries are added here. But still, this needn’t be shown when there is nothing to be shown. Oh! we just removed two layers of overdraw in a jiffy. 😀 Now let’s start removing unwanted views.
Cover Images: Just like the web interface, all the images are http url links. Until these images are loaded, as we can see ourselves while scrolling, a nice blue color is shown. But then, there are couple of problems. First, the blue image is not removed once the image is loaded. Overdraw? Definitely! The other problem is that the blue is not a background of an
ImageView. It’s actually the background of a
ViewGroup that holds the
ImageView. The idea behind this approach is that this allows an image to fade-in while the background is still seen. But then, can’t we have an alpha animation on a
Drawable? Wouldn’t the “source” image then just fade-in while the background blue still waits? That removes one layer from the view hierarchy. Oh wait! That removes many such “one layers” from the view hierarchy, as all ImageViews are implemented this way! Win!
The cover photos shown inline in the feed, say for friends having a birthday, have a mild black overlay on them, to emphasize on the profile photo. This still works as the UrlImage above. The problem here is that the black overlay is a separate
View added to the hierarchy. Ummm… Shaders? Right! Adding a
ComposeShader with a
LinearGradient of the color the value (for both start and stop) and a
BitmapShader of the actual image will draw this in one go. That removes (a) Overdraw, (b) one view in the hierarchy. Oh! we do that with our lightweight themes in Firefox! Also, there is an obscured white background for the profile image (actual profile image hidden in the screenshot for privacy). That… is another overdraw. What’s the probability of someone’s name running past the device width? (Not all have a long last name like mine: “Ramasubramanian”). But still, the two
TextViews (obscured for privacy) can be combined into one single
TextView. The small change in text color/size of the sub text can be made a Spannable string. We just reduced two views that way — the sub text’s view and the parent of both the views.
Moving to the profile page. The horizontal scrollable list of photos, friends, map, books, apps, etc. is.. not a list! Instead its a
LinearLayout with each individual tiles part of the hierarchy all the time. Basically this is not backed by an adapter. Now look closely. The photos tile in turn has 6 tiles in them. Each one is an UrlImage without the background removed. The easiest way is for the server to combine all these images and send as a single image. If that cannot be done, a custom view can be implemented where the six images are drawn to canvas inside “one” draw() call. That means, we just reduced 12 views from the hierarchy (and the 6 overdraws caused by the background). No, we shouldn’t stop there! :D. There is a small black filter on top of these UrlImage views. This isn’t a simple color filter like cover photo above. But still a shader can do the job efficiently. Each tile has two
FrameLayouts at the minimum. They are redundant and not even needed. That’s like 20 unnecessary views added as a part of 10 tiles. This is all sandwiched inside a
LinearLayout. Now if all these images can be drawn as a part of a single
Drawable or an image, this can be easily made as a compound drawable for the
TextView. We just reduced 5 layers for each tite to a single view — and there are 10-12 such tiles. That was a massacre of 60 views!
Look up above a bit. The unordered list of details about a person has a small icon and a text describing each entry. Unfortunately each image is again an UrlImage. But aren’t these pre-determined images? Can’t these be shipped with the APK itself? Also, why aren’t they compound drawables. What would generally take one single view is using four views here!
If you are still with me, let’s move on to the major part — the feed item! This is the integral part of Facebook and its app. This has to be the “most optimized” of all. Now to give a comparison, the search results list on the Firefox app should be the most optimized and allow smooooooth scrolling. Unfortunately each feed item feels bloated. The entire hierarchy of one feed item couldn’t fit on the side screen in the screenshot. The background of the item is optimized — nice! But the drawable doesn’t define a proper drawable padding. What can be merely tweaked by adding a padding to the background is done by adding two
Views of height 8dp at the top and bottom. The UrlImage (again) adds a background. In this case, a small shadow is shown for the ImageView from this background. “Mmmm.. 9-png-drawable? as a background for ImageView? Won’t that work?” is probably you are thinking. And that would work just fine in this case. Each button in the bottom bar shows an image and a text view. The reason they weren’t made as compound drawable is that the like button has a nice animation. But then, the drawable in the compound drawable can have animations too. Probably that would make them all have a single
TextView. The “FeedbackActionButtonBar” holding the three buttons draws a pale white background. Since each post will have this bar, this could be merged with the optimized background. That would save one more level of overdraw.
But the biggest problem in the feed is that, to make use of the list view recycling, Facebook app adds all possible things to each feed item. This causes the hierarchy to bloat. The better option is to classify different options available and load different row item types. If that’s hard, the simple solution would be have
ViewStubs until some piece needs to be inflated actually. This reduces the layout pass on each row.
After all these simpler fixes (esp. the UrlImage view problem), Facebook app could run at least 50% faster. There are even more things that can be fixed by just looking at the Droid Inspector output. As my intention is to try to see and show how apps can use Droid Inspector to optimize their UI, and not to tarnish Facebook’s image, I stop here. :D. I hope Facebook would act upon this at the earliest and fix all these issues, as they have more than 500M users!
P.S.: I know it’s a bit hard to look through the images and understood easily. I’ve put up a similar HTML capture (needs WebGL and CSS Flex box) here and there for you to try.
Reblogged this on macressler.
Serban Constantin said:
Just wanted to say that this is a really great article.
One thing that popped into my mind when I saw those views and UrlImages is that it might allow them to more easily change the layout of the app without specifically pushing a new .apk with each change (or group of changes).