Tip:
Highlight text to annotate it
X
>> CHUNG: [SPEAKING IN FOREIGN LANGUAGE] Good afternoon. Hi, everyone. I hope you guys having
a great time so far and still awake. It's in the afternoon. My name is Fred Chung. I'm
a Developer Advocate at Google working on Android, I'm actually working very closely
with Tim. You guys just learned many things about group-bearing great looking applications.
And in this session, we're going to talk about creating applications that are flexible enough
to be able to work across different hardware configurations and implementations, and different
locales out there. So we're going to be discussing some technical, you know, best practices and
techniques that, as developers, you can leverage to build your apps such that they are flexible.
And I guess the expectation is that this is an advanced topic. And I guess it would be
helpful if you have done one or maybe a few Android apps and you just learned about making
your apps look good in the previous session and now you want to make it, you know, even
better. And by the way, if you have any feedback and have access to Wi-Fi, use the link displayed
up there to provide any feedback, we'd love to hear it from you. Okay, let's get started.
I think you've seen this slide earlier, right? The point here is that we've seen a lot of
momentum in the Android space in the--in the previous few quarters and we are very excited
about it. We are seeing a lot of support from various players in the ecosystem including,
obviously, the developers like you guys, users, and the OEMs, right? As illustrated on the
slide here, starting on the upper left-hand corner, you have the Motorola Milestone device.
You know, it's got a great physical keyboard, big screen, and the right-hand side device,
you know, it's an X10 device; also a very great-looking device. Down below, we have,
you know, the Galaxy Tab, seven-inch; a really, really good looking device and I think you've--you
guys probably have seen that as well. And on the right-hand side lower corner, you have
Google TV which was just launched a couple of weeks ago--last week or a couple of weeks
ago. So the point here is that with these different foreign factors, large screens,
small screens, physical keyboard, with trackball, without trackball, the thing is with Android
,right, and the various implementations out there, it appeals to people with different
hardware preferences, right? Some like physical keyboards; some don't. And as developers,
it's very, very important for you to understand that there are various techniques that are
available for you to create your application to work across these multiple devices. And
the good thing is that despite these different screen sizes and screen densities, what we've
found is that a lot of existing apps out there, even, you know, those apps that were published
before, for example, the Galaxy Tab was available, these apps looked fine on the Galaxy Tab.
They worked well on Galaxy Tab. Thanks to the Android platform and we're going to look
at, you know, some specifics on how it works. Okay? Let's have a look at how Android as
a system handles resource loading. So for those of you that have built some apps, you
probably are familiar with this directory structure in Eclipse. This is actually a screenshot
from Eclipse. And it's got a bunch of directory; one of those is RES, R-E-S, which stands for
resources. It includes things such as drawables. What we call drawable refers to the bitmaps,
the pictures that you like to put on your application, layouts. You know, these layout
XML files define the, you know, how your application is presented to users, and Android does a
pretty good job in separating out the presentation logic from the business logic which makes
it very, very easy. It's for you to reiterate the layout if you wanted without affecting
the business logic. And if you've noticed on the screenshot, specifically drawable,
you see a couple of funny identifiers next to it; drawable-hdpi, drawable-mdpi. So what
do these identifiers mean? Well, through these qualifiers, these allow you to provide alternative
resources for different configurations. What that means is that for different pictures
that you put in these different photos that have the pre--the different suffixes, hdpi,
mdpi, what happens is that at runtime, the system will determine, "Hey, what's my current
configuration?" And then it automatically picks up--picks the pictures that you have
prepared from these respective directories and present to users. There's actually a bunch
of, you know, qualifiers out there. There's about a dozen of them, 12 of them. And most
notably, I like to share with you four that are related to today's topic, namely, the
screen size, the screen density, right, because these define sort of the different foreign
factors that are available out there; and screen orientation, right? You can--you know,
always certain that your users would rotate their device while using your application;
and SDK versions. And we're going to talk more about that today. You may ask, "Well,
there are a lot of devices out there," right, "with different screen sizes and density.
How does the system actually match these different screen sizes?" Right? Therefore, example,
3.8-inch, 4-inch screen, right, how does the system go about matching these different physical
screen sizes with the identifiers that you've defined? I think it's best described using
a diagram like this. It's a three by three matrix as you can see. Across the top, it
defines the various buckets or categories of densities. So we are talking about low-density,
medium-density, and high-density. And the vertical access, you have the different screen
sizes; small, medium, large, right? Just like when you go to Starbucks, for example. When
you order coffee, you order different sizes instead of the exact volume. How Android works
is that, you know, it sort of maps the different implementations out there to one of these
buckets. For example, the G1 in the middle, it is actually our baseline model. It is considered
a normal screen and medium-density device. To the right of the G1, you have the Droid
X, the Nexus Ones; these are considered enormous screen device and high-density devices. So
through this mechanism, Android knows how to map these different device implementations
out there to one of the--one of the nine predefined buckets, right here. And as I said earlier,
if you look at the directory structure screenshot here, it--at run time, the system would map
the different layout in this case to a different actual hardware. So that's screen size. Screen
density works very similarly. If you look at the directory structure up there, drawable-hidpi,
drawable-lowdpi so you just create different graphics, right? For example, if you have
a graphical icon that represents your company, you want to, you know, put the icons into
these different directories.
So in terms of presenting drawables or these graphical elements on screen, I guess one
of the goals of the system is to make sure these different graphics appear at approximately
the same size across devices of different densities as illustrated on the three diagrams
here. So you have a low sort of a three grids, right? The one--the one on your left represents
a low DPI screen and the one to your right represents a higher density screen. And the
system is trying to make sure, for example, if you want to put a logo like the star. Let's
say, the star represents your company's logo. You want to--you want these stars to show
up at approximately the same size across different devices. And if you look at the number of
pixels involved in one of these stars or your company's logo, on the left hand side, right,
the number of pixels involved is fewer than those on the right hand side, right, in order
to retain the shape of the picture. And at run time, the system is going to either scale
the pictures up or down based on the availability of these graphics and the actual device that's
being run on. And when it comes to the generation of these graphics, for example, when you're
using your favorite photo editing software, Photoshop, or a GIMP, or whatever, I guess
the technique is that you always want to first of all, identify the buckets, the different
density buckets that you like to support, right? For example, you want to, say, support
all three, you always want to create the graphics with the highest density first, right, and
then scale them down based on these principles. For example, if the width of the highest density
graphic is 150, and then you want to scale it down by creating a smaller graphics with,
you know, 100 pixel in width because that's the difference that's represented by the three-grid
model. Well, so that's concept. Let's have a look at actual implementation. I drew this
picture. Is it pretty? I'm proud of it. So this is the actual screen shot that I took
from a G1 device. As you can recall, G1 device is classified as a medium screen size and
medium density. So it looks okay, right? What if I just use the same graphic and then render
it on a medium screen device and higher density device like the Nexus One? You'll get something
on the right. While it still works, the system, you know, does its magic to scale it up to
a screen of higher density, you can see that the graphics on the right is blurred, right?
Can you guys notice that difference? And then what if you want to optimize it, right? We
always want to make sure our applications look good as we learned from the previous
session, right? So you want to take the approach that I just described by introducing a higher
density graphics in the HDPI photo of drawable. And this is the result. And I don't know if
you can tell from the projection but I can, you know, noticeably see the difference in
crispiness of the picture in the middle and that's generated from--on the same device
on the Nexus One, but with the optimized assets. So we just learned that the system does actually
a pretty good job in auto scaling in the different images for you, right? What if you're getting
adventurous, right? What if you have actually identified ways that you think warrants the
needs for you to do the scaling itself, right? My advice is for you is don't do it. Let the
system do its job. The system knows to do it pretty well. And if you, for example, insist--if
you insist on doing it, I would like to ask you to re-think. Ask your colleagues, your--the
neighbors next to you, ask your boyfriends, girlfriends for their confirmation. Is this
the right thing for you to do? The point being, the system is smart enough and it knows how
to do it for you pretty well. But let's say you want to do it, right? Here's the step
involved. There are a couple of parameters involved if you look at the code snippets
when you want to render in Bitmap; bitmapfactory.options, right? It's–-it comes with a default options
and you want to overwrite it. And the specific two parameters that you want to overwrite
are the InDensity and the InTargetDensity. What these parameters mean is that the InDensity
is actually the density for the incoming drawable that you want to render. And the InTargetDensity
is the density of the actual device that the program is running on. So at run time, normally,
the system is going to scale your pixels based on this formula; InTargetDensity divided by
InDensity. And if you want to disable that auto-scaling effect, right? You want to, you
know, make sure that formula equates to one. And the way to do it is to make sure those
values match by, you know, manually setting the values to be identical for the three values
you see on the last bullet, okay? But again, I request you to re-think if you are kind
of wondering about whether it makes sense for you to manually manipulate the pictures.
Okay, we talked about screen sizes and densities. What about orientation as I alluded to you
earlier in the session? So as I mentioned earlier that users will probably rotate the
devices, right, you have the option of just letting the operating system handle the rotation
for you or you can disable it and decide to handle the rotation yourself. And keep in
mind that there are different hardware implementations out there, right? They are, you know, naturally
landscape devices, they are naturally portrait devices, and there are some square devices.
I guess the key point here is that when you are developing an application, don't always
assume that, okay, it's going be always be run on a milestone, for example, where users,
you know, have the tendency of holding it upright or in a portrait mode. Be flexible
to expect the possibility that they might have a natural tendency to use it in a landscape
mode. For tablet, for example, right? Why the device seven-inch Samsung tablet, for
example. I think the natural orientation is probably landscape. [INDISTINCT] a lot of
people would like to use it to read e-books or whatever, and therefore I guess the natural
orientation could be landscape. And if you want to determine the natural orientation
at run time, you can use code--this code snippet, right? You can get it--hand it to the display
object and read the orientation based on the get orientation API and that particularly
API's been duplicated. And if you are running on Froyo or if you're targeting on Froyo,
you want to use to get rotation API, okay? Let's talk a little bit about, you know, best
practices involved with creating flexible UIs. So there are two screen shots up there
while the two applications are perfectly functional, they look a little bit odd. The one on the
right looks a little bit odd, and the reason being those were generated using an Absolute
Layout. And for those of you that may not be familiar with Absolute Layout is that it's
a method to layout graphical elements on the UI. For example, you want to specify that,
"I want to place the input box five pixels below the top and 20 pixels to the right of
the left--of the left edge." And while this works on the screen shot on the left hand
side which was produced from G1 which is a low density device and a screen shot on the
right hand side is come--actually came from Nexus One which is a higher density device.
As we learned from the star diagrams, the three stars diagrams, the number of pixels
involved--while the number of pixels involved to define the input box might be identical,
it's narrower on higher density devices, right? So you can't reliably depend on Absolute Layout
and we advise you to kind of use another method which is Relative Layout. It is basically
a method to specify UI elements based on relative positioning with respect to other elements.
So let's use this example. You would say, "I want to put the input box to the right
of the label,” which is called input, just like that, and then the system would automatically
put it to the right of it. And then, you want to use something called DIP, density-independent
pixel, rather than absolute pixel because, again, you don't want--you want to avoid absolute
pixels for the various reasons that I just mentioned before and use this density-independent
pixel. What happens is that remember the three-by-three matrix, right? So that's--that defines the
logical pixel unit. And then once defined, the system would automatically scale that
based on the actual hardware implementation that the app is deployed on. And as far as
defining text size, you want to use something called SP instead of, again, absolute pixel
to define the size of the text because what we've seen is that while the text size may
look okay on one implementation, namely, for example, G1, it may look like a small font
on a Milestone, for example, just because, again, the differences in different screen
densities. Okay, here's another observation that I've learned over the months. This is
an actual screenshot that was taken from a Galaxy Tab which is defined as a high, you
know, large screen-sized and high density. And what happens is that if you have a Legacy
app, right? A Legacy app probably has the targetSdkVersion defined as something smaller
than four like a cupcake, a three or something. And then, you know, if you can recall back
in Version 3, the difference screen sizes were not introduced to the SDK. So the system
would think, "Okay, this app is not capable of being rendered on a larger screen, so I'm
going to put a border around it." This is something what we call a Shoebox. And, again,
while it works functionally, it doesn't look good, right? It's not appealing to your users
and that's not to the best of your interest. And one quick way to sort of a remedy this
situation is to make sure that in your Android Manifest, you have the support screens element
properly declaring the support of large screens, or medium screens, or any density to true.
That way, the system knows that, "Okay, this application is probably able to be rendered
on a bigger screen and it would stretch it to take the four sides of the screen." All
right. So we talked about the screen size and screen densities, the different form factors
that you need to be aware of. What about the different versions out there, right, because
as you probably can recall, we have brought out a few versions out there over the years.
And as illustrated in this picture here, right, this is a panoramic picture I took with my
G1 several days ago and as you can see, we have the various sculptures out there on the
lawn. This is coming from the Mountain View Headquarters. And traditionally, whenever
the team launches a new version, they put up a new sculpture. So as you can see the
cupcakes, the donuts, and whatever, over the years. So the expectation is that, you know,
there would always be multiple versions out there in the market and we're going to talk
a little about how to--how do you go about handling that in the future. And also one
more thing, last Friday, they put out--put out a new sculpture out there and that's what--not
what I'm going to talk about today. I don't have more information there. Okay, so we talked
with a lot of developers, right? A very--a question that comes up over and over again
is that, "Okay, you have multiple versions out there, which version should I target?"
Right? This is our smart answer to it; “It really depends.” And then there's a number
of reasons, right, there's a number of factors involved. And the two most important factors,
I think, are the number of users and the number of recent features or the number of advanced
features you want to implement. In this non-scientific graph that I drew--hand drawn, you can probably
see that, you know, the more advanced of a feature that you wanted to target, the fewer
the number of users that it probably can benefit. And the reason being, you know, it take--it
generally takes some time for users out there on the field to upgrade to newer versions
for whatever reasons. And the question really is, you're going to ask yourself, "Okay, for
the app that you are trying to develop, what's my feature sets?" Right? You want to look
at feature sets and then try to compare the various features that are available on the
various version of Android. For example, if you want to, you know, have a news widget
kind of thing, that was introduced way back in 1.5, which means that, you know, the SDK
version that you can use to target is probably--you can--you can go back all the way to 1.5 which
will appear to almost, you know, practically 100% of the devices out there versus if you
want to target, you know, the data backup feature or live wallpaper features that are
introduced in more recent versions. You're going to have to just, you know, balance that
yourself to figure out, "Okay, what's my cost benefit? Additional features or relatively
speaking, fewer users?" But over time, you can sort of expect that users will be upgraded
to newer versions and newer devices that come out will be running newer versions. So here's
another tool that helps us make that decision like, which version should I target. And by
the way, this is publicly available on our websites and it basically illustrates that
over time, you know, it confirms the point that I just I make; over time, more and more
devices will have newer versions. And if you look at this graph, anything with Éclair
or 2.1, or more recent, we're talking about roughly 75% of devices out there, which is
pretty great. Okay, so with the different versions out there, right, I mentioned that
you can expect to see multiple versions out there; out two to three versions or whatever.
How do you go about creating an application that would work seamlessly across these different
versions? So, here's the thing that you want to figure out, right, with API changes. For
example, you're developing on 2.2, right, Froyo. You might be leveraging or using some
APIs that might not be available on older versions. How do you make sure the backward
compatibility of your application to maximize your reach? I guess the general recommendation
is leverage Java Reflection, right? At runtime, you want figure out, okay, on my--on the current
system that I'm running is the particular class or is the particular method that I'm
trying to use is available? And then you can provide a wrapper around that reflection logic,
right? Whenever you want to access that information from your business logic, you want to poke
the--poke the class and say, "Hey, is this class or is this method available at runtime?"
And if it is, use it. If not, you're going to have to have a backup plan. So here's the
general strategy in theory and let's have a look at some actual code. In this use case,
I'm trying to use the getDensity method from the canvas class, as you can see pointed to
you by the arrow, right? And this getDensity method specifically was introduced back in
level four or at Donut. What if I want to target Cupcake devices that don't have this
method available? So I would create this wrapper class called Canary with a static--method
is just a Java Reflection class that represents the method itself. And then you try to say,
get me the method called getDensity. And if that getDensity method is found on the runtime,
it will return the reference to that method and that reference, based on this code snippet,
it start in "mGetDensity" reference handle. If it's not found, an exception will be thrown
and, you know, you're going to have to do something differently. So that's the wrapper
method. And let's have a look at how this method should be used. We're talking about
the same class--the same wrapper class. And whenever you need to access the getDensity,
you want to do something like this, you want to check for the reference, right? If the
method was successfully identified, it's going to be available in the mGetDensity reference
or handle. If it's found, then invoke it. If it's not found in the [INDISTINCT] class,
you might want to do something different by having--in this particular instance, having
a default variable or something so that your application won't crash, which is a great
thing for users--from a user experience standpoint. So that was a new method, right? What about
you using a class that might not be familiar--I mean, that might not be available in the previous
version of Android? Very, very similar approach is used here. Again, create a wrapper class,
right? In the wrapper class within a static block, you want to try to load that class,
in this case, called EventLog, which was introduced in Froyo. It's something used to read diagnostic
information at the system level. You want to try to load this using the forName method,
okay? If it's found, everything is good. If it's not found, an exception will be thrown.
And then there's this checkAvailable--whoops. There's this checkAvailable method, which
is a static method, MT method, and the idea is that a caller can call this checkAvailable.
And since this is static, it would--it would invoke the execution of the static block above
it to check the forName and so on and so forth. Okay? And the third method in this wrapper
is the writeEvent, which is nothing but a wrapper for the actual class that you are
trying to invoke. And then here's the actual usage of the wrapper class. For example, in
this MyActivity, you want to use it, so you would do EventLogWrapper.checkAvailable. As
you can recall, this is the static method that is used to trigger the invocation of
the static block which calls forName, right? If forName works well, everything is good,
right? And then you want set the boolean to true. The boolean is about called mAPIAvailable,
set into true, indicating that, yes, the class is available, otherwise, if the exception
is affirmed which implies that the class is not available, then you would set that boolean
to false. So when you are actually trying to use this class, you would just check for
the, you know, the boolean value. If it's true, then it means that the class is available,
then use it. If not, do something differently. So this is one of the ways developers can
use to make sure they have maximum compatibility across different versions. But I would say
it--while this is a good method, it may not work for all features. For example, account
manager which is more involved than just making API cause, that's something that, you know,
that makes it very, very hard to use a strategy like this to maximize the compatibility. But
for the most part, you can leverage what I--what we just described to kind of make sure your
application is version-proof, sort of. Okay, we talked about, you know, version compatibility.
Let's move on to localization, right? We are talking about an international audience here.
You have pretty much have access to sell your application for Android market to the rest
of the other countries that support Android market. So let's have a quick look at localization
here. Here's a quick code snippet to show, you know, a button, you want to render a button
on the screen, and put a text on it. Voila, right? It works perfectly fine. But can this
be improved? We all, as programmers, are lazy, right? We have the tendency to just hot code
labels. And while it works, again, you know, it may not be that flexible in the event that
you want to localize your application to, say, English speaking countries or Spanish
speaking audience. So to do that, it's really simple. For those experienced programmers
out there, you guys should be all familiar with the resource bundles concept, same ideas
applicable here. In the values folder of your project, you just have to create different
values folders prefix or suffix rather by different languages and regions; for example
-ES, -PT region Brazil, for example. Within the strings.XML files that--which we'll see
later, it shows you that how you can put different languages into the different bundles. So this
is the setup. So how does it work at runtime? At runtime, the system would sort of go through
this logic, the four bullet points out there; what is my current system locale, right? It
could be Portuguese or something else, right, English or whatever. And the thing is it's
got a default version--default locale. And at runtime, the--it would query the app package,
the APK file to say, hey, if this developer has paid attention in Fret's talk and created
the different folders with the different localized strings, and if they did and if it's able
to find the corresponding folder that matches their current locale, then it would use it.
If it's not found, it falls back to the default, the values one without any suffixes. Okay,
here's an actual example of formatting string. You want to put a label and a text, right?
This is an application that tries to figure out the number of engineers needed to change
a light bulb. And in the different strings, you know, different localized strings, resource
files that I mentioned, you just put in the contents, the localized contents. The first
version, you know, it's Portuguese. I hope that's right. That's based on Google Translate
output. And the second strings.XML contains the strings, you know, in other language,
in English, in this case. Going back to the code snippets, this is how you would instantiate
this string by calling, you know, getResources.getString by referring to the R.string.question, which
automatically maps to one of these depending on, again, the current system locale. And
as you can see, you can also merge data at runtime. You can, you know, merge an integer
value to the string that--to be displayed on the UI. Okay, we’ve talked about localization.
How about from an Android market standpoint, right? Once you've developed your app, you've
tested your app, you want to distribute it. Android Market plays a very important role.
It's sort of a matchmaking role between, you know, apps and devices. On one hand, you have
a bunch of developers creating great apps, publishing apps. And on the other hand, you
have a bunch of devices checking in, "Hey, get me some apps. Get me some apps." And whenever
these devices check in, they report their device hardware capabilities. And different
hardware versions or different hardware models, as we've talked about, have different capabilities;
some have cameras, some don't, so on and so forth. And let’s see, you know, what sort
of mechanism Android Market has to make sure actually the intended audience gets access
to application. The worst thing that could happen is that if your application requires
camera and what if the device doesn't have a camera? That would be a bad user experience.
So there are two sides. On the application publishing side through Android Manifest,
you can create, you know, roughly six different filters, right? And I'm just calling out four
ones that are relevant to this talk; uses features, uses configurations. And these tags
actually tells the system, hey, my app actually will require telephony features, or it does
require OpenGL. Or in a case of users configuration, it says, okay, my app requires a physical
keyboard for whatever reason, or it uses SDK. My app requires a minimum version of SDK be
it Froyo or Cupcake or whatever. And then you can also define things such as the support
screen; what kind of screen sizes can my app support? So through these different cues,
Android Market knows, okay, here are the apps' capabilities or requirements. And then these
are created through Android Manifest. But through other methods, you can also specify,
okay, here's the geographical location I want to target. Here's the carrier restrictions
for making my apps available and native platform if your application uses NDK which we saw
in Tim's talk. It--you know, NDKs targets a specific hardware and then this is kind
of--these are kind of filtering criteria you can specify. And you can have a look at this
URL to understand the full definition of these different criteria. And also--so we talked
about app publishing aspect of it. A U.S. developer can say, "Okay, here are the requirements
for my app." But for those devices that are checking in, how do we make sure those devices
conform to a certain standard? And that's done through Android compatibility definition,
right? If you go to the URL, you can find that, you know, this definition defines things
such as the minimum requirements an Android compatible device must have. For example,
touch screens, USB slots, so on and so forth, it defines, you know, some of the minimum
requirements to be compatible with Android, and to ensure that these devices that are
checking in to Android Market actually conform to a certain standard so apps won't break.
And from an OEM standpoint, they would have to make sure that they run through the CTS,
compatibility test suites, to make sure they are compatible. Okay. So that's kind of completes
the whole cycle from app publishing to devices checking in, how Android markets make sure
your apps are only visible to the intended audience. Okay. I guess that wraps up my talk.
And to summarize, I want to say that it's actually pretty straightforward to make sure
your app could be, you know, compatible to various devices out there with different hardware
configurations and versions. And I strongly encourage you to have a look at our online
documentation for the information that we just discussed. Thank you very much. [SPEAKING
IN FOREIGN LANGUAGE].