Android Design Patterns
Android Design Patterns is a website for developers who wish to better understand the Android application framework. The tutorials here emphasize proper code design and project maintainability.
Since the very beginning, one of the most annoying aspects of using VectorDrawables on Android has been the lack of a reliable SVG converter. Google has recently made huge strides towards improving Android Studio’s SVG-to-VectorDrawable tool in order to improve this experience. However, there has always been one major issue for me: the tool doesn’t support batch conversion of SVGs and can’t be invoked from the command line. Working at Lyft, it’s not uncommon for me to need to convert hundreds of SVGs into VectorDrawables at a time. Having to go through Android Studio’s GUI in order to convert each SVG one-by-one is simply not realistic. Well, this weekend my good buddy Nick Butcher taught me how to build the tool as a binary that can be executed from the command line, and I’m so excited about it that I had to share it with the world! :) Where can I download it? I anticipate many won’t want to go through the trouble of building these binaries from scratch, so I built them myself and hosted them on Google Drive here. How do I run it? The following command will convert all SVGs in a directory called svgs/, convert them all into VectorDrawables, and write them to a directory called vectors/. Note that both directories must exist beforehand. ./vd-tool -c -in svgs/ -out vectors/ How do I build it? In case you want to build these yourself, here’s how I did it. First, follow the Downloading the Source guide to install and set up the repo tool, but instead of running the listed repo commands to initialize the repository, run the folowing: repo init -u https://android.googlesource.com/platform/manifest -b At the time of this writing, the most recent Android Studio branch was studio-3.2.1. This will obviously change over time as newer versions of Android Studio are released. Now that your repository is set to pull only what you need for building and running the tool, download the code using the following command (you might want to grab a coffee or something too, as this command might take a while to complete): repo sync -j8 -c Finally, execute the following to build the binaries: cd ./tools/base ../gradlew publishLocal Once it completes, you should find the binaries in a /out/build/base/vector-drawable-tool/build/distributions/vd-tool.zip file. Unzip it and it will extract a /bin directory that contains binaries compatible with Mac OS X, Linux, and Windows. How do I report bugs? Now for the most important part of this blog post… Please, please, please file bugs if you discover SVGs that don’t convert properly! File the bugs in the Android Studio public issue tracker. Here’s an example bug I recently reported if you need a template to go by. My goal is to make the Android Studio converter the most reliable SVG-to-VectorDrawable converter out there. So if and when you file a bug, feel free to hit me up on Twitter with a link to the report and I’ll do my best to ensure the bug is fixed as quickly as possible! Happy converting!
Today I am open sourcing the first alpha release of an animation library I’ve been writing named Kyrie. Think of it as a superset of Android’s VectorDrawable and AnimatedVectorDrawable classes: it can do everything they can do and more. Motivation Let me start by explaining why I began writing this library in the first place. If you read my blog post on icon animations, you know that VectorDrawables are great because they provide density independence—they can be scaled arbitrarily on any device without loss of quality. AnimatedVectorDrawables make them even more awesome, allowing us to animate specific properties of a VectorDrawable in a variety of ways. However, these two classes also have several limitations: They can’t be dynamically created at runtime (they must be inflated from a drawable resource). They can’t be paused, resumed, or seeked. They only support a small subset of features that SVGs provide on the web. I started writing Kyrie in an attempt to address these problems. Examples Let’s walk through a few examples from the sample app that accompanies the library. The first snippet of code below shows how we can use Kyrie to transform an existing AnimatedVectorDrawable resource into a KyrieDrawable that can be scrubbed with a SeekBar: KyrieDrawable drawable = KyrieDrawable.create(context, R.drawable.avd_heartbreak); seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { long totalDuration = drawable.getTotalDuration(); drawable.setCurrentPlayTime((long) (progress / 100f * totalDuration)); } /* ... */ }); The video in Figure 1 shows the resulting animation. We can pause/resume the animation by calling pause() and resume() respectively, and can also listen to animation events using a KyrieDrawable.Listener. In the future, I plan to add a couple more features as well, such as the ability to customize the playback speed and/or play the animation in reverse. Figure 1 - Creating a seekable animation from an existing AnimatedVectorDrawable resource (source code). Click to play. We can also create KyrieDrawables dynamically at runtime using the builder pattern. KyrieDrawables are similar to SVGs and VectorDrawables in that they are tree-like structures built of Nodes. As we build the tree, we can optionally assign Animations to the properties of each Node to create a more elaborate animation. The code below shows how we can create a path morphing animation this way: // Fill colors. int hippoFillColor = ContextCompat.getColor(context, R.color.hippo); int elephantFillColor = ContextCompat.getColor(context, R.color.elephant); int buffaloFillColor = ContextCompat.getColor(context, R.color.buffalo); // SVG path data objects. PathData hippoPathData = PathData.parse(getString(R.string.hippo)); PathData elephantPathData = PathData.parse(getString(R.string.elephant)); PathData buffaloPathData = PathData.parse(getString(R.string.buffalo)); KyrieDrawable drawable = KyrieDrawable.builder() .viewport(409, 280) .child( PathNode.builder() .strokeColor(Color.BLACK) .strokeWidth(1f) .fillColor( Animation.ofArgb(hippoFillColor, elephantFillColor).duration(300), Animation.ofArgb(buffaloFillColor).startDelay(600).duration(300), Animation.ofArgb(hippoFillColor).startDelay(1200).duration(300)) .pathData( Animation.ofPathMorph( Keyframe.of(0, hippoPathData), Keyframe.of(0.2f, elephantPathData), Keyframe.of(0.4f, elephantPathData), Keyframe.of(0.6f, buffaloPathData), Keyframe.of(0.8f, buffaloPathData), Keyframe.of(1, hippoPathData)) .duration(1500))) .build(); Figure 2 shows the resulting animation. Note that Animations can also be constructed using Keyframes, just as we would do so with a PropertyValuesHolder. Figure 2 - Creating a path morphing animation using keyframes (source code). Click to play. Kyrie also supports animating along a path using the Animation#ofPathMotion method. Say, for example, we wanted to recreate the polygon animations from Nick Butcher’s Playing with Paths blog post (the full source code is available in the sample app): KyrieDrawable.Builder builder = KyrieDrawable.builder().viewport(1080, 1080); // Draw each polygon using a PathNode with a custom stroke color. for (Polygon polygon : polygons) { builder.child( PathNode.builder() .pathData(PathData.parse(polygon.pathData)) .strokeWidth(4f) .strokeColor(polygon.color)); } // Animate a black dot along each polygon's perimeter. for (Polygon polygon : polygons) { PathData pathData = PathData.parse(TextUtils.join(" ", Collections.nCopies(polygon.laps, polygon.pathData))); Animation pathMotion = Animation.ofPathMotion(PathData.toPath(pathData)).duration(7500); builder.child( CircleNode.builder() .fillColor(Color.BLACK) .radius(8) .centerX(pathMotion.transform(p -> p.x)) .centerY(pathMotion.transform(p -> p.y))); } The left half of Figure 3 shows the resulting animation. Note that Animation#ofPathMotion returns an Animation that computes PointF objects, where each point represents a location along the specified path as the animation progresses. In order to animate each black circle’s location along this path, we use the Animation#transform method to transform the points into streams of x/y coordinates that can be consumed by the CircleNode’s centerX and centerY properties. Figure 3 - Recreating the polygon animations from Nick Butcher's Playing with Paths blog post (source code). Click to play. Future work I have a lot of ideas on how to further improve this library, but right now I am interested in what you think. Make sure you file any feature requests you might have on GitHub! And like I said, the library is still in alpha, so make sure you report bugs too. :) Links GitHub Documentation
One of the coolest projects I worked on during my 3 years at Google was Google Expeditions, a virtual reality app that allows teachers to lead students on immersive virtual field trips around the world. I especially enjoyed working on the app’s field trip selector screen, which renders a SurfaceView behind a beautifully designed card-based layout that allows the user to quickly switch between different VR experiences. It’s been awhile since I’ve written Android code (I’ve spent a majority of the past year building Android developer tools like Shape Shifter and avocado), so the other day I challenged myself to rewrite parts of the screen as an exercise. Figure 1 shows a side-by-side comparison of Google Expeditions’ field trip selector screen and the resulting sample app I wrote (available on GitHub and Google Play). Figure 1 - A side-by-side comparison of the Google Expeditions' Android app vs. the sample app I wrote for this blog post. As I was working through the code, I was reminded of some of the challenges I faced with Android’s nested scrolling APIs when I first wrote the screen a couple of years ago. Introduced in API 21, the nested scrolling APIs make it possible for a scrollable parent view to contain scrollable children views, enabling us to create the scrolling gestures that Material Design formalizes on its scrolling techniques patterns page. Figure 2 shows a common use case of these APIs involving a parent CoordinatorLayout and a child NestedScrollView. Without nested scrolling, the NestedScrollView scrolls independently of its surroundings. Once enabled, however, the CoordinatorLayout and NestedScrollView take turns intercepting and consuming the scroll, creating a ‘collapsing toolbar’ effect that looks more natural. Figure 2 - A side-by-side comparison of Chris Banes' Cheese Square app with nested scrolling disabled vs. enabled. How exactly do the nested scrolling APIs work? For starters, you need a parent view that implements NestedScrollingParent and a child view that implements NestedScrollingChild. In Figure 3 below, the NestedScrollView (NSV) is the parent and the RecyclerView (RV) is the child: Figure 3 - The sample app consists of a parent NestedScrollView and a child RecyclerView. Let’s say the user attempts to scroll the RV above. Without nested scrolling, the RV will immediately consume the scroll event, resulting in the undesirable behavior we saw in Figure 2. What we really want is to create the illusion that the two views are scrolling together as a single unit. More specifically:1 If the RV is at the top of its content, scrolling the RV up should cause the NSV to scroll up. If the NSV is not at the bottom of its content, scrolling the RV down should cause the NSV to scroll down. As you might expect, the nested scrolling APIs provide a way for the NSV and RV to communicate with each other throughout the duration of the scroll, so that each view can confidently determine who should consume each scroll event. This becomes clear when you consider the sequence of events that takes place when the user drags their finger on top of the RV: The RV’s onTouchEvent(ACTION_MOVE) method is called. The RV calls its own dispatchNestedPreScroll() method, which notifies the NSV that it is about to consume a portion of the scroll. The NSV’s onNestedPreScroll() method is called, giving the NSV an opportunity to react to the scroll event before the RV consumes it. The RV consumes the remainder of the scroll (or does nothing if the NSV consumed the entire event). The RV calls its own dispatchNestedScroll() method, which notifies the NSV that it has consumed a portion of the scroll. The NSV’s onNestedScroll() method is called, giving the NSV an opportunity to consume any remaining scroll pixels that have still not been consumed. The RV returns true from the current call to onTouchEvent(ACTION_MOVE), consuming the touch event.2 Unfortunately, simply using a NSV and RV was not enough to get the scrolling behavior I wanted. Figure 4 shows the two problematic bugs that I needed to fix. The cause of both problems is that the RV is consuming scroll and fling events when it shouldn’t be. On the left, the RV should not begin scrolling until the card has reached the top of the screen. On the right, flinging the RV downwards should collapse the card in a single smooth motion. Figure 4 - On the left, the RV should not begin scrolling until the card has reached the top of the screen. On the right, flinging the RV downwards should collapse the card in a single smooth motion. Fixing these two problems is relatively straightforward now that we understand how the nested scrolling APIs work. All we need to do is create a CustomNestedScrollView class and customize its scrolling behavior by overriding the onNestedPreScroll() and onNestedPreFling() methods: /** * A NestedScrollView with our custom nested scrolling behavior. */ public class CustomNestedScrollView extends NestedScrollView { // The NestedScrollView should steal the scroll/fling events away from // the RecyclerView if: (1) the user is dragging their finger down and // the RecyclerView is scrolled to the top of its content, or (2) the // user is dragging their finger up and the NestedScrollView is not // scrolled to the bottom of its content. @Override public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) { final RecyclerView rv = (RecyclerView) target; if ((dy 0 && !isNsvScrolledToBottom(this))) { // Scroll the NestedScrollView's content and record the number of pixels consumed // (so that the RecyclerView will know not to perform the scroll as well). scrollBy(0, dy); consumed[1] = dy; return; } super.onNestedPreScroll(target, dx, dy, consumed); } @Override public boolean onNestedPreFling(View target, float velX, float velY) { final RecyclerView rv = (RecyclerView) target; if ((velY 0 && !isNsvScrolledToBottom(this))) { // Fling the NestedScrollView's content and return true (so that the RecyclerView // will know not to perform the fling as well). fling((int) velY); return true; } return super.onNestedPreFling(target, velX, velY); } /** * Returns true iff the NestedScrollView is scrolled to the bottom of its * content (i.e. if the card's inner RecyclerView is completely visible). */ private static boolean isNsvScrolledToBottom(NestedScrollView nsv) { return !nsv.canScrollVertically(1); } /** * Returns true iff the RecyclerView is scrolled to the top of its * content (i.e. if the RecyclerView's first item is completely visible). */ private static boolean isRvScrolledToTop(RecyclerView rv) { final LinearLayoutManager lm = (LinearLayoutManager) rv.getLayoutManager(); return lm.findFirstVisibleItemPosition() == 0 && lm.findViewByPosition(0).getTop() == 0; } } We’re nearly there! For the perfectionists out there, however, Figure 5 shows one more bug that would be nice to fix. In the video on the left, the fling stops abruptly once the child reaches the top of its content. What we want is for the fling to finish in a single, fluid motion, as shown in the video on the right. Figure 5 - On the left, the fling stops abruptly once the child reaches the top of its content. On the right, the fling completes in a single, fluid motion. The crux of the problem is that up until recently, the support library did not provide a way for nested scroll children to transfer leftover nested fling velocity up to a nested scroll parent. I won’t go into too much detail here because Chris Banes has already written a detailed blog post explaining the issue and how it should be fixed.3 But to summarize, all we need to do is update our parent and child views to implement the new-and-improved NestedScrollingParent2 and NestedScrollingChild2 interfaces, which were specifically added to address this problem in v26 of the support library. Unfortunately NestedScrollView still implements the older NestedScrollingParent interface, so I had to create my own NestedScrollView2 class that implements the NestedScrollingParent2 interface to get things working.4 The final, bug-free NestedScrollView implementation is given below: /** * A NestedScrollView that implements the new-and-improved NestedScrollingParent2 * interface and that defines its own customized nested scrolling behavior. View * source code for the NestedScrollView2 class here: j.mp/NestedScrollView2 */ public class CustomNestedScrollView2 extends NestedScrollView2 { @Override public void onNestedPreScroll(View target, int dx, int dy, int[] consumed, int type) { final RecyclerView rv = (RecyclerView) target; if ((dy 0 && !isNsvScrolledToBottom(this))) { scrollBy(0, dy); consumed[1] = dy; return; } super.onNestedPreScroll(target, dx, dy, consumed, type); } // Note that we no longer need to override onNestedPreFling() here; the // new-and-improved nested scrolling APIs give us the nested flinging // behavior we want already by default! private static boolean isNsvScrolledToBottom(NestedScrollView nsv) { return !nsv.canScrollVertically(1); } private static boolean isRvScrolledToTop(RecyclerView rv) { final LinearLayoutManager lm = (LinearLayoutManager) rv.getLayoutManager(); return lm.findFirstVisibleItemPosition() == 0 && lm.findViewByPosition(0).getTop() == 0; } } That’s all I’ve got for now! Thanks for reading, and don’t forget to leave a comment below if you have any questions! 1 This blog post uses the same terminology that the framework uses to describe scroll directions. That is, dragging your finger toward the bottom of the screen causes the view to scroll up, and dragging your finger towards the top of the screen causes the view to scroll down. ↩ 2 It’s worth noting that nested flings are handled in a very similar fashion. The child detects a fling in its onTouchEvent(ACTION_UP) method and notifies the parent by calling its own dispatchNestedPreFling() and dispatchNestedFling() methods. This triggers calls to the parent’s onNestedPreFling() and onNestedFling() methods and gives the parent an opportunity to react to the fling before and after the child consumes it. ↩ 3 I recommend watching the second half of Chris Banes’ Droidcon 2016 talk for more information on this topic as well. ↩ 4 If enough people star this bug, maybe this won’t be necessary in the future! :) ↩
Creative customization is one of the tenets of material design; the subtle addition of an icon animation can add an element of wonder to the user experience, making your app feel more natural and alive. Unfortunately, building an icon animation from scratch using VectorDrawables can be challenging. Not only does it take a fair amount of work to implement, but it also requires a vision of how the final result should look and feel. If you aren’t familiar with the different techniques that are most often used to create icon animations, you’re going to have a hard time designing your own. This blog post covers several different techniques that you can use to create beautiful icon animations. The best way to learn is by example, so as you read through the post you’ll encounter interactive demos highlighting how each technique works. I hope this blog post can at the very least open your eyes to how icon animations behave under-the-hood, because I genuinely believe that understanding how they work is the first step towards creating your own. This post is split into the following sections: Drawing paths Transforming groups of paths Trimming stroked paths Morphing paths Clipping paths Conclusion: putting it all together All of the icon animations in this blog post are available in AnimatedVectorDrawable format on GitHub. I also encourage you to check out Shape Shifter, a side project I’ve been working on that helps simplify the process of creating path morphing animations for Android and the web. Drawing paths Before we can begin creating animated icons, we first need to understand how they are drawn. In Android, we’ll create each icon using the relatively new VectorDrawable class. VectorDrawables are similar in concept to SVGs on the web: they allow us to create scalable, density-independent assets by representing each icon as a series of lines and shapes called paths. Each path’s shape is determined by a sequence of drawing commands, represented by a space/comma-separated string using a subset of the SVG path data spec. The spec defines many different types of commands, some of which are summarized in the table below: Command Description M x,y Begin a new subpath by moving to (x,y). L x,y Draw a line to (x,y). C x1,y1 x2,y2 x,y Draw a cubic bezier curve to (x,y) using control points (x1,y1) and (x2,y2). Z Close the path by drawing a line back to the beginning of the current subpath. All paths come in one of two forms: filled or stroked. If the path is filled, the interiors of its shape will be painted. If the path is stroked, the paint will be applied along the outline of its shape. Both types of paths have their own set of animatable attributes that further modify their appearance: Property name Element type Value type Min Max android:fillAlpha float 0 1 android:fillColor integer - - - - - - android:strokeAlpha float 0 1 android:strokeColor integer - - - - - - android:strokeWidth float 0 - - - Let’s see how this all works with an example. Say we wanted to create a play, pause, and record icon for a music application. We can represent each icon using a single path: The triangular play and circular record icons are both filled paths with orange and red fill colors respectively. The pause icon, on the other hand, is a stroked path with a green stroke color and a stroke width of 2. Figure 1 illustrates each path’s drawing commands executed inside a 12x12 grid: 1, 4 2 3 M 4,2.5 L 4,9.5 L 9.5,6 Z 1 2 3 4 M 4,2.5 L 4,9.5 M 8,2.5 L 8,9.5 1, 5 2 3 4 M 2,6 C 2,3.8 3.8,2 6,2 C 8.2,2 10,3.8 10,6 C 10,8.2 8.2,10 6,10 C 3.8,10 2,8.2 2,6 Figure 1. Understanding how paths are drawn using path commands. The numbers in the diagrams match the position of the path after each command is executed. In each diagram, the top left coordinate is (0,0) and the bottom right coordinate is (12,12). The source code for each icon is available on GitHub. As we previously mentioned, one of the benefits of VectorDrawables is that they provide density independence, meaning that they can be scaled arbitrarily on any device without loss of quality. This ends up being both convenient and efficient: developers no longer need to go through the tedious process of exporting different sized PNGs for each screen density, which in turn also leads to a smaller APK size. In our case, however, the reason we want to use VectorDrawables is so we can animate their individual paths using the AnimatedVectorDrawable class. AnimatedVectorDrawables are the glue that connect VectorDrawables with ObjectAnimators: the VectorDrawable assigns each animated path (or group of paths) a unique name, and the AnimatedVectorDrawable maps each of these names to their corresponding ObjectAnimators. As we’ll see below, the ability to animate the individual elements within a VectorDrawable can be quite powerful. Transforming groups of paths In the previous section we learned how to alter a path’s appearance by directly modifying its properties, such as its opacity and color. In addition to this, VectorDrawables also support group transformations using the tag, which allows us to apply transformations on multiple paths at a time using the following animatable attributes: Property name Element type Value type android:pivotX float android:pivotY float android:rotation float android:scaleX float android:scaleY float android:translateX float android:translateY float It’s important to understand the order in which nested group transformations are applied. The two rules to remember are (1) children groups inherit the transformations applied by their parent groups, and (2) transformations made to the same group are applied in order of scale, rotation, and then translation. As an example, consider the following group transformations applied to the play, pause, and record icons discussed above: The transformed icons are shown in Figure 2 below. Toggle the checkboxes to see how the different combinations of transformations affect the results! translateX="2" rotation="90" scaleX="1.5" rotation="90" translateX="2" scaleX="1.5" scaleX="1.5" rotation="90" translateX="2" Figure 2. The effects of different combinations of transformations on a play, pause, and record icon. The order of the checkboxes matches the order in which the transformations are applied in the sample code above. Android source code for each icon is available on GitHub. The ability to chain together group transformations makes it possible to achieve a variety of cool effects. Figure 3 shows three such examples: The expand/collapse icon is drawn using two rectangular paths. When clicked, the two paths are simultaneously rotated 90° and vertically translated to create the transition. The alarm clock icon draws its bells using two rectangular paths. When clicked, a containing the two paths is rotated back and forth about the center to create a ‘ringing’ effect. The radio button icon animation is one of my favorites due to its clever simplicity. The icon is drawn using only two paths: a filled inner dot and a stroked outer ring. When the radio button transitions between an unchecked to checked state, three properties are animated: Time Outer ring strokeWidth Outer ring scale{X,Y} Inner dot scale{X,Y} 0 2 1 0 0.333 18 0.5 0 0.334 2 0.9 1.5 1 2 1 1 Pay particular attention to the first third of the animation, when the outer ring’s stroke width and scale are simultaneously increased and decreased respectively to make it look as if the outer ring is collapsing inwards towards the center—a pretty awesome effect! Highlight animated paths Slow animation Figure 3. Understanding how transformations can be used to create icon animations. Android source code for each is available on GitHub: (a) expand to collapse, (b) alarm clock, and (c) radio button. Click each icon to start its animation. One last animation that makes use of group transformations is the horizontal indeterminate progress bar. A horizontal indeterminate progress bar consists of three paths: a translucent background and two inner rectangular paths. During the animation the two inner rectangles are horizontally translated and scaled at varying degrees. Toggle the checkboxes in Figure 4 below to see how each transformation individually contributes to the animation! Animate scale Animate translation Figure 4. Understanding how scale and translation are used to animate a horizontal indeterminate progress indicator (source code). Trimming stroked paths A lesser known property of stroked paths is that they can be trimmed. That is, given a stroked path we can choose to show only a portion of it before it is drawn to the display. In Android, this is done using the following animatable attributes: Property name Element type Value type Min Max android:trimPathStart float 0 1 android:trimPathEnd float 0 1 android:trimPathOffset float 0 1 trimPathStart determines where the visible portion of the path will begin, while trimPathEnd determines where the visible portion of the path will end. An additional trimPathOffset may also be appended to the start and end values if desired. Figure 5 demonstrates how this all works—update the sliders to see how different values affect what is drawn to the display! Note that it is perfectly fine for trimPathStart to be greater than trimPathEnd; if this occurs, the visible portion of the path simply wraps around the end of the segment back to the beginning. trimPathStart="0" trimPathEnd="0.5" trimPathOffset="0" Figure 5. Understanding the effects of the trimPathStart, trimPathEnd, and trimPathOffset properties on a stroked path. The ability to animate these three properties opens up a world of possibilities. Figure 6 shows four such examples: The fingerprint icon consists of 5 stroked paths, each with their trim path start and end values initially set to 0 and 1 respectively. When hidden, the difference is quickly animated to 0 until the icon is no longer visible, and then quickly back to 1 when the icon is later shown. The cursive handwriting icon behaves similarly, except instead of animating the individual paths all at once, they are animated sequentially as if the word was being written out by hand. The search to back icon uses a clever combination of trim path animations in order to seamlessly transition between the stem of the search icon and the stem of a back arrow. Enable the ‘show trim paths’ checkbox and you’ll see how the changing trimPathStart and trimPathEnd values affect the relative location of the stem as it animates to its new state. Enable the ‘slow animation’ checkbox and you’ll also notice that the visible length of the stem changes over time: it expands slightly at the beginning and shrinks towards the end, creating a subtle ‘stretching’ effect that feels more natural. Creating this effect is actually quite easy: just begin animating one of the trim properties with a small start delay to make it look like one end of the path is animating faster than the other. Each animating digit in the Google IO 2016 icon consists of 4 paths, each with a different stroke color and each with trim path start and end values covering a quarter of the digit’s total length. Each path’s trimPathOffset is then animated from 0 to 1 in order to create the effect. Show trim paths Slow animation Figure 6. Understanding how trimming stroked paths can be used to create icon animations. Android source code for each is available on GitHub: (a) fingerprint, (b) search to back arrow, (c) cursive handwriting, and (d) Google IO 2016. Click each icon to start its animation. Lastly, Figure 7 shows how a stroked trim path is used to animate the familiar circular indeterminate progress bar. The icon consists of a single, circular stroked path that is animated as follows: A containing the progress bar path is rotated from 0° to 720° over the course of 4,444ms. The progress bar path’s trim path offset is animated from 0 to 0.25 over the course of 1,333ms. Portions of the progress bar path are trimmed over the course of 1,333ms. Specifically, it animates through the following values: Time trimPathStart trimPathEnd trimPathOffset 0 0 0.03 0 0.5 0 0.75 0.125 1 0.75 0.78 0.25 At time t = 0.0, the progress bar is at its smallest size (only 3% is visible). At t = 0.5, the progress bar has stretched to its maximum size (75% is visible). And at time t = 1.0, the progress bar has shrunk back to its smallest size, just as the animation is about to restart. Animate rotation Animate trim path offset Animate trim path start/end Show trim paths Slow animation Figure 7. Understanding how rotation and a stroked trim path are used to animate a circular indeterminate progress indicator (source code). Morphing paths The most advanced icon animation technique we’ll cover in this post is path morphing. Path morphing allows us to seamlessly transform the shapes of two paths by animating the differences in their drawing commands, as specified in their android:pathData attributes. With path morphing, we can transform a plus sign into a minus sign, a play icon into a pause icon, or even an overflow icon into a back arrow, as seen in Figure 8 below. Property name Element type Value type android:pathData string The first thing to consider when implementing a path morphing animation is whether or not the paths you want to morph are compatible. In order to morph path A into path B the following conditions must be met: A and B have the same number of drawing commands. The ith drawing command in A must have the same type as the ith drawing command in B, for all i. The ith drawing command in A must have the same number of parameters as the ith drawing command in B, for all i. If any of these conditions aren’t met (i.e. attempting to morph an L command into a C command, or an l command with 2 coordinates into an l command with 4 coordinates, etc.), the application will crash with an exception. The reason these rules must be enforced is due to the way path morphing animations are implemented under-the-hood. Before the animation begins, the framework extracts the command types and their coordinates from each path’s android:pathData attribute. If the conditions above are met, then the framework can assume that the only difference between the two paths are the values of the coordinates embedded in their drawing command strings. Under this assumption, the framework can execute the same sequence of drawing commands on each new display frame, re-calculating the values of the coordinates to use based on the current progress of the animation. Figure 8 illustrates this concept nicely. First disable ‘animate rotation’, then enable the ‘show path coordinates’ and ‘slow animation’ checkboxes below. Notice how each path’s red coordinates change during the course of the animation: they travel a straight line from their starting positions in path A to their ending positions in path B. Path morphing animations are really that simple! Animate rotation Show path coordinates Slow animation Figure 8. Understanding how path morphing can be used to create icon animations. Android source code for each is available on GitHub: (a) plus to minus, (b) cross to tick, (c) drawer to arrow, (d) overflow to arrow, (e) play to pause to stop, and (f) animating digits. Click each icon to start its animation. Although conceptually simple, path morphing animations are known at times for being tedious and time-consuming to implement. For example, you’ll often need to tweak the start and end paths by hand in order to make the two paths compatible to be morphed, which, depending on the complexity of the paths, is where most of the work will probably be spent. Listed below are several tips and tricks that I’ve found useful in getting started: Adding dummy coordinates is often necessary in order to make a simple path compatible with a more complex path. Dummy coordinates were added to nearly all of the examples shown Figure 8. For example, consider the plus-to-minus animation. We could draw a rectangular minus path using only 4 drawing commands. However, drawing the more complex plus path requires 12 drawing commands, so in order to make the two paths compatible we must add 8 additional noop drawing commands to the simpler minus path. Compare the two paths’ drawing command strings and see if you can identify these dummy coordinates for yourself! A cubic bezier curve command can be used to draw a straight line by setting its pair of control points equal to its start and end points respectively. This can be useful to know if you ever find yourself morphing an L command into a C command (such as in the overflow-to-arrow and animating digit examples above). It is also possible to estimate an elliptical arc command using one or more cubic bezier curves, as I previously discussed here. This can also be useful to know if you ever find yourself in a situation where you need to morph a C command into an A command. Sometimes morphing one path into another looks awkward no matter how you do it. In my experience, I’ve found that adding a 180° or 360° degree rotation to the animation can make them look significantly better: the additional rotation distracts the eye from the morphing paths and adds a layer of motion that makes the animation seem more responsive to the user’s touch. Remember that path morphing animations are ultimately determined by the relative positioning of each path’s drawing command coordinates. For best results, try to minimize the distance each coordinate has to travel over the course of the animation: the smaller the distance each coordinate has to animate, the more seamless the path morphing animation will usually appear. Clipping paths The last technique we’ll cover involves animating the bounds of a . A clip path restricts the region to which paint can be applied to the canvas—anything that lies outside of the region bounded by a clip path will not be drawn. By animating the bounds of these regions, we can create some cool effects, as we’ll see below. Property name Element type Value type android:pathData string A ’s bounds can be animated via path morphing by animating the differences in its path commands, as specified by its android:pathData attribute. Take a look at the examples in Figure 9 to get a better idea of how these animations work. Enabling the ‘show clip paths’ checkbox will show a red overlay mask representing the bounds of the currently active , which in turn dictates the portions of its sibling s that will be drawn. Clip path are especially useful for animating ‘fill’ effects, as demonstrated in the hourglass and heart fill/break examples below. Show clip paths Slow animation Figure 9. Understanding how s can be used to create icon animations. Android source code for each is available on GitHub: (a) hourglass, (b) eye visibility, and (c) heart fill/break. Click each icon to start its animation. Conclusion: putting it all together If you’ve made it this far in the blog post, that means you now have all of the fundamental building blocks you need in order to design your own icon animations from scratch! To celebrate, let’s finish off this ridiculously enormous blog post once and for all with one last kickass example! Consider the progress icon in Figure 10, which animates the following six properties: Fill alpha (at the end when fading out the downloading arrow). Stroke width (during the progress indicator to check mark animation). Translation and rotation (at the beginning to create the ‘bouncing arrow’ effect). Trim path start/end (at the end when transitioning from the progress bar to the check mark). Path morphing (at the beginning to create the ‘bouncing line’ effect, and at the end while transitioning the check mark back into an arrow). Clip path (vertically filling the contents of the downloading arrow to indicate indeterminate progress). Show path coordinates Show trim paths Show clip paths Slow animation Figure 10. A downloading progress icon animation that demonstrates a combination of several techniques discussed in this blog post. Android source code is available on GitHub: (a) in-progress download and (b) download complete. Click the icon to start its animation. That’s all I’ve got for now… thanks for reading! Remember to +1 this blog or leave a comment below if you have any questions. And remember that all of the icon animations in this blog post (and more) are available in AnimatedVectorDrawable format on GitHub. Feel free to steal them for your own application if you want! Reporting bugs & feedback If you notice a glitch in one of the animated demos on this page, please report them here. All of the animations work fine for me using the latest version of Chrome. That said, I only began learning JavaScript a few weeks ago so I wouldn’t be surprised if I made a mistake somewhere along the line. I want this blog post to be perfect, so I’d really appreciate it! :) Special thanks I’d like to give a huge thanks to Nick Butcher, because I probably would never have written this blog post without his help and advice! Several of the animations in this blog post were borrowed from his amazing open source application Plaid, which I highly recommend you check out if you haven’t already. I’d also like to thank Roman Nurik for his Android Icon Animator tool and for inspiring the path morphing animations in Figure 10. Finally, I’d like to thank Sriram Ramani for his blog post on number tweening, which inspired the animated digits demo in Figure 8. Thanks again!
Say you want to change the background color of a Button. How can this be done? This blog post covers two different approaches. In the first approach, we’ll use AppCompat’s Widget.AppCompat.Button.Colored style and a custom ThemeOverlay to modify the button’s background color directly, and in the second, we’ll use AppCompat’s built-in background tinting support to achieve an identical effect. Approach #1: Modifying the button’s background color w/ a ThemeOverlay Before we get too far ahead of ourselves, we should first understand how button background colors are actually determined. The material design spec has very specific requirements about what a button should look like in both light and dark themes. How are these requirements met under-the-hood? The Widget.AppCompat.Button button styles To answer this question, we’ll first need a basic understanding of how AppCompat determines the default appearance of a standard button. AppCompat defines a number of styles that can be used to alter the appearance of a button, each of which extend a base Widget.AppCompat.Button style that is applied to all buttons by default.1 Specifying a default style to be applied to all views of a certain type is a common technique used throughout the Android source code. It gives the framework an opportunity to apply a set of default values for each widget, encouraging a more consistent user experience. For Buttons, the default Widget.AppCompat.Button style ensures that: All buttons share the same default minimum width and minimum height (88dp and 48dp respectively, as specified by the material design spec). All buttons share the same default TextAppearance (i.e. text displayed in all capital letters, the same default font family, font size, etc.). All buttons share the same default button background (i.e. same background color, same rounded-rectangular shape, same amount of insets and padding, etc.). Great, so the Widget.AppCompat.Button style helps ensure that all buttons look roughly the same by default. But how are characteristics such as the button’s background color chosen in light vs. dark themes, not only in its normal state, but in its disabled, pressed, and focused states as well? To achieve this, AppCompat depends mainly on three different theme attributes: R.attr.colorButtonNormal: The color used as a button’s background color in its normal state. Resolves to #ffd6d7d7 for light themes and #ff5a595b for dark themes. android.R.attr.disabledAlpha: A floating point number that determines the alpha values to use for disabled framework widgets. Resolves to 0.26f for light themes and 0.30f for dark themes. R.attr.colorControlHighlight: The translucent overlay color drawn on top of widgets when they are pressed and/or focused (used by things like ripples on post-Lollipop devices and foreground list selectors on pre-Lollipop devices). Resolves to 12% black for light themes and 20% white for dark themes (#1f000000 and #33ffffff respectively). That’s a lot to take in for something as simple as changing the background color of a button! Fortunately, AppCompat handles almost everything for us behind the scenes by providing a second Widget.AppCompat.Button.Colored style that makes altering the background color of a button relatively easy. As its name suggests, the style extends Widget.AppCompat.Button and thus inherits all of the same attributes with one notable exception: the R.attr.colorAccent theme attribute determines the button’s base background color instead. Creating custom themes using ThemeOverlays So now we know that button backgrounds can be customized using the Widget.AppCompat.Button.Colored style, but how should we go about customizing the theme’s accent color? One way we could update the color pointed to by the R.attr.colorAccent theme attribute is by modifying the application’s theme directly. However, this is rarely desirable since most of the time we only want to change the background color of a single button in our app. Modifying the theme attribute at the application level will change the background color of all buttons in the entire application. Instead, a much better solution is to assign the button its own custom theme in XML using android:theme and a ThemeOverlay. Let’s say we want to change the button’s background color to Google Red 500. To achieve this, we can define the following theme: @color/googred500 …and set it on our button in the layout XML as follows: And that’s it! You’re probably still wondering what’s up with that weird ThemeOverlay though. Unlike the themes we use in our AndroidManifest.xml files (i.e. Theme.AppCompat.Light, Theme.AppCompat.Dark, etc.), ThemeOverlays define only a small set of material-styled theme attributes that are most often used when theming each view’s appearance (see the source code for a complete list of these attributes). As a result, they are very useful in cases where you only want to modify one or two properties of a particular view: just extend the ThemeOverlay, update the attributes you want to modify with their new values, and you can be sure that your view will still inherit all of the correct light/dark themed values that would have otherwise been used by default.2 Approach #2: Setting the AppCompatButton’s background tint Hopefully you’ve made it this far in the post, because you’ll be happy to know that there is an even more powerful way to color a button’s background using a relatively new feature in AppCompat known as background tinting. You probably know that AppCompat injects its own widgets in place of many framework widgets, giving AppCompat greater control over tinting widgets according to the material design spec even on pre-Lollipop devices. At runtime, Buttons become AppCompatButtons, ImageViews become AppCompatImageViews, CheckBoxs become AppCompatCheckBoxs, and so on and so forth. What you may not know is that any AppCompat widget that implements the TintableBackgroundView interface can have its background tint color changed by declaring a ColorStateList: …and either setting it in the layout XML: …or programatically via the ViewCompat#setBackgroundTintList(View, ColorStateList) method:3 final ColorStateList backgroundTintList = AppCompatResources.getColorStateList(context, R.color.btn_colored_background_tint); ViewCompat.setBackgroundTintList(button, backgroundTintList); While this approach to coloring a button is much more powerful in the sense that it can be done entirely programatically (whereas ThemeOverlays must be defined in XML and cannot be constructed at runtime), it also requires a bit more work on our end if we want to ensure our button exactly meets the material design spec. Let’s create a simple BackgroundTints utility class that makes it quick and easy to construct colored background tint lists: /** * Utility class for creating background tint {@link ColorStateList}s. */ public final class BackgroundTints { private static final int[] DISABLED_STATE_SET = new int[]{-android.R.attr.state_enabled}; private static final int[] PRESSED_STATE_SET = new int[]{android.R.attr.state_pressed}; private static final int[] FOCUSED_STATE_SET = new int[]{android.R.attr.state_focused}; private static final int[] EMPTY_STATE_SET = new int[0]; /** * Returns a {@link ColorStateList} that can be used as a colored button's background tint. * Note that this code makes use of the {@code android.support.v4.graphics.ColorUtils} * utility class. */ public static ColorStateList forColoredButton(Context context, @ColorInt int backgroundColor) { // On pre-Lollipop devices, we need 4 states total (disabled, pressed, focused, and default). // On post-Lollipop devices, we need 2 states total (disabled and default). The button's // RippleDrawable will animate the pressed and focused state changes for us automatically. final int numStates = Build.VERSION.SDK_INT @color/indigo500 @color/indigo700 @color/pinkA200 In addition to this, the following custom themes are declared as well: @color/googred500 @color/googred500 What will the following XML look like in the on API 19 and API 23 devices when the buttons are put in default, pressed, and disabled states? Assume that background tints are set programatically on the 4th and 8th buttons as follows: final int googRed500 = ContextCompat.getColor(activity, R.color.googred500); final View button4 = activity.findViewById(R.id.button4); ViewCompat.setBackgroundTintList( button4, BackgroundTints.forColoredButton(button4.getContext(), googRed500)); final View button8 = activity.findViewById(R.id.button8); ViewCompat.setBackgroundTintList( button8, BackgroundTints.forColoredButton(button8.getContext(), googRed500)); Solutions See the below links to view screenshots of the solutions: API 19, default state API 19, pressed state API 19, disabled state API 23, default state API 23, pressed state API 23, disabled state (Note that the incorrect disabled text color in the screenshots is a known issue and will be fixed in an upcoming version of the support library.) As always, thanks for reading! Feel free to leave a comment if you have any questions, and don’t forget to +1 and/or share this blog post if you found it helpful! And check out the source code for these examples on GitHub as well! 1 Just in case you don’t believe me, the default style applied to an AppCompatButtons is the style pointed to by the R.attr.buttonStyle theme attribute, which points to the Widget.AppCompat.Button style here. Check out Dan Lew’s great blog post for more information about default styles in Android. ↩ 2 ThemeOverlays aren’t only useful for changing your theme’s accent color. They can be used to alter any theme attribute you want! For example, you could use one to customize the color of an RecyclerView’s overscroll ripple by modifying the color of the android.R.attr.colorEdgeEffect theme attribute. Check out this Medium post and this Google+ pro tip for more information about ThemeOverlays. ↩ 3 Note that AppCompat widgets do not expose a setBackgroundTintList() methods as part of their public API. Clients must use the ViewCompat#setBackgroundTintList() static helper methods to modify background tints programatically. Also note that using the AppCompatResources class to inflate the ColorStateList is important here. Check out my previous blog post for more detailed information on that topic. ↩
You’ve probably noticed that when you write something like: context.getResources().getColor(R.color.some_color_resource_id); Android Studio will give you a lint message warning you that the Resources#getColor(int) method was deprecated in Marshmallow in favor of the new, Theme-aware Resources#getColor(int, Theme) method. You also probably know by now that the easy alternative to avoiding this lint warning these days is to call: ContextCompat.getColor(context, R.color.some_color_resource_id); which under-the-hood is essentially just a shorthand way of writing: if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { return context.getResources().getColor(id, context.getTheme()); } else { return context.getResources().getColor(id); } Easy enough. But what is actually going on here? Why were these methods deprecated in the first place and what do the new Theme-aware methods have to offer that didn’t exist before? The problem with Resources#getColor(int) & Resources#getColorStateList(int) First, let’s be clear on what these old, deprecated methods actually do: Resources#getColor(int) returns the color associated with the passed in color resource ID. If the resource ID points to a ColorStateList, the method will return the ColorStateList’s default color. Resources#getColorStateList(int) returns the ColorStateList associated with the passed in resource ID. “When will these two methods break my code?” To understand why these methods were deprecated in the first place, consider the ColorStateList declared in XML below. When this ColorStateList is applied to a TextView, its disabled and enabled text colors should take on the colors pointed to by the R.attr.colorAccent and R.attr.colorPrimary theme attributes respectively: Now let’s say you want to obtain an instance of this ColorStateList programatically: ColorStateList csl = context.getResources().getColorStateList(R.color.button_text_csl); Perhaps surprisingly, the result of the above call is undefined! You’ll see a stack trace in your logcat output similar to the one below: W/Resources: ColorStateList color/button_text_csl has unresolved theme attributes! Consider using Resources.getColorStateList(int, Theme) or Context.getColorStateList(int) at android.content.res.Resources.getColorStateList(Resources.java:1011) ... “What went wrong?” The problem is that Resources objects are not intrinsically linked to a specific Theme in your app, and as a result, they will be unable to resolve the values pointed to by theme attributes such as R.attr.colorAccent and R.attr.colorPrimary on their own. In fact, specifying theme attributes in ColorStateList XML files was not supported until API 23, which introduced two new methods for extracting ColorStateLists from XML: Resources#getColor(int, Theme) returns the color associated with the passed in resource ID. If the resource ID points to a ColorStateList, the method will return the ColorStateList’s default color. Any theme attributes specified in the ColorStateList will be resolved using the passed in Theme argument. Resources#getColorStateList(int, Theme) returns the ColorStateList associated with the passed in resource ID. Any theme attributes specified in the ColorStateList will be resolved using the passed in Theme argument. Additional convenience methods were also added to Context and to the support library’s ResourcesCompat and ContextCompat classes as well. “That stinks! How can I workaround these problems?” As of v24.0 of the AppCompat support library, you can now workaround all of these problems using the new AppCompatResources class! To extract a themed ColorStateList from XML, just use: ColorStateList csl = AppCompatResources.getColorStateList(context, R.color.button_text_csl); On API 23+, AppCompat will delegate the call to the corresponding framework method, and on earlier platforms it will manually parse the XML itself, resolving any theme attributes it encounters along the way. If that isn’t enough, it also backports the ColorStateList’s new android:alpha attribute as well (which was previously only available to devices running API 23 and above)! The problem with Resources#getDrawable(int) You guessed it! The recently deprecated Resources#getDrawable(int) method shares pretty much the exact same problem as the Resources#getColor(int) and Resources#getColorStateList(int) methods discussed above. As a result, theme attributes in drawable XML files will not resolve properly prior to API 21, so if your app supports pre-Lollipop devices, either avoid theme attributes entirely or resolve them in your Java code and construct the Drawable programatically instead. “I don’t believe you! Are there really no exceptions?” Of course there is an exception, isn’t there always? :) It turns out that similar to the AppCompatResources class, the VectorDrawableCompat and AnimatedVectorDrawableCompat classes were able to workaround these issues and are actually smart enough to resolve the theme attributes it detects in XML across all platform versions as well. For example, if you want to color your VectorDrawableCompat the standard shade of grey, you can reliably tint the drawable with ?attr/colorControlNormal while still maintaining backwards compatibility with older platform versions: (If you’re curious how this is implemented under-the-hood, the short answer is that the support library does their own custom XML parsing and uses the Theme#obtainStyledAttributes(AttributeSet, int[], int, int) method to resolve the theme attributes it encounters. Pretty cool!) Pop quiz! Let’s test our knowledge with a short example. Consider the following ColorStateList: And assume you’re writing an app that declares the following themes: @color/vanillared500 @color/vanillared700 @color/googgreen500 @color/brown500 @color/yellow900 And finally, assume that you have the following helper methods to resolve theme attributes and construct ColorStateLists programatically: @ColorInt private static int getThemeAttrColor(Context context, @AttrRes int colorAttr) { TypedArray array = context.obtainStyledAttributes(null, new int[]{colorAttr}); try { return array.getColor(0, 0); } finally { array.recycle(); } } private static ColorStateList createColorStateList(Context context) { return new ColorStateList( new int[][]{ new int[]{-android.R.attr.state_enabled}, // Disabled state. StateSet.WILD_CARD, // Enabled state. }, new int[]{ getThemeAttrColor(context, R.attr.colorAccent), // Disabled state. getThemeAttrColor(context, R.attr.colorPrimary), // Enabled state. }); } Try to see if you can predict the enabled and disabled appearance of a button on both an API 19 and API 23 device in each of the following scenarios (for scenarios #5 and #8, assume that the button has been given a custom theme in XML using android:theme="@style/CustomButtonTheme"): Resources res = ctx.getResources(); // (1) int deprecatedTextColor = res.getColor(R.color.button_text_csl); button1.setTextColor(deprecatedTextColor); // (2) ColorStateList deprecatedTextCsl = res.getColorStateList(R.color.button_text_csl); button2.setTextColor(deprecatedTextCsl); // (3) int textColorXml = AppCompatResources.getColorStateList(ctx, R.color.button_text_csl).getDefaultColor(); button3.setTextColor(textColorXml); // (4) ColorStateList textCslXml = AppCompatResources.getColorStateList(ctx, R.color.button_text_csl); button4.setTextColor(textCslXml); // (5) Context themedCtx = button5.getContext(); ColorStateList textCslXmlWithCustomTheme = AppCompatResources.getColorStateList(themedCtx, R.color.button_text_csl); button5.setTextColor(textCslXmlWithCustomTheme); // (6) int textColorJava = getThemeAttrColor(ctx, R.attr.colorPrimary); button6.setTextColor(textColorJava); // (7) ColorStateList textCslJava = createColorStateList(ctx); button7.setTextColor(textCslJava); // (8) Context themedCtx = button8.getContext(); ColorStateList textCslJavaWithCustomTheme = createColorStateList(themedCtx); button8.setTextColor(textCslJavaWithCustomTheme); Solutions Here are the screenshots of what the buttons look like on API 19 vs. API 23 devices: API 19 solutions API 23 solutions Note that there isn’t anything special about the weird pink color in the two screenshots. That’s just the “undefined behavior” that results when you try to resolve a theme attribute without a corresponding Theme. :) As always, thanks for reading! Feel free to leave a comment if you have any questions, and don’t forget to +1 and/or share this blog post if you found it helpful! And check out the source code for these examples on GitHub as well!
This post continues our in-depth analysis of shared element transitions by discussing an important feature of the Lollipop Transition API: postponed shared element transitions. It is the fourth of a series of posts I will be writing on the topic: Part 1: Getting Started with Activity & Fragment Transitions Part 2: Content Transitions In-Depth Part 3a: Shared Element Transitions In-Depth Part 3b: Postponed Shared Element Transitions Part 3c: Implementing Shared Element Callbacks (coming soon!) Part 4: Activity & Fragment Transition Examples (coming soon!) Until I write part 4, an example application demonstrating some advanced activity transitions is available here. We begin by discussing the need to postpone certain shared element transitions due to a common problem. Understanding the Problem A common source of problems when dealing with shared element transitions stems from the fact that they are started by the framework very early in the Activity lifecycle. Recall from part 1 that Transitions must capture both the start and end state of its target views in order to build a properly functioning animation. Thus, if the framework starts the shared element transition before its shared elements are given their final size and position and size within the called Activity, the transition will capture the incorrect end values for its shared elements and the resulting animation will fail completely (see Video 3.3 for an example of what the failed enter transition might look like). Video 3.3 - Fixing a broken shared element enter animation by postponing the transition. Click to play. Whether or not the shared elements’ end values will be calculated before the transition begins depends mainly on two factors: (1) the complexity and depth of the called activity’s layout and (2) the amount of time it takes for the called activity to load its required data. The more complex the layout, the longer it will take to determine the shared elements’ position and size on the screen. Similarly, if the shared elements’ final appearance within the activity depends on asynchronously loaded data, there is a chance that the framework might automatically start the shared element transition before that data is delivered back to the main thread. Listed below are some of the common cases in which you might encounter these issues: The shared element lives in a Fragment hosted by the called activity. FragmentTransactions are not executed immediately after they are committed; they are scheduled as work on the main thread to be done at a later time. Thus, if the shared element lives inside the Fragment’s view hierarchy and the FragmentTransaction is not executed quickly enough, it is possible that the framework will start the shared element transition before the shared element is properly measured and laid out on the screen.1 The shared element is a high-resolution image. Setting a high resolution image that exceeds the ImageView’s initial bounds might end up triggering an additional layout pass on the view hierarchy, therefore increasing the chances that the transition will begin before the shared element is ready. The asynchronous nature of popular bitmap loading/scaling libraries, such as Volley and Picasso, will not reliably fix this problem: the framework has no prior knowledge that images are being downloaded, scaled, and/or fetched from disk on a background thread and will start the shared element transition whether or not images are still being processed. The shared element depends on asynchronously loaded data. If the shared elements require data loaded by an AsyncTask, an AsyncQueryHandler, a Loader, or something similar before their final appearance within the called activity can be determined, the framework might start the transition before that data is delivered back to the main thread. postponeEnterTransition() and startPostponedEnterTransition() At this point you might be thinking, “If only there was a way to temporarily delay the transition until we know for sure that the shared elements have been properly measured and laid out.” Well, you’re in luck, because the Activity Transitions API2 gives us a way to do just that! To temporarily prevent the shared element transition from starting, call postponeEnterTransition() in your called activity’s onCreate() method. Later, when you know for certain that all of your shared elements have been properly positioned and sized, call startPostponedEnterTransition() to resume the transition. A common pattern you’ll find useful is to start the postponed transition in an OnPreDrawListener, which will be called after the shared element has been measured and laid out:3 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Postpone the shared element enter transition. postponeEnterTransition(); // TODO: Call the "scheduleStartPostponedTransition()" method // below when you know for certain that the shared element is // ready for the transition to begin. } /** * Schedules the shared element transition to be started immediately * after the shared element has been measured and laid out within the * activity's view hierarchy. Some common places where it might make * sense to call this method are: * * (1) Inside a Fragment's onCreateView() method (if the shared element * lives inside a Fragment hosted by the called Activity). * * (2) Inside a Picasso Callback object (if you need to wait for Picasso to * asynchronously load/scale a bitmap before the transition can begin). * * (3) Inside a LoaderCallback's onLoadFinished() method (if the shared * element depends on data queried by a Loader). */ private void scheduleStartPostponedTransition(final View sharedElement) { sharedElement.getViewTreeObserver().addOnPreDrawListener( new ViewTreeObserver.OnPreDrawListener() { @Override public boolean onPreDraw() { sharedElement.getViewTreeObserver().removeOnPreDrawListener(this); startPostponedEnterTransition(); return true; } }); } Despite their names, these two methods can also be used to postpone shared element return transitions as well. Simply postpone the return transition within the calling Activity’s onActivityReenter() method instead:4 /** * Don't forget to call setResult(Activity.RESULT_OK) in the returning * activity or else this method won't be called! */ @Override public void onActivityReenter(int resultCode, Intent data) { super.onActivityReenter(resultCode, data); // Postpone the shared element return transition. postponeEnterTransition(); // TODO: Call the "scheduleStartPostponedTransition()" method // above when you know for certain that the shared element is // ready for the transition to begin. } Despite making your shared element transitions smoother and more reliable, it’s important to also be aware that introducing postponed shared element transitions into your application could also have some potentially harmful side-effects: Never forget to call startPostponedEnterTransition() after calling postponeEnterTransition. Forgetting to do so will leave your application in a state of deadlock, preventing the user from ever being able to reach the next Activity screen. Never postpone a transition for longer than a fraction of a second. Postponing a transition for even a fraction of a second could introduce unwanted lag into your application, annoying the user and slowing down the user experience. As always, thanks for reading! Feel free to leave a comment if you have any questions, and don’t forget to +1 and/or share this blog post if you found it helpful! 1 Of course, most applications can usually workaround this issue by calling FragmentManager#executePendingTransactions(), which will force any pending FragmentTransactions to execute immediately instead of asynchronously. ↩ 2 Note that the postponeEnterTransition() and startPostponedEnterTransition() methods only work for Activity Transitions and not for Fragment Transitions. For an explanation and possible workaround, see this StackOverflow answer and this Google+ post. ↩ 3 Pro tip: you can verify whether or not allocating the OnPreDrawListener is needed by calling View#isLayoutRequested() beforehand, if necessary. View#isLaidOut() may come in handy in some cases as well. ↩ 4 A good way to test the behavior of your shared element return/reenter transitions is by going into the Developer Options and enabling the “Don’t keep activities” setting. This will help test the worst case scenario in which the calling activity will need to recreate its layout, requery any necessary data, etc. before the return transition begins. ↩
This post will give an in-depth analysis of shared element transitions and their role in the Activity and Fragment Transitions API. This is the third of a series of posts I will be writing on the topic: Part 1: Getting Started with Activity & Fragment Transitions Part 2: Content Transitions In-Depth Part 3a: Shared Element Transitions In-Depth Part 3b: Postponed Shared Element Transitions Part 3c: Implementing Shared Element Callbacks (coming soon!) Part 4: Activity & Fragment Transition Examples (coming soon!) Until I write part 4, an example application demonstrating some advanced activity transitions is available here. Part 3 of this series will be broken up into three parts: part 3a will focus on how shared elements operate under-the-hood and part 3b and part 3c will focus more on the implementation-specific details of the API, such as the importance of postponing certain shared element transitions and implementing SharedElementCallbacks. We begin by summarizing what we learned about shared element transitions in part 1 and illustrating how they can be used to achieve smooth, seamless animations in Android Lollipop. What is a Shared Element Transition? A shared element transition determines how shared element views—also called hero views—are animated from one Activity/Fragment to another during a scene transition. Shared elements are animated by the called Activity/Fragment’s enter and return shared element transitions,1 each of which can be specified using the following Window and Fragment methods: setSharedElementEnterTransition() - B’s enter shared element transition animates shared element views from their starting positions in A to their final positions in B. setSharedElementReturnTransition() - B’s return shared element transition animates shared element views from their starting positions in B to their final positions in A. Video 3.1 - Shared element transitions in action in the Google Play Music app (as of v5.6). Click to play. Video 3.1 illustrates how shared element transitions are used in the Google Play Music app. The transition consists of two shared elements: an ImageView and its parent CardView. During the transition, the ImageView seamlessly animates between the two activities while the CardView gradually expands/contracts into place. Whereas part 1 only briefly introduced the subject, this blog post aims to give a much more in-depth analysis of shared element transitions. How are shared element transitions triggered under-the-hood? Which types of Transition objects can be used? How and where are shared element views drawn during the transition? In the next couple sections, we’ll tackle these questions one-by-one. Shared Element Transitions Under-The-Hood Recall from the previous two posts that a Transition has two main responsibilities: capturing the start and end state of its target views and creating an Animator that will animate the views between the two states. Shared element transitions operate no differently: before a shared element transition can create its animation, it must first capture each shared element’s start and end state—namely its position, size, and appearance in both the calling and called Activities/Fragments. With this information, the transition can determine how each shared element view should animate into place. Similar to how content transitions operate under-the-hood, the framework feeds the shared element transition this state information by directly modifying each shared element’s view properties at runtime. More specifically, when Activity A starts Activity B the following sequence of events occurs:2 Activity A calls startActivity() and Activity B is created, measured, and laid out with an initially translucent window and transparent window background color. The framework repositions each shared element view in B to match its exact size and location in A. Shortly after, B’s enter transition captures the start state of all the shared elements in B. The framework repositions each shared element view in B to match its final size and location in B. Shortly after, B’s enter transition captures the end state of all the shared elements in B. B’s enter transition compares the start and end state of its shared element views and creates an Animator based on the differences. The framework instructs A to hide its shared element views from sight and the resulting Animator is run. As B’s shared element views animate into place, B’s window background gradually fades in on top A until B is entirely opaque and the transition completes. Whereas content transitions are governed by changes to each transitioning view’s visibility, shared element transitions are governed by changes to each shared element view’s position, size, and appearance. As of API 21, the framework provides several different Transition implementations that can be used to customize how shared elements are animated during a scene change: ChangeBounds - Captures the layout bounds of shared element views and animates the differences. ChangeBounds is frequently used in shared element transitions, as most shared elements will differ in size and/or location within either of the two Activities/Fragments. ChangeTransform - Captures the scale and rotation of shared element views and animates the differences.3 ChangeClipBounds - Captures the clip bounds of shared element views and animates the differences. ChangeImageTransform - Captures the transform matrices of shared element ImageViews and animates the differences. In combination with ChangeBounds, this transition allows ImageViews that change in size, shape, and/or ImageView.ScaleType to animate smoothly and efficiently. @android:transition/move - A TransitionSet that plays all four transition types above in parallel. As discussed in part 1, if an enter/return shared element transition is not explicitly specified, the framework will run this transition by default. In the example above, we also can see that shared element view instances are not actually “shared” across Activities/Fragments. In fact, almost everything the user sees during both enter and return shared element transitions is drawn directly inside B’s content view. Instead of somehow transferring the shared element view instance from A to B, the framework uses a different means of achieving the same visual effect. When A starts B, the framework collects all of the relevant state information about the shared elements in A and passes it to B. B then uses this information to initialize the start state of its shared elements views, each of which will initially match the exact position, size, and appearance they had in A. When the transition begins, everything in B except the shared elements are initially invisible to the user. As the transition progresses, however, the framework gradually fades in B’s Activity window until the shared elements in B finish animating and B’s window background is opaque. Using the Shared Element Overlay4 Video 3.2 - A simple app that illustrates a potential bug that can result when the shared element overlay is disabled. Click to play. Finally, before we can gain a complete understanding of how shared element transitions are drawn by the framework, we must discuss the shared element overlay. Although not immediately obvious, shared elements are drawn on top of the entire view hierarchy in the window’s ViewOverlay by default. In case you haven’t heard of it before, the ViewOverlay class was introduced in API 18 as a way to easily draw on top of a View. Drawables and views that are added to a view’s ViewOverlay are guaranteed to be drawn on top of everything else—even a ViewGroup’s children. With this in mind, it makes sense why the framework would choose to draw shared elements in the window’s ViewOverlay on top of everything else in the view hierarchy by default. Shared elements views should be the focus throughout the entire transition; the possibility of transitioning views accidentally drawing on top of the shared elements would immediately ruin the effect.5 Although shared elements are drawn in the shared element ViewOverlay by default, the framework does give us the ability to disable the overlay by calling the Window#setSharedElementsUseOverlay(false) method, just in case you ever find it necessary. If you ever do choose to disable the overlay, be wary of the undesired side-effects it might cause. As an example, Video 3.2 runs a simple shared element transition twice, with and without the shared element overlay enabled respectively. The first time the transition is run, the shared element ImageView animates as expected in the shared element overlay, on top of all other views in the hierarchy. The second time the transition is run, however, we can clearly see that disabling the overlay has introduced a problem. As the bottom transitioning view slides up into the called Activity’s content view, the shared element ImageView is partially covered as and is drawn below the transitioning view for nearly the first half of the transition. Although there is a chance that this could be fixed by altering the order in which views are drawn in the layout and/or by setting setClipChildren(false) on the shared element’s parent, these sort of “hacky” modifications can easily become unmanagable and more trouble than they are worth. In short, try not to disable the shared element overlay unless you find it absolutely necessary, and you’ll likely benefit from simpler and more dramatic shared element transitions as a result. Conclusion Overall, this post presented three important points: A shared element transition determines how shared element views—also called hero views—are animated from one Activity/Fragment to another during a scene transition. Shared element transitions are governed by changes to each shared element view’s position, size, and appearance. Shared elements are drawn on top of the entire view hierarchy in the window’s ViewOverlay by default. As always, thanks for reading! Feel free to leave a comment if you have any questions, and don’t forget to +1 and/or share this blog post if you found it helpful! 1 Note that the Activity Transition API gives you the ability to also specify exit and reenter shared element transitions using the setSharedElementExitTransition() and setSharedElementReenterTransition() methods, although doing so is usually not necessary. For an example illustrating one possible use case, check out this blog post. For an explanation why exit and reenter shared element transitions are not available for Fragment Transitions, see George Mount’s answer and comments in this StackOverflow post. ↩ 2 A similar sequence of events occurs during the exit/return/reenter transitions for both Activities and Fragments. ↩ 3 One other subtle feature of ChangeTransform is that it can detect and handle changes made to a shared element view’s parent during a transition. This comes in handy when, for example, the shared element’s parent has an opaque background and is by default selected to be a transitioning view during the scene change. In this case, the ChangeTransform will detect that the shared element’s parent is being actively modified by the content transition, pull out the shared element from its parent, and animate the shared element separately. See George Mount’s StackOverflow answer for more information. ↩ 4 Note that this section only pertains to Activity Transitions. Unlike Activity Transitions, shared elements are not drawn in a ViewOverlay by default during Fragment Transitions. That said, you can achieve a similar effect by applying a ChangeTransform transition, which will have the shared element drawn on top of the hierarchy in a ViewOverlay if it detects that its parent has changed. See this StackOverflow post for more information. ↩ 5 Note that one negative side-effect of having shared elements drawn on top of the entire view hierarchy is that this means it will become possible for shared elements to draw on top of the System UI (such as the status bar, navigation bar, and action bar). For more information on how you can prevent this from happening, see this Google+ post. ↩
This post will give an in-depth analysis of content transitions and their role in the Activity and Fragment Transitions API. This is the second of a series of posts I will be writing on the topic: Part 1: Getting Started with Activity & Fragment Transitions Part 2: Content Transitions In-Depth Part 3a: Shared Element Transitions In-Depth Part 3b: Postponed Shared Element Transitions Part 3c: Implementing Shared Element Callbacks (coming soon!) Part 4: Activity & Fragment Transition Examples (coming soon!) Until I write part 4, an example application demonstrating some advanced activity transitions is available here. We begin by summarizing what we learned about content transitions in part 1 and illustrating how they can be used to achieve smooth, seamless animations in Android Lollipop. What is a Content Transition? A content transition determines how the non-shared views—called transitioning views—enter or exit the scene during an Activity or Fragment transition. Motivated by Google’s new Material Design language, content transitions allow us to coordinate the entrance and exit of each Activity/Fragment’s views, making the act of switching between screens smooth and effortless. Beginning with Android Lollipop, content transitions can be set programatically by calling the following Window and Fragment methods: setExitTransition() - A’s exit transition animates transitioning views out of the scene when A starts B. setEnterTransition() - B’s enter transition animates transitioning views into the scene when A starts B. setReturnTransition() - B’s return transition animates transitioning views out of the scene when B returns to A. setReenterTransition() - A’s reenter transition animates transitioning views into the scene when B returns to A. Video 2.1 - Content transitions in the Google Play Games app (as of v2.2). Click to play. As an example, Video 2.1 illustrates how content transitions are used in the Google Play Games app to achieve smooth animations between activities. When the second activity starts, its enter content transition gently shuffles the user avatar views into the scene from the bottom edge of the screen. When the back button is pressed, the second activity’s return content transition splits the view hierarchy into two and animates each half off the top and bottom of the screen. So far our analysis of content transitions has only scratched the surface; several important questions still remain. How are content transitions triggered under-the-hood? Which types of Transition objects can be used? How does the framework determine the set of transitioning views? Can a ViewGroup and its children be animated together as a single entity during a content transition? In the next couple sections, we’ll tackle these questions one-by-one. Content Transitions Under-The-Hood Recall from the previous post that a Transition has two main responsibilities: capturing the start and end state of its target views and creating an Animator that will animate the views between the two states. Content transitions are no different: before a content transition’s animation can be created, the framework must give it the state information it needs by altering each transitioning view’s visibility. More specifically, when Activity A starts Activity B the following sequence of events occurs:1 Activity A calls startActivity(). The framework traverses A's view hierarchy and determines the set of transitioning views that will exit the scene when A's exit transition is run. A's exit transition captures the start state for the transitioning views in A. The framework sets all transitioning views in A to INVISIBLE. On the next display frame, A's exit transition captures the end state for the transitioning views in A. A's exit transition compares the start and end state of each transitioning view and creates an Animator based on the differences. The Animator is run and the transitioning views exit the scene. Activity B is started. The framework traverses B's view hierarchy and determines the set of transitioning views that will enter the scene when B's enter transition is run. The transitioning views are initially set to INVISIBLE. B's enter transition captures the start state for the transitioning views in B. The framework sets all transitioning views in B to VISIBLE. On the next display frame, B's enter transition captures the end state for the transitioning views in B. B's enter transition compares the start and end state of each transitioning view and creates an Animator based on the differences. The Animator is run and the transitioning views enter the scene. By toggling each transitioning view’s visibility between INVISIBLE and VISIBLE, the framework ensures that the content transition is given the state information it needs to create the desired animation. Clearly all content Transition objects then must at the very least be able to capture and record each transitioning view’s visibility in both its start and end states. Fortunately, the abstract Visibility class already does this work for you: subclasses of Visibility need only implement the onAppear() and onDisappear() factory methods, in which they must create and return an Animator that will either animate the views into or out of the scene. As of API 21, three concrete Visibility implementations exist—Fade, Slide, and Explode—all of which can be used to create Activity and Fragment content transitions. If necessary, custom Visibility classes may be implemented as well; doing so will be covered in a future blog post. Transitioning Views & Transition Groups Up until now, we have assumed that content transitions operate on a set of non-shared views called transitioning views. In this section, we will discuss how the framework determines this set of views and how it can be further customized using transition groups. Before the transition starts, the framework constructs the set of transitioning views by performing a recursive search on the Activity window’s (or Fragment’s) entire view hierarchy. The search begins by calling the overridden recursive ViewGroup#captureTransitioningViews method on the hierarchy’s root view, the source code of which is given below: /** @hide */ @Override public void captureTransitioningViews(List transitioningViews) { if (getVisibility() != View.VISIBLE) { return; } if (isTransitionGroup()) { transitioningViews.add(this); } else { int count = getChildCount(); for (int i = 0; i < count; i++) { View child = getChildAt(i); child.captureTransitioningViews(transitioningViews); } } } Video 2.2 - A simple Radiohead app that illustrates a potential bug involving transition groups and WebViews. Click to play. The recursion is relatively straightforward: the framework traverses each level of the tree until it either finds a VISIBLE leaf view or a transition group. Transition groups essentially allow us to animate entire ViewGroups as single entities during an Activity/Fragment transition. If a ViewGroup’s isTransitionGroup()2 method returns true, then it and all of its children views will be animated together as one. Otherwise, the recursion will continue and the ViewGroup’s transitioning children views will be treated independently during the animation. The final result of the search is the complete set of transitioning views that will be animated by the content transition.3 An example illustrating transition groups in action can be seen in Video 2.1 above. During the enter transition, the user avatars shuffle into the screen independently of the others, whereas during the return transition the parent ViewGroup containing the user avatars is animated as one. The Google Play Games app likely uses a transition group to achieve this effect, making it look as if the current scene splits in half when the user returns to the previous activity. Sometimes transition groups must also be used to fix mysterious bugs in your Activity/Fragment transitions. For example, consider the sample application in Video 2.2: the calling Activity displays a grid of Radiohead album covers and the called Activity shows a background header image, the shared element album cover, and a WebView. The app uses a return transition similar to the Google Play Games app, sliding the top background image and bottom WebView off the top and bottom of the screen respectively. However, as you can see in the video, a glitch occurs and the WebView fails to slide smoothly off the screen. So what went wrong? Well, the problem stems from the fact that WebView is a ViewGroup and as a result is not selected to be a transitioning view by default. Thus, when the return content transition is run, the WebView will be ignored entirely and will remain drawn on the screen before being abruptly removed when the transition ends. Fortunately, we can easily fix this by calling webView.setTransitionGroup(true) at some point before the return transition begins. Conclusion Overall, this post presented three important points: A content transition determines how an Activity or Fragment’s non-shared views—called transitioning views—enter or exit the scene during an Activity or Fragment transition. Content transitions are triggered by changes made to its transitioning views’ visibility and should almost always extend the abstract Visibility class as a result. Transition groups enable us to animate entire ViewGroups as single entities during a content transition. As always, thanks for reading! Feel free to leave a comment if you have any questions, and don’t forget to +1 and/or share this blog post if you found it helpful! 1 A similar sequence of events occurs during return/reenter transitions for both Activities and Fragments. ↩ 2 Note that isTransitionGroup() will return true if the ViewGroup has a non-null background drawable and/or non-null transition name by default (as stated in the method’s documentation). ↩ 3 Note that any views that were explicitly added or excluded in the content Transition object will also be taken into account when the transition is run. ↩
This post gives a brief overview of Transitions and introduces the new Activity & Fragment transition APIs that were added in Android 5.0 Lollipop. This is the first of a series of posts I will be writing on the topic: Part 1: Getting Started with Activity & Fragment Transitions Part 2: Content Transitions In-Depth Part 3a: Shared Element Transitions In-Depth Part 3b: Postponed Shared Element Transitions Part 3c: Implementing Shared Element Callbacks (coming soon!) Part 4: Activity & Fragment Transition Examples (coming soon!) Until I write part 4, an example application demonstrating some advanced activity transitions is available here. We begin by answering the following question: what is a Transition? What is a Transition? Activity and Fragment transitions in Lollipop are built on top of a relatively new feature in Android called Transitions. Introduced in KitKat, the transition framework provides a convenient API for animating between different UI states in an application. The framework is built around two key concepts: scenes and transitions. A scene defines a given state of an application’s UI, whereas a transition defines the animated change between two scenes. When a scene changes, a Transition has two main responsibilities: Capture the state of each view in both the start and end scenes, and Create an Animator based on the differences that will animate the views from one scene to the other. As an example, consider an Activity which fades its views in or out when the user taps the screen. We can achieve this effect with only a few lines using Android’s transition framework, as shown in the code1 below: public class ExampleActivity extends Activity implements View.OnClickListener { private ViewGroup mRootView; private View mRedBox, mGreenBox, mBlueBox, mBlackBox; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mRootView = (ViewGroup) findViewById(R.id.layout_root_view); mRootView.setOnClickListener(this); mRedBox = findViewById(R.id.red_box); mGreenBox = findViewById(R.id.green_box); mBlueBox = findViewById(R.id.blue_box); mBlackBox = findViewById(R.id.black_box); } @Override public void onClick(View v) { TransitionManager.beginDelayedTransition(mRootView, new Fade()); toggleVisibility(mRedBox, mGreenBox, mBlueBox, mBlackBox); } private static void toggleVisibility(View... views) { for (View view : views) { boolean isVisible = view.getVisibility() == View.VISIBLE; view.setVisibility(isVisible ? View.INVISIBLE : View.VISIBLE); } } } To better understand what happens under-the-hood in this example, let’s analyze the process step-by-step assuming that each view is initially VISIBLE on screen: Video 1.1 - Running the example transition above using a Fade, Slide, and Explode. Click to play. A click is detected and the developer calls beginDelayedTransition(), passing the scene root and a Fade transition as the arguments. The framework immediately calls the transition’s captureStartValues() method for each view in the scene and the transition records each view’s visibility. When the call returns, the developer sets each view in the scene to INVISIBLE. On the next display frame, the framework calls the transition’s captureEndValues() method for each view in the scene and the transition records each view’s (recently updated) visibility. The framework calls the transition’s createAnimator() method. The transition analyzes the start and end values of each view and notices a difference: the views are VISIBLE in the start scene but INVISIBLE in the end scene. The Fade transition uses this information to create and return an AnimatorSet that will fade each view’s alpha property to 0f. The framework runs the returned Animator, causing all views to gradually fade out of the screen. This simple example highlights two main advantages that the transition framework has to offer. First, Transitions abstract the idea of Animators from the developer. As a result, Transitions can significantly reduce the amount of code you write in your activities and fragments: all the developer must do is set the views’ start and end values and the Transition will automatically construct an animation based on the differences. Second, animations between scenes can be easily changed by using different Transition objects. Video 1.1, for example, illustrates the dramatically different effects we can achieve by replacing the Fade transition with a Slide or Explode. As we will see moving forward, these advantages will allow us to build complex Activity and Fragment transition animations with a relatively small amount of code. In the next few sections, we will see for ourselves how this can be done using Lollipop’s new Activity and Fragment transition APIs. Activity & Fragment Transitions in Android Lollipop As of Android 5.0, Transitions can now be used to perform elaborate animations when switching between different Activitys or Fragments. Although Activity and Fragment animations could already be specified in previous platform versions using the Activity#overridePendingTransition() and FragmentTransaction#setCustomAnimation() methods, they were limited in that they could only animate the entire Activity/Fragment container as a whole. The new Lollipop APIs take this a step further, making it possible to animate individual views as they enter or exit their containers and even allowing us to animate shared views from one Activity/Fragment container to the other. Let’s begin by discussing the terminology that will be used in this series of posts. Note that although the terminology below is defined in terms of Activity transitions, the exact same terminology will be used for Fragment transitions as well: Let A and B be activities and assume activity A starts activity B. We refer to A as the “calling Activity” (the activity that “calls” startActivity()) and B as the “called Activity”. The Activity transition APIs are built around the idea of exit, enter, return, and reenter transitions. In the context of activities A and B defined above, we can describe each as follows: Activity A’s exit transition determines how views in A are animated when A starts B. Activity B’s enter transition determines how views in B are animated when A starts B. Activity B’s return transition determines how views in B are animated when B returns to A. Activity A’s reenter transition determines how views in A are animated when B returns to A. Video 1.2 - Content transitions and shared element transitions in action in the Google Play Newsstand app (as of v3.3). Click to play. Lastly, the framework provides APIs for two types of Activity transitions—content transitions and shared element transitions—each of which allow us to customize the animations between Activities in unique ways: A content transition determines how an activity’s non-shared views (also called transitioning views) enter or exit the activity scene. A shared element transition determines how an activity’s shared elements (also called hero views) are animated between two activities. Video 1.2 gives a nice illustration of content transitions and shared element transitions used in the Google Play Newsstand app. Although we can’t be sure without looking at the Newsstand source code, my best guess is that the following transitions are used: The exit and reenter content transitions for activity A (the calling activity) are both null. We can tell because the non-shared views in A are not animated when the user exits and reenters the activity.2 The enter content transition for activity B (the called activity) uses a custom slide-in transition that shuffles the list items into place from the bottom of the screen. The return content transition for activity B is a TransitionSet that plays two child transitions in parallel: a Slide(Gravity.TOP) transition targeting the views in the top half of the activity and a Slide(Gravity.BOTTOM) transition targeting the views in the bottom half of the activity. The result is that the activity appears to “break in half” when the user clicks the back button and returns to activity A. The enter and return shared element transitions both use a ChangeImageTransform, causing the ImageView to be animated seamlessly between the two activities. You’ve probably also noticed the cool circular reveal animation that plays under the shared element during the transition. We will cover how this can be done in a future blog post. For now, let’s keep things simple and familiarize ourselves with the Activity and Fragment transition APIs. Introducing the Activity Transition API Creating a basic Activity transition is relatively easy using the new Lollipop APIs. Summarized below are the steps you must take in order to implement one in your application. In the posts that follow, we will go through much more advanced use-cases and examples, but for now the next two sections will serve as a good introduction: Enable the new transition APIs by requesting the Window.FEATURE_ACTIVITY_TRANSITIONS window feature in your called and calling Activities, either programatically or in your theme’s XML.3 Material-themed applications have this flag enabled by default. Set exit and enter content transitions for your calling and called activities respectively. Material-themed applications have their exit and enter content transitions set to null and Fade respectively by default. If the reenter or return transitions are not explicitly set, the activity’s exit and enter content transitions respectively will be used in their place instead. Set exit and enter shared element transitions for your calling and called activities respectively. Material-themed applications have their shared element exit and enter transitions set to @android:transition/move by default. If the reenter or return transitions are not explicitly set, the activity’s exit and enter shared element transitions respectively will be used in their place instead. To start an Activity transition with content transitions and shared elements, call the startActivity(Context, Bundle) method and pass the following Bundle as the second argument: ActivityOptions.makeSceneTransitionAnimation(activity, pairs).toBundle(); where pairs is an array of Pair objects listing the shared element views and names that you’d like to share between activities.4 Don’t forget to give your shared elements unique transition names, either programatically or in XML. Otherwise, the transition will not work properly! To programatically trigger a return transition, call finishAfterTransition() instead of finish(). By default, material-themed applications have their enter/return content transitions started a tiny bit before their exit/reenter content transitions complete, creating a small overlap that makes the overall effect more seamless and dramatic. If you wish to explicitly disable this behavior, you can do so by calling the setWindowAllowEnterTransitionOverlap() and setWindowAllowReturnTransitionOverlap() methods or by setting the corresponding attributes in your theme’s XML. Introducing the Fragment Transition API If you are working with Fragment transitions, the API is similar with a few small differences: Content exit, enter, reenter, and return transitions should be set by calling the corresponding methods in the Fragment class or as attributes in your Fragment’s XML tag. Shared element enter and return transitions should be set by calling the corresponding methods in the Fragment class or as attributes in your Fragment’s XML. Whereas Activity transitions are triggered by explicit calls to startActivity() and finishAfterTransition(), Fragment transitions are triggered automatically when a fragment is added, removed, attached, detached, shown, or hidden by a FragmentTransaction. Shared elements should be specified as part of the FragmentTransaction by calling the addSharedElement(View, String) method before the transaction is committed. Conclusion In this post, we have only given a brief introduction to the new Activitiy and Fragment transition APIs. However, as we will see in the next few posts having a solid understanding of the basics will significantly speed up the development process in the long-run, especially when it comes to writing custom Transitions. In the posts that follow, we will cover content transitions and shared element transitions in even more depth and will obtain an even greater understanding of how Activity and Fragment transitions work under-the-hood. As always, thanks for reading! Feel free to leave a comment if you have any questions, and don’t forget to +1 and/or share this blog post if you found it helpful! 1 If you want to try the example out yourself, the XML layout code can be found here. ↩ 2 It might look like the views in A are fading in/out of the screen at first, but what you are really seeing is activity B fading in/out of the screen on top of activity A. The views in activity A are not actually animating during this time. You can adjust the duration of the background fade by calling setTransitionBackgroundFadeDuration() on the called activity’s Window. ↩ 3 For an explanation describing the differences between the FEATURE_ACTIVITY_TRANSITIONS and FEATURE_CONTENT_TRANSITIONS window feature flags, see this StackOverflow post. ↩ 4 To start an Activity transition with content transitions but no shared elements, you can create the Bundle by calling ActivityOptions.makeSceneTransitionAnimation(activity).toBundle(). To disable content transitions and shared element transitions entirely, don’t create a Bundle object at all—just pass null instead. ↩
This post will give an overview of how thread scheduling works in Android, and will briefly demonstrate how to explicitly set thread priorities yourself to ensure that your application remains responsive even as multiple threads run in the background. For those who are unfamiliar with the term, a thread scheduler is the part of the operating system in charge of deciding which threads in the system should run, when, and for how long. Android’s thread scheduler uses two main factors to determine how threads are scheduled across the entire system: nice values and cgroups. Nice values Similar to how they are used in Linux’s completely fair scheduling policy, nice values in Android are used as a measure of a thread’s priority. Threads with a higher nice value (i.e., lower priority, as in they are being “nice” to other threads in the system) will run less often than those with lower nice values (i.e., higher priority). The two most important of these are the default and background thread priorities. Intuitively, thread priorities should be chosen inverse-proportionally to the amount of work the thread is expected to do: the more work the thread will do, the less favorable thread priority it should get so that it doesn’t starve the system. For this reason, user interface threads (such as the main thread of a foreground Activity) are typically given a default priority, whereas background threads (such as a thread executing an AsyncTask) are typically given a background priority. Nice values are theoretically important because they help reduce background work that might otherwise interrupt the user interface. In practice, however, they alone are not sufficient. For example, consider twenty background threads and a single foreground thread driving the UI. Despite their low individual priorities, collectively the twenty background threads will still likely impact the performance of the single foreground thread, resulting in lag and hurting the user experience. Since at any given moment several applications could potentially have background threads waiting to run, the Android OS must somehow address these scenarios. Enter cgroups. Cgroups To address this problem, Android enforces an even stricter foreground vs. background scheduling policy using Linux cgroups (control groups). Threads with background priorities are implicitly moved into a background cgroup, where they are limited to only a small percentage1 of the available CPU if threads in other groups are busy. This separation allows background threads to make some forward progress, without having enough of an impact on the foreground threads to be visible to the user. In addition to automatically assigning low-priority threads to the background cgroup, Android ensures that all threads belonging to applications not currently running in the foreground are implicitly moved to the background cgroup as well. This automatic assignment of application threads to cgroups ensures that the current foreground application thread will always be the priority, regardless of how many applications are running in the background. Setting Priorities with Process#setThreadPriority(int) For the most part, the Android APIs already assign worker threads a background priority for you (for example, see the source code for HandlerThread and AsyncTask). It is important to remember, however, that this will not always be the case. Threads and ExecutorServices that are instantiated on the main UI thread, for example, will inherit a default, foreground priority, making lag more likely and possibly hurting the application’s performance. In these cases, you should always remember to set the thread’s priority by calling Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND) before the Thread is run. Doing so is straightforward, as shown in the example below: new Thread(new Runnable() { @Override public void run() { Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); // ... } }).start(); As always, thanks for reading, and leave a comment if you have any questions. Don’t forget to +1 this blog post too! 1 This percentage was 5% at the time of this writing, though it is possible that this value could change in the future. As of Android 4.4.2, cgroup mount points are created and initialized at boot-up in /system/core/rootdir/init.rc (see lines 100-123). ↩
A couple weeks ago, I began the ambitious task of rewriting this blog from scratch. Today, I’m happy to introduce a brand new look: one that is cleaner, faster, and more responsive. Several of the major changes are listed below. If this is your first time visiting this blog, you can find the old version of the site here to use as a reference. Goodbye Blogger, hello Jekyll. I’ve never been a huge fan of Blogger and was eager to try something new. After a bit of research I decided to give Jekyll a shot. Unlike Blogger, which dynamically parses content and templates on each request, Jekyll generates the entire website once beforehand to serve statically and is much more efficient as a result. It’s a bit under-documented and I’m not a huge fan of Liquid (the templating language Jekyll uses to process templates), but other than that I have no complaints. I’d take Jekyll over Blogger any day. 100% open-source. The source code powering this blog can be found on GitHub, and may be used by others as the basis of their own blogging templates under the MIT license (with the exception of the contents of the actual posts, of course :P). Given that Android Design Patterns wouldn’t even exist without Android—one of the largest open-source projects in the world—making this blog 100% open-source seemed fitting. Another cool implication of an entirely open-source blog is that readers can propose corrections themselves by submitting pull requests to GitHub. Faster page load times. Check out the old version of this blog and see for yourself! Page Speed reports an increase from 65/100 to 89/100 for mobile devices and 86/100 to 95/100 for desktop computers. Clean, responsive, and mobile-friendly. First of all, I’d like to thank +Shannon Lee for coming up with most of the new design. I consider myself a beginner at web design, so this couldn’t have been done without her! That said, I definitely recommend checking out the site on your phone or tablet, as it’s one of my favorite aspects of the new design. Below is a comparison of the old vs. new versions of the site on a Nexus 7: Disqus comments. Picking a third-party commenting platform to use was difficult, as there weren’t many options to choose from. I ended up choosing Disqus, as it was one of the few commenting systems that I could find that would correctly and reliably import my old comments from Blogger (spam detection is also a plus). One of the consequences of the migration, however, is that all old threaded comments are now unthreaded, meaning that most of the old comments are a bit of a mess right now. I plan on manually cleaning up these at some point in the future. Going forward, all new comments will thread normally. Let me know what you think of the new design in the comments below! Make sure to also leave any suggestions, criticisms, or feature requests too. The design will continue to be refined over time until it’s perfect. :)
The following stack trace and exception message has plagued StackOverflow ever since Honeycomb’s initial release: java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1341) at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1352) at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:595) at android.support.v4.app.BackStackRecord.commit(BackStackRecord.java:574) This post will explain why and when this exception is thrown, and will conclude with several suggestions that will help ensure it never crashes your application again. Why was the exception thrown? The exception was thrown because you attempted to commit a FragmentTransaction after the activity’s state had been saved, resulting in a phenomenon known as Activity state loss. Before we get into the details of what this actually means, however, let’s first take a look at what happens under-the-hood when onSaveInstanceState() is called. As I discussed in my last post about Binders & Death Recipients, Android applications have very little control over their destiny within the Android runtime environment. The Android system has the power to terminate processes at any time to free up memory, and background activities may be killed with little to no warning as a result. To ensure that this sometimes erratic behavior remains hidden from the user, the framework gives each Activity a chance to save its state by calling its onSaveInstanceState() method before making the Activity vulnerable to destruction. When the saved state is later restored, the user will be given the perception that they are seamlessly switching between foreground and background activities, regardless of whether or not the Activity had been killed by the system. When the framework calls onSaveInstanceState(), it passes the method a Bundle object for the Activity to use to save its state, and the Activity records in it the state of its dialogs, fragments, and views. When the method returns, the system parcels the Bundle object across a Binder interface to the System Server process, where it is safely stored away. When the system later decides to recreate the Activity, it sends this same Bundle object back to the application, for it to use to restore the Activity’s old state. So why then is the exception thrown? Well, the problem stems from the fact that these Bundle objects represent a snapshot of an Activity at the moment onSaveInstanceState() was called, and nothing more. That means when you call FragmentTransaction#commit() after onSaveInstanceState() is called, the transaction won’t be remembered because it was never recorded as part of the Activity’s state in the first place. From the user’s point of view, the transaction will appear to be lost, resulting in accidental UI state loss. In order to protect the user experience, Android avoids state loss at all costs, and simply throws an IllegalStateException whenever it occurs. When is the exception thrown? If you’ve encountered this exception before, you’ve probably noticed that the moment when it is thrown is slightly inconsistent across different platform versions. For example, you probably found that older devices tended to throw the exception less frequently, or that your application was more likely to crash when using the support library than when using the official framework classes. These slight inconsistencies have led many to assume that the support library is buggy and can’t be trusted. These assumptions, however, are generally not true. The reason why these slight inconsistencies exist stems from a significant change to the Activity lifecycle that was made in Honeycomb. Prior to Honeycomb, activities were not considered killable until after they had been paused, meaning that onSaveInstanceState() was called immediately before onPause(). Beginning with Honeycomb, however, Activities are considered to be killable only after they have been stopped, meaning that onSaveInstanceState() will now be called before onStop() instead of immediately before onPause(). These differences are summarized in the table below: pre-Honeycomb post-Honeycomb Activities can be killed before onPause()? NO NO Activities can be killed before onStop()? YES NO onSaveInstanceState(Bundle) is guaranteed to be called before… onPause() onStop() As a result of the slight changes that were made to the Activity lifecycle, the support library sometimes needs to alter its behavior depending on the platform version. For example, on Honeycomb devices and above, an exception will be thrown each and every time a commit() is called after onSaveInstanceState() to warn the developer that state loss has occurred. However, throwing an exception every time this happened would be too restrictive on pre-Honeycomb devices, which have their onSaveInstanceState() method called much earlier in the Activity lifecycle and are more vulnerable to accidental state loss as a result. The Android team was forced to make a compromise: for better inter-operation with older versions of the platform, older devices would have to live with the accidental state loss that might result between onPause() and onStop(). The support library’s behavior across the two platforms is summarized in the table below: pre-Honeycomb post-Honeycomb commit() before onPause() OK OK commit() between onPause() and onStop() STATE LOSS OK commit() after onStop() EXCEPTION EXCEPTION How to avoid the exception? Avoiding Activity state loss becomes a whole lot easier once you understand what is actually going on. If you’ve made it this far in the post, hopefully you understand a little better how the support library works and why it is so important to avoid state loss in your applications. In case you’ve referred to this post in search of a quick fix, however, here are some suggestions to keep in the back of your mind as you work with FragmentTransactions in your applications: Be careful when committing transactions inside Activity lifecycle methods. A large majority of applications will only ever commit transactions the very first time onCreate() is called and/or in response to user input, and will never face any problems as a result. However, as your transactions begin to venture out into the other Activity lifecycle methods, such as onActivityResult(), onStart(), and onResume(), things can get a little tricky. For example, you should not commit transactions inside the FragmentActivity#onResume() method, as there are some cases in which the method can be called before the activity’s state has been restored (see the documentation for more information). If your application requires committing a transaction in an Activity lifecycle method other than onCreate(), do it in either FragmentActivity#onResumeFragments() or Activity#onPostResume(). These two methods are guaranteed to be called after the Activity has been restored to its original state, and therefore avoid the possibility of state loss all together. (As an example of how this can be done, check out my answer to this StackOverflow question for some ideas on how to commit FragmentTransactions in response to calls made to the Activity#onActivityResult() method). Avoid performing transactions inside asynchronous callback methods. This includes commonly used methods such as AsyncTask#onPostExecute() and LoaderManager.LoaderCallbacks#onLoadFinished(). The problem with performing transactions in these methods is that they have no knowledge of the current state of the Activity lifecycle when they are called. For example, consider the following sequence of events: An activity executes an AsyncTask. The user presses the “Home” key, causing the activity’s onSaveInstanceState() and onStop() methods to be called. The AsyncTask completes and onPostExecute() is called, unaware that the Activity has since been stopped. A FragmentTransaction is committed inside the onPostExecute() method, causing an exception to be thrown. In general, the best way to avoid the exception in these cases is to simply avoid committing transactions in asynchronous callback methods all together. Google engineers seem to agree with this belief as well. According to this post on the Android Developers group, the Android team considers the major shifts in UI that can result from committing FragmentTransactions from within asynchronous callback methods to be bad for the user experience. If your application requires performing the transaction inside these callback methods and there is no easy way to guarantee that the callback won’t be invoked after onSaveInstanceState(), you may have to resort to using commitAllowingStateLoss() and dealing with the state loss that might occur. (See also these two StackOverflow posts for additional hints, here and here). Use commitAllowingStateLoss() only as a last resort. The only difference between calling commit() and commitAllowingStateLoss() is that the latter will not throw an exception if state loss occurs. Usually you don’t want to use this method because it implies that there is a possibility that state loss could happen. The better solution, of course, is to write your application so that commit() is guaranteed to be called before the activity’s state has been saved, as this will result in a better user experience. Unless the possibility of state loss can’t be avoided, commitAllowingStateLoss() should not be used. Hopefully these tips will help you resolve any issues you have had with this exception in the past. If you are still having trouble, post a question on StackOverflow and post a link in a comment below and I can take a look. :) As always, thanks for reading, and leave a comment if you have any questions. Don’t forget to +1 this blog and share this post on Google+ if you found it interesting!
Note: before you begin, make sure you’ve read my previous post about Binder tokens! Since the very beginning, Android’s central focus has been the ability to multitask. In order to achieve it, Android takes a unique approach by allowing multiple applications to run at the same time. Applications are never explicitly closed by the user, but are instead left running at a low priority to be killed by the system when memory is low. This ability to keep processes waiting in the background speeds up app-switching later down the line. Developers learn early on that the key to how Android handles applications in this way is that processes aren’t shut down cleanly. Android doesn’t rely on applications being well-written and responsive to polite requests to exit. Rather, it brutally force-kills them without warning, allowing the kernel to immediately reclaim resources associated with the process. This helps prevent serious out of memory situations and gives Android total control over misbehaving apps that are negatively impacting the system. For this reason, there is no guarantee that any user-space code (such as an Activity’s onDestroy() method) will ever be executed when an application’s process goes away. Considering the limited memory available in mobile environments, this approach seems promising. However, there is still one issue that needs to be addressed: how should the system detect an application’s death so that it can quickly clean up its state? When an application dies, its state will be spread over dozens of system services (the Activity Manager, Window Manager, Power Manager, etc.) and several different processes. These system services need to be notified immediately when an application dies so that they can clean up its state and maintain an accurate snapshot of the system. Enter death recipients. Death Recipients As it turns out, this task is made easy using the Binder’s “link-to-death” facility, which allows a process to get a callback when another process hosting a binder object goes away. In Android, any process can receive a notification when another process dies by taking the following steps: First, the process creates a DeathRecipient callback object containing the code to be executed when the death notification arrives. Next, it obtains a reference to a Binder object that lives in another process and calls its linkToDeath(IBinder.DeathRecipient recipient, int flags), passing the DeathRecipient callback object as the first argument. Finally, it waits for the process hosting the Binder object to die. When the Binder kernel driver detects that the process hosting the Binder is gone, it will notify the registered DeathRecipient callback object by calling its binderDied() method. Analyzing the source code once again gives some insight into how this pattern is used inside the framework. Consider an example application that (similar to the example given in my previous post) acquires a wake lock in onCreate(), but is abruptly killed by the system before it is able to release the wake lock in onDestroy(). How and when will the PowerManagerService be notified so that it can quickly release the wake lock before wasting the device’s battery? As you might expect, the PowerManagerService achieves this by registering a DeathRecipient (note that some of the source code has been cleaned up for the sake of brevity): /** * The global power manager system service. Application processes * interact with this class remotely through the PowerManager class. * * @see frameworks/base/services/java/com/android/server/power/PowerManagerService.java */ public final class PowerManagerService extends IPowerManager.Stub { // List of all wake locks acquired by applications. private List mWakeLocks = new ArrayList(); @Override // Binder call public void acquireWakeLock(IBinder token, int flags, String tag) { WakeLock wakeLock = new WakeLock(token, flags, tag); // Register to receive a notification when the process hosting // the token goes away. token.linkToDeath(wakeLock, 0); // Acquire the wake lock by adding it as an entry to the list. mWakeLocks.add(wakeLock); updatePowerState(); } @Override // Binder call public void releaseWakeLock(IBinder token, int flags) { int index = findWakeLockIndex(token); if (index < 0) { // The client app has sent us an invalid token, so ignore // the request. return; } // Release the wake lock by removing its entry from the list. WakeLock wakeLock = mWakeLocks.get(index); mWakeLocks.remove(index); // We no longer care about receiving death notifications. wakeLock.mToken.unlinkToDeath(wakeLock, 0); updatePowerState(); } private int findWakeLockIndex(IBinder token) { for (int i = 0; i < mWakeLocks.size(); i++) { if (mWakeLocks.get(i).mToken == token) { return i; } } return -1; } /** * Represents a wake lock that has been acquired by an application. */ private final class WakeLock implements IBinder.DeathRecipient { public final IBinder mToken; public final int mFlags; public final String mTag; public WakeLock(IBinder token, inf flags, String tag) { mToken = token; mFlags = flags; mTag = tag; } @Override public void binderDied() { int index = mWakeLocks.indexOf(this); if (index < 0) { return; } // The token's hosting process was killed before it was // able to explicitly release the wake lock, so release // it for them. mWakeLocks.remove(index); updatePowerState(); } } /** * Updates the global power state of the device. */ private void updatePowerState() { // ... } } The code might seem a little dense at first, but the concept is simple: When the application requests to acquire a wake lock, the power manager service’s acquireWakeLock() method is called. The power manager service registers the wake lock for the application, and also links to the death of the wake lock’s unique Binder token so that it can get notified if the application process is abruptly killed. When the application requests to release a wake lock, the power manager service’s releaseWakeLock() method is called. The power manager service releases the wake lock for the application, and also unlinks to the death of the wake lock’s unique Binder token (as it no longer cares about getting notified when the application process dies). When the application is abruptly killed before the wake lock is explicitly released, the Binder kernel driver notices that the wake lock’s Binder token has been linked to the death of the application process. The Binder kernel driver quickly dispatches a death notification to the registered death recipient’s binderDied() method, which quickly releases the wake lock and updates the device’s power state. Examples in the Framework The Binder’s link-to-death feature is an incredibly useful tool that is used extensively by the framework’s system services. Here are some of the more interesting examples: The window manager links to the death of the window’s callback interface. In the rare case that the application’s process is killed while its windows are still showing, the window manager will receive a death notification callback, at which point it can clean up after the application by closing its windows. When an application binds to a remote service, the application links to the death of the binder stub returned by the remote service’s onBind() method. If the remote service suddenly dies, the registered death recipient’s binderDied() method is called, which contains some clean up code, as well as the code that calls the onServiceDisconnected(ComponentName) method (the source code illustrating how this is done is located here). Many other system services depend on the Binder’s link-to-death facility in order to ensure that expensive resources aren’t leaked when an application process is forcefully killed. Some other examples are the VibratorService, LocationManagerService, GpsLocationProvider, and the WifiService. Additional Reading If you would like to learn more about Binders and how they work at a deeper level, I’ve included some links below. These articles were extremely helpful to me as I was writing these last two posts about Binder tokens and DeathRecipients, and I would strongly recommend reading them if you get a chance! This post is what initially got me interested in this topic. Special thanks to +Dianne Hackborn for explaining this! A great paper which explains almost everything you need to know about Binders. These slides are another great Binder resource. This Google+ post talks about how/why live wallpapers are given their own window. A great blog post discussing multitasking in Android. This Google+ post talks about how windows are crucial in achieving secure and efficient graphics performance. This book taught me a lot about how the application framework works from an embedded systems point of view… and it taught me a couple of really cool adb commands too! As always, thanks for reading, and leave a comment if you have any questions. Don’t forget to +1 this blog and share this post on Google+ if you found it interesting!
Note: if you liked this post, be sure to read my second blog post about Binders & Death Recipients as well! One of Android’s key design goals was to provide an open platform that doesn’t rely on a central authority to verify that applications do what they claim. To achieve this, Android uses application sandboxes and Linux process isolation to prevent applications from being able to access the system or other applications in ways that are not controlled and secure. This architecture was chosen with both developers and device users in mind: neither should have to take extra steps to protect the device from malicious applications. Everything should be taken care of automatically by the system. For a long time I took this security for granted, not completely understanding how it was actually enforced. But recently I became curious. What mechanism prevents me from, for example, tricking the system into releasing a wake lock acquired by another application, or from hiding another application’s windows from the screen? More generally, how do Android’s core system services respond to requests made by third-party applications in a way that is both efficient and secure? To my surprise, the answer to nearly all of my questions was pretty simple: the Binder. Binders are the cornerstone of Android’s architecture; they abstract the low-level details of IPC from the developer, allowing applications to easily talk to both the System Server and others’ remote service components. But Binders also have a number of other cool features that are used extensively throughout the system in a mix of clever ways, making it much easier for the framework to address security issues. This blog post will cover one of these features in detail, known as Binder tokens. Binder Tokens An interesting property of Binder objects is that each instance maintains a unique identity across all processes in the system, no matter how many process boundaries it crosses or where it goes. This facility is provided by the Binder kernel driver, which analyzes the contents of each Binder transaction and assigns a unique 32-bit integer value to each Binder object it sees. To ensure that Java’s == operator adheres to the Binder’s unique, cross-process object identity contract, a Binder’s object reference is treated a little differently than those of other objects. Specifically, each Binder’s object reference is assigned either, A virtual memory address pointing to a Binder object in the same process, or A unique 32-bit handle (as assigned by the Binder kernel driver) pointing to the Binder’s virtual memory address in a different process. The Binder kernel driver maintains a mapping of local addresses to remote Binder handles (and vice versa) for each Binder it sees, and assigns each Binder’s object reference its appropriate value to ensure that equality will behave as expected even in remote processes. The Binder’s unique object identity rules allow them to be used for a special purpose: as shared, security access tokens.1 Binders are globally unique, which means if you create one, nobody else can create one that appears equal to it. For this reason, the application framework uses Binder tokens extensively in order to ensure secure interaction between cooperating processes: a client can create a Binder object to use as a token that can be shared with a server process, and the server can use it to validate the client’s requests without there being anyway for others to spoof it. Let’s see how this works through a simple example. Consider an application which makes a request to the PowerManager to acquire (and later release) a wake lock: /** * An example activity that acquires a wake lock in onCreate() * and releases it in onDestroy(). */ public class MyActivity extends Activity { private PowerManager.WakeLock wakeLock; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "My Tag"); wakeLock.acquire(); } @Override protected void onDestroy() { super.onDestroy(); wakeLock.release(); } } Inspecting the PowerManager source code helps us understand what’s happening under the hood (note that some of the source code has been cleaned-up for the sake of brevity): /** * The interface that applications use to talk to the global power manager * system service. * * @see frameworks/base/core/java/android/os/PowerManager.java */ public final class PowerManager { // Our handle on the global power manager service. private final IPowerManager mService; public WakeLock newWakeLock(int levelAndFlags, String tag) { return new WakeLock(levelAndFlags, tag); } public final class WakeLock { private final IBinder mToken; private final int mFlags; private final String mTag; WakeLock(int flags, String tag) { // Create a token that uniquely identifies this wake lock. mToken = new Binder(); mFlags = flags; mTag = tag; } public void acquire() { // Send the power manager service a request to acquire a wake // lock for the application. Include the token as part of the // request so that the power manager service can validate the // application's identity when it requests to release the wake // lock later on. mService.acquireWakeLock(mToken, mFlags, mTag); } public void release() { // Send the power manager service a request to release the // wake lock associated with 'mToken'. mService.releaseWakeLock(mToken); } } } So what’s going on? Let’s walk through the code step-by-step: The client application requests an instance of the PowerManager class in onCreate(). The PowerManager class provides an interface for the client application to talk to the global PowerManagerService, which runs in the System Server process and is in charge of managing the device’s power state (i.e. determining the screen’s brightness, starting Daydreams, detecting when the device is plugged into a dock, etc.). The client application creates and acquires a wake lock in onCreate(). The PowerManager sends the WakeLock’s unique Binder token as part of the acquire() request. When the PowerManagerService receives the request, it holds onto the token for safe-keeping and forces the device to remain awake, until… The client application releases the wake lock in onDestroy(). The PowerManager sends the WakeLock’s unique Binder token as part of the request. When the PowerManagerService receives the request, it compares the token against all other WakeLock tokens it has stored, and only releases the WakeLock if it finds a match. This additional “validation step” is an important security measure put in place to guarantee that other applications cannot trick the PowerManagerService into releasing a WakeLock held by a different application. Because of their unique object-identity capabilities, Binder tokens are used extensively2 in the system for security. Perhaps the most interesting example of how they are used in the framework is the “window token,” which we will now discuss below. Window Tokens If you’ve ever scrolled through the official documentation for Android’s View class, chances are you’ve stumbled across the getWindowToken() method and wondered what it meant. As its name implies, a window token is a special type of Binder token that the window manager uses to uniquely identify a window in the system. Window tokens are important for security because they make it impossible for malicious applications to draw on top of the windows of other applications. The window manager protects against this by requiring applications to pass their application’s window token as part of each request to add or remove a window.3 If the tokens don’t match, the window manager rejects the request and throws a BadTokenException. Without window tokens, this necessary identification step wouldn’t be possible and the window manager wouldn’t be able to protect itself from malicious applications. By this point you might be wondering about the real-world scenarios in which you would need to obtain a window token. Here are some examples: When an application starts up for the first time, the ActivityManagerService4 creates a special kind of window token called an application window token, which uniquely identifies the application’s top-level container window.5 The activity manager gives this token to both the application and the window manager, and the application sends the token to the window manager each time it wants to add a new window to the screen. This ensures secure interaction between the application and the window manager (by making it impossible to add windows on top of other applications), and also makes it easy for the activity manager to make direct requests to the window manager. For example, the activity manager can say, “hide all of this token’s windows”, and the window manager will be able to correctly identify the set of windows which should be closed.6 Developers implementing their own custom Launchers can interact with the live wallpaper window that sits directly behind them by calling the sendWallpaperCommand(IBinder windowToken, String action, int x, int y, int z, Bundle extras) method. To ensure that no other application other than the Launcher is able to interact with the live wallpaper, the framework requires developers to pass a window token as the first argument to the method. If the window token does not identify the current foreground activity window sitting on top of the wallpaper, the command is ignored and a warning is logged. Applications can ask the InputMethodManager to hide the soft keyboard by calling the hideSoftInputFromWindow(IBinder windowToken, int flags) method, but must provide a window token as part of the request. If the token doesn’t match the window token belonging to the window currently accepting input, the InputMethodManager will reject the request. This makes it impossible for malicious applications to force-close a soft keyboard opened by another application. Applications which manually add new windows to the screen (i.e. using the addView(View, WindowManager.LayoutParams) method) may need to specify their application’s window token by setting the WindowManager.LayoutParams.token field. It is very unlikely that any normal application would ever have to do this, since the getWindowManager() method returns a WindowManager which will automatically set the token’s value for you. That said, if at some point in the future you encounter a situation in which you need to add a panel window to the screen from a background service, know that you would need to manually sign the request with your application window token in order to achieve it. :P Conclusion Though their existence is for the most part hidden from developers, Binder tokens are used extensively in the system for security. Android is a massively distributed system of cooperating processes reliant on the fact that Binder objects are unique across all processes on the device. Binder tokens are the driving force behind interaction in the framework, and without them secure communication between application processes and the system would be difficult to achieve. As always, thanks for reading, and leave a comment if you have any questions. Don’t forget to +1 this blog in the top right corner! 1 The documentation actually hints that Binders can be used for this purpose: “You can… simply instantiate a raw Binder object directly to use as a token that can be shared across processes.” ↩ 2 Pick a random file in frameworks/base/services/java/com/android/server and chances are it makes use of Binder tokens in some shape or form. Another cool example involves the status bar, notification manager, and the system UI. Specifically, the StatusBarManagerService maintains a global mapping of Binder tokens to notifications. When the NotificationManagerService makes a request to the status bar manager to add a notification to the status bar, the status bar manager creates a binder token uniquely identifying the notification and passes it to both the notification manager and the SystemUI. Since all three parties know the notification’s Binder token, any changes to the notification from that point forward (i.e. the notification manager cancels the notification, or the SystemUI detects that the user has swiped a notification off screen) will go through the status bar manager first. This makes it easier for the three system services to stay in sync: the status bar manager can be in charge of centralizing all of the information about which notifications should currently be shown without the SystemUI and notification manager ever having to interact with each other directly. ↩ 3 Applications that hold the android.permission.SYSTEM_ALERT_WINDOW permission (a.k.a. the “draw over other apps” permission) are notable exceptions to this rule. Facebook Messenger and DicePlayer are two popular applications which require this permission, and use it to add windows on top of other applications from a background service. ↩ 4 The ActivityManagerService is the global system service (running in the System Server process) that is in charge of starting (and managing) new components, such as Activities and Services. It’s also involved in the maintenance of OOM adjustments used by the in-kernel low-memory handler, permissions, task management, etc. ↩ 5 You can obtain a reference by calling getApplicationWindowToken(). ↩ 6 This explanation barely scratches the surface. For a more detailed explanation of how the SurfaceFlinger, WindowManager, and application interact with each other, see this article. The third paragraph of the “SurfaceFlinger and Hardware Composer” section briefly mentions the Binder application window token that is passed to the application as I discussed above. ↩
This post addresses a common question that is frequently asked on StackOverflow: What is the best way to retain active objects—such as running Threads, Sockets, and AsyncTasks—across device configuration changes? To answer this question, we will first discuss some of the common difficulties developers face when using long-running background tasks in conjunction with the Activity lifecycle. Then, we will describe the flaws of two common approaches to solving the problem. Finally, we will conclude with sample code illustrating the recommended solution, which uses retained Fragments to achieve our goal. Configuration Changes & Background Tasks One problem with configuration changes and the destroy-and-create cycle that Activitys go through as a result stems from the fact that these events are unpredictable and may occur at any time. Concurrent background tasks only add to this problem. Assume, for example, that an Activity starts an AsyncTask and soon after the user rotates the screen, causing the Activity to be destroyed and recreated. When the AsyncTask eventually finishes its work, it will incorrectly report its results back to the old Activity instance, completely unaware that a new Activity has been created. As if this wasn’t already an issue, the new Activity instance might waste valuable resources by firing up the background work again, unaware that the old AsyncTask is still running. For these reasons, it is vital that we correctly and efficiently retain active objects across Activity instances when configuration changes occur. Bad Practice: Retain the Activity Perhaps the hackiest and most widely abused workaround is to disable the default destroy-and-recreate behavior by setting the android:configChanges attribute in your Android manifest. The apparent simplicity of this approach makes it extremely attractive to developers; Google engineers, however, discourage its use. The primary concern is that it requires you to handle device configuration changes manually in code. Handling configuration changes requires you to take many additional steps to ensure that each and every string, layout, drawable, dimension, etc. remains in sync with the device’s current configuration, and if you aren’t careful, your application can easily have a whole series of resource-specific bugs as a result. Another reason why Google discourages its use is because many developers incorrectly assume that setting android:configChanges="orientation" (for example) will magically protect their application from unpredictable scenarios in which the underlying Activity will be destroyed and recreated. This is not the case. Configuration changes can occur for a number of reasons—not just screen orientation changes. Inserting your device into a display dock, changing the default language, and modifying the device’s default font scaling factor are just three examples of events that can trigger a device configuration change, all of which signal the system to destroy and recreate all currently running Activitys the next time they are resumed. As a result, setting the android:configChanges attribute is generally not good practice. Deprecated: Override onRetainNonConfigurationInstance() Prior to Honeycomb’s release, the recommended means of transferring active objects across Activity instances was to override the onRetainNonConfigurationInstance() and getLastNonConfigurationInstance() methods. Using this approach, transferring an active object across Activity instances was merely a matter of returning the active object in onRetainNonConfigurationInstance() and retrieving it in getLastNonConfigurationInstance(). As of API 13, these methods have been deprecated in favor of the more Fragment’s setRetainInstance(boolean) capability, which provides a much cleaner and modular means of retaining objects during configuration changes. We discuss this Fragment-based approach in the next section. Recommended: Manage the Object Inside a Retained Fragment Ever since the introduction of Fragments in Android 3.0, the recommended means of retaining active objects across Activity instances is to wrap and manage them inside of a retained “worker” Fragment. By default, Fragments are destroyed and recreated along with their parent Activitys when a configuration change occurs. Calling Fragment#setRetainInstance(true) allows us to bypass this destroy-and-recreate cycle, signaling the system to retain the current instance of the fragment when the activity is recreated. As we will see, this will prove to be extremely useful with Fragments that hold objects like running Threads, AsyncTasks, Sockets, etc. The sample code below serves as a basic example of how to retain an AsyncTask across a configuration change using retained Fragments. The code guarantees that progress updates and results are delivered back to the currently displayed Activity instance and ensures that we never accidentally leak an AsyncTask during a configuration change. The design consists of two classes, a MainActivity… /** * This Activity displays the screen's UI, creates a TaskFragment * to manage the task, and receives progress updates and results * from the TaskFragment when they occur. */ public class MainActivity extends Activity implements TaskFragment.TaskCallbacks { private static final String TAG_TASK_FRAGMENT = "task_fragment"; private TaskFragment mTaskFragment; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); FragmentManager fm = getFragmentManager(); mTaskFragment = (TaskFragment) fm.findFragmentByTag(TAG_TASK_FRAGMENT); // If the Fragment is non-null, then it is currently being // retained across a configuration change. if (mTaskFragment == null) { mTaskFragment = new TaskFragment(); fm.beginTransaction().add(mTaskFragment, TAG_TASK_FRAGMENT).commit(); } // TODO: initialize views, restore saved state, etc. } // The four methods below are called by the TaskFragment when new // progress updates or results are available. The MainActivity // should respond by updating its UI to indicate the change. @Override public void onPreExecute() { ... } @Override public void onProgressUpdate(int percent) { ... } @Override public void onCancelled() { ... } @Override public void onPostExecute() { ... } } …and a TaskFragment… /** * This Fragment manages a single background task and retains * itself across configuration changes. */ public class TaskFragment extends Fragment { /** * Callback interface through which the fragment will report the * task's progress and results back to the Activity. */ interface TaskCallbacks { void onPreExecute(); void onProgressUpdate(int percent); void onCancelled(); void onPostExecute(); } private TaskCallbacks mCallbacks; private DummyTask mTask; /** * Hold a reference to the parent Activity so we can report the * task's current progress and results. The Android framework * will pass us a reference to the newly created Activity after * each configuration change. */ @Override public void onAttach(Activity activity) { super.onAttach(activity); mCallbacks = (TaskCallbacks) activity; } /** * This method will only be called once when the retained * Fragment is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Retain this fragment across configuration changes. setRetainInstance(true); // Create and execute the background task. mTask = new DummyTask(); mTask.execute(); } /** * Set the callback to null so we don't accidentally leak the * Activity instance. */ @Override public void onDetach() { super.onDetach(); mCallbacks = null; } /** * A dummy task that performs some (dumb) background work and * proxies progress updates and results back to the Activity. * * Note that we need to check if the callbacks are null in each * method in case they are invoked after the Activity's and * Fragment's onDestroy() method have been called. */ private class DummyTask extends AsyncTask { @Override protected void onPreExecute() { if (mCallbacks != null) { mCallbacks.onPreExecute(); } } /** * Note that we do NOT call the callback object's methods * directly from the background thread, as this could result * in a race condition. */ @Override protected Void doInBackground(Void... ignore) { for (int i = 0; !isCancelled() && i < 100; i++) { SystemClock.sleep(100); publishProgress(i); } return null; } @Override protected void onProgressUpdate(Integer... percent) { if (mCallbacks != null) { mCallbacks.onProgressUpdate(percent[0]); } } @Override protected void onCancelled() { if (mCallbacks != null) { mCallbacks.onCancelled(); } } @Override protected void onPostExecute(Void ignore) { if (mCallbacks != null) { mCallbacks.onPostExecute(); } } } } Flow of Events When the MainActivity starts up for the first time, it instantiates and adds the TaskFragment to the Activity’s state. The TaskFragment creates and executes an AsyncTask and proxies progress updates and results back to the MainActivity via the TaskCallbacks interface. When a configuration change occurs, the MainActivity goes through its normal lifecycle events, and once created the new Activity instance is passed to the onAttach(Activity) method, thus ensuring that the TaskFragment will always hold a reference to the currently displayed Activity instance even after the configuration change. The resulting design is both simple and reliable; the application framework will handle re-assigning Activity instances as they are torn down and recreated, and the TaskFragment and its AsyncTask never need to worry about the unpredictable occurrence of a configuration change. Note also that it is impossible for onPostExecute() to be executed in between the calls to onDetach() and onAttach(), as explained in this StackOverflow answer and in my reply to Doug Stevenson in this Google+ post (there is also some discussion about this in the comments below). Conclusion Synchronizing background tasks with the Activity lifecycle can be tricky and configuration changes will only add to the confusion. Fortunately, retained Fragments make handling these events very easy by consistently maintaining a reference to its parent Activity, even after being destroyed and recreated. A sample application illustrating how to correctly use retained Fragments to achieve this effect is available for download on the Play Store. The source code is available on GitHub. Download it, import it into Eclipse, and modify it all you want! As always, leave a comment if you have any questions and don’t forget to +1 this blog in the top right corner!
Note: the source code in this blog post is available on GitHub. A common difficulty in Android programming is coordinating long-running tasks over the Activity lifecycle and avoiding the subtle memory leaks which might result. Consider the Activity code below, which starts and loops a new thread upon its creation: /** * Example illustrating how threads persist across configuration * changes (which cause the underlying Activity instance to be * destroyed). The Activity context also leaks because the thread * is instantiated as an anonymous class, which holds an implicit * reference to the outer Activity instance, therefore preventing * it from being garbage collected. */ public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); exampleOne(); } private void exampleOne() { new Thread() { @Override public void run() { while (true) { SystemClock.sleep(1000); } } }.start(); } } When a configuration change occurs, causing the entire Activity to be destroyed and re-created, it is easy to assume that Android will clean up after us and reclaim the memory associated with the Activity and its running thread. However, this is not the case. Both will leak never to be reclaimed, and the result will likely be a significant reduction in performance. How to Leak an Activity The first memory leak should be immediately obvious if you read my previous post on Handlers and inner classes. In Java, non-static anonymous classes hold an implicit reference to their enclosing class. If you’re not careful, storing this reference can result in the Activity being retained when it would otherwise be eligible for garbage collection. Activity objects hold a reference to their entire view hierarchy and all its resources, so if you leak one, you leak a lot of memory. The problem is only exacerbated by configuration changes, which signal the destruction and re-creation of the entire underlying Activity. For example, after ten orientation changes running the code above, we can see (using Eclipse Memory Analyzer) that each Activity object is in fact retained in memory as a result of these implicit references: Figure 1. Activity instances retained in memory after ten orientation changes. After each configuration change, the Android system creates a new Activity and leaves the old one behind to be garbage collected. However, the thread holds an implicit reference to the old Activity and prevents it from ever being reclaimed. As a result, each new Activity is leaked and all resources associated with them are never able to be reclaimed. The fix is easy once we’ve identified the source of the problem: declare the thread as a private static inner class as shown below. /** * This example avoids leaking an Activity context by declaring the * thread as a private static inner class, but the threads still * continue to run even across configuration changes. The DVM has a * reference to all running threads and whether or not these threads * are garbage collected has nothing to do with the Activity lifecycle. * Active threads will continue to run until the kernel destroys your * application's process. */ public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); exampleTwo(); } private void exampleTwo() { new MyThread().start(); } private static class MyThread extends Thread { @Override public void run() { while (true) { SystemClock.sleep(1000); } } } } The new thread no longer holds an implicit reference to the Activity, and the Activity will be eligible for garbage collection after the configuration change. How to Leak a Thread The second issue is that for each new Activity that is created, a thread is leaked and never able to be reclaimed. Threads in Java are GC roots; that is, the Dalvik Virtual Machine (DVM) keeps hard references to all active threads in the runtime system, and as a result, threads that are left running will never be eligible for garbage collection. For this reason, you must remember to implement cancellation policies for your background threads! One example of how this might be done is shown below: /** * Same as example two, except for this time we have implemented a * cancellation policy for our thread, ensuring that it is never * leaked! onDestroy() is usually a good place to close your active * threads before exiting the Activity. */ public class MainActivity extends Activity { private MyThread mThread; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); exampleThree(); } private void exampleThree() { mThread = new MyThread(); mThread.start(); } /** * Static inner classes don't hold implicit references to their * enclosing class, so the Activity instance won't be leaked across * configuration changes. */ private static class MyThread extends Thread { private boolean mRunning = false; @Override public void run() { mRunning = true; while (mRunning) { SystemClock.sleep(1000); } } public void close() { mRunning = false; } } @Override protected void onDestroy() { super.onDestroy(); mThread.close(); } } In the code above, closing the thread in onDestroy() ensures that you never accidentally leak the thread. If you want to persist the same thread across configuration changes (as opposed to closing and re-creating a new thread each time), consider using a retained, UI-less worker fragment to perform the long-running task. Check out my blog post, titled Handling Configuration Changes with Fragments, for an example explaining how this can be done. There is also a comprehensive example available in the API demos which illustrates the concept. Conclusion In Android, coordinating long-running tasks over the Activity lifecycle can be difficult and memory leaks can result if you aren’t careful. Here are some general tips to consider when dealing with coordinating your long-running background tasks with the Activity lifecycle: Favor static inner classes over nonstatic. Each instance of a nonstatic inner class will have an extraneous reference to its outer Activity instance. Storing this reference can result in the Activity being retained when it would otherwise be eligible for garbage collection. If your static inner class requires a reference to the underlying Activity in order to function properly, make sure you wrap the object in a WeakReference to ensure that you don’t accidentally leak the Activity. Don’t assume that Java will ever clean up your running threads for you. In the example above, it is easy to assume that when the user exits the Activity and the Activity instance is finalized for garbage collection, any running threads associated with that Activity will be reclaimed as well. This is never the case. Java threads will persist until either they are explicitly closed or the entire process is killed by the Android system. As a result, it is extremely important that you remember to implement cancellation policies for your background threads, and to take appropriate action when Activity lifecycle events occur. Consider whether or not you should use a Thread. The Android application framework provides many classes designed to make background threading easier for developers. For example, consider using a Loader instead of a thread for performing short-lived asynchronous background queries in conjunction with the Activity lifecycle. Likewise, if the background thread is not tied to any specific Activity, consider using a Service and report the results back to the UI using a BroadcastReceiver. Lastly, remember that everything discussed regarding threads in this blog post also applies to AsyncTasks (since the AsyncTask class uses an ExecutorService to execute its tasks). However, given that AsyncTasks should only be used for short-lived operations (“a few seconds at most”, as per the documentation), leaking an Activity or a thread by these means should never be an issue. The source code for this blog post is available on GitHub. A standalone application (which mirrors the source code exactly) is also available for download on Google Play. As always, leave a comment if you have any questions and don’t forget to +1 this blog in the top right corner!
Consider the following code: public class SampleActivity extends Activity { private final Handler mLeakyHandler = new Handler() { @Override public void handleMessage(Message msg) { // ... } }; } While not readily obvious, this code can cause cause a massive memory leak. Android Lint will give the following warning: In Android, Handler classes should be static or leaks might occur. But where exactly is the leak and how might it happen? Let’s determine the source of the problem by first documenting what we know: When an Android application first starts, the framework creates a Looper object for the application’s main thread. A Looper implements a simple message queue, processing Message objects in a loop one after another. All major application framework events (such as Activity lifecycle method calls, button clicks, etc.) are contained inside Message objects, which are added to the Looper’s message queue and are processed one-by-one. The main thread’s Looper exists throughout the application’s lifecycle. When a Handler is instantiated on the main thread, it is associated with the Looper’s message queue. Messages posted to the message queue will hold a reference to the Handler so that the framework can call Handler#handleMessage(Message) when the Looper eventually processes the message. In Java, non-static inner and anonymous classes hold an implicit reference to their outer class. Static inner classes, on the other hand, do not. So where exactly is the memory leak? It’s very subtle, but consider the following code as an example: public class SampleActivity extends Activity { private final Handler mLeakyHandler = new Handler() { @Override public void handleMessage(Message msg) { // ... } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Post a message and delay its execution for 10 minutes. mLeakyHandler.postDelayed(new Runnable() { @Override public void run() { /* ... */ } }, 1000 * 60 * 10); // Go back to the previous Activity. finish(); } } When the activity is finished, the delayed message will continue to live in the main thread’s message queue for 10 minutes before it is processed. The message holds a reference to the activity’s Handler, and the Handler holds an implicit reference to its outer class (the SampleActivity, in this case). This reference will persist until the message is processed, thus preventing the activity context from being garbage collected and leaking all of the application’s resources. Note that the same is true with the anonymous Runnable class on line 15. Non-static instances of anonymous classes hold an implicit reference to their outer class, so the context will be leaked. To fix the problem, subclass the Handler in a new file or use a static inner class instead. Static inner classes do not hold an implicit reference to their outer class, so the activity will not be leaked. If you need to invoke the outer activity’s methods from within the Handler, have the Handler hold a WeakReference to the activity so you don’t accidentally leak a context. To fix the memory leak that occurs when we instantiate the anonymous Runnable class, we make the variable a static field of the class (since static instances of anonymous classes do not hold an implicit reference to their outer class): public class SampleActivity extends Activity { /** * Instances of static inner classes do not hold an implicit * reference to their outer class. */ private static class MyHandler extends Handler { private final WeakReference mActivity; public MyHandler(SampleActivity activity) { mActivity = new WeakReference(activity); } @Override public void handleMessage(Message msg) { SampleActivity activity = mActivity.get(); if (activity != null) { // ... } } } private final MyHandler mHandler = new MyHandler(this); /** * Instances of anonymous classes do not hold an implicit * reference to their outer class when they are "static". */ private static final Runnable sRunnable = new Runnable() { @Override public void run() { /* ... */ } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Post a message and delay its execution for 10 minutes. mHandler.postDelayed(sRunnable, 1000 * 60 * 10); // Go back to the previous Activity. finish(); } } The difference between static and non-static inner classes is subtle, but is something every Android developer should understand. What’s the bottom line? Avoid using non-static inner classes in an activity if instances of the inner class could outlive the activity’s lifecycle. Instead, prefer static inner classes and hold a weak reference to the activity inside. As always, leave a comment if you have any questions and don’t forget to +1 this blog in the top right corner! :)
A couple weeks ago I wrote a library that simplifies the interaction between Go-based application servers and Google Cloud Messaging servers. I plan on covering GCM (both the application-side and server-side aspects) in more detail in a future blog post, but for now I will just leave a link to the library to encourage more people to write their GCM application servers using the Go Programming Language (Google App Engine, hint hint). …but why Go? I’m glad you asked. There are several reasons: Go is modern. Programming languages like C, C++, and Java are old, designed before the advent of multicore machines, networking, and web application development. Go was designed to be suitable for writing large Google programs such as web servers. Go is concise, yet familiar. Tasks that require 40+ lines of code in Java (i.e. setting up HTTP servers and parsing JSON responses) can be done in 1 or 2 lines. Go significantly reduces the amount of work required to write simple programs, and yet the language’s syntax is not too radical, still resembling the most common procedural languages. Go is easy to learn. Learn the language in a day: A Tour of Go and Effective Go. Go was invented at Google. Enough said. :) That’s all for now… but expect a lot more on GCM, Google App Engine, and Golang later! The comments are open as always, and don’t forget to +1 this post! Links Google Cloud Messaging for Go Google App Engine A Tour of Go Effective Go golang.org
WARNING: Many of the APIs used in this code have been deprecated since I initially wrote this post. Check out the official documentation for the latest instructions. One of the trickiest aspects of writing a robust web-based Android application is authentication, simply due to its asynchronous nature and the many edge cases that one must cover. Thankfully, the recently released Google Play Services API greatly simplifies the authentication process, providing developers with a consistent and safe way to grant and receive OAuth2 access tokens to Google services. Even so, there are still several cases that must be covered in order to provide the best possible user experience. A professionally built Android application should be able to react to even the most unlikely events, for example, if a previously logged in user uninstalls Google Play Services, or navigates to the system settings and clears the application’s data when the foreground Activity is in a paused state. This post focuses on how to make use of the Google Play Services library while still accounting for edge cases such as these. Verifying Google Play Services In this post, we will implement a very basic (but robust) Android application that authenticates a user with Google services. Our implementation will consist of a single Activity: public class AuthActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); } } As you probably already know, before we attempt authentication using Google Play Services, we must first verify that the service is up-to-date and installed on the device. This seems easy enough, but where should these checks be performed? As with most edge-case checks, it makes the most sense to verify that our device is properly configured in the Activity’s onResume() method. Verifying in onResume() is important because it has the application perform a check each time the Activity is brought into the foreground, thus guaranteeing that our application will never incorrectly assume that Google Play Services is properly configured: @Override protected void onResume() { super.onResume(); if (checkPlayServices()) { // Then we're good to go! } } Now let’s implement checkPlayServices(), which will return true if and only if Google Play Services is correctly installed and configured on the device: private boolean checkPlayServices() { int status = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this); if (status != ConnectionResult.SUCCESS) { if (GooglePlayServicesUtil.isUserRecoverableError(status)) { showErrorDialog(status); } else { Toast.makeText(this, "This device is not supported.", Toast.LENGTH_LONG).show(); finish(); } return false; } return true; } void showErrorDialog(int code) { GooglePlayServicesUtil.getErrorDialog(code, this, REQUEST_CODE_RECOVER_PLAY_SERVICES).show(); } And we implement onActivityResult as follows: static final int REQUEST_CODE_RECOVER_PLAY_SERVICES = 1001; @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { switch (requestCode) { case REQUEST_CODE_RECOVER_PLAY_SERVICES: if (resultCode == RESULT_CANCELED) { Toast.makeText(this, "Google Play Services must be installed.", Toast.LENGTH_SHORT).show(); finish(); } return; } super.onActivityResult(requestCode, resultCode, data); } If Google Play Services are available, the method will return true. If Google Play Services is not available and error is deemed “unrecoverable,” a Toast will indicate to the user that the device is not supported. Otherwise, an error dialog will be shown and a result will eventually propagate back to onActivityResult. Note that onActivityResult is called before onResume, so when a result is returned, we will perform one final check just to be sure that everything has been setup correctly. Checking the Currently Logged In User What we have so far is enough to ensure that our users will be able to use our application if and only if Google Play Services is installed and up-to-date. Now let’s assume that our application also stores the name of the currently logged in user in its SharedPreferences. How should our application respond in the case that the current user is unexpectedly logged out (i.e. the user has clicked “Clear data” in the app’s system settings)? It turns out that we can do something very similar. (Note that the code below makes use of a simple utility file named AccountUtils.java, which provides some helper methods for reading/writing account information to the app’s SharedPreferences). First, define a method that will verify the existence of a single authenticated user in the application’s shared preferences and update the onResume() method accordingly: @Override protected void onResume() { super.onResume(); if (checkPlayServices() && checkUserAccount()) { // Then we're good to go! } } private boolean checkPlayServices() { /* ... */ } private boolean checkUserAccount() { String accountName = AccountUtils.getAccountName(this); if (accountName == null) { // Then the user was not found in the SharedPreferences. Either the // application deliberately removed the account, or the application's // data has been forcefully erased. showAccountPicker(); return false; } Account account = AccountUtils.getGoogleAccountByName(this, accountName); if (account == null) { // Then the account has since been removed. AccountUtils.removeAccount(this); showAccountPicker(); return false; } return true; } private void showAccountPicker() { Intent pickAccountIntent = AccountPicker.newChooseAccountIntent( null, null, new String[] { GoogleAuthUtil.GOOGLE_ACCOUNT_TYPE }, true, null, null, null, null); startActivityForResult(pickAccountIntent, REQUEST_CODE_PICK_ACCOUNT); } Note that in the case that a user is not already signed in, an AccountPicker dialog will be launched, requesting that the user selects a Google account with which the application will use to authenticate requests. The result will eventually be returned back to the Activity, so we must update the onActivityResult method accordingly: static final int REQUEST_CODE_RECOVER_PLAY_SERVICES = 1001; static final int REQUEST_CODE_PICK_ACCOUNT = 1002; @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { switch (requestCode) { case REQUEST_CODE_RECOVER_PLAY_SERVICES: /* ... */ case REQUEST_CODE_PICK_ACCOUNT: if (resultCode == RESULT_OK) { String accountName = data.getStringExtra( AccountManager.KEY_ACCOUNT_NAME); AccountUtils.setAccountName(this, accountName); } else if (resultCode == RESULT_CANCELED) { Toast.makeText(this, "This application requires a Google account.", Toast.LENGTH_SHORT).show(); finish(); } return; } super.onActivityResult(requestCode, resultCode, data); } As was the case before, onResume will be called after onActivityResult, ensuring that Google Play Services is still installed and up-to-date, and that a Google account has indeed been selected and saved to the disk. Conclusion However unlikely they might be, covering edge cases in your Android applications is very important. If a user deliberately tries to break your application (by, for example, clearing the application’s data in the system settings), the app should immediately recognize the event and act appropriately. The same concept outlined in this post applies to many areas of Android development, not just those apps which make use of Google Play Services. The full source code for this post is provided here AccountUtils.java and AuthActivity.java. As always, leave a comment if you have any questions and don’t forget to +1 this post!
A common source of confusion when implementing ContentProviders is that of thread-safety. We all know that any potentially expensive query should be asynchronous so as not to block the UI thread, but when, if ever, is it OK to make calls to the ContentProvider from multiple threads? Threads and Content Providers The documentation on ContentProviders warns that its methods may be called from multiple threads and therefore must be thread-safe: Data access methods (such as insert(Uri, ContentValues) and update(Uri, ContentValues, String, String[])) may be called from many threads at once, and must be thread-safe. In other words, Android does not synchronize access to the ContentProvider for you. If two calls to the same method are made simultaneously from separate threads, neither call will wait for the other. Requiring the client to deal with concurrency themselves makes sense from a framework developer’s point of view. The abstract ContentProvider class cannot assume that its subclasses will require synchronization, as doing so would be horribly inefficient. Ensuring Thread Safety So now that we know that the ContentProvider is not thread safe, what do we need to do in order to eliminate potential race conditions? Just make every method synchronized, right? Well… no, not necessarily. Consider a ContentProvider that uses a SQLiteDatabase as its backing data source. As per the documentation, access to the SQLiteDatabase is synchronized by default, thus guaranteeing that no two threads will ever touch it at the same time. In this case, synchronizing each of the ContentProvider’s methods is both unnecessary and costly. Remember that a ContentProvider serves as a wrapper around the underlying data source; whether or not you must take extra measures to ensure thread safety often depends on the data source itself. Conclusion Although the ContentProvider lacks in thread-safety, often times you will find that no further action is required on your part with respect to preventing potential race conditions. The canonical example is when your ContentProvider is backed by a SQLiteDatabase; when two threads attempt to write to the database at the same time, the SQLiteDatabase will lock itself down, ensuring that one will wait until the other has completed. Each thread will be given mutually exclusive access to the data source, ensuring the thread safety is met. This has been a rather short post, so don’t hesitate to leave a comment if you have any clarifying questions. Don’t forget to +1 this post below if you found it helpful!
This will be my fourth and final post on Loaders and the LoaderManager. Let me know in the comments if they have been helpful! Links to my previous Loader-related posts are given below: Part 1: Life Before Loaders Part 2: Understanding the LoaderManager Part 3: Implementing Loaders Part 4: Tutorial: AppListLoader Due to public demand, I’ve written a sample application that illustrates how to correctly implement a custom Loader. The application is named AppListLoader, and it is a simple demo application that queries and lists all installed applications on your Android device. The application is a modified, re-thought (and bug-free) extension of the LoaderCustom.java sample that is provided in the API Demos. The application uses an AppListLoader (a subclass of AsyncTaskLoader) to query its data, and the LoaderManager to manage the Loader across the Activity/Fragment lifecycle: The AppListLoader registers two BroadcastReceivers which observe/listen for system-wide broadcasts that impact the underlying data source. The InstalledAppsObserver listens for newly installed, updated, or removed applications, and the SystemLocaleObserver listens for locale changes. For example, if the user changes the language from English to Spanish, the SystemLocaleObserver will notify the AppListLoader to re-query its data so that the application can display each application’s name in Spanish (assuming an alternate Spanish name has been provided). Click “Change language” in the options menu and watch the Loader’s seamless reaction to the event (it’s awesome, isn’t it? :P). Log messages are written to the logcat whenever an important Loader/LoaderManager-related event occurs, so be sure to run the application while analyzing the logcat! Hopefully it’ll give you a better understanding of how Loaders work in conjunction with the LoaderManager and the Activity/Fragment lifecycle. Be sure to filter the logcat by application name (“com.adp.loadercustom”) for the best results! You can download the application from Google Play by clicking the badge below: The source code is available on GitHub. An excessive amount of comments flesh out the entire application-Loader workflow. Download it, import it as an eclipse project, and modify it all you want! Let me know if these posts have been helpful by leaving a comment below! As always, don’t hesitate to ask questions either!
Hi all, I’ve recently made this blog available on Google Currents! Install the application and subscribe by clicking this link. If you have never used Google Currents, I strongly recommend that you try it out. It’s a really great way to keep up with the latest news, blogs, and your favorite Google+ streams, and it works seamlessly offline (which I’ve found is great for long plane rides). If you’re a long time Flipboard user, I recommend you give it a try as well… in my opinion, Currents is easier to navigate and feels much more like a native Android application. That said, I do tend to be a bit biased towards the native Google apps. :P As always, don’t hesitate to leave a comment if you find a bug or have any suggestions on how I can improve the edition! I’m going to try really hard to keep it up-to-date for those of you who follow this blog and can’t get enough of Google Currents! Cheers, Alex
This post introduces the Loader class as well as custom Loader implementations. This is the third of a series of posts I will be writing on Loaders and the LoaderManager: Part 1: Life Before Loaders Part 2: Understanding the LoaderManager Part 3: Implementing Loaders Part 4: Tutorial: AppListLoader First things first, if you haven’t read my previous two posts, I suggest you do so before continuing further. Here is a very brief summary of what this blog has covered so far. Life Before Loaders (part 1) described the flaws of the pre-Honeycomb 3.0 API and its tendency to perform lengthy queries on the main UI thread. These UI-unfriendly APIs resulted in unresponsive applications and were the primary motivation for introducing the Loader and the LoaderManager in Android 3.0. Understanding the LoaderManager (part 2) introduced the LoaderManager class and its role in delivering asynchronously loaded data to the client. The LoaderManager manages its Loaders across the Activity/Fragment lifecycle and can retain loaded data across configuration changes. Loader Basics Loaders are responsible for performing queries on a separate thread, monitoring the data source for changes, and delivering new results to a registered listener (usually the LoaderManager) when changes are detected. These characteristics make Loaders a powerful addition to the Android SDK for several reasons: They encapsulate the actual loading of data. The Activity/Fragment no longer needs to know how to load data. Instead, the Activity/Fragment delegates the task to the Loader, which carries out the request behind the scenes and has its results delivered back to the Activity/Fragment. They abstract out the idea of threads from the client. The Activity/Fragment does not need to worry about offloading queries to a separate thread, as the Loader will do this automatically. This reduces code complexity and eliminates potential thread-related bugs. They are entirely event-driven. Loaders monitor the underlying data source and automatically perform new loads for up-to-date results when changes are detected. This makes working with Loaders easy, as the client can simply trust that the Loader will auto-update its data on its own. All the Activity/Fragment has to do is initialize the Loader and respond to any results that might be delivered. Everything in between is done by the Loader. Loaders are a somewhat advanced topic and may take some time getting used to. We begin by analyzing its four defining characteristics in the next section. What Makes Up a Loader? There are four characteristics which ultimately determine a Loader’s behavior: A task to perform the asynchronous load. To ensure that loads are done on a separate thread, subclasses should extend AsyncTaskLoader as opposed to the Loader class. AsyncTaskLoader is an abstract Loader which provides an AsyncTask to do its work. When subclassed, implementing the asynchronous task is as simple as implementing the abstract loadInBackground() method, which is called on a worker thread to perform the data load. A registered listener to receive the Loader’s results when it completes a load.1 For each of its Loaders, the LoaderManager registers an OnLoadCompleteListener which will forward the Loader’s delivered results to the client with a call to onLoadFinished(Loader loader, D result). Loaders should deliver results to these registered listeners with a call to Loader#deliverResult(D result). One of three2 distinct states. Any given Loader will either be in a started, stopped, or reset state: Loaders in a started state execute loads and may deliver their results to the listener at any time. Started Loaders should monitor for changes and perform new loads when changes are detected. Once started, the Loader will remain in a started state until it is either stopped or reset. This is the only state in which onLoadFinished will ever be called. Loaders in a stopped state continue to monitor for changes but should not deliver results to the client. From a stopped state, the Loader may either be started or reset. Loaders in a reset state should not execute new loads, should not deliver new results, and should not monitor for changes. When a loader enters a reset state, it should invalidate and free any data associated with it for garbage collection (likewise, the client should make sure they remove any references to this data, since it will no longer be available). More often than not, reset Loaders will never be called again; however, in some cases they may be started, so they should be able to start running properly again if necessary. An observer to receive notifications when the data source has changed. Loaders should implement an observer of some sort (i.e. a ContentObserver, a BroadcastReceiver, etc.) to monitor the underlying data source for changes. When a change is detected, the observer should call Loader#onContentChanged(), which will either (a) force a new load if the Loader is in a started state or, (b) raise a flag indicating that a change has been made so that if the Loader is ever started again, it will know that it should reload its data. By now you should have a basic understanding of how Loaders work. If not, I suggest you let it sink in for a bit and come back later to read through once more (reading the documentation never hurts either!). That being said, let’s get our hands dirty with the actual code! Implementing the Loader As I stated earlier, there is a lot that you must keep in mind when implementing your own custom Loaders. Subclasses must implement loadInBackground() and should override onStartLoading(), onStopLoading(), onReset(), onCanceled(), and deliverResult(D results) to achieve a fully functioning Loader. Overriding these methods is very important as the LoaderManager will call them regularly depending on the state of the Activity/Fragment lifecycle. For example, when an Activity is first started, the Activity instructs the LoaderManager to start each of its Loaders in Activity#onStart(). If a Loader is not already started, the LoaderManager calls startLoading(), which puts the Loader in a started state and immediately calls the Loader’s onStartLoading() method. In other words, a lot of work that the LoaderManager does behind the scenes relies on the Loader being correctly implemented, so don’t take the task of implementing these methods lightly! The code below serves as a template of what a Loader implementation typically looks like. The SampleLoader queries a list of SampleItem objects and delivers a List to the client: public class SampleLoader extends AsyncTaskLoader> { // We hold a reference to the Loader’s data here. private List mData; public SampleLoader(Context ctx) { // Loaders may be used across multiple Activitys (assuming they aren't // bound to the LoaderManager), so NEVER hold a reference to the context // directly. Doing so will cause you to leak an entire Activity's context. // The superclass constructor will store a reference to the Application // Context instead, and can be retrieved with a call to getContext(). super(ctx); } /****************************************************/ /** (1) A task that performs the asynchronous load **/ /****************************************************/ @Override public List loadInBackground() { // This method is called on a background thread and should generate a // new set of data to be delivered back to the client. List data = new ArrayList(); // TODO: Perform the query here and add the results to 'data'. return data; } /********************************************************/ /** (2) Deliver the results to the registered listener **/ /********************************************************/ @Override public void deliverResult(List data) { if (isReset()) { // The Loader has been reset; ignore the result and invalidate the data. releaseResources(data); return; } // Hold a reference to the old data so it doesn't get garbage collected. // We must protect it until the new data has been delivered. List oldData = mData; mData = data; if (isStarted()) { // If the Loader is in a started state, deliver the results to the // client. The superclass method does this for us. super.deliverResult(data); } // Invalidate the old data as we don't need it any more. if (oldData != null && oldData != data) { releaseResources(oldData); } } /*********************************************************/ /** (3) Implement the Loader’s state-dependent behavior **/ /*********************************************************/ @Override protected void onStartLoading() { if (mData != null) { // Deliver any previously loaded data immediately. deliverResult(mData); } // Begin monitoring the underlying data source. if (mObserver == null) { mObserver = new SampleObserver(); // TODO: register the observer } if (takeContentChanged() || mData == null) { // When the observer detects a change, it should call onContentChanged() // on the Loader, which will cause the next call to takeContentChanged() // to return true. If this is ever the case (or if the current data is // null), we force a new load. forceLoad(); } } @Override protected void onStopLoading() { // The Loader is in a stopped state, so we should attempt to cancel the // current load (if there is one). cancelLoad(); // Note that we leave the observer as is. Loaders in a stopped state // should still monitor the data source for changes so that the Loader // will know to force a new load if it is ever started again. } @Override protected void onReset() { // Ensure the loader has been stopped. onStopLoading(); // At this point we can release the resources associated with 'mData'. if (mData != null) { releaseResources(mData); mData = null; } // The Loader is being reset, so we should stop monitoring for changes. if (mObserver != null) { // TODO: unregister the observer mObserver = null; } } @Override public void onCanceled(List data) { // Attempt to cancel the current asynchronous load. super.onCanceled(data); // The load has been canceled, so we should release the resources // associated with 'data'. releaseResources(data); } private void releaseResources(List data) { // For a simple List, there is nothing to do. For something like a Cursor, we // would close it in this method. All resources associated with the Loader // should be released here. } /*********************************************************************/ /** (4) Observer which receives notifications when the data changes **/ /*********************************************************************/ // NOTE: Implementing an observer is outside the scope of this post (this example // uses a made-up "SampleObserver" to illustrate when/where the observer should // be initialized). // The observer could be anything so long as it is able to detect content changes // and report them to the loader with a call to onContentChanged(). For example, // if you were writing a Loader which loads a list of all installed applications // on the device, the observer could be a BroadcastReceiver that listens for the // ACTION_PACKAGE_ADDED intent, and calls onContentChanged() on the particular // Loader whenever the receiver detects that a new application has been installed. // Please don’t hesitate to leave a comment if you still find this confusing! :) private SampleObserver mObserver; } Conclusion I hope these posts were useful and gave you a better understanding of how Loaders and the LoaderManager work together to perform asynchronous, auto-updating queries. Remember that Loaders are your friends… if you use them, your app will benefit in both responsiveness and the amount of code you need to write to get everything working properly! Hopefully I could help lessen the learning curve a bit by detailing them out! As always, please don’t hesitate to leave a comment if you have any questions! And don’t forget to +1 this blog in the top right corner if you found it helpful! 1 You don’t need to worry about registering a listener for your Loader unless you plan on using it without the LoaderManager. The LoaderManager will act as this “listener” and will forward any results that the Loader delivers to the LoaderCallbacks#onLoadFinished method. ↩ 2 Loaders may also be in an “abandoned” state. This is an optional intermediary state between “stopped” and “reset” and is not discussed here for the sake of brevity. That said, in my experience implementing onAbandon() is usually not necessary. ↩
Here’s a question that is worth thinking about: Should I implement an “Exit application?” dialog in my app? In my experience, the answer is almost always no. Consider the official Flickr app, as an example. At the main screen, the user clicks the back button and is immediately prompted with a dialog, questioning whether or not the user wishes to exit the application: (a) Back button pressed. (b) "Exit Flickr?" So what went wrong? Well, pretty much everything, at least in my opinion. Here are the three major flaws I see in Flickr’s decision to include the dialog: It slows down the user experience. An additional click is required to leave the application. Sure, it doesn’t seem like much… but zero clicks is always better than one. Including the dialog will annoy the occasional meticulous power user and will make it much more likely that people like me will write-up angry rants about it online. To make matters worse, Flickr’s dialog incorrectly positions the “OK” and “Cancel” buttons, which as of Android 4.0, should be positioned on the right and left respectively. This is also not a huge deal, but it forces users to think more than they should need to, and the simple action of exiting the application is no longer seamless as a result. It is inconsistent. Name one native Android application that warns the user when they are about to exit the application. If you can’t, that’s because there are none. Of all the familiar, Google-made Android apps (Gmail, Google Drive, etc.), exactly none of them exhibit this behavior. The user expects the back button to bring him or her back to the top activity on the Activity Stack; there is no reason why it shouldn’t do otherwise in this simple situation. It serves absolutely no purpose. What baffles me the most, however, is that there is no reason to confirm exit in the first place. Maybe the dialog would be OK if there was a long-running operation running in the background that is specific to the Activity (i.e. an AsyncTask that the user might not want canceled). A dialog might also make sense if the application took a long time to load, for example, a fancy, video intensive FPS like Dead Trigger. In Flickr’s case, there is no acceptable reason why the user shouldn’t be allowed to “back-out” of the application immediately. In my opinion, dialogs are both slow and annoying, and should be used as little as possible. Always prefer the faster “edit in place” user model (as described here) when it comes to saving persistent state, and never prompt the user when they wish to “back-out” of the application unless you have a very good reason for doing so. As always, let me know if you agree or disagree in the comments below! EDIT: For more discussion on this topic, I recommend reading through the content/comments of this Google+ post (made by +Cyril Mottier, a very talented Android developer recognized by Google as a Android Developer Expert).
This post introduces the LoaderManager class. This is the second of a series of posts I will be writing on Loaders and the LoaderManager: Part 1: Life Before Loaders Part 2: Understanding the LoaderManager Part 3: Implementing Loaders Part 4: Tutorial: AppListLoader Note: Understanding the LoaderManager requires some general knowledge about how Loaders work. Their implementation will be covered extensively in my next post. For now, you should think of Loaders as simple, self-contained objects that (1) load data on a separate thread, and (2) monitor the underlying data source for updates, re-querying when changes are detected. This is more than enough to get you through the contents of this post. All Loaders are assumed to be 100% correctly implemented in this post. What is the LoaderManager? Simply stated, the LoaderManager is responsible for managing one or more Loaders associated with an Activity or Fragment. Each Activity and each Fragment has exactly one LoaderManager instance that is in charge of starting, stopping, retaining, restarting, and destroying its Loaders. These events are sometimes initiated directly by the client, by calling initLoader(), restartLoader(), or destroyLoader(). Just as often, however, these events are triggered by major Activity/Fragment lifecycle events. For example, when an Activity is destroyed, the Activity instructs its LoaderManager to destroy and close its Loaders (as well as any resources associated with them, such as a Cursor). The LoaderManager does not know how data is loaded, nor does it need to. Rather, the LoaderManager instructs its Loaders when to start/stop/reset their load, retaining their state across configuration changes and providing a simple interface for delivering results back to the client. In this way, the LoaderManager is a much more intelligent and generic implementation of the now-deprecated startManagingCursor method. While both manage data across the twists and turns of the Activity lifecycle, the LoaderManager is far superior for several reasons: startManagingCursor manages Cursors, whereas the LoaderManager manages Loader objects. The advantage here is that Loader is generic, where D is the container object that holds the loaded data. In other words, the data source doesn’t have to be a Cursor; it could be a List, a JSONArray… anything. The LoaderManager is independent of the container object that holds the data and is much more flexible as a result. Calling startManagingCursor will make the Activity call requery() on the managed cursor. As mentioned in the previous post, requery() is a potentially expensive operation that is performed on the main UI thread. Subclasses of the Loader class, on the other hand, are expected to load their data asynchronously, so using the LoaderManager will never block the UI thread. startManagingCursor does not retain the Cursor’s state across configuration changes. Instead, each time the Activity is destroyed due to a configuration change (a simple orientation change, for example), the Cursor is destroyed and must be requeried. The LoaderManager is much more intelligent in that it retains its Loaders’ state across configuration changes, and thus doesn’t need to requery its data. The LoaderManager provides seamless monitoring of data! Whenever the Loader’s data source is modified, the LoaderManager will receive a new asynchronous load from the corresponding Loader, and will return the updated data to the client. (Note: the LoaderManager will only be notified of these changes if the Loader is implemented correctly. We will discuss how to implement custom Loaders in part 3 of this series of posts). If you feel overwhelmed by the details above, I wouldn’t stress over it. The most important thing to take away from this is that the LoaderManager makes your life easy. It initializes, manages, and destroys Loaders for you, reducing both coding complexity and subtle lifecycle-related bugs in your Activitys and Fragments. Further, interacting with the LoaderManager involves implementing three simple callback methods. We discuss the LoaderManager.LoaderCallbacks in the next section. Implementing the LoaderManager.LoaderCallbacks Interface The LoaderManager.LoaderCallbacks interface is a simple contract that the LoaderManager uses to report data back to the client. Each Loader gets its own callback object that the LoaderManager will interact with. This callback object fills in the gaps of the abstract LoaderManager implementation, telling it how to instantiate the Loader (onCreateLoader) and providing instructions when its load is complete/reset (onLoadFinished and onLoadReset, respectively). Most often you will implement the callbacks as part of the component itself, by having your Activity or Fragment implement the LoaderManager.LoaderCallbacks interface: public class SampleActivity extends Activity implements LoaderManager.LoaderCallbacks { public Loader onCreateLoader(int id, Bundle args) { ... } public void onLoadFinished(Loader loader, D data) { ... } public void onLoaderReset(Loader loader) { ... } /* ... */ } Once instantiated, the client passes the callbacks object (“this”, in this case) as the third argument to the LoaderManager’s initLoader method, and will be bound to the Loader as soon as it is created. Overall, implementing the callbacks is straightforward. Each callback method serves a specific purpose that makes interacting with the LoaderManager easy: onCreateLoader is a factory method that simply returns a new Loader. The LoaderManager will call this method when it first creates the Loader. onLoadFinished is called automatically when a Loader has finished its load. This method is typically where the client will update the application’s UI with the loaded data. The client may (and should) assume that new data will be returned to this method each time new data is made available. Remember that it is the Loader’s job to monitor the data source and to perform the actual asynchronous loads. The LoaderManager will receive these loads once they have completed, and then pass the result to the callback object’s onLoadFinished method for the client (i.e. the Activity/Fragment) to use. Lastly, onLoadReset is called when the Loader’s data is about to be reset. This method gives you the opportunity to remove any references to old data that may no longer be available. In the next section, we will discuss a commonly asked question from beginning Android developers: how to transition from outdated managed Cursors to the much more powerful LoaderManager. Transitioning from Managed Cursors to the LoaderManager The code below is similar in behavior to the sample in my previous post. The difference, of course, is that it has been updated to use the LoaderManager. The CursorLoader ensures that all queries are performed asynchronously, thus guaranteeing that we won’t block the UI thread. Further, the LoaderManager manages the CursorLoader across the Activity lifecycle, retaining its data on configuration changes and directing each new data load to the callback’s onLoadFinished method, where the Activity is finally free to make use of the queried Cursor. public class SampleListActivity extends ListActivity implements LoaderManager.LoaderCallbacks { private static final String[] PROJECTION = new String[] { "_id", "text_column" }; // The loader's unique id. Loader ids are specific to the Activity or // Fragment in which they reside. private static final int LOADER_ID = 1; // The callbacks through which we will interact with the LoaderManager. private LoaderManager.LoaderCallbacks mCallbacks; // The adapter that binds our data to the ListView private SimpleCursorAdapter mAdapter; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); String[] dataColumns = { "text_column" }; int[] viewIDs = { R.id.text_view }; // Initialize the adapter. Note that we pass a 'null' Cursor as the // third argument. We will pass the adapter a Cursor only when the // data has finished loading for the first time (i.e. when the // LoaderManager delivers the data to onLoadFinished). Also note // that we have passed the '0' flag as the last argument. This // prevents the adapter from registering a ContentObserver for the // Cursor (the CursorLoader will do this for us!). mAdapter = new SimpleCursorAdapter(this, R.layout.list_item, null, dataColumns, viewIDs, 0); // Associate the (now empty) adapter with the ListView. setListAdapter(mAdapter); // The Activity (which implements the LoaderCallbacks // interface) is the callbacks object through which we will interact // with the LoaderManager. The LoaderManager uses this object to // instantiate the Loader and to notify the client when data is made // available/unavailable. mCallbacks = this; // Initialize the Loader with id '1' and callbacks 'mCallbacks'. // If the loader doesn't already exist, one is created. Otherwise, // the already created Loader is reused. In either case, the // LoaderManager will manage the Loader across the Activity/Fragment // lifecycle, will receive any new loads once they have completed, // and will report this new data back to the 'mCallbacks' object. LoaderManager lm = getLoaderManager(); lm.initLoader(LOADER_ID, null, mCallbacks); } @Override public Loader onCreateLoader(int id, Bundle args) { // Create a new CursorLoader with the following query parameters. return new CursorLoader(SampleListActivity.this, CONTENT_URI, PROJECTION, null, null, null); } @Override public void onLoadFinished(Loader loader, Cursor cursor) { // A switch-case is useful when dealing with multiple Loaders/IDs switch (loader.getId()) { case LOADER_ID: // The asynchronous load is complete and the data // is now available for use. Only now can we associate // the queried Cursor with the SimpleCursorAdapter. mAdapter.swapCursor(cursor); break; } // The listview now displays the queried data. } @Override public void onLoaderReset(Loader loader) { // For whatever reason, the Loader's data is now unavailable. // Remove any references to the old data by replacing it with // a null Cursor. mAdapter.swapCursor(null); } } Conclusion As its name suggests, the LoaderManager is responsible for managing Loaders across the Activity/Fragment lifecycle. The LoaderManager is simple and its implementation usually requires very little code. The tricky part is implementing the Loaders, the topic of the next post: Implementing Loaders (part 3). Leave a comment if you have any questions, or just to let me know if this post helped or not! Don’t forget to +1 this blog in the top right corner too! :)
This post gives a brief introduction to Loaders and the LoaderManager. The first section describes how data was loaded prior to the release of Android 3.0, pointing out out some of the flaws of the pre-Honeycomb APIs. The second section defines the purpose of each class and summarizes their powerful ability in asynchronously loading data. This is the first of a series of posts I will be writing on Loaders and the LoaderManager: Part 1: Life Before Loaders Part 2: Understanding the LoaderManager Part 3: Implementing Loaders Part 4: Tutorial: AppListLoader If you know nothing about Loaders and the LoaderManager, I strongly recommend you read the documentation before continuing forward. The Not-So-Distant Past Before Android 3.0, many Android applications lacked in responsiveness. UI interactions glitched, transitions between activities lagged, and ANR (Application Not Responding) dialogs rendered apps totally useless. This lack of responsiveness stemmed mostly from the fact that developers were performing queries on the UI thread—a very poor choice for lengthy operations like loading data. While the documentation has always stressed the importance of instant feedback, the pre-Honeycomb APIs simply did not encourage this behavior. Before Loaders, cursors were primarily managed and queried for with two (now deprecated) Activity methods: public void startManagingCursor(Cursor) Tells the activity to take care of managing the cursor’s lifecycle based on the activity’s lifecycle. The cursor will automatically be deactivated (deactivate()) when the activity is stopped, and will automatically be closed (close()) when the activity is destroyed. When the activity is stopped and then later restarted, the Cursor is re-queried (requery()) for the most up-to-date data. public Cursor managedQuery(Uri, String, String, String, String) A wrapper around the ContentResolver’s query() method. In addition to performing the query, it begins management of the cursor (that is, startManagingCursor(cursor) is called before it is returned). While convenient, these methods were deeply flawed in that they performed queries on the UI thread. What’s more, the “managed cursors” did not retain their data across Activity configuration changes. The need to requery() the cursor’s data in these situations was unnecessary, inefficient, and made orientation changes clunky and sluggish as a result. The Problem with “Managed Cursors” Let’s illustrate the problem with “managed cursors” through a simple code sample. Given below is a ListActivity that loads data using the pre-Honeycomb APIs. The activity makes a query to the ContentProvider and begins management of the returned cursor. The results are then bound to a SimpleCursorAdapter, and are displayed on the screen in a ListView. The code has been condensed for simplicity. public class SampleListActivity extends ListActivity { private static final String[] PROJECTION = new String[] {"_id", "text_column"}; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Performs a "managed query" to the ContentProvider. The Activity // will handle closing and requerying the cursor. // // WARNING!! This query (and any subsequent re-queries) will be // performed on the UI Thread!! Cursor cursor = managedQuery( CONTENT_URI, // The Uri constant in your ContentProvider class PROJECTION, // The columns to return for each data row null, // No where clause null, // No where clause null); // No sort order String[] dataColumns = { "text_column" }; int[] viewIDs = { R.id.text_view }; // Create the backing adapter for the ListView. // // WARNING!! While not readily obvious, using this constructor will // tell the CursorAdapter to register a ContentObserver that will // monitor the underlying data source. As part of the monitoring // process, the ContentObserver will call requery() on the cursor // each time the data is updated. Since Cursor#requery() is performed // on the UI thread, this constructor should be avoided at all costs! SimpleCursorAdapter adapter = new SimpleCursorAdapter( this, // The Activity context R.layout.list_item, // Points to the XML for a list item cursor, // Cursor that contains the data to display dataColumns, // Bind the data in column "text_column"... viewIDs); // ...to the TextView with id "R.id.text_view" // Sets the ListView's adapter to be the cursor adapter that was // just created. setListAdapter(adapter); } } There are three problems with the code above. If you have understood this post so far, the first two shouldn’t be difficult to spot: managedQuery performs a query on the main UI thread. This leads to unresponsive apps and should no longer be used. As seen in the Activity.java source code, the call to managedQuery begins management of the returned cursor with a call to startManagingCursor(cursor). Having the activity manage the cursor seems convenient at first, as we no longer need to worry about deactivating/closing the cursor ourselves. However, this signals the activity to call requery() on the cursor each time the activity returns from a stopped state, and therefore puts the UI thread at risk. This cost significantly outweighs the convenience of having the activity deactivate/close the cursor for us. The SimpleCursorAdapter constructor is deprecated and should not be used. The problem with this constructor is that it will have the SimpleCursorAdapter auto-requery its data when changes are made. More specifically, the CursorAdapter will register a ContentObserver that monitors the underlying data source for changes, calling requery() on its bound cursor each time the data is modified. The standard constructor should be used instead (if you intend on loading the adapter’s data with a CursorLoader, make sure you pass 0 as the last argument). Don’t worry if you couldn’t spot this one… it’s a very subtle bug. With the first Android tablet about to be released, something had to be done to encourage UI-friendly development. The larger, 7-10” Honeycomb tablets called for more complicated, interactive, multi-paned layouts. Further, the introduction of the Fragment meant that applications were about to become more dynamic and event-driven. A simple, single-threaded approach to loading data could no longer be encouraged. Thus, the Loader and the LoaderManager were born. Android 3.0, Loaders, and the LoaderManager Prior to Honeycomb, it was difficult to manage cursors, synchronize correctly with the UI thread, and ensure all queries occurred on a background thread. Android 3.0 introduced the Loader and LoaderManager classes to help simplify the process. Both classes are available for use in the Android Support Library, which supports all Android platforms back to Android 1.6. The new Loader API is a huge step forward, and significantly improves the user experience. Loaders ensure that all cursor operations are done asynchronously, thus eliminating the possibility of blocking the UI thread. Further, when managed by the LoaderManager, Loaders retain their existing cursor data across the activity instance (for example, when it is restarted due to a configuration change), thus saving the cursor from unnecessary, potentially expensive re-queries. As an added bonus, Loaders are intelligent enough to monitor the underlying data source for updates, re-querying automatically when the data is changed. Conclusion Since the introduction of Loaders in Honeycomb and Compatibility Library, Android applications have changed for the better. Making use of the now deprecated startManagingCursor and managedQuery methods are extremely discouraged; not only do they slow down your app, but they can potentially bring it to a screeching halt. Loaders, on the other hand, significantly speed up the user experience by offloading the work to a separate background thread. In the next post (titled Understanding the LoaderManager), we will go more in-depth on how to fix these problems by completing the transition from “managed cursors” to making use of Loaders and the LoaderManager. Don’t forget to +1 this blog in the top right corner if you found this helpful!
Content Providers and Content Resolvers are a common source of confusion for beginning Android developers. Further, online tutorials and sample code are not sufficient in describing how the two classes work together to provide access to the Android data model. This post hopes to fill in this gap by explaining their place in the android.content package. It concludes with a walk through the life of a simple query to the Content Resolver. The android.content Package The android.content package contains classes for accessing and publishing data. The Android framework enforces a robust and secure data sharing model. Applications are not allowed direct access to other application’s internal data. Two classes in the package help enforce this requirement: the ContentResolver and the ContentProvider. What is the Content Resolver? The Content Resolver is the single, global instance in your application that provides access to your (and other applications’) content providers. The Content Resolver behaves exactly as its name implies: it accepts requests from clients, and resolves these requests by directing them to the content provider with a distinct authority. To do this, the Content Resolver stores a mapping from authorities to Content Providers. This design is important, as it allows a simple and secure means of accessing other applications’ Content Providers. The Content Resolver includes the CRUD (create, read, update, delete) methods corresponding to the abstract methods (insert, query, update, delete) in the Content Provider class. The Content Resolver does not know the implementation of the Content Providers it is interacting with (nor does it need to know); each method is passed an URI that specifies the Content Provider to interact with. What is a Content Provider? Whereas the Content Resolver provides an abstraction from the application’s Content Providers, Content Providers provide an abstraction from the underlying data source (i.e. a SQLite database). They provide mechanisms for defining data security (i.e. by enforcing read/write permissions) and offer a standard interface that connects data in one process with code running in another process. Content Providers provide an interface for publishing and consuming data, based around a simple URI addressing model using the content:// schema. They enable you to decouple your application layers from the underlying data layers, making your application data-source agnostic by abstracting the underlying data source. The Life of a Query So what exactly is the step-by-step process behind a simple query? As described above, when you query data from your database via the content provider, you don’t communicate with the provider directly. Instead, you use the Content Resolver object to communicate with the provider. The specific sequence of events that occurs when a query is made is given below: A call to getContentResolver().query(Uri, String, String, String, String) is made. The call invokes the Content Resolver’s query method, not the ContentProvider’s. When the query method is invoked, the Content Resolver parses the uri argument and extracts its authority. The Content Resolver directs the request to the content provider registered with the (unique) authority. This is done by calling the Content Provider’s query method. When the Content Provider’s query method is invoked, the query is performed and a Cursor is returned (or an exception is thrown). The resulting behavior depends entirely on the Content Provider’s implementation. Conclusion An integral part of the android.content package, the ContentResolver and ContentProvider classes work together to ensure secure access to other applications’ data. Understanding how the underlying system works becomes second nature once you’ve written enough Android code, but I hope that someone finds this explanation helpful some day. Let me know if you have any questions about the process!
The following question has plagued StackOverflow ever since Ice Cream Sandwich’s initial release: My application works fine on devices running Android 2.x, but force closes on devices running Honeycomb (3.x) and Ice Cream Sandwich (4.x). Why does this occur? This is a great question; after all, newer versions of Android are released with the expectation that old apps will remain compatible with new devices. In my experience, there are a couple reasons why this might occur. Most of the time, however, the reason is simple: you are performing a potentially expensive operation on the UI thread. What is the UI Thread? The concept and importance of the application’s main UI thread is something every Android developer should understand. Each time an application is launched, the system creates a thread called “main” for the application. The main thread (also known as the “UI thread”) is in charge of dispatching events to the appropriate views/widgets and thus is very important. It’s also the thread where your application interacts with running components of your application’s UI. For instance, if you touch a button on the screen, the UI thread dispatches the touch event to the view, which then sets its pressed state and posts an invalidate request to the event queue. The UI thread dequeues this request and then tells the view to redraw itself. This single-thread model can yield poor performance unless Android applications are implemented properly. Specifically, if the UI thread was in charge of running everything in your entire application, performing long operations such as network access or database queries on the UI thread would block the entire user interface. No event would be able to be dispatched—including drawing and touchscreen events—while the long operation is underway. From the user’s perspective, the application will appear to be frozen. In these situations, instant feedback is vital. Studies show that 0.1 seconds is about the limit for having the user feel that the system is reacting instantaneously. Anything slower than this limit will probably be considered as lag (Miller 1968; Card et al. 1991). While a fraction of a second might not seem harmful, even a tenth of a second can be the difference between a good review and a bad review on Google Play. Even worse, if the UI thread is blocked for more than about five seconds, the user is presented with the notorious “application not responding” (ANR) dialog and the app is force closed. Why Android Crashes Your App The reason why your application crashes on Android versions 3.0 and above, but works fine on Android 2.x is because Honeycomb and Ice Cream Sandwich are much stricter about abuse against the UI Thread. For example, when an Android device running HoneyComb or above detects a network access on the UI thread, a NetworkOnMainThreadException will be thrown: E/AndroidRuntime(673): java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example/com.example.ExampleActivity}: android.os.NetworkOnMainThreadException The explanation as to why this occurs is well documented on the Android developer’s site: A NetworkOnMainThreadException is thrown when an application attempts to perform a networking operation on its main thread. This is only thrown for applications targeting the Honeycomb SDK or higher. Applications targeting earlier SDK versions are allowed to do networking on their main event loop threads, but it’s heavily discouraged. Some examples of other operations that ICS and Honeycomb won’t allow you to perform on the UI thread are: Opening a Socket connection (i.e. new Socket()). HTTP requests (i.e. HTTPClient and HTTPUrlConnection). Attempting to connect to a remote MySQL database. Downloading a file (i.e. Downloader.downloadFile()). If you are attempting to perform any of these operations on the UI thread, you must wrap them in a worker thread. The easiest way to do this is to use of an AsyncTask, which allows you to perform asynchronous work on your user interface. An AsyncTask will perform the blocking operations in a worker thread and will publish the results on the UI thread, without requiring you to handle threads and/or handlers yourself. Conclusion The reason why I decided to write about this topic is because I have seen it come up on StackOverflow and other forums countless times. The majority of the time the error stems from placing expensive operations directly on the UI thread. To ensure you don’t disrupt the user experience, it is very important to execute Socket connections, HTTP requests, file downloads, and other long-term operations on a separate Thread. The easiest way to do this is to wrap your operation in an AsyncTask, which launches a new thread and allows you to perform asynchronous work on your user interface. As always, let me know if this was helpful by +1-ing the post or leaving a comment below! Feel free to ask questions too… I respond to them quickly. :) Helpful Links Here are some helpful links that might help you get started with AsyncTasks: AsyncTask documentation Multithreading For Performance
This post introduces the concept of a utility class and gives a simple example of how you can use one to tidy up your code. As Android projects grow in size, it becomes increasingly important that your code remains organized and well-structured. Providing a utility class for commonly called methods can help tremendously in reducing the complexity of your project, allowing you to structure your code in a readable and easily understandable way. Here’s a simple example. Let’s say you are building an Android application that frequently checks the device’s SDK version code, to ensure backward compatibility. You’ll need to use the constants provided in the android.os.Build.VERSION_CODES class, but these constants are long and can quickly clutter up your code. In this case, it might be a good idea to create a CompatabilityUtil utility class. A sample implementation is given below: public class CompatibilityUtil { /** Get the current Android API level. */ public static int getSdkVersion() { return Build.VERSION.SDK_INT; } /** Determine if the device is running API level 8 or higher. */ public static boolean isFroyo() { return getSdkVersion() >= Build.VERSION_CODES.FROYO; } /** Determine if the device is running API level 11 or higher. */ public static boolean isHoneycomb() { return getSdkVersion() >= Build.VERSION_CODES.HONEYCOMB; } /** * Determine if the device is a tablet (i.e. it has a large screen). * * @param context The calling context. */ public static boolean isTablet(Context context) { return (context.getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_LARGE; } /** * Determine if the device is a HoneyComb tablet. * * @param context The calling context. */ public static boolean isHoneycombTablet(Context context) { return isHoneycomb() && isTablet(context); } /** This class can't be instantiated. */ private CompatibilityUtil() { } } Developers often create a separate package called [package name].util for frequently used utility classes. So for example, if your package name is com.example.myapp, then a nice place to put your utility classes would be in a separate package called com.example.myapp.util. However, remember that there’s no need to over-organize your project. Creating a separate package might be a good idea for a larger project, but is completely unnecessary if your project contains only 5-10 classes. I might write a post about package/class organization in the future. For now, check out the (very well-designed) Google I/O 2011 app’s source code. You will learn a lot!
Note: please read this short post before continuing forward. A common issue in Android development is backwards compatibility. How can we add cool new features from the most recent Android API while still ensuring that it runs correctly on devices running older versions of Android? This post discusses the problem by means of a simple example, and proposes a scalable, well-designed solution. The Problem Let’s say we are writing an application that reads and writes pictures to new albums (i.e. folders) located on external storage, and that we want our application to support all devices running Donut (Android 1.6, SDK version 4) and above. Upon consulting the documentation, we realize there is a slight problem. With the introduction of Froyo (Android 2.2, SDK version 8) came a somewhat radical change in how external storage was laid out and represented on Android devices, as well as several new API methods (see android.os.Environment) that allow us access to the public storage directories. To ensure backwards compatibility all the way back to Donut, we must provide two separate implementations: one for older, pre-Froyo devices, and another for devices running Froyo and above. Setting up the Manifest Before we dive into the implementation, we will first update our uses-sdk tag in the Android manifest. There are two attributes we must set, android:minSdkVersion="4". This attribute defines a minimum API level required for the application to run. We want our application to run on devices running Donut and above, so we set its value to "4". android:targetSdkVersion="15". This attribute is a little trickier to understand (and is incorrectly defined on blogs all over the internet). This attribute specifies the API level on which the application is designed to run. Preferably we would want its value to correspond to the most recently released SDK ("15", at the time of this posting). Strictly speaking, however, its value should be given by the largest SDK version number that we have tested your application against (we will assume we have done so for the remainder of this example). The resulting tag in our manifest is as follows: Implementation Our implementation will consist of an abstract class and two subclasses that extend it. The abstract AlbumStorageDirFactory class enforces a simple contract by requiring its subclasses to implement the getAlbumStorageDir method. The actual implementation of this method depends on the device’s SDK version number. Specifically, if we are using a device running Froyo or above, its implementation will make use of new methods introduced in API level 8. Otherwise, the correct directory must be determined using pre-Froyo method calls, to ensure that our app remains backwards compatible. public abstract class AlbumStorageDirFactory { /** * Returns a File object that points to the folder that will store * the album's pictures. */ public abstract File getAlbumStorageDir(String albumName); /** * A static factory method that returns a new AlbumStorageDirFactory * instance based on the current device's SDK version. */ public static AlbumStorageDirFactory newInstance() { // Note: the CompatibilityUtil class is implemented // and discussed in a previous post, entitled // "Ensuring Compatibility with a Utility Class". if (CompatabilityUtil.isFroyo()) { return new FroyoAlbumDirFactory(); } else { return new BaseAlbumDirFactory(); } } } The two subclasses and their implementation are given below.The class also provides a static factory newInstance method (note that this method makes use of the CompatabilityUtil utility class, which was both implemented and discussed in a previous post). We discuss this method in detail in the next section. The BaseAlbumDirFactory subclass handles pre-Froyo SDK versions: public class BaseAlbumDirFactory extends AlbumStorageDirFactory { /** * For pre-Froyo devices, we must provide the name of the photo directory * ourselves. We choose "/dcim/" as it is the widely considered to be the * standard storage location for digital camera files. */ private static final String CAMERA_DIR = "/dcim/"; @Override public File getAlbumStorageDir(String albumName) { return new File(Environment.getExternalStorageDirectory() + CAMERA_DIR + albumName); } } The FroyoAlbumDirFactory subclass handles Froyo and above: public class FroyoAlbumDirFactory extends AlbumStorageDirFactory { @Override public File getAlbumStorageDir(String albumName) { return new File(Environment.getExternalStoragePublicDirectory( Environment.DIRECTORY_PICTURES), albumName); } } Making Sense of the Pattern Take a second to study the structure of the code above. Our implementation ensures compatibility with pre-Froyo devices through a simple design. To ensure compatibility, we simply request a new AlbumStorageDirFactory and call the abstract getAlbumStorageDir method. The subclass is determined and instantiated at runtime depending on the Android device’s SDK version number. See the sample activity below for an example on how an ordinary Activity might use this pattern to retrieve an album’s directory. public class SampleActivity extends Activity { private AlbumStorageDirFactory mAlbumFactory; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Instantiate the AlbumStorageDirFactory. Instead of // invoking the subclass' default constructors directly, // we make use of the Abstract Factory design pattern, // which encapsulates the inner details. As a result, the // Activity does not need to know `anything` about the // compatibility-specific implementation--all of this is // done behind the scenes within the "mAlbumFactory" object. mAlbumFactory = AlbumStorageDirFactory.newInstance(); // get the album's directory File sampleAlbumDir = getAlbumDir("sample_album"); } /** * A simple helper method that returns a File corresponding * to the album named "albumName". The helper method invokes * the abstract "getAlbumStorageDir" method, which will return * correct location of the directory depending on the subclass * that was returned in "newInstance" (which depends entirely * on the device's SDK version number). */ private File getAlbumDir(String albumName) { return mAlbumFactory.getAlbumStorageDir(albumName); } } There are a couple benefits to organizing the code the way we have: It’s easily extendable. While there is certainly no need to separate our implementations into classes for simple examples (such as the one discussed above), doing so is important when working with large, complicated projects, as it will ensure changes can quickly be made down the line. It encapsulates the implementation-specific details. Abstracting these details from the client makes our code less cluttered and easier to read (note: in this case, “the client” was the person who wrote the Activity class). Conclusion Android developers constantly write code to ensure backwards compatibility. As projects expand and applications become more complex, it becomes increasingly important to ensure your implementation is properly designed. Hopefully this post helped and will encourage you to more elegant solutions in the future! Leave a comment if you have any questions or criticisms… or just to let me know that you managed to read through this entire post!
As with most areas in software engineering, debugging is a crucial aspect of Android development. Properly setting up your application for debugging can save you hours of work and frustration. Unfortunately, in my experience not many beginners learn how to properly make use of the utility classes provided in the Android SDK. Unless you are an experienced developer, it is my personal belief that Android debugging should follow a pattern. This will prove beneficial for a couple reasons: It allows you to anticipate bugs down the line. Setting up your development work space for debugging will give you a head start on bugs you might encounter in the future. It gives you centralized control over the debugging process. Disorganized and sparse placement of log messages in your class can clutter your logcat output, making it difficult to interpret debugging results. The ability to toggle certain groups of log messages on/off can make your life a whole lot easier, especially if your application is complex. The Log Class For those of you who don’t know, the Android SDK includes a useful logging utility class called android.util.Log. The class allows you to log messages categorized based severity; each type of logging message has its own message. Here is a listing of the message types, and their respective method calls, ordered from lowest to highest priority: The Log.v() method is used to log verbose messages. The Log.d() method is used to log debug messages. The Log.i() method is used to log informational messages. The Log.w() method is used to log warnings. The Log.e() method is used to log errors. The Log.wtf() method is used to log events that should never happen (“wtf” being an abbreviation for “What a Terrible Failure”, of course). You can think of this method as the equivalent of Java’s assert method. One should always consider a message’s type when assigning log messages to one of the six method calls, as this will allow you to filter your logcat output when appropriate. It is also important to understand when it is acceptable to compile log messages into your application: Verbose logs should never be compiled into an application except during development. When development is complete and you are ready to release your application to the world, you should remove all verbose method calls either by commenting them out, or using ProGuard to remove any verbose log statements directly from the bytecode of your compiled JAR executable, as described in Christopher’s answer in this StackOverflow post. Debug logs are compiled in but are ignored at runtime. Error, warning, and informational logs are always kept. A Simple Pattern A simple way to organize debugging is with the sample pattern implemented below. A global, static string is given to represent the specific class (an Activity in this example, but it could be a service, an adapter, anything), and a boolean variable to represent whether or not log messages should be printed to the logcat. public class SampleActivity extends Activity { /** * A string constant to use in calls to the "log" methods. Its * value is often given by the name of the class, as this will * allow you to easily determine where log methods are coming * from when you analyze your logcat output. */ private static final String TAG = "SampleActivity"; /** * Toggle this boolean constant's value to turn on/off logging * within the class. */ private static final boolean VERBOSE = true; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (VERBOSE) Log.v(TAG, "+++ ON CREATE +++"); } @Override public void onStart() { super.onStart(); if (VERBOSE) Log.v(TAG, "++ ON START ++"); } @Override public void onResume() { super.onResume(); if (VERBOSE) Log.v(TAG, "+ ON RESUME +"); } } Don’t be afraid to be creative in how you print your log messages to the logcat! For instance, when the sample activity above is launched, the resulting logcat is presented in an nicely formatted and human-readable way: V SampleActivity +++ ON CREATE +++ V SampleActivity ++ ON START++ V SampleActivity + ON RESUME + Conclusion In this post, I have covered the basics in which Android debugging can (and should) be performed. In a future post, I will go into a bit more depth by providing some more advanced patterns that will give you more control over how debugging is performed at runtime. Leave a comment if this helped… it’ll motivate me to write more of these blog posts in the future! :)
With Android 3.0 came the introduction of the LoaderManager class, an abstract class associated with an Activity or Fragment for managing one or more Loader instances. The LoaderManager class is one of my favorite additions to the Android framework for a number of reasons, but mostly because it significantly reduces code complexity and makes your application run a lot smoother. Implementing data loaders with the LoaderManager is simple to implement, and takes care of everything about lifecycle management so are much less error prone. While applications are free to write their own loaders for loading various types of data, the most common (and simplest) use of the LoaderManager is with a CursorLoader. When done correctly, the CursorLoader class offloads the work of loading data on a thread, and keeps the data persistent during short term activity refresh events, such as an orientation change. In addition to performing the initial query, the CursorLoader registers a ContentObserver with the dataset you requested and calls forceLoad() on itself when the data set changes, and is thus auto-updating. This is extremely convenient for the programmer, as he doesn’t have to worry about performing these queries himself. Further, for bigger screens it becomes more important that you query on a separate thread since configuration changes involve recreating the entire view layout, a complex operation that can cause disasters when blocked. As mentioned earlier, one could implement his or her class to load data on a separate thread using an AsyncTask or even a Thread. The point, however, is that the LoaderManager does this all for you, so it’s convenient for the developer, less error prone, and simple to implement. Of course it is possible to use an AsyncTask to keep your application UI thread friendly, but it will involve a lot more code, and implementing your class so that it will retain the loaded Cursor over the twists and turns of the Activity lifecycle won’t be simple. The bottom line is that LoaderManager will do this automatically for you, as well as taking care of correctly creating and closing the Cursor based on the Activity lifecycle. To use LoaderManager with (or without) the CursorLoader in an app targeting pre-Honeycomb devices, you should make use of the classes provided in the Android Support Package, including the FragmentActivity class. A FragmentActivity is just an Activity that has been created for Android compatibility support, and does not require the use of fragments in your application. When transitioning from an Activitys to FragmentActivitys, be extremely careful that you use the getSupportLoaderManager() instead of getLoaderManager(). FragmentActivity extends Activity, thus inheriting all of its methods, and as a result the compiler will not complain if you accidentally mix up these methods, so be very careful! Leave a comment if you have any questions or criticisms... or just to let me know that you managed to read through this entire post without getting distracted! I'm also open to providing more explicit code samples if anyone asks.
I recently came across an interesting question on StackOverflow regarding Fragment instantiation: What is the difference between new MyFragment() and MyFragment.newInstance()? Should I prefer one over the other? Good question. The answer, as the title of this blog suggests, is a matter of proper design. In this case, the newInstance() method is a “static factory method,” allowing us to initialize and setup a new Fragment without having to call its constructor and additional setter methods. Providing static factory methods for your fragments is good practice because it encapsulates and abstracts the steps required to setup the object from the client. For example, consider the following code: public class MyFragment extends Fragment { /** * Static factory method that takes an int parameter, * initializes the fragment's arguments, and returns the * new fragment to the client. */ public static MyFragment newInstance(int index) { MyFragment f = new MyFragment(); Bundle args = new Bundle(); args.putInt("index", index); f.setArguments(args); return f; } } Rather than having the client call the default constructor and manually set the fragment’s arguments themselves, we provide a static factory method that does this for them. This is preferred over the default constructor for two reasons. One, it’s convenient for the client, and two, it enforces well-defined behavior. By providing a static factory method, we protect ourselves from bugs down the line—we no longer need to worry about accidentally forgetting to initialize the fragment’s arguments or incorrectly doing so. Overall, while the difference between the two is mostly just a matter of design, this difference is really important because it provides another level of abstraction and makes code a lot easier to understand. Feel free to leave a comment if this blog post helped (it will motivate me to write more in the future)! :)
One thing that I’ve noticed other Android developers having trouble with is properly setting up their SQLiteDatabase. Often times, I come across questions on StackOverflow asking about error messages such as, E/Database(234): Leak found E/Database(234): Caused by: java.lang.IllegalStateException: SQLiteDatabase created and never closed As you have probably figured out, this exception is thrown when you have opened more SQLiteDatabase instances than you have closed. Managing the database can be complicated when first starting out with Android development, especially to those who are just beginning to understand the Activity lifecycle. The easiest solution is to make your database instance a singleton instance across the entire application’s lifecycle. This will ensure that no leaks occur, and will make your life a lot easier since it eliminates the possibility of forgetting to close your database as you code. Here are two examples that illustrates three possible approaches in managing your singleton database. These will ensure safe access to the database throughout the application. Approach #1: Use a Singleton to Instantiate the SQLiteOpenHelper Declare your database helper as a static instance variable and use the Singleton pattern to guarantee the singleton property. The sample code below should give you a good idea on how to go about designing the DatabaseHelper class correctly. The static getInstance() method ensures that only one DatabaseHelper will ever exist at any given time. If the sInstance object has not been initialized, one will be created. If one has already been created then it will simply be returned. You should not initialize your helper object using with new DatabaseHelper(context)! Instead, always use DatabaseHelper.getInstance(context), as it guarantees that only one database helper will exist across the entire application’s lifecycle. public class DatabaseHelper extends SQLiteOpenHelper { private static DatabaseHelper sInstance; private static final String DATABASE_NAME = "database_name"; private static final String DATABASE_TABLE = "table_name"; private static final int DATABASE_VERSION = 1; public static synchronized DatabaseHelper getInstance(Context context) { // Use the application context, which will ensure that you // don't accidentally leak an Activity's context. // See this article for more information: http://bit.ly/6LRzfx if (sInstance == null) { sInstance = new DatabaseHelper(context.getApplicationContext()); } return sInstance; } /** * Constructor should be private to prevent direct instantiation. * make call to static method "getInstance()" instead. */ private DatabaseHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } } Approach #2: Wrap the SQLiteDatabase in a ContentProvider This is also a nice approach. For one, the new CursorLoader class requires ContentProviders, so if you want an Activity or Fragment to implement LoaderManager.LoaderCallbacks with a CursorLoader (as discussed in this post), you’ll need to implement a ContentProvider for your application. Further, you don’t need to worry about making a singleton database helper with ContentProviders. Simply call getContentResolver() from the Activity and the system will take care of everything for you (in other words, there is no need for designing a Singleton pattern to prevent multiple instances from being created). Leave a comment if this helped or if you have any questions!
您可以订阅此RSS以获取更多信息