Are there any geeks out there interested in new podcasts? What about podcasts about Android development?
Tor Norbye and I are proud to announce a new podcast we've started called Android Developers Backstage. It's a podcast by and for Android programmers, featuring engineers on the Android team at Google talking about features, APIs, and technologies that we think are important for developers to know about. Or which we find interesting. Or which randomly happened to come up on the show.
If your podcast client still has room and you have an extra half-hour (ish) every month (ish), then subscribe and tune in. You can find the podcast on Feedburner. Just click on one of the various links on that page to add it to your podcast client of choice.
The inaugural episode is about Android KitKat, with Tor and I talking about some of the new features in the latest release. In future episodes of the podcast, we'll interview other engineers on the team to deep-dive technologies they've worked on. Android development info, straight from the source.
Graphics geek, performance pusher, animation animal
All content on this blog, unless marked otherwise, is original and is copyright © Chet Haase 2006-2015, all rights reserved.
Wednesday, November 27, 2013
Thursday, October 31, 2013
Android KitKat: Developer Info
We just released Android KitKat and posted lots of information about it:
Developer highlights: http://developer.android.com/about/versions/kitkat.html
Android Developers Blog: http://android-developers.blogspot.com/2013/10/android-44-kitkat-and-updated-developer.html
Videos (an overview one plus lots of others that dive deeper into specific features): https://www.youtube.com/watch?v=sONcojECWXs&list=PLWz5rJ2EKKc-2quE-o0enpILZF3nBZg_K&index=1
I'll specifically call out the video on Transitions, which is something I recorded last week after finally finishing the feature. I hope that the API is another step toward making animations easier and more automatic in Android applications.
Developer highlights: http://developer.android.com/about/versions/kitkat.html
Android Developers Blog: http://android-developers.blogspot.com/2013/10/android-44-kitkat-and-updated-developer.html
Videos (an overview one plus lots of others that dive deeper into specific features): https://www.youtube.com/watch?v=sONcojECWXs&list=PLWz5rJ2EKKc-2quE-o0enpILZF3nBZg_K&index=1
I'll specifically call out the video on Transitions, which is something I recorded last week after finally finishing the feature. I hope that the API is another step toward making animations easier and more automatic in Android applications.
Thursday, September 12, 2013
Lazy Lists
Let's talk about laziness. No, let's not just talk about being lazy - let's do something about it. Let's be lazy.
There's a common pattern that I use in Android programming where I will create objects lazily, especially when these objects will not necessarily or frequently be needed at runtime. This is especially true for code that I write for the Android framework, where we like to be as lean as possible and leave more memory available for the system and the application.
Often, these objects are collections. My personal favorite is
Of course, you can't simply start poking into a lazily-created collection willy-nilly, unless you're a big fan of NullPointerExceptions. So you need to first check whether the thing is null, and then create it as necessary. Similarly, if you're removing an item from the collection, you might want to check if it's now empty and reset the field to
None of this is difficult... but it is tedious. And it tends to bloat the code that you have to read and write and maintain all over the place. And it's exactly the kind of thing that is easy to get wrong because your mind just blurs over the repeated pattern like it's yet another pile of dirty laundry next to the bed.
So I wondered if there was a way I could encapsulate the behavior I wanted to make my laziness easier, simpler, and more readable. And it turns out there was such a way (or else this would be a very short article that would have ended right around... now).
But first, let's look at the problem a bit more, to understand what we're trying to fix.
I have a class called
Then there are add/remove fields for the int/float types I care about:
There are a few things to notice about these methods:
We can call the methods above and produce lists that dynamically change with the items that we add/remove. For example, this code creates the class, adds items to the two lists, and removes those items:
Adding a bit of tracing code gives us this output:
So there's not too much cruft above, but I figure the second time I'm repeating the same code, I should think about refactoring it in a way that avoids the repetition. And it's easy to imagine that there might be several places in real code that wants to add/remove items, or several different types going into several different types of collections. Then it's easy to see the little bits of repeated code above bloating into more than you might want to manage in the long haul.
There are various ways that you could do this, depending on the collection(s) you want to support, the extra logic you'd like (like my requirement for uniqueness in the lists), and stylistic elements about static methods, etc. But here's what I wound up with:
This simple class has two static methods on it to support adding and removing from an arbitrary List object. As needed, it will create a new List (actually, an ArrayList, but that's an implementation detail). It will check for uniqueness in the
There is an important piece here that makes this work - callers must supply their target list as both a parameter to the function and as the recipient of the return value; this is what makes it possible for these utility methods to allocate or null-out the list as appropriate (since they do not have access to the original list, but only have a reference to it).
Given these two static utility methods, we can now write new
Calling these methods looks remarkably similar to what we saw before:
and results in exactly the same output (which shouldn't be a surprise. If the results were different, this approach wouldn't be a utility as much as a bug):
The
If this were public API, I could envision the manager class offering various different kinds of collections and options, or maybe wrapping more of the capabilities of collections classes (like isEmpty() or contains()). But for now, it's a nice little internal class that can help me simplify my code whenever I need to use this lazy-allocation pattern.
All of the interesting code is inline above, but if you want the two source files, you can download them here.
There's a common pattern that I use in Android programming where I will create objects lazily, especially when these objects will not necessarily or frequently be needed at runtime. This is especially true for code that I write for the Android framework, where we like to be as lean as possible and leave more memory available for the system and the application.
Often, these objects are collections. My personal favorite is
ArrayList
(ever since I moved on years ago from the original, but somewhat crusty, Vector
class), although I am also known to use Hashmap
for those key/value pair situations (especially after I got used to ActionScript's paradigm of everything-is-a-hashmap).Of course, you can't simply start poking into a lazily-created collection willy-nilly, unless you're a big fan of NullPointerExceptions. So you need to first check whether the thing is null, and then create it as necessary. Similarly, if you're removing an item from the collection, you might want to check if it's now empty and reset the field to
null
again.None of this is difficult... but it is tedious. And it tends to bloat the code that you have to read and write and maintain all over the place. And it's exactly the kind of thing that is easy to get wrong because your mind just blurs over the repeated pattern like it's yet another pile of dirty laundry next to the bed.
So I wondered if there was a way I could encapsulate the behavior I wanted to make my laziness easier, simpler, and more readable. And it turns out there was such a way (or else this would be a very short article that would have ended right around... now).
But first, let's look at the problem a bit more, to understand what we're trying to fix.
I have a class called
LazyLists
with a couple of List fields and some methods for adding and removing items of various types. First, there are the fields:List<Integer> intList = null; List<Float> floatList = null;
Then there are add/remove fields for the int/float types I care about:
public void addItem(int item) { if (intList == null) { intList = new ArrayList(); } if (!intList.contains(item)) { intList.add(item); } } public void removeItem(int item) { if (intList != null) { intList.remove((Object) item); if (intList.isEmpty()) { intList = null; } } } public void addItem(float item) { if (floatList == null) { floatList = new ArrayList (); } if (!floatList.contains(item)) { floatList.add(item); } } public void removeItem(float item) { if (floatList != null) { floatList.remove(item); if (floatList.isEmpty()) { floatList = null; } } }
There are a few things to notice about these methods:
- There's all of that boilerplate code I mentioned before that lazily creates and nulls out the appropriate list based on the state of the list at the time. This is what we'd like to clean up, since this code is repeated as many times as we have to access these list fields.
- I run a uniqueness check in the
addItem()
methods because it suits me; I only want to add unique items, not the same items over and over. That's kind of a detail that's specific to my situation, but produces more boilerplate that I'd love to get rid of. - There an interesting nuance to the int variation of
removeItem()
. Do you see it? It's the cast toObject
prior to removing the item fromintList
. This is because of the awkward crossover between primitive types (int, float, etc.) and Object types (Integer, Float, etc.) in Java. There are actually tworemove()
methods on List, one that takes anint
and one that takes anInteger
. The one that takes anint
removes the item at that index, whereas theInteger
variant removes that item itself. That's a pretty huge distinction. And maybe it's well-known to you if you've worked with Lists and ints, but I hit it when working on this example, and thought it wasint
eresting enough to call out.
We can call the methods above and produce lists that dynamically change with the items that we add/remove. For example, this code creates the class, adds items to the two lists, and removes those items:
LazyLists lists = new LazyLists(); lists.addItem(0); lists.addItem(1f); lists.removeItem(0); lists.removeItem(1f);
Adding a bit of tracing code gives us this output:
starting lists = null, null populated lists = [0], [1.0] ending lists = null, null
So there's not too much cruft above, but I figure the second time I'm repeating the same code, I should think about refactoring it in a way that avoids the repetition. And it's easy to imagine that there might be several places in real code that wants to add/remove items, or several different types going into several different types of collections. Then it's easy to see the little bits of repeated code above bloating into more than you might want to manage in the long haul.
There are various ways that you could do this, depending on the collection(s) you want to support, the extra logic you'd like (like my requirement for uniqueness in the lists), and stylistic elements about static methods, etc. But here's what I wound up with:
public class LazyListManager { public staticList add(List list, T item) { if (list == null) { list = new ArrayList (); list.add(item); } else if (!list.contains(item)) { list.add(item); } return list; } public static List remove(List list, T item) { if (list != null) { list.remove(item); if (list.isEmpty()) { list = null; } } return list; } }
This simple class has two static methods on it to support adding and removing from an arbitrary List object. As needed, it will create a new List (actually, an ArrayList, but that's an implementation detail). It will check for uniqueness in the
add()
method, check for nullness in the remove()
method, and null out an empty list in remove()
as appropriate.There is an important piece here that makes this work - callers must supply their target list as both a parameter to the function and as the recipient of the return value; this is what makes it possible for these utility methods to allocate or null-out the list as appropriate (since they do not have access to the original list, but only have a reference to it).
Given these two static utility methods, we can now write new
addItem()
and removeItem()
methods that are a significantly better (you can't get less than one line of code, unless I missed that part in my CS education):public void addItemBetter(int item) { intList = LazyListManager.add(intList, item); } public void removeItemBetter(int item) { intList = LazyListManager.remove(intList, item); } public void addItemBetter(float item) { floatList = LazyListManager.add(floatList, item); } public void removeItemBetter(float item) { floatList = LazyListManager.remove(floatList, item); }
Calling these methods looks remarkably similar to what we saw before:
lists.addItemBetter(0); lists.addItemBetter(1f); lists.removeItemBetter(0); lists.removeItemBetter(1f);
and results in exactly the same output (which shouldn't be a surprise. If the results were different, this approach wouldn't be a utility as much as a bug):
starting lists = null, null populated lists = [0], [1.0] ending lists = null, null populated lists = [0], [1.0] ending lists = null, null
The
LazyListManager
class has taken out all of the tedious boilerplate related to null checks, uniqueness, allocation, and nullification, and has left us with just one line of code to write whenever we want to add or remove items to/from one of our lists. That's just about the right amount of code for me to write without making a typo or a copy/paste error along the way.If this were public API, I could envision the manager class offering various different kinds of collections and options, or maybe wrapping more of the capabilities of collections classes (like isEmpty() or contains()). But for now, it's a nice little internal class that can help me simplify my code whenever I need to use this lazy-allocation pattern.
All of the interesting code is inline above, but if you want the two source files, you can download them here.
Friday, August 9, 2013
DevBytes: Cartoon Animation Techniques
This time, we wrap up the series on cartoon animation techniques with a demo that shows a few of the techniques in the context of a larger application. Because it's nice to know how to write the code, but you might also be wondering why you might want to.
For some real-world context, you could also check out games such as Candy Crush Saga (a horribly addictive game I've gotten sucked into that's less like casual gaming and more like casual crack). It uses a veritable plethora of cartoon animation techniques to keep the player engaged with the game and disengaged from their life.
This and other cartoon animation techniques were discussed in the talk A Moving Experience at Google I/O 2013
Code: http://developer.android.com/shareables/devbytes/ToonGame.zip
YouTube: https://www.youtube.com/watch?v=8sG3bAPOhyw&list=PLWz5rJ2EKKc_XOgcRukSoKKjewFJZrKV0
For some real-world context, you could also check out games such as Candy Crush Saga (a horribly addictive game I've gotten sucked into that's less like casual gaming and more like casual crack). It uses a veritable plethora of cartoon animation techniques to keep the player engaged with the game and disengaged from their life.
This and other cartoon animation techniques were discussed in the talk A Moving Experience at Google I/O 2013
Code: http://developer.android.com/shareables/devbytes/ToonGame.zip
YouTube: https://www.youtube.com/watch?v=8sG3bAPOhyw&list=PLWz5rJ2EKKc_XOgcRukSoKKjewFJZrKV0
Friday, August 2, 2013
DevBytes: Squash & Stretch
Cartoon animation uses a technique called "squash & stretch" for achieving different effects of objects interacting with their environment.
This episode shows how we can use similar techniques to get more organic and lively animations in user interfaces.
This and other cartoon animation techniques were discussed in the talk A Moving Experience at Google I/O 2013.
Code: http://developer.android.com/shareables/devbytes/SquashAndStretch.zip
YouTube: https://www.youtube.com/watch?v=wJL1oW6DlCc
This episode shows how we can use similar techniques to get more organic and lively animations in user interfaces.
This and other cartoon animation techniques were discussed in the talk A Moving Experience at Google I/O 2013.
Code: http://developer.android.com/shareables/devbytes/SquashAndStretch.zip
YouTube: https://www.youtube.com/watch?v=wJL1oW6DlCc
Friday, July 26, 2013
DevBytes: Anticipation & Overshoot, Part 2
Like my previous DevBytes episode, Anticipation and Overshoot, Part I," this episode covers cartoon animation techniques for making UI elements more engaging and playful. The code in this episode shows how to change and animate how a button is drawn to make it seem more alive and organic.
This and other cartoon animation techniques were discussed in the talk A Moving Experience at Google I/O 2013.
Code: http://developer.android.com/shareables/devbytes/Anticipation.zip
YouTube: DevBytes: Anticipation and Overshoot - Part 2
This and other cartoon animation techniques were discussed in the talk A Moving Experience at Google I/O 2013.
Code: http://developer.android.com/shareables/devbytes/Anticipation.zip
YouTube: DevBytes: Anticipation and Overshoot - Part 2
Thursday, July 25, 2013
New in Android 4.3: ViewOverlay
Since we just released Android 4.3 yesterday, I thought I'd wax poetic about one of the features I worked on in the release: Overlays.
There are many ways to get custom graphics (drawables) to appear in a view. You can set a background drawable if that's what you want, or you can use an ImageView, or you can create a custom View subclass and override onDraw(). Or if you want to draw them over the children in a layout, you can override the layout and override dispatchDraw() to draw them after all of the children (after a call to super.dispatchDraw()). But sometimes you just want something simpler: ViewOverlay.
Here's how it works:
You call View.getOverlay(), which returns a ViewOverlay object. Then you call add(Drawable) on that overlay object... and that's it. You can also call remove(Drawable) to get rid of the drawable or clear() to get rid of everything in the overlay, but that's the entire API. The only other detail is that you need to set the bounds on your drawables (as you would if you were drawing them manually in onDraw()); the overlay doesn't do any positioning or sizing, it simply draws everything you tell it to, in the order in which the objects are added.
Oh, and one other thing: you can also add View objects to an overlay if you get that overlay from a ViewGroup. That is, ViewGroup overrides getOverlay() to return a ViewGroupOverlay, which is a subclass of ViewOverlay that also deals with views in addition to drawables. Once again, the API is simple: there's an add(View) method and a remove(View) method. And once again, you are responsible for positioning/sizing the views where you want them in the overlay; the overlay does not do any layout.
Essentially, the overlay is a container for drawables (and sometimes views) that is the exact size of its host view. It doesn't do layout; it just draws the stuff its told to, after it's drawn everything else in its host view (including any children of that host view, if the view is a ViewGroup).
Important caveat, especially with respect to ViewGroupOverlay: The overlay is intended for temporary display of views, not for any long-term or functional use for views. In particular, the views there are not laid out by the container (as noted above) and do not participate in focus or input events. Overlays were created to be a visual-only mechanism, for things like transient animations. Think about things like fading out a view. This can be tricky in some situations where removing it immediately removes it from the view hierarchy and makes it tough to have it drawn while it's fading out. Now you can pop it into the overlay of its parent while it's going away.
Here's another interesting thing to ponder: you can add drawables/graphics anywhere in the view hierarchy that makes sense for your situation. So, for example, if you wanted to fade out a view or some drawable while sliding it off the screen, or up-scaling it, outside of its parent container, this might normally be a problem because parent containers like to clip their children to their bounds by default. Now you can add the object into an overlay anywhere up the tree, to give you the flexibility to move/resize it as you see fit without worrying about the clipping constraints of that original parent.
Anyway, I'm sure you can find interesting things to do with overlays. So go ahead!
Android devs
Come out to play:
Use the new
ViewOverlay.
Here's how it works:
You call View.getOverlay(), which returns a ViewOverlay object. Then you call add(Drawable) on that overlay object... and that's it. You can also call remove(Drawable) to get rid of the drawable or clear() to get rid of everything in the overlay, but that's the entire API. The only other detail is that you need to set the bounds on your drawables (as you would if you were drawing them manually in onDraw()); the overlay doesn't do any positioning or sizing, it simply draws everything you tell it to, in the order in which the objects are added.
Oh, and one other thing: you can also add View objects to an overlay if you get that overlay from a ViewGroup. That is, ViewGroup overrides getOverlay() to return a ViewGroupOverlay, which is a subclass of ViewOverlay that also deals with views in addition to drawables. Once again, the API is simple: there's an add(View) method and a remove(View) method. And once again, you are responsible for positioning/sizing the views where you want them in the overlay; the overlay does not do any layout.
Essentially, the overlay is a container for drawables (and sometimes views) that is the exact size of its host view. It doesn't do layout; it just draws the stuff its told to, after it's drawn everything else in its host view (including any children of that host view, if the view is a ViewGroup).
Important caveat, especially with respect to ViewGroupOverlay: The overlay is intended for temporary display of views, not for any long-term or functional use for views. In particular, the views there are not laid out by the container (as noted above) and do not participate in focus or input events. Overlays were created to be a visual-only mechanism, for things like transient animations. Think about things like fading out a view. This can be tricky in some situations where removing it immediately removes it from the view hierarchy and makes it tough to have it drawn while it's fading out. Now you can pop it into the overlay of its parent while it's going away.
Here's another interesting thing to ponder: you can add drawables/graphics anywhere in the view hierarchy that makes sense for your situation. So, for example, if you wanted to fade out a view or some drawable while sliding it off the screen, or up-scaling it, outside of its parent container, this might normally be a problem because parent containers like to clip their children to their bounds by default. Now you can add the object into an overlay anywhere up the tree, to give you the flexibility to move/resize it as you see fit without worrying about the clipping constraints of that original parent.
Anyway, I'm sure you can find interesting things to do with overlays. So go ahead!
Friday, July 19, 2013
DevBytes: Anticipation and Overshoot, Part 1
Some principles of cartoon animation can be used to provide more engaging and more interactive experiences in applications.
This episode demonstrates principles of anticipation and overshoot with a simple button click, showing how to animate changes in the button's appearance to make it interact more playfully with the user.
This and other cartoon animation techniques were discussed in the talk A Moving Experience at Google I/O 2013
Code: http://developer.android.com/shareables/devbytes/LiveButton.zip
YouTube: https://www.youtube.com/watch?v=uQ7PTe7QMQM
This episode demonstrates principles of anticipation and overshoot with a simple button click, showing how to animate changes in the button's appearance to make it interact more playfully with the user.
This and other cartoon animation techniques were discussed in the talk A Moving Experience at Google I/O 2013
Code: http://developer.android.com/shareables/devbytes/LiveButton.zip
YouTube: https://www.youtube.com/watch?v=uQ7PTe7QMQM
Friday, July 12, 2013
DevBytes: Curved Motion
This is a demo that +Romain Guy and I showed in our A Moving Experience talk at Google I/O this year, showing how to use the existing TypeEvaluator and Animator APIs to get curved motion for your animations.
In the real world, things don't move in straight lines. Moving items around on the screen should feel as natural as possible; sometimes curved motion can help.
This episode shows how to use the existing animation APIs to get easy curved motion for your UIs.
The helper classes come from an article I posted on my blog last year:
http://graphics-geek.blogspot.com/2012/01/curved-motion-in-android.html
YouTube: https://www.youtube.com/watch?v=JVGg4zPRHNE
Code: http://developer.android.com/shareables/devbytes/CurvedMotion.zip
In the real world, things don't move in straight lines. Moving items around on the screen should feel as natural as possible; sometimes curved motion can help.
This episode shows how to use the existing animation APIs to get easy curved motion for your UIs.
The helper classes come from an article I posted on my blog last year:
http://graphics-geek.blogspot.com/2012/01/curved-motion-in-android.html
YouTube: https://www.youtube.com/watch?v=JVGg4zPRHNE
Code: http://developer.android.com/shareables/devbytes/CurvedMotion.zip
Friday, June 28, 2013
DevBytes: Animating Multiple Properties in Parallel
Suppose you want to animate multiple properties in parallel on some target object. How would you do it? ValueAnimator? Multiple ObjectAnimators?
This episode covers different ways of animating multiple properties, and specifically covers the use of the lesser-known PropertyValuesHolder class.
YouTube: https://www.youtube.com/watch?v=WvCZcy3WGP4
Code: http://developer.android.com/shareables/devbytes/MultiPropertyAnimations.zip
This episode covers different ways of animating multiple properties, and specifically covers the use of the lesser-known PropertyValuesHolder class.
YouTube: https://www.youtube.com/watch?v=WvCZcy3WGP4
Code: http://developer.android.com/shareables/devbytes/MultiPropertyAnimations.zip
Friday, June 21, 2013
DevBytes: Animating ListView Deletion: Now on Gingerbread!
In this episode, I tried to anticipate the question I knew would come up on my previous show about animating ListView deletion: "But how would I do this on Gingerbread?"
Here's how.
YouTube: https://www.youtube.com/watch?v=PeuVuoa13S8&list=PLWz5rJ2EKKc_XOgcRukSoKKjewFJZrKV0&index=1
Code: http://developer.android.com/shareables/devbytes/ListViewItemAnimations.zip
Here's how.
YouTube: https://www.youtube.com/watch?v=PeuVuoa13S8&list=PLWz5rJ2EKKc_XOgcRukSoKKjewFJZrKV0&index=1
Code: http://developer.android.com/shareables/devbytes/ListViewItemAnimations.zip
Friday, June 14, 2013
DevBytes: Animating ListView Deletion
It's Friday: must be time for another DevBytes episode.
Finally, here is the DevBytes episode around the ListView animation demo in the talk A Moving Experience that I gave with Romain Guy at Google I/O last month.
The code is nearly the same as that in the I/O talk, except I tweaked it to make it more general-purpose. Specifically, it no longer depends on the setHasTransientState() method (introduced in Jellybean 4.1) to keep the views around while fiddling with layout. Instead, it uses an adapter with stable itemIds, and uses those Ids to track where the content is before and after layout.
As written, the code is still dependent on Android 4.1, but that's only because of the use of ViewPropertyAnimator.withEndAction() method, which is really just syntactic sugar around adding a listener and running the end-action code in the onAnimationEnd() callback. So you could totally port this code all the way back to Android 3.1 (when ViewPropertyAnimator was introduced) or even 3.0 (if you use ObjectAnimator instead).
Watch the video. Check out the code. Play with ListView. Animate. Enjoy.
A Moving Experience: http://www.youtube.com/watch?v=ihzZrS69i_s
DevBytes on YouTube: http://www.youtube.com/playlist?list=PLWz5rJ2EKKc_XOgcRukSoKKjewFJZrKV0
Code: http://developer.android.com/shareables/devbytes/ListViewRemovalAnimation.zip
Finally, here is the DevBytes episode around the ListView animation demo in the talk A Moving Experience that I gave with Romain Guy at Google I/O last month.
The code is nearly the same as that in the I/O talk, except I tweaked it to make it more general-purpose. Specifically, it no longer depends on the setHasTransientState() method (introduced in Jellybean 4.1) to keep the views around while fiddling with layout. Instead, it uses an adapter with stable itemIds, and uses those Ids to track where the content is before and after layout.
As written, the code is still dependent on Android 4.1, but that's only because of the use of ViewPropertyAnimator.withEndAction() method, which is really just syntactic sugar around adding a listener and running the end-action code in the onAnimationEnd() callback. So you could totally port this code all the way back to Android 3.1 (when ViewPropertyAnimator was introduced) or even 3.0 (if you use ObjectAnimator instead).
Watch the video. Check out the code. Play with ListView. Animate. Enjoy.
A Moving Experience: http://www.youtube.com/watch?v=ihzZrS69i_s
DevBytes on YouTube: http://www.youtube.com/playlist?list=PLWz5rJ2EKKc_XOgcRukSoKKjewFJZrKV0
Code: http://developer.android.com/shareables/devbytes/ListViewRemovalAnimation.zip
Friday, June 7, 2013
DevBytes: Custom Activity Animations
It's Friday: time for another DevBytes.
It's been a while, but they're back. At least until the queue runs out again and I write another glut of demos to talk about.
This episode, and most of the upcoming ones (teaser alert!) are around demos/code that we showed in A Moving Experience, the animation talk that +Romain Guy and I gave at Google I/O 2013.
Custom Activity Animations:
Window animations provide an easy way to animate transitions between activities. The animations can be customized to some extent, but there's only so much interaction between the launching and launched activities that you can take advantage of with the standard window animations.
This episode covers a different technique for fully customizable animations.
Video: http://www.youtube.com/watch?v=CPxkoe2MraA
Code: http://developer.android.com/shareables/devbytes/ActivityAnimations.zip
It's been a while, but they're back. At least until the queue runs out again and I write another glut of demos to talk about.
This episode, and most of the upcoming ones (teaser alert!) are around demos/code that we showed in A Moving Experience, the animation talk that +Romain Guy and I gave at Google I/O 2013.
Custom Activity Animations:
Window animations provide an easy way to animate transitions between activities. The animations can be customized to some extent, but there's only so much interaction between the launching and launched activities that you can take advantage of with the standard window animations.
This episode covers a different technique for fully customizable animations.
Video: http://www.youtube.com/watch?v=CPxkoe2MraA
Code: http://developer.android.com/shareables/devbytes/ActivityAnimations.zip
Friday, May 17, 2013
Google I/O Presentations Now Live
Romain Guy and I gave two talks at Google I/O yesterday which are now live on YouTube:
Android Graphics Performance: http://www.youtube.com/watch?v=vQZFaec9NpA
Slides for Android Graphics Performance are posted on Romain's blog.
A Moving Experience: http://www.youtube.com/watch?v=ihzZrS69i_s
A Moving Experience Slides:
The code from the animation demos will be posted soon, probably in conjunction with future DevBytes episodes.
Android Graphics Performance: http://www.youtube.com/watch?v=vQZFaec9NpA
Slides for Android Graphics Performance are posted on Romain's blog.
A Moving Experience: http://www.youtube.com/watch?v=ihzZrS69i_s
A Moving Experience Slides:
The code from the animation demos will be posted soon, probably in conjunction with future DevBytes episodes.
Thursday, March 28, 2013
For API Nerds: Interfaces and Inner Classes
This article is for API developers only. If you're writing an application, you may not care about APIs, because you probably don't have any. But if you're writing a library that others will use, public API comes into play.
API is like a pile of laundry: you can either spread the huge, reeking mess out all over your floor, or you can stuff it into a nice, clean hamper. While the first approach provides the convenience of being able to select each day's pre-soiled attire quickly, since it is arrayed out in front of you when you get out of bed, the second solution presents a clean interface to people that make the mistake of walking into your pathetic, slovenly life.
Good APIs prefer hampers.
I was implementing a new piece of API and functionality and wondered the following: Is there any reason to not put my implementation (which I did not want in the public API, and which would be the only implementation of said interface) inside the interface itself? It seems odd, perhaps, maybe even a bit tawdry, but is there anything wrong with it?
My motivation was simple: I'm adding this API and implementation into a package that already has many classes to wade through. Should I bother adding more noise to the list for an implementation detail of this single interface? Why not put it into the interface file, and just bundle up that implementation in a logical place, tightly bound to the interface that it implements?
So I did this, coming up with something like the following:
Additionally, an existing class exposed a single method giving a reference to this interface:
This worked well - the users of the public
Done!
Then I ran JavaDocs on my project and realized my mistake: my supposedly package-private implementation class was now part of the public API, showing up as the public class
Interfaces happened. Interfaces do not use the same access rules as classes. Instead, all members of an interface are public by default. So while I used the correct (to my mind) syntax for package-private access, I was actually using the correct (to the mind of my interface) syntax for declaring that inner class public, and the JavaDocs did the rest.
Do I hear you yelling, "You could make it private!?" Or is that just the echo of my internal shouting when I first saw the problem? This is what I tried to do. This fails for (at least) two reasons. One is that I actually needed this class to be package-private (not private), so that I could instantiate it and override it from outside of this interface. An even better reason is that you cannot declare different access permissions than the default that the interface uses. In this case, that means that I cannot have a public interface with a private inner class. I could declare the entire interface to be private... but that defeats the whole thing I was going for by exposing the interface
There are other ways around this. For example, there is a mechanism we use in Android framework code,
In the end, I just pulled the class out and put it at the same level as
The moral of the story is: API design is tricky. Consider not only the internal implementation details ("Can I make my life easier by implementing the solution in this particular way?"), but also (and wayyyyy more importantly), "Can I make the API, and therefore the lives of external developers, better by doing it in this other way?"
Here is a technical diagram, illustrating the problem and the original solution:
API is like a pile of laundry: you can either spread the huge, reeking mess out all over your floor, or you can stuff it into a nice, clean hamper. While the first approach provides the convenience of being able to select each day's pre-soiled attire quickly, since it is arrayed out in front of you when you get out of bed, the second solution presents a clean interface to people that make the mistake of walking into your pathetic, slovenly life.
Good APIs prefer hampers.
I was implementing a new piece of API and functionality and wondered the following: Is there any reason to not put my implementation (which I did not want in the public API, and which would be the only implementation of said interface) inside the interface itself? It seems odd, perhaps, maybe even a bit tawdry, but is there anything wrong with it?
My motivation was simple: I'm adding this API and implementation into a package that already has many classes to wade through. Should I bother adding more noise to the list for an implementation detail of this single interface? Why not put it into the interface file, and just bundle up that implementation in a logical place, tightly bound to the interface that it implements?
So I did this, coming up with something like the following:
public interface A { void a(); static class AImpl implements A { @Override public void a() { // ... } } }
Additionally, an existing class exposed a single method giving a reference to this interface:
public A getA() { return new AImpl(); }
This worked well - the users of the public
getA()
method got what they needed: an object of type A
with its spectacular, if slightly under-documented, method a()
. And I successfully hid the implementation class inside of this same file, as a package-private static class, saving my package the unbearable burden of yet another file.Done!
Then I ran JavaDocs on my project and realized my mistake: my supposedly package-private implementation class was now part of the public API, showing up as the public class
A.AImpl
. What th-... I didn't say it was public! In fact, I explicitly made it package-private, so that the class exposing the new getA()
method could instantiate an instance of that class. So what happened?Interfaces happened. Interfaces do not use the same access rules as classes. Instead, all members of an interface are public by default. So while I used the correct (to my mind) syntax for package-private access, I was actually using the correct (to the mind of my interface) syntax for declaring that inner class public, and the JavaDocs did the rest.
Do I hear you yelling, "You could make it private!?" Or is that just the echo of my internal shouting when I first saw the problem? This is what I tried to do. This fails for (at least) two reasons. One is that I actually needed this class to be package-private (not private), so that I could instantiate it and override it from outside of this interface. An even better reason is that you cannot declare different access permissions than the default that the interface uses. In this case, that means that I cannot have a public interface with a private inner class. I could declare the entire interface to be private... but that defeats the whole thing I was going for by exposing the interface
A
as a public API.There are other ways around this. For example, there is a mechanism we use in Android framework code,
@hide
, to solve the problem of having to expose API internally but not wanting it to be a part of the public API (a workaround for the language not having the ability to differentiate between internal and external access to library APIs). But at the point where I considered using this workaround, the awkwardness of putting the class inside of the interface just got to be too much.In the end, I just pulled the class out and put it at the same level as
A
. It added another file to the package, but that really wasn't that big a deal anyway. And it was certainly better than the mess I was creating with my class-inside-interface approach.The moral of the story is: API design is tricky. Consider not only the internal implementation details ("Can I make my life easier by implementing the solution in this particular way?"), but also (and wayyyyy more importantly), "Can I make the API, and therefore the lives of external developers, better by doing it in this other way?"
Here is a technical diagram, illustrating the problem and the original solution:
Friday, March 22, 2013
DevBytes: Layout Transitions
The LayoutTransition class (added in Android 3.0) enables easy fade/move/resize animations when items are added to or removed from a ViewGroup, usually with just one line of code. This video shows how this works and also shows the new ability added in JellyBean (Android 4.1) to animate other changes to the layout as well, not limited to items being added or removed.
YouTube:
https://www.youtube.com/watch?v=55wLsaWpQ4g
Code:
http://developer.android.com/shareables/devbytes/LayoutTransChanging.zip
YouTube:
https://www.youtube.com/watch?v=55wLsaWpQ4g
Code:
http://developer.android.com/shareables/devbytes/LayoutTransChanging.zip
Friday, March 15, 2013
DevBytes: PictureViewer
PictureViewer: How to use ViewPropertyAnimator to get a cross-fade effect as new bitmaps get installed in an ImageView.
TransitionDrawable is a handy and easy facility for cross-fading between two drawables. But if you want to cross-fade between an arbitrary set of images, you might want something more general-purpose. Here's one approach.
YouTube: https://www.youtube.com/watch?v=9XbKMUtVnJA
Code: http://developer.android.com/shareables/devbytes/PictureViewer.zip
TransitionDrawable is a handy and easy facility for cross-fading between two drawables. But if you want to cross-fade between an arbitrary set of images, you might want something more general-purpose. Here's one approach.
YouTube: https://www.youtube.com/watch?v=9XbKMUtVnJA
Code: http://developer.android.com/shareables/devbytes/PictureViewer.zip
Friday, March 8, 2013
DevBytes: Request During Layout
Horrible things can result from calling requestLayout() during a layout pass. DON'T DO THIS.
The demo that I wrote to show why this is bad seems very contrived, but I have since run across application code that did nearly the exact same thing, explicitly calling requestLayout() during onLayout(). Ugh. Typically, the cases where this problem occurs are a tad more subtle than that.
YouTube: https://www.youtube.com/watch?v=HbAeTGoKG6k
Code: http://developer.android.com/shareables/devbytes/RequestDuringLayout.zip
The demo that I wrote to show why this is bad seems very contrived, but I have since run across application code that did nearly the exact same thing, explicitly calling requestLayout() during onLayout(). Ugh. Typically, the cases where this problem occurs are a tad more subtle than that.
YouTube: https://www.youtube.com/watch?v=HbAeTGoKG6k
Code: http://developer.android.com/shareables/devbytes/RequestDuringLayout.zip
Thursday, March 7, 2013
Crystal Methodology: The Future of Software Development Process Methodology Effectiveness
Most people that know me know that I can't say enough about process. In this presentation at Devoxx 2012, I tried to change that.
If the embed below isn't working for you, you can watch the talk on the parleys.com site.
If the embed below isn't working for you, you can watch the talk on the parleys.com site.
Friday, March 1, 2013
DevBytes: ListView Deletion
How animating ListView views can lead to artifacts if those views are recycled before you animate them.
YouTube: https://www.youtube.com/watch?v=NewCSg2JKLk
Code: http://developer.android.com/shareables/devbytes/ListViewDeletion.zip
YouTube: https://www.youtube.com/watch?v=NewCSg2JKLk
Code: http://developer.android.com/shareables/devbytes/ListViewDeletion.zip
Friday, February 22, 2013
DevBytes: ListView Animations
How animating ListView items can lead to problems as views are recycled, and how to perform these types of animations correctly with new API added in Jellybean.
Code: http://developer.android.com/shareables/devbytes/ListViewAnimations.zip
YouTube: https://www.youtube.com/watch?v=8MIfSxgsHIs
Code: http://developer.android.com/shareables/devbytes/ListViewAnimations.zip
YouTube: https://www.youtube.com/watch?v=8MIfSxgsHIs
Sunday, February 17, 2013
Engineers Do It Forever
I read an interesting article on The Verge today (Photoshop is a city for everyone), which detailed a short history of Photoshop, to explain why it is the way it is, and how it will continue to be that way.
I enjoyed the article, but one quote in there bugged me:
What irks me is this: Just because an engineer has some level of success, or got the chance to work on a successful product in some previous generation, doesn’t have any bearing on whether they want to let someone else do it. In fact, I’d argue the opposite: any engineer that has worked on ground-breaking, cool stuff will continue to want to do so. You don’t reach a point in your career and think, “Okay, that’s enough.” Or at least, I don’t think you do if you honestly give a crap about what you’re working on, which is the case with most of the people with whom I’ve had the honor to work.
The fact that you’ve done great stuff in the past means you want to do it again. And again. And again.
Sure, there’s probably a time in life where you just get really, really tired. Or dead. But until that time, how the Hell could you live and work in a place and an industry like this and not continue trying to do amazing new things? Case in point: the article’s author interviewed someone that worked on the original version of Photoshop, and then more recently invented Lightroom during a summer vacation. That doesn’t sound like someone that was ready to let that next generation take over. Or maybe he just didn't have any kids to hand it over to?
Maybe this is just me mis-reading the author’s intention here, but it seemed worth clarifying this for posterity, or at least for myself:
Engineers do what we do because we love doing it, not because it pays the bills. So excuse us while we continue doing it until they pry our cold, dead fingers off our keyboards.
I enjoyed the article, but one quote in there bugged me:
"Many of the Adobe employees I met — all fantastic conversationalists — seem like the sort of people whose children grow up to become employees of Google. You know, smart people who’ve had their own heyday, and are now ready for the next generation to shine.”The quote felt more personal to me because I’ve worked at Adobe (albeit not on Photoshop), as well as other venerable tech companies in Silicon Valley, and now find myself at Google.
What irks me is this: Just because an engineer has some level of success, or got the chance to work on a successful product in some previous generation, doesn’t have any bearing on whether they want to let someone else do it. In fact, I’d argue the opposite: any engineer that has worked on ground-breaking, cool stuff will continue to want to do so. You don’t reach a point in your career and think, “Okay, that’s enough.” Or at least, I don’t think you do if you honestly give a crap about what you’re working on, which is the case with most of the people with whom I’ve had the honor to work.
The fact that you’ve done great stuff in the past means you want to do it again. And again. And again.
Sure, there’s probably a time in life where you just get really, really tired. Or dead. But until that time, how the Hell could you live and work in a place and an industry like this and not continue trying to do amazing new things? Case in point: the article’s author interviewed someone that worked on the original version of Photoshop, and then more recently invented Lightroom during a summer vacation. That doesn’t sound like someone that was ready to let that next generation take over. Or maybe he just didn't have any kids to hand it over to?
Maybe this is just me mis-reading the author’s intention here, but it seemed worth clarifying this for posterity, or at least for myself:
Engineers do what we do because we love doing it, not because it pays the bills. So excuse us while we continue doing it until they pry our cold, dead fingers off our keyboards.
Friday, February 15, 2013
DevBytes: KeyFrame Animations
How to use AnimationDrawable to construct a keyframe animation where each frame is shown for a specified duration.
YouTube: http://www.youtube.com/watch?v=V3ksidLf7vA
Code: http://developer.android.com/shareables/devbytes/KeyframeAnimation.zip
YouTube: http://www.youtube.com/watch?v=V3ksidLf7vA
Code: http://developer.android.com/shareables/devbytes/KeyframeAnimation.zip
Wednesday, February 13, 2013
Dealing with Data: A Hard Drivin' Tale of Woe
My hard drive crashed last week, taking all of my data and digital life with it. I've written and rhymed about this already, but I thought I'd jot down some notes about how it happened and how I got my life back in case it helps anyone else in the same boat.
Background:
I have an iMac at home, running Snow Leopard, purchased about 3 years ago. It's been reasonably solid; we haven't had any hardware issues with it until this doozy.
Part I: The Symptoms
My wife complained that the computer had gotten painfully slow, so I took a look at it. Indeed, it was horrible, taking tens of seconds to perform simple tasks and minutes to do something really complicated like launch an application.
I ran Activity Monitor to see what was causing the problem and ... nothing. The CPU was nearly idle, memory usage was well below the physical limits, and the disk didn't seem to be thrashing.
On a hunch, I thought I'd check the disk, so I ran Disk Utility.
(Note to self: This would have been a good time to back up the data on the disk.)
Part II: The Botched Repair Job
"Verify Disk" in the Disk Utility app told me that the disk was not healthy and that I should repair it. It wouldn't let me do this on the boot disk, and told me to boot from the OS installation disk and try it again.
(Note to self: This was another great opportunity for me to back up the disk.)
Eventually (after I asked my wife) I was able to locate the install disk and booted from it (shut the computer down, power it on, hold down "c" after you hear the chime). I ran Disk Utility again, only this time the "Repair Disk" button was not dimmed out, so I clicked it, hoping that it would magically fix whatever had happened.
Repair Disk ran for a minute and then stopped and said that it was unable to repair the disk. It suggested that I back up the disk, format it, and re-install the OS.
(Note to self: This was a good time to regret having not backed up the data already.)
I could not get to the disk at this point, so I rebooted the machine normally (not from the install disk). This caused the machine to boot into the grey booting screen... and then shut off. I tried this a couple of times, and then booted with the install disk again. Once more, I was able to get into the OS, but I was not able to get to the disk (either through the UI or through the shell in a Terminal window). Moreover, the "Repair Disk" button (which had worked so beautifully the first time around) was now dimmed out again. In fact, the drive itself was dimmed out, like the system could see that there was something there, but couldn't actually mount it.
I tried running a Terminal window and getting to the disk, but it was not mounted (nothing in /Volumes), so I couldn't get there and couldn't copy the data.
(Note to self: This was when I realized that I really needed to have backed up everything several steps before.)
Part III: Go to Apple
I took the machine into the Apple Store, hoping that they'd have some advice for me.
The main thing they said was that they don't deal with personal data; as soon as the guy realized that there was data loss involved, he said there was nothing he could do. He gave me the card of a company two hours away and said that they might be able to retrieve the data for me for anywhere from a few hundred to several thousand dollars.
I love my family. And I love the digital pictures of my family that I'd apparently just lost. But I don't love them that much.
He said there was one other thing I could try, although it could risk further corruption that would make retrieval more difficult. But in case I wanted another option: boot the machine as a "target disk."
Part IV: Recovery
Booting as target disk means that you're running the computer as just an incredibly expensive external drive for some other computer. You power it on while holding the "t" key. Meanwhile, you've connected another computer to this machine via a cable that you almost certainly don't have, and the machine looks like just another disk drive to that other computer.
Details:
At this point, my bluetooth keyboard flaked out, in a combination of dying batteries and possibly confusion over bluetooth connection to a machine that didn't have a brain anymore. Fortunately, even a PC USB keyboard has a "t" key, so I was able to use that keyboard to boot into target mode.
I didn't have the cable I needed for this experiment (FireWire). Moreover, none of my other computers even have a FireWire port (newer Macs have Thunderbolt ports instead. I have a theory that they invent new interface standards every couple of years to keep cable companies in business). I ended up ordering a FireWire to Thunderbolt converter and a FireWire cable, which plugged together to connect a MacBook Pro to the dead iMac.
And ... voilà! This actually worked. I plugged it all together, booted the dead machine as a target disk, and was able to see that disk on my other computer. I quickly (okay, it took hours for this much data to travel over FireWire) copied everything I could see on that drive over to my other system and to every other disk I could lay my hands on. I hired monks to transcribe it. I transmitted the bits as smoke signals. I tattooed the binary on my body. I had my children memorize long strings of hex.
Data recovered.
The weird thing here, to me (arguably not the most knowledgeable of people about disk drive technology) is that the disk was clearly screwed up enough that it would not mount when the machine was booted with the install disk, but it was good enough to be visible across FireWire.
Whatever. I had my data back.
(Note to self: At this point, I felt like I'd cheated the system, since by rights I should not have been allowed to get my data back. But I'm okay with this. Crime may not pay, but cheating's not exactly a crime.)
Part V: Replacement
I searched for information on replacing the internal drive on an iMac and discovered that my machine/drive was actually under recall because apparently these particular Seagate drives are known to fail. So on the positive side, at least the replacement was easy and free. It's something I'd rather have known before the drive failed (apparently they do notify customers of these recalls; I may have missed that crucial memo). But at least I got the new hardware I needed, along with a clean install of my original OS.
Part VI: Back Up Your Damn Data
Now I have a working machine/drive again, and I have all (I hope) of my data on other drives. I am now ready to copy everything back and start using the machine again. But first, I'm going to figure out a backup plan. Because I know me, and if I just start using assuming I'm going to get to it soon, I'll be writing another piece in a couple of years about how I lost everything. Again.
I've polled several friends about what they do and the options boil down to these:
- Local backup
- Cloud backup
- Local + Cloud backup (for the truly paranoid, in which crowd I might now count myself)
Local backup:
There are a few options here, including:
- USB drive connected to a computer you want to back up, or a network drive connected to the network that the system is on (or at least can reach). Copy stuff manually (probably not a good idea, unless you're way better at doing this than I am), or set up some kind of automated backup solution, including the Mac's Time Machine. Note that you probably want to ensure that the backups actually happen; I had actually set up Time Machine in the past for the machine that died, but the drive decided to go to sleep and never wake up. So that didn't help.
- RAID array: This is an option on some desktop systems, where you can have disks that are essentially copies of each other, so if one goes out, the data is still alive on the other one. Swap out the bad disk for a good one and you're back in parallelizing business. This is not an obvious solution for an iMac since it only has the one internal drive.
Cloud backup:
The options that I've heard about most frequently included:
- DropBox and Google Drive: I've used the free version of these applications for a while, as a convenient way to exchange large files between computers. Recently, I've also started to use DropBox as a synchronization mechanism for some software that I use. And I use Google Drive for my various Google Docs. They both offer a small amount of storage for free (2GB on DropBox, 5GB on Google Drive). If you pay, they'll provide more storage (DropBox currently offers 100GB for about $10/month, Google Drive is about $5/month for the same 100GB).
The way these services work as a backup solution is that applications create a folder on your computer that is linked to their cloud service. You then create/copy/move anything you want backed up into that folder and it automatically syncs to the servers of these apps in the cloud. Essentially, you start using that cloud folder for all of you documents that you want to back up, and the service takes care of uploading it for you automatically.
- CrashPlan: This application is a bit more flexible; it allows you to specify various parts of your system that you want backed up and it automatically copies those items to its servers and keeps them sync'd over time. Also, it apparently encrypts the data that it uploads, so that you can feel a bit easier about your secure files that you're tossing up into the cloud. The prices vary, depending on how much storage you want, how many computers you want to back up, and how long a term you want to pre-pay.
- Carbonite: Like CrashPlan, Carbonite backs up everything you choose on a single system, encrypts it, and sends it off to the company's servers. You pay an annual fee ($59), or less per year for longer terms.
Local + Cloud
Some people are doing a combination of the above, where they have multiple local copies, but they also keep some of their core data backed up using one of the cloud services above.
I'm sure I haven't covered the full spectrum of backup possibilities here, but these are the main ones that have come up in my recent discussions with colleagues, friends, family pets... anyone that would listen to my tale of woe and help me figure out how to not fall into this pit of data Hell again.
Part VII: Conclusion
See Part VI: Back up your damn data.
Watch my video on the subject if you need more motivation.
Background:
I have an iMac at home, running Snow Leopard, purchased about 3 years ago. It's been reasonably solid; we haven't had any hardware issues with it until this doozy.
Part I: The Symptoms
My wife complained that the computer had gotten painfully slow, so I took a look at it. Indeed, it was horrible, taking tens of seconds to perform simple tasks and minutes to do something really complicated like launch an application.
I ran Activity Monitor to see what was causing the problem and ... nothing. The CPU was nearly idle, memory usage was well below the physical limits, and the disk didn't seem to be thrashing.
On a hunch, I thought I'd check the disk, so I ran Disk Utility.
(Note to self: This would have been a good time to back up the data on the disk.)
Part II: The Botched Repair Job
"Verify Disk" in the Disk Utility app told me that the disk was not healthy and that I should repair it. It wouldn't let me do this on the boot disk, and told me to boot from the OS installation disk and try it again.
(Note to self: This was another great opportunity for me to back up the disk.)
Eventually (after I asked my wife) I was able to locate the install disk and booted from it (shut the computer down, power it on, hold down "c" after you hear the chime). I ran Disk Utility again, only this time the "Repair Disk" button was not dimmed out, so I clicked it, hoping that it would magically fix whatever had happened.
Repair Disk ran for a minute and then stopped and said that it was unable to repair the disk. It suggested that I back up the disk, format it, and re-install the OS.
(Note to self: This was a good time to regret having not backed up the data already.)
I could not get to the disk at this point, so I rebooted the machine normally (not from the install disk). This caused the machine to boot into the grey booting screen... and then shut off. I tried this a couple of times, and then booted with the install disk again. Once more, I was able to get into the OS, but I was not able to get to the disk (either through the UI or through the shell in a Terminal window). Moreover, the "Repair Disk" button (which had worked so beautifully the first time around) was now dimmed out again. In fact, the drive itself was dimmed out, like the system could see that there was something there, but couldn't actually mount it.
I tried running a Terminal window and getting to the disk, but it was not mounted (nothing in /Volumes), so I couldn't get there and couldn't copy the data.
(Note to self: This was when I realized that I really needed to have backed up everything several steps before.)
Part III: Go to Apple
I took the machine into the Apple Store, hoping that they'd have some advice for me.
The main thing they said was that they don't deal with personal data; as soon as the guy realized that there was data loss involved, he said there was nothing he could do. He gave me the card of a company two hours away and said that they might be able to retrieve the data for me for anywhere from a few hundred to several thousand dollars.
I love my family. And I love the digital pictures of my family that I'd apparently just lost. But I don't love them that much.
He said there was one other thing I could try, although it could risk further corruption that would make retrieval more difficult. But in case I wanted another option: boot the machine as a "target disk."
Part IV: Recovery
Booting as target disk means that you're running the computer as just an incredibly expensive external drive for some other computer. You power it on while holding the "t" key. Meanwhile, you've connected another computer to this machine via a cable that you almost certainly don't have, and the machine looks like just another disk drive to that other computer.
Details:
At this point, my bluetooth keyboard flaked out, in a combination of dying batteries and possibly confusion over bluetooth connection to a machine that didn't have a brain anymore. Fortunately, even a PC USB keyboard has a "t" key, so I was able to use that keyboard to boot into target mode.
I didn't have the cable I needed for this experiment (FireWire). Moreover, none of my other computers even have a FireWire port (newer Macs have Thunderbolt ports instead. I have a theory that they invent new interface standards every couple of years to keep cable companies in business). I ended up ordering a FireWire to Thunderbolt converter and a FireWire cable, which plugged together to connect a MacBook Pro to the dead iMac.
And ... voilà! This actually worked. I plugged it all together, booted the dead machine as a target disk, and was able to see that disk on my other computer. I quickly (okay, it took hours for this much data to travel over FireWire) copied everything I could see on that drive over to my other system and to every other disk I could lay my hands on. I hired monks to transcribe it. I transmitted the bits as smoke signals. I tattooed the binary on my body. I had my children memorize long strings of hex.
Data recovered.
The weird thing here, to me (arguably not the most knowledgeable of people about disk drive technology) is that the disk was clearly screwed up enough that it would not mount when the machine was booted with the install disk, but it was good enough to be visible across FireWire.
Whatever. I had my data back.
(Note to self: At this point, I felt like I'd cheated the system, since by rights I should not have been allowed to get my data back. But I'm okay with this. Crime may not pay, but cheating's not exactly a crime.)
Part V: Replacement
I searched for information on replacing the internal drive on an iMac and discovered that my machine/drive was actually under recall because apparently these particular Seagate drives are known to fail. So on the positive side, at least the replacement was easy and free. It's something I'd rather have known before the drive failed (apparently they do notify customers of these recalls; I may have missed that crucial memo). But at least I got the new hardware I needed, along with a clean install of my original OS.
Part VI: Back Up Your Damn Data
Now I have a working machine/drive again, and I have all (I hope) of my data on other drives. I am now ready to copy everything back and start using the machine again. But first, I'm going to figure out a backup plan. Because I know me, and if I just start using assuming I'm going to get to it soon, I'll be writing another piece in a couple of years about how I lost everything. Again.
I've polled several friends about what they do and the options boil down to these:
- Local backup
- Cloud backup
- Local + Cloud backup (for the truly paranoid, in which crowd I might now count myself)
Local backup:
There are a few options here, including:
- USB drive connected to a computer you want to back up, or a network drive connected to the network that the system is on (or at least can reach). Copy stuff manually (probably not a good idea, unless you're way better at doing this than I am), or set up some kind of automated backup solution, including the Mac's Time Machine. Note that you probably want to ensure that the backups actually happen; I had actually set up Time Machine in the past for the machine that died, but the drive decided to go to sleep and never wake up. So that didn't help.
- RAID array: This is an option on some desktop systems, where you can have disks that are essentially copies of each other, so if one goes out, the data is still alive on the other one. Swap out the bad disk for a good one and you're back in parallelizing business. This is not an obvious solution for an iMac since it only has the one internal drive.
Cloud backup:
The options that I've heard about most frequently included:
- DropBox and Google Drive: I've used the free version of these applications for a while, as a convenient way to exchange large files between computers. Recently, I've also started to use DropBox as a synchronization mechanism for some software that I use. And I use Google Drive for my various Google Docs. They both offer a small amount of storage for free (2GB on DropBox, 5GB on Google Drive). If you pay, they'll provide more storage (DropBox currently offers 100GB for about $10/month, Google Drive is about $5/month for the same 100GB).
The way these services work as a backup solution is that applications create a folder on your computer that is linked to their cloud service. You then create/copy/move anything you want backed up into that folder and it automatically syncs to the servers of these apps in the cloud. Essentially, you start using that cloud folder for all of you documents that you want to back up, and the service takes care of uploading it for you automatically.
- CrashPlan: This application is a bit more flexible; it allows you to specify various parts of your system that you want backed up and it automatically copies those items to its servers and keeps them sync'd over time. Also, it apparently encrypts the data that it uploads, so that you can feel a bit easier about your secure files that you're tossing up into the cloud. The prices vary, depending on how much storage you want, how many computers you want to back up, and how long a term you want to pre-pay.
- Carbonite: Like CrashPlan, Carbonite backs up everything you choose on a single system, encrypts it, and sends it off to the company's servers. You pay an annual fee ($59), or less per year for longer terms.
Local + Cloud
Some people are doing a combination of the above, where they have multiple local copies, but they also keep some of their core data backed up using one of the cloud services above.
I'm sure I haven't covered the full spectrum of backup possibilities here, but these are the main ones that have come up in my recent discussions with colleagues, friends, family pets... anyone that would listen to my tale of woe and help me figure out how to not fall into this pit of data Hell again.
Part VII: Conclusion
See Part VI: Back up your damn data.
Watch my video on the subject if you need more motivation.
Friday, February 8, 2013
DevBytes: Bounce Animations
Simple uses of ValueAnimator, ObjectAnimator, and interpolators to control how a shape is moved around on the screen.
Code: http://developer.android.com/shareables/devbytes/Bouncer.zip
Video: https://www.youtube.com/watch?v=vCTcmPIKgpM
Thursday, February 7, 2013
DevBytes: Any Requests?
"What is it you wanna hear?"
- Lynyrd Skynyrd
Hey #android developers:
Laundry list time. Now that I've started doing these DevBytes videos (something I've been planning on for a while now), it would be great to hear from you about tutorial topics that you would find helpful. I have a few more episodes in the can that will roll out over the next few weeks, but eventually I'll write some more apps and record shows around them, so I'm up for ideas on what you'd like to see.
Terribly important caveats:
- In general, I code/write/present around the topics of graphics and animation. These are the technology areas, and Android areas, that I'm most familiar with, and where I could be most helpful, because I might have some understanding of the issues to begin with. Having me do a tutorial on, say, location providers, or fragments, or activity lifecycle would take me more time to create something useful and probably would not result in something as useful to you.
- The whole idea of DevBytes (and most of the demos I ever write for presentations) is to be small and focused. If I have to explain, and you have to understand, unrelated bits about the application's architecture in order to understand the point I'm trying to make, I've lost. So rather than giving a tutorial on, say, the optimal architecture design for event processing in a photo viewing application, I'd rather write an app that shows how to display the photos quickly, or load them optimally, or filter them cool-ly. I've sat through presentations with huge impressive applications before, and walked away knowing nothing more than when I got there; I'd rather focus on the little things and build them up one by opne. Maybe it's just me, but that's the way I prefer to do these things.
- I'm not going to get to everything on this, or any, list. So I'm not necessarily going to get to your pet item (sorry!). The items I get to will be because of a combination of how I could contribute something useful, how I could incorporate it into an app and a show, how broadly applicable I thought the problem/solution was, what time of year it is, how warm it is, and whether I completely forgot that you requested it.
- Don't bet on this happening Immediately. It takes me a while to get to these apps, a while to set up a time to shoot the shows, a while to do the minimal post-processing that they take (mostly slapping a banner on each show and supplying a professional actor's voice, of course), and a while for them to roll out into the stream. So these are not short-term "how do you do this, I need it for my homework assignment next week?", but rather medium-term "I'd love to understand this eventually in case I have homework related to this next term" topics.
Enough constraints for you?
So, on with the ideas. Post something you'd like to see an explanation of below. If you like someone else's suggestion, +1 it to give me a [very] rough metric of its popularity (which I will use as a random filter in figuring out what to do in the future).
p.s. Don't worry if you don't have suggestions; I can always come up with some of my own, certainly enough to keep making these shows. I'm more wondering whether there are things that application developers would like to know about that I wouldn't have thought of all on my lonesome.
Thursday, January 31, 2013
DevBytes: BitmapAllocation
This example shows how to speed up bitmap loading and reduce garbage collection by reusing existing bitmaps.
Note that the reused bitmap must be mutable and of the same size and configuration as the bitmap you load into it. Not sure if this is clear from the video, although it is documented in the API.
Code: http://developer.android.com/shareables/devbytes/BitmapAllocation.zip
Video: http://www.youtube.com/watch?v=rsQet4nBVi8
Note that the reused bitmap must be mutable and of the same size and configuration as the bitmap you load into it. Not sure if this is clear from the video, although it is documented in the API.
Code: http://developer.android.com/shareables/devbytes/BitmapAllocation.zip
Video: http://www.youtube.com/watch?v=rsQet4nBVi8
DevBytes: BitmapScaling
This example shows how the use of BitmapOptions affects the resulting size of a loaded bitmap. Sub-sampling can speed up load times and reduce the need for large bitmaps in memory if your target bitmap size is much smaller, although it's good to understand that you can't get specific Bitmap sizes, but rather power-of-two reductions in sizes.
Code: http://developer.android.com/shareables/devbytes/BitmapScaling.zip
Video: http://www.youtube.com/watch?v=12cB7gnL6po
Code: http://developer.android.com/shareables/devbytes/BitmapScaling.zip
Video: http://www.youtube.com/watch?v=12cB7gnL6po
Wednesday, January 16, 2013
DevBytes: Short, Focused Android Tutorials
The Android team has just rolled out the first four shows in a new series called DevBytes, which is intended to be very short, focused tutorials on specific developer topics. A quick explanation, maybe a demo, and a few lines of code to walk through and it's done. Taking a break from work? Want to learn something new? Or just wonder how a specific piece of technology works on the platform? Tune into one of the DevBytes episodes to get a quick deep dive into that exact topic (well, assuming we've done a show on that topic. Maybe we could interest you in one of the shows that we've actually filmed yet instead?)
The first few of these feature ... me! (Surprise, surprise!) I'll be doing many of these tutorials over time, especially in the areas of animation, graphics, and UI development, starting with this first set:
ViewAnimations:
This show covers the basics of creating and running pre-3.0 Animations on View objects.
PropertyAnimations:
This show parallels the ViewAnimations demo, but uses the new Animator classes introduced in Android 3.0.
WindowAnimations:
Ever want to use a custom window animation to transition between sub activities? Watch this show.
CrossFading:
TransitionDrawable can be used for very simple cross-fade effects between two different Drawable objects.
There will be other shows coming online in the future as well; we're just starting with some of the ones that I did. I wouldn't mind if everything were about animation, but that's just me...
There are code links in the video descriptions if you want to see the code I cover in the videos. I'll also be posting individual blogs with the video and code links to make this easier, I just wanted to get this introduction/overview out there to get it rolling.
These are just a start: we have more videos on the way in the coming weeks. So tune in and geek out.
The first few of these feature ... me! (Surprise, surprise!) I'll be doing many of these tutorials over time, especially in the areas of animation, graphics, and UI development, starting with this first set:
ViewAnimations:
This show covers the basics of creating and running pre-3.0 Animations on View objects.
PropertyAnimations:
This show parallels the ViewAnimations demo, but uses the new Animator classes introduced in Android 3.0.
WindowAnimations:
Ever want to use a custom window animation to transition between sub activities? Watch this show.
CrossFading:
TransitionDrawable can be used for very simple cross-fade effects between two different Drawable objects.
There will be other shows coming online in the future as well; we're just starting with some of the ones that I did. I wouldn't mind if everything were about animation, but that's just me...
There are code links in the video descriptions if you want to see the code I cover in the videos. I'll also be posting individual blogs with the video and code links to make this easier, I just wanted to get this introduction/overview out there to get it rolling.
These are just a start: we have more videos on the way in the coming weeks. So tune in and geek out.
Subscribe to:
Posts (Atom)