Friday, August 29, 2014

Android Rect

While working on a performance-related redesign of Idamu Caverns, I came across some disappointing issues with the design of android.graphics.Rect (issues which are shared with RectF in the same package).

If you're not familiar with the android.graphics package, it contains a number of classes related to 2D drawing.  With Idamu Caverns being a 2D game, it makes heavy use of the classes in that package.

To condense the story into its essential parts: the Rect class is used extensively to communicate drawing information to other classes and methods for the purposes of 2D rendering.  For that reason, Rect objects are peppered throughout the drawing code of Idamu Caverns.

The first issue I came across was a subtle bug in my code that took me far longer than it should have to isolate and resolve.  In the end, it turned out that I was accidentally inverting the top and bottom of a Rect object used to draw the parts that weren't working right.

The Rect object is fussy about it's parameters.  This is only slightly hinted at by the documentation, which states that it doesn't check them for validity (i.e. that top is a smaller value than bottom).  What is hidden behind that comment is the fact that Rect will allow you to set invalid parameters without any errors or warnings.  But furthermore, when you try to use that invalid Rect anywhere, instead of generating an error or warning, you'll just get the wrong result.

The first issue is less of a bug and more of a missed opportunity.  While it's understandable that Rect is designed to work with the coordinate system native to the screen, it would not have been that difficult to make it work with other coordinate systems, such as the Cartesian coordinate system that's rather popular.

The second issue, however, I'd classify as a bug.  If you have a set of parameters that have an important relationship between them (such as the top/bottom situation) it's expected that the internal workings of the class will enforce those relationships, or at least give you a clear warning when you've violated them and things aren't going to work right.

I was pretty upset when I finally identified the problem.  Upset because I trusted the Rect class to be reliable, and had spent a lot of time looking everywhere else in the code on the assumption that if the parameters were invalid I would have gotten an exception.

But my disappointment in the design didn't stop there.  I decided to do the right thing and subclass Rect into a ScreenRect class that would validate it's parameters and provide the type of sanity checking that would make my development more efficient.  Since ScreenRect would subclass Rect, I could use it in any of the android methods that required a Rect.  Object oriented programming at it's finest, right?

Except that the Rect class is final and therefore can't be subclassed.  I spent far too much time trying to understand why anyone would make the Rect class final, as I can think of a number of theoretical reasons to subclass it.

As a result, I had to make the ScreenRect class do all of the work I wanted, while providing a getRect() method to extract a Rect from it when the underlying API requires it.  Feels like really hacky programming to me.

All said, it feels like Rect wasn't really designed so much as thrown together without any real thought.  If anyone knows a valid reason why Rect has all these shortcomings, I'd be interested to hear it.

3 comments:

  1. Thanks for the write up. I too share your confusion on why Rect and other classes have been marked as final. Its almost as if the developers were purposefully making it difficult to extend the framework.

    ReplyDelete
  2. I am not an Android developer, but I have done a fair amount of graphics programming on various platforms over the years. As I understand it, the graphics on Android are actually implemented in the Skia C++ library (or some other native library) and just interfaced to Java. Performance is everything in graphics code, and low level objects like rectangles get created, destroyed, and copied a lot. Putting in a lot of error checking code is going to add noticeable and unwanted overhead. Exceptions in C++ are generally not a great idea in time critical code either, so they are usually disabled in the compiler. They probably won't let you inherit it because you could easily break it or slow it down by doing so and then you might complain about that to them and they don't want to deal with it. Making the inheritance work with the underlying native code is also probably tricky and may adversely affect performance.

    ReplyDelete
  3. I can say definitively that Rect is implemented in pure Java: http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/2.2_r1.1/android/graphics/Rect.java

    ReplyDelete