diff options
Diffstat (limited to 'docs/html/training')
| -rw-r--r-- | docs/html/training/accessibility/index.jd | 1 | ||||
| -rw-r--r-- | docs/html/training/basics/firstapp/building-ui.jd | 363 | ||||
| -rw-r--r-- | docs/html/training/basics/firstapp/creating-project.jd | 142 | ||||
| -rw-r--r-- | docs/html/training/basics/firstapp/index.jd | 64 | ||||
| -rw-r--r-- | docs/html/training/basics/firstapp/running-app.jd | 178 | ||||
| -rw-r--r-- | docs/html/training/basics/firstapp/starting-activity.jd | 308 | ||||
| -rw-r--r-- | docs/html/training/displaying-bitmaps/cache-bitmap.jd | 337 | ||||
| -rw-r--r-- | docs/html/training/displaying-bitmaps/display-bitmap.jd | 400 | ||||
| -rw-r--r-- | docs/html/training/displaying-bitmaps/index.jd | 78 | ||||
| -rw-r--r-- | docs/html/training/displaying-bitmaps/load-bitmap.jd | 165 | ||||
| -rw-r--r-- | docs/html/training/displaying-bitmaps/process-bitmap.jd | 239 | ||||
| -rw-r--r-- | docs/html/training/tv/index.jd | 52 | ||||
| -rw-r--r-- | docs/html/training/tv/optimizing-layouts-tv.jd | 246 | ||||
| -rw-r--r-- | docs/html/training/tv/optimizing-navigation-tv.jd | 206 | ||||
| -rw-r--r-- | docs/html/training/tv/unsupported-features-tv.jd | 156 |
15 files changed, 2934 insertions, 1 deletions
diff --git a/docs/html/training/accessibility/index.jd b/docs/html/training/accessibility/index.jd index d5178a9..333f9f2 100644 --- a/docs/html/training/accessibility/index.jd +++ b/docs/html/training/accessibility/index.jd @@ -13,7 +13,6 @@ next.link=accessible-app.html <h2>Dependencies and prerequisites</h2> <ul> <li>Android 2.0 (API Level 5) or higher</li> -Playback</a></li> </ul> <h2>You should also read</h2> diff --git a/docs/html/training/basics/firstapp/building-ui.jd b/docs/html/training/basics/firstapp/building-ui.jd new file mode 100644 index 0000000..847163a --- /dev/null +++ b/docs/html/training/basics/firstapp/building-ui.jd @@ -0,0 +1,363 @@ +page.title=Building a Simple User Interface +parent.title=Building Your First App +parent.link=index.html + +trainingnavtop=true +previous.title=Running Your App +previous.link=running-app.html +next.title=Starting Another Activity +next.link=starting-activity.html + +@jd:body + + +<!-- This is the training bar --> +<div id="tb-wrapper"> +<div id="tb"> + +<h2>This lesson teaches you to</h2> + +<ol> + <li><a href="#LinearLayout">Use a Linear Layout</a></li> + <li><a href="#TextInput">Add a Text Input Box</a></li> + <li><a href="#Strings">Add String Resources</a></li> + <li><a href="#Button">Add a Button</a></li> + <li><a href="#Weight">Make the Input Box Fill in the Screen Width</a></li> +</ol> + + +<h2>You should also read</h2> +<ul> + <li><a href="{@docRoot}guide/topics/ui/declaring-layout.html">XML Layouts</a></li> +</ul> + + +</div> +</div> + + + +<p>The graphical user interface for an Android app is built using a hierarchy of {@link +android.view.View} and {@link android.view.ViewGroup} objects. {@link android.view.View} objects are +usually UI widgets such as a button or text field and {@link android.view.ViewGroup} objects are +invisible view containers that define how the child views are laid out, such as in a +grid or a vertical list.</p> + +<p>Android provides an XML vocabulary that corresponds to the subclasses of {@link +android.view.View} and {@link android.view.ViewGroup} so you can define your UI in XML with a +hierarchy of view elements.</p> + + +<div class="sidebox-wrapper"> +<div class="sidebox"> + <h2>Alternative Layouts</h2> + <p>Separating the UI layout into XML files is important for several reasons, +but it's especially important on Android because it allows you to define alternative layouts for +different screen sizes. For example, you can create two versions of a layout and tell +the system to use one on "small" screens and the other on "large" screens. For more information, +see the class about <a +href="{@docRoot}training/supporting-hardware/index.html">Supporting Various Hardware</a>.</p> +</div> +</div> + +<img src="{@docRoot}images/viewgroup.png" alt="" /> +<p class="img-caption"><strong>Figure 1.</strong> Illustration of how {@link +android.view.ViewGroup} objects form branches in the layout and contain {@link +android.view.View} objects.</p> + +<p>In this lesson, you'll create a layout in XML that includes a text input field and a +button. In the following lesson, you'll respond when the button is pressed by sending the +content of the text field to another activity.</p> + + + +<h2 id="LinearLayout">Use a Linear Layout</h2> + +<p>Open the <code>main.xml</code> file from the <code>res/layout/</code> +directory (every new Android project includes this file by default).</p> + +<p class="note"><strong>Note:</strong> In Eclipse, when you open a layout file, you’re first shown +the ADT Layout Editor. This is an editor that helps you build layouts using WYSIWYG tools. For this +lesson, you’re going to work directly with the XML, so click the <em>main.xml</em> tab at +the bottom of the screen to open the XML editor.</p> + +<p>By default, the <code>main.xml</code> file includes a layout with a {@link +android.widget.LinearLayout} root view group and a {@link android.widget.TextView} child view. +You’re going to re-use the {@link android.widget.LinearLayout} in this lesson, but change its +contents and layout orientation.</p> + +<p>First, delete the {@link android.widget.TextView} element and change the value +<a href="{@docRoot}reference/android/widget/LinearLayout.html#attr_android:orientation">{@code +android:orientation}</a> to be <code>"horizontal"</code>. The result looks like this:</p> + +<pre> +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:orientation="horizontal" > +</LinearLayout> +</pre> + +<p>{@link android.widget.LinearLayout} is a view group (a subclass of {@link +android.view.ViewGroup}) that lays out child views in either a vertical or horizontal orientation, +as specified by the <a +href="{@docRoot}reference/android/widget/LinearLayout.html#attr_android:orientation">{@code +android:orientation}</a> attribute. Each child of a {@link android.widget.LinearLayout} appears on +the screen in the order in which it appears in the XML.</p> + +<p>The other two attributes, <a +href="{@docRoot}reference/android/view/View.html#attr_android:layout_width">{@code +android:layout_width}</a> and <a +href="{@docRoot}reference/android/view/View.html#attr_android:layout_height">{@code +android:layout_height}</a>, are required for all views in order to specify their size.</p> + +<p>Because the {@link android.widget.LinearLayout} is the root view in the layout, it should fill +the entire screen area that's +available to the app by setting the width and height to +<code>"fill_parent"</code>.</p> + +<p class="note"><strong>Note:</strong> Beginning with Android 2.2 (API level 8), +<code>"fill_parent"</code> has been renamed <code>"match_parent"</code> to better reflect the +behavior. The reason is that if you set a view to <code>"fill_parent"</code> it does not expand to +fill the remaining space after sibling views are considered, but instead expands to +<em>match</em> the size of the parent view no matter what—it will overlap any sibling +views.</p> + +<p>For more information about layout properties, see the <a +href="{@docRoot}guide/topics/ui/declaring-layout.html">XML Layout</a> guide.</p> + + + +<h2 id="TextInput">Add a Text Input Box</h2> + +<p>To create a user-editable text box, add an {@link android.widget.EditText +<EditText>} element inside the {@link android.widget.LinearLayout <LinearLayout>}. The {@link +android.widget.EditText} class is a subclass of {@link android.view.View} that displays an editable +text box.</p> + +<p>Like every {@link android.view.View} object, you must define certain XML attributes to specify +the {@link android.widget.EditText} object's properties. Here’s how you should declare it +inside the {@link android.widget.LinearLayout <LinearLayout>} element:</p> + +<pre> + <EditText android:id="@+id/edit_message" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:hint="@string/edit_message" /> +</pre> + + +<div class="sidebox-wrapper"> +<div class="sidebox"> + <h3>About resource objects</h3> + <p>A resource object is simply a unique integer name that's associated with an app resource, +such as a bitmap, layout file, or string.</p> + <p>Every resource has a +corresponding resource object defined in your project's {@code gen/R.java} file. You can use the +object names in the {@code R} class to refer to your resources, such as when you need to specify a +string value for the <a +href="{@docRoot}reference/android/widget/TextView.html#attr_android:hint">{@code android:hint}</a> +attribute. You can also create arbitrary resource IDs that you associate with a view using the <a +href="{@docRoot}reference/android/view/View.html#attr_android:id">{@code android:id}</a> attribute, +which allows you to reference that view from other code.</p> + <p>The SDK tools generate the {@code R.java} each time you compile your app. You should never +modify this file by hand.</p> +</div> +</div> + +<p>About these attributes:</p> + +<dl> +<dt><a href="{@docRoot}reference/android/view/View.html#attr_android:id">{@code android:id}</a></dt> +<dd>This provides a unique identifier for the view, which you can use to reference the object +from your app code, such as to read and manipulate the object (you'll see this in the next +lesson). + +<p>The at-symbol (<code>@</code>) is required when you want to refer to a resource object from +XML, followed by the resource type ({@code id} in this case), then the resource name ({@code +edit_message}). (Other resources can use the same name as long as they are not the same +resource type—for example, the string resource uses the same name.)</p> + +<p>The plus-symbol (<code>+</code>) is needed only when you're defining a resource ID for the +first time. It tells the SDK tools that the resource ID needs to be created. Thus, when the app is +compiled, the SDK tools use the ID value, <code>edit_message</code>, to create a new identifier in +your project's {@code gen/R.java} file that is now assiciated with the {@link +android.widget.EditText} element. Once the resource ID is created, other references to the ID do not +need the plus symbol. See the sidebox for more information about resource objects.</p></dd> + +<dt><a +href="{@docRoot}reference/android/view/View.html#attr_android:layout_width">{@code +android:layout_width}</a> and <a +href="{@docRoot}reference/android/view/View.html#attr_android:layout_height">{@code +android:layout_height}</a></dt> +<dd>Instead of using specific sizes for the width and height, the <code>"wrap_content"</code> value +specifies that the view should be only as big as needed to fit the contents of the view. If you +were to instead use <code>"fill_parent"</code>, then the {@link android.widget.EditText} +element would fill the screen, because it'd match the size of the parent {@link +android.widget.LinearLayout}. For more information, see the <a +href="{@docRoot}guide/topics/ui/declaring-layout.html">XML Layouts</a> guide.</dd> + +<dt><a +href="{@docRoot}reference/android/widget/TextView.html#attr_android:hint">{@code +android:hint}</a></dt> +<dd>This is a default string to display when the text box is empty. Instead of using a hard-coded +string as the value, the value given in this example refers to a string resource. When you add the +{@code +"@string/edit_message"} value, you’ll see a compiler error because there’s no matching string +resource by that name. You'll fix this in the next section by defining the string +resource.</dd> +</dl> + + + +<h2 id="Strings">Add String Resources</h2> + +<p>When you need to add text in the user interface, you should always specify each string of text in +a resource file. String resources allow you to maintain a single location for all string +values, which makes it easier to find and update text. Externalizing the strings also allows you to +localize your app to different languages by providing alternative definitions for each +string.</p> + +<p>By default, your Android project includes a string resource file at +<code>res/values/strings.xml</code>. Open this file, delete the existing <code>"hello"</code> +string, and add one for the +<code>"edit_message"</code> string used by the {@link android.widget.EditText <EditText>} +element.</p> + +<p>While you’re in this file, also add a string for the button you’ll soon add, called +<code>"button_send"</code>.</p> + +<p>The result for <code>strings.xml</code> looks like this:</p> + +<pre> +<?xml version="1.0" encoding="utf-8"?> +<resources> + <string name="app_name">My First App</string> + <string name="edit_message">Enter a message</string> + <string name="button_send">Send</string> +</resources> +</pre> + +<p>For more information about using string resources to localize your app for several languages, +see the <a +href="{@docRoot}training/basics/supporting-devices/index.html">Supporting Various Devices</a> +class.</p> + + + + +<h2 id="Button">Add a Button</h2> + +<p>Now add a {@link android.widget.Button <Button>} to the layout, immediately following the +{@link android.widget.EditText <EditText>} element:</p> + +<pre> + <Button + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/button_send" /> +</pre> + +<p>The height and width are set to <code>"wrap_content"</code> so the button is only as big as +necessary to fit the button's text.</p> + + + +<h2 id="Weight">Make the Input Box Fill in the Screen Width</h2> + +<p>The layout is currently designed so that both the {@link android.widget.EditText} and {@link +android.widget.Button} widgets are only as big as necessary to fit their content, as shown in +figure 2.</p> + +<img src="{@docRoot}images/training/firstapp/edittext_wrap.png" /> +<p class="img-caption"><strong>Figure 2.</strong> The {@link android.widget.EditText} and {@link +android.widget.Button} widgets have their widths set to +<code>"wrap_content"</code>.</p> + +<p>This works fine for the button, but not as well for the text box, because the user might type +something longer and there's extra space left on the screen. So, it'd be nice to fill that width +using the text box. +{@link android.widget.LinearLayout} enables such a design with the <em>weight</em> property, which +you can specify using the <a +href="{@docRoot}reference/android/widget/LinearLayout.LayoutParams.html#weight">{@code +android:layout_weight}</a> attribute.</p> + +<p>The weight value allows you to specify the amount of remaining space each view should consume, +relative to the amount consumed by sibling views, just like the ingredients in a drink recipe: "2 +parts vodka, 1 part coffee liquer" means two-thirds of the drink is vodka. For example, if you give +one view a weight of 2 and another one a weight of 1, the sum is 3, so the first view gets 2/3 of +the remaining space and the second view gets the rest. If you give a third view a weight of 1, +then the first view now gets 1/2 the remaining space, while the remaining two each get 1/4.</p> + +<p>The default weight for all views is 0, so if you specify any weight value +greater than 0 to only one view, then that view fills whatever space remains after each view is +given the space it requires. So, to fill the remaining space with the {@link +android.widget.EditText} element, give it a weight of 1 and leave the button with no weight.</p> + +<pre> + <EditText + android:layout_weight="1" + ... /> +</pre> + +<p>In order to improve the layout efficiency when you specify the weight, you should change the +width of the {@link android.widget.EditText} to be +zero (0dp). Setting the width to zero improves layout performance because using +<code>"wrap_content"</code> as the width requires the system to calculate a width that is +ultimately irrelevant because the weight value requires another width calculation to fill the +remaining space.</p> +<pre> + <EditText + android:layout_weight="1" + android:layout_width="0dp" + ... /> +</pre> + +<p>Figure 3 +shows the result when you assign all weight to the {@link android.widget.EditText} element.</p> + +<img src="{@docRoot}images/training/firstapp/edittext_gravity.png" /> +<p class="img-caption"><strong>Figure 3.</strong> The {@link android.widget.EditText} widget is +given all the layout weight, so fills the remaining space in the {@link +android.widget.LinearLayout}.</p> + +<p>Here’s how your complete layout file should now look:</p> + +<pre> +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:orientation="horizontal"> + <EditText android:id="@+id/edit_message" + android:layout_weight="1" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:hint="@string/edit_message" /> + <Button android:id="@+id/button_send" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/button_send" /> +</LinearLayout> +</pre> + +<p>This layout is applied by the default {@link android.app.Activity} class +that the SDK tools generated when you created the project, so you can now run the app to see the +results:</p> + +<ul> + <li>In Eclipse, click <strong>Run</strong> from the toolbar.</li> + <li>Or from a command line, change directories to the root of your Android project and +execute: +<pre> +ant debug +adb install bin/MyFirstApp-debug.apk +</pre></li> +</ul> + +<p>Continue to the next lesson to learn how you can respond to button presses, read content +from the text field, start another activity, and more.</p> + + + diff --git a/docs/html/training/basics/firstapp/creating-project.jd b/docs/html/training/basics/firstapp/creating-project.jd new file mode 100644 index 0000000..5a89f2e --- /dev/null +++ b/docs/html/training/basics/firstapp/creating-project.jd @@ -0,0 +1,142 @@ +page.title=Creating an Android Project +parent.title=Building Your First App +parent.link=index.html + +trainingnavtop=true +next.title=Running Your App +next.link=running-app.html + +@jd:body + + +<!-- This is the training bar --> +<div id="tb-wrapper"> +<div id="tb"> + +<h2>This lesson teaches you to</h2> + +<ol> + <li><a href="#Eclipse">Create a Project with Eclipse</a></li> + <li><a href="#CommandLine">Create a Project with Command Line Tools</a></li> +</ol> + +<h2>You should also read</h2> + +<ul> + <li><a href="{@docRoot}sdk/installing.html">Installing the +SDK</a></li> + <li><a href="{@docRoot}guide/developing/projects/index.html">Managing Projects</a></li> +</ul> + + +</div> +</div> + +<p>An Android project contains all the files that comprise the source code for your Android +app. The Android SDK tools make it easy to start a new Android project with a set of +default project directories and files.</p> + +<p>This lesson +shows how to create a new project either using Eclipse (with the ADT plugin) or using the +SDK tools from a command line.</p> + +<p class="note"><strong>Note:</strong> You should already have the Android SDK installed, and if +you're using Eclipse, you should have installed the <a +href="{@docRoot}sdk/eclipse-adt.html">ADT plugin</a> as well. If you have not installed +these, see <a href="{@docRoot}sdk/installing.html">Installing the Android SDK</a> and return here +when you've completed the installation.</p> + + +<h2 id="Eclipse">Create a Project with Eclipse</h2> + +<div class="figure" style="width:416px"> +<img src="{@docRoot}images/training/firstapp/adt-firstapp-setup.png" alt="" /> +<p class="img-caption"><strong>Figure 1.</strong> The new project wizard in Eclipse.</p> +</div> + +<ol> + <li>In Eclipse, select <strong>File > New > Project</strong>. +The resulting dialog should have a folder labeled <em>Android</em>. (If you don’t see the +<em>Android</em> folder, +then you have not installed the ADT plugin—see <a +href="{@docRoot}sdk/eclipse-adt.html#installing">Installing the ADT Plugin</a>).</li> + <li>Open the <em>Android</em> folder, select <em>Android Project</em> and click +<strong>Next</strong>.</li> + <li>Enter a project name (such as "MyFirstApp") and click <strong>Next</strong>.</li> + <li>Select a build target. This is the platform version against which you will compile your app. +<p>We recommend that you select the latest version possible. You can still build your app to +support older versions, but setting the build target to the latest version allows you to +easily optimize your app for a great user experience on the latest Android-powered devices.</p> +<p>If you don't see any built targets listed, you need to install some using the Android SDK +Manager tool. See <a href="{@docRoot}sdk/installing.html#AddingComponents">step 4 in the +installing guide</a>.</p> +<p>Click <strong>Next</strong>.</p></li> + <li>Specify other app details, such as the: + <ul> + <li><em>Application Name</em>: The app name that appears to the user. Enter "My First +App".</li> + <li><em>Package Name</em>: The package namespace for your app (following the same +rules as packages in the Java programming language). Your package name +must be unique across all packages installed on the Android system. For this reason, it's important +that you use a standard domain-style package name that’s appropriate to your company or +publisher entity. For +your first app, you can use something like "com.example.myapp." However, you cannot publish your +app using the "com.example" namespace.</li> + <li><em>Create Activity</em>: This is the class name for the primary user activity in your +app (an activity represents a single screen in your app). Enter "MyFirstActivity".</li> + <li><em>Minimum SDK</em>: Select <em>4 (Android 1.6)</em>. + <p>Because this version is lower than the build target selected for the app, a warning +appears, but that's alright. You simply need to be sure that you don't use any APIs that require an +<a href="{@docRoot}guide/appendix/api-levels.html">API level</a> greater than the minimum SDK +version without first using some code to verify the device's system version (you'll see this in some +other classes).</p> + </li> + </ul> + <p>Click <strong>Finish</strong>.</p> + </li> +</ol> + +<p>Your Android project is now set up with some default files and you’re ready to begin +building the app. Continue to the <a href="running-app.html">next lesson</a>.</p> + + + +<h2 id="CommandLine">Create a Project with Command Line Tools</h2> + +<p>If you're not using the Eclipse IDE with the ADT plugin, you can instead create your project +using the SDK tools in a command line:</p> + +<ol> + <li>Change directories into the Android SDK’s <code>tools/</code> path.</li> + <li>Execute: +<pre class="no-pretty-print">android list targets</pre> +<p>This prints a list of the available Android platforms that you’ve downloaded for your SDK. Find +the platform against which you want to compile your app. Make a note of the target id. We +recommend that you select the highest version possible. You can still build your app to +support older versions, but setting the build target to the latest version allows you to optimize +your app for the latest devices.</p> +<p>If you don't see any targets listed, you need to +install some using the Android SDK +Manager tool. See <a href="{@docRoot}sdk/installing.html#AddingComponents">step 4 in the +installing guide</a>.</p></li> + <li>Execute: +<pre class="no-pretty-print"> +android create project --target <target-id> --name MyFirstApp \ +--path <path-to-workspace>/MyFirstApp --activity MyFirstActivity \ +--package com.example.myapp +</pre> +<p>Replace <code><target-id></code> with an id from the list of targets (from the previous step) +and replace +<code><path-to-workspace></code> with the location in which you want to save your Android +projects.</p></li> +</ol> + +<p>Your Android project is now set up with several default configurations and you’re ready to begin +building the app. Continue to the <a href="running-app.html">next lesson</a>.</p> + +<p class="note"><strong>Tip:</strong> Add the <code>platform-tools/</code> as well as the +<code>tools/</code> directory to your <code>PATH</code> environment variable.</p> + + + + diff --git a/docs/html/training/basics/firstapp/index.jd b/docs/html/training/basics/firstapp/index.jd new file mode 100644 index 0000000..a95ed8e --- /dev/null +++ b/docs/html/training/basics/firstapp/index.jd @@ -0,0 +1,64 @@ +page.title=Building Your First App + +trainingnavtop=true +startpage=true +next.title=Creating an Android Project +next.link=creating-project.html + +@jd:body + +<div id="tb-wrapper"> +<div id="tb"> + +<h2>Dependencies and prerequisites</h2> + +<ul> + <li>Android 1.6 or higher</li> + <li><a href="http://developer.android.com/sdk/index.html">Android SDK</a></li> +</ul> + +</div> +</div> + +<p>Welcome to Android application development!</p> + +<p>This class teaches you how to build your first Android app. You’ll learn how to create an Android +project and run a debuggable version of the app. You'll also learn some fundamentals of Android app +design, including how to build a simple user interface and handle user input.</p> + +<p>Before you start this class, be sure that you have your development environment set up. You need +to:</p> +<ol> + <li>Download the Android SDK Starter Package.</li> + <li>Install the ADT plugin for Eclipse (if you’ll use the Eclipse IDE).</li> + <li>Download the latest SDK tools and platforms using the SDK Manager.</li> +</ol> + +<p>If you haven't already done this setup, read <a href="{@docRoot}sdk/installing.html">Installing +the SDK</a>. Once you've finished the setup, you're ready to begin this class.</p> + +<p>This class uses a tutorial format that incrementally builds a small Android app in order to teach +you some fundamental concepts about Android development, so it's important that you follow each +step.</p> + +<p><strong><a href="creating-project.html">Start the first lesson ›</a></strong></p> + + +<h2>Lessons</h2> + +<dl> + <dt><b><a href="creating-project.html">Creating an Android Project</a></b></dt> + <dd>Shows how to create a project for an Android app, which includes a set of default +app files.</dd> + + <dt><b><a href="running-app.html">Running Your Application</a></b></dt> + <dd>Shows how to run your app on an Android-powered device or the Android +emulator.</dd> + + <dt><b><a href="building-ui.html">Building a Simple User Interface</a></b></dt> + <dd>Shows how to create a new user interface using an XML file.</dd> + + <dt><b><a href="starting-activity.html">Starting Another Activity</a></b></dt> + <dd>Shows how to respond to a button press, start another activity, send it some +data, then receive the data in the subsequent activity.</dd> +</dl> diff --git a/docs/html/training/basics/firstapp/running-app.jd b/docs/html/training/basics/firstapp/running-app.jd new file mode 100644 index 0000000..2398fa0 --- /dev/null +++ b/docs/html/training/basics/firstapp/running-app.jd @@ -0,0 +1,178 @@ +page.title=Running Your App +parent.title=Building Your First App +parent.link=index.html + +trainingnavtop=true +previous.title=Creating a Project +previous.link=creating-project.html +next.title=Building a Simple User Interface +next.link=building-ui.html + +@jd:body + + +<!-- This is the training bar --> +<div id="tb-wrapper"> +<div id="tb"> + +<h2>This lesson teaches you to</h2> + +<ol> + <li><a href="#RealDevice">Run on a Real Device</a></li> + <li><a href="#Emulator">Run on the Emulator</a></li> +</ol> + +<h2>You should also read</h2> + +<ul> + <li><a href="{@docRoot}guide/developing/device.html">Using Hardware Devices</a></li> + <li><a href="{@docRoot}guide/developing/devices/index.html">Managing Virtual Devices</a></li> + <li><a href="{@docRoot}guide/developing/projects/index.html">Managing Projects</a></li> +</ul> + + +</div> +</div> + + +<p>If you followed the <a href="{@docRoot}creating-project.html">previous lesson</a> to create an +Android project, it includes a default set of "Hello World" source files that allow you to +run the app right away.</p> + +<p>How you run your app depends on two things: whether you have a real Android-powered device and +whether you’re using Eclipse. This lesson shows you how to install and run your app on a +real device and on the Android emulator, and in both cases with either Eclipse or the command line +tools.</p> + +<p>Before you run your app, you should be aware of a few directories and files in the Android +project:</p> + +<dl> + <dt><code>AndroidManifest.xml</code></dt> + <dd>This manifest file describes the fundamental characteristics of the app and defines each of +its components. You'll learn about various declarations in this file as you read more training +classes.</dd> + <dt><code>src/</code></dt> + <dd>Directory for your app's main source files. By default, it includes an {@link +android.app.Activity} class that runs when your app is launched using the app icon.</dd> + <dt><code>res/</code></dt> + <dd>Contains several sub-directories for app resources. Here are just a few: + <dl style="margin-top:1em"> + <dt><code>drawable-hdpi/</code></dt> + <dd>Directory for drawable objects (such as bitmaps) that are designed for high-density +(hdpi) screens. Other drawable directories contain assets designed for other screen densities.</dd> + <dt><code>layout/</code></dt> + <dd>Directory for files that define your app's user interface.</dd> + <dt><code>values/</code></dt> + <dd>Directory for other various XML files that contain a collection of resources, such as +string and color definitions.</dd> + </dl> + </dd> +</dl> + +<p>When you build and run the default Android project, the default {@link android.app.Activity} +class in the <code>src/</code> directory starts and loads a layout file from the +<code>layout/</code> directory, which includes a "Hello World" message. Not real exciting, but it's +important that you understand how to build and run your app before adding real functionality to +the app.</p> + + + +<h2 id="RealDevice">Run on a Real Device</h2> + +<p>Whether you’re using Eclipse or the command line, you need to:</p> + +<ol> + <li>Plug in your Android-powered device to your machine with a USB cable. +If you’re developing on Windows, you might need to install the appropriate USB driver for your +device. For help installing drivers, see the <a href=”{@docRoot}sdk/oem-usb.html”>OEM USB +Drivers</a> document.</li> + <li>Ensure that <strong>USB debugging</strong> is enabled in the device Settings (open Settings +and navitage to <strong>Applications > Development</strong> on most devices, or select +<strong>Developer options</strong> on Android 4.0 and higher).</li> +</ol> + +<p>To run the app from Eclipse, open one of your project's files and click +<strong>Run</strong> from the toolbar. Eclipse installs the app on your connected device and starts +it.</p> + + +<p>Or to run your app from a command line:</p> + +<ol> + <li>Change directories to the root of your Android project and execute: +<pre class="no-pretty-print">ant debug</pre></li> + <li>Make sure the Android SDK <code>platform-tools/</code> directory is included in your +<code>PATH</code> environment variable, then execute: +<pre class="no-pretty-print">adb install bin/MyFirstApp-debug.apk</pre></li> + <li>On your device, locate <em>MyFirstActivity</em> and open it.</li> +</ol> + +<p>To start adding stuff to the app, continue to the <a href="building-ui.html">next +lesson</a>.</p> + + + +<h2 id="Emulator">Run on the Emulator</h2> + +<p>Whether you’re using Eclipse or the command line, you need to first create an <a +href="{@docRoot}guide/developing/devices/index.html">Android Virtual +Device</a> (AVD). An AVD is a +device configuration for the Android emulator that allows you to model +different device configurations.</p> + +<div class="figure" style="width:457px"> + <img src="{@docRoot}images/screens_support/avds-config.png" alt="" /> + <p class="img-caption"><strong>Figure 1.</strong> The AVD Manager showing a few virtual +devices.</p> +</div> + +<p>To create an AVD:</p> +<ol> + <li>Launch the Android Virtual Device Manager: + <ol type="a"> + <li>In Eclipse, select <strong>Window > AVD Manager</strong>, or click the <em>AVD +Manager</em> icon in the Eclipse toolbar.</li> + <li>From the command line, change directories to <code><sdk>/tools/</code> and execute: +<pre class="no-pretty-print">./android avd</pre></li> + </ol> + </li> + <li>In the <em>Android Virtual Device Device Manager</em> panel, click <strong>New</strong>.</li> + <li>Fill in the details for the AVD. +Give it a name, a platform target, an SD card size, and a skin (HVGA is default).</li> + <li>Click <strong>Create AVD</strong>.</li> + <li>Select the new AVD from the <em>Android Virtual Device Manager</em> and click +<strong>Start</strong>.</li> + <li>After the emulator boots up, unlock the emulator screen.</li> +</ol> + +<p>To run the app from Eclipse, open one of your project's files and click +<strong>Run</strong> from the toolbar. Eclipse installs the app on your AVD and starts it.</p> + + +<p>Or to run your app from the command line:</p> + +<ol> + <li>Change directories to the root of your Android project and execute: +<pre class="no-pretty-print">ant debug</pre></li> + <li>Make sure the Android SDK <code>platform-tools/</code> directory is included in your +<code>PATH</code> environment +variable, then execute: +<pre class="no-pretty-print">adb install bin/MyFirstApp-debug.apk</pre></li> + <li>On the emulator, locate <em>MyFirstActivity</em> and open it.</li> +</ol> + + +<p>To start adding stuff to the app, continue to the <a href="building-ui.html">next +lesson</a>.</p> + + + + + + + + + + + diff --git a/docs/html/training/basics/firstapp/starting-activity.jd b/docs/html/training/basics/firstapp/starting-activity.jd new file mode 100644 index 0000000..16a6fd8 --- /dev/null +++ b/docs/html/training/basics/firstapp/starting-activity.jd @@ -0,0 +1,308 @@ +page.title=Starting Another Activity +parent.title=Building Your First App +parent.link=index.html + +trainingnavtop=true +previous.title=Building a Simpler User Interface +previous.link=building-ui.html + +@jd:body + + +<!-- This is the training bar --> +<div id="tb-wrapper"> +<div id="tb"> + +<h2>This lesson teaches you to</h2> + +<ol> + <li><a href="#RespondToButton">Respond to the Send Button</a></li> + <li><a href="#BuildIntent">Build an Intent</a></li> + <li><a href="#StartActivity">Start the Second Activity</a></li> + <li><a href="#CreateActivity">Create the Second Activity</a> + <ol> + <li><a href="#AddToManifest">Add it to the manifest</a></li> + </ol> + </li> + <li><a href="#ReceiveIntent">Receive the Intent</a></li> + <li><a href="#DisplayMessage">Display the Message</a></li> +</ol> + +<h2>You should also read</h2> + +<ul> + <li><a href="{@docRoot}sdk/installing.html">Installing the +SDK</a></li> +</ul> + + +</div> +</div> + + + +<p>After completing the <a href="building-ui.html">previous lesson</a>, you have an app that +shows an activity (a single screen) with a text box and a button. In this lesson, you’ll add some +code to <code>MyFirstActivity</code> that +starts a new activity when the user selects the Send button.</p> + + +<h2 id="RespondToButton">Respond to the Send Button</h2> + +<p>To respond to the button's on-click event, open the <code>main.xml</code> layout file and add the +<a +href="{@docRoot}reference/android/view/View.html#attr_android:onClick">{@code android:onClick}</a> +attribute to the {@link android.widget.Button <Button>} element:</p> + +<pre> +<Button android:id="@+id/button_send" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/button_send" + android:onClick="sendMessage" /> +</pre> + +<p>The <a +href="{@docRoot}reference/android/view/View.html#attr_android:onClick">{@code +android:onClick}</a> attribute’s value, <code>sendMessage</code>, is the name of a method in your +activity that you want to call when the user selects the button.</p> + +<p>Add the corresponding method inside the <code>MyFirstActivity</code> class:</p> + +<pre> +/** Called when the user selects the Send button */ +public void sendMessage(View view) { + // Do something in response to button +} +</pre> + +<p class="note"><strong>Tip:</strong> In Eclipse, press Ctrl + Shift + O to import missing classes +(Cmd + Shift + O on Mac).</p> + +<p>Note that, in order for the system to match this method to the method name given to <a +href="{@docRoot}reference/android/view/View.html#attr_android:onClick">{@code android:onClick}</a>, +the signature must be exactly as shown. Specifically, the method must:</p> + +<ul> +<li>Be public</li> +<li>Have a void return value</li> +<li>Have a {@link android.view.View} as the only parameter (this will be the {@link +android.view.View} that was clicked)</li> +</ul> + +<p>Next, you’ll fill in this method to read the contents of the text box and deliver that text to +another activity.</p> + + + +<h2 id="BuildIntent">Build an Intent</h2> + +<p>An {@link android.content.Intent} is an object that provides runtime binding between separate +components (such as two activities). The {@link android.content.Intent} represents an +app’s "intent to do something." You can use an {@link android.content.Intent} for a wide +variety of tasks, but most often they’re used to start another activity.</p> + +<p>Inside the {@code sendMessage()} method, create an {@link android.content.Intent} to start +an activity called {@code DisplayMessageActvity}:</p> + +<pre> +Intent intent = new Intent(this, DisplayMessageActivity.class); +</pre> + +<p>The constructor used here takes two parameters:</p> +<ul> + <li>A {@link +android.content.Context} as its first parameter ({@code this} is used because the {@link +android.app.Activity} class is a subclass of {@link android.content.Context}) + <li>The {@link java.lang.Class} of the app component to which the system should deliver +the {@link android.content.Intent} (in this case, the activity that should be started) +</ul> + +<div class="sidebox-wrapper"> +<div class="sidebox"> + <h3>Sending an intent to other apps</h3> + <p>The intent created in this lesson is what's considered an <em>explicit intent</em>, because the +{@link android.content.Intent} +specifies the exact app component to which the intent should be given. However, intents +can also be <em>implicit</em>, in which case the {@link android.content.Intent} does not specify +the desired component, but allows any app installed on the device to respond to the intent +as long as it satisfies the meta-data specifications for the action that's specified in various +{@link android.content.Intent} parameters. For more informations, see the class about <a +href="{@docRoot}training/intents/index.html">Interacting with Other Apps</a>.</p> +</div> +</div> + +<p class="note"><strong>Note:</strong> The reference to {@code DisplayMessageActivity} +will raise an error if you’re using an IDE such as Eclipse because the class doesn’t exist yet. +Ignore the error for now; you’ll create the class soon.</p> + +<p>An intent not only allows you to start another activity, but can carry a bundle of data to the +activity as well. So, use {@link android.app.Activity#findViewById findViewById()} to get the +{@link android.widget.EditText} element and add its message to the intent:</p> + +<pre> +Intent intent = new Intent(this, DisplayMessageActivity.class); +EditText editText = (EditText) findViewById(R.id.edit_message); +String message = editText.getText().toString(); +intent.putExtra(EXTRA_MESSAGE, message); +</pre> + +<p>An {@link android.content.Intent} can carry a collection of various data types as key-value +pairs called <em>extras</em>. The {@link android.content.Intent#putExtra putExtra()} method takes a +string as the key and the value in the second parameter.</p> + +<p>In order for the next activity to query the extra data, you should define your keys using a +public constant. So add the {@code EXTRA_MESSAGE} definition to the top of the {@code +MyFirstActivity} class:</p> + +<pre> +public class MyFirstActivity extends Activity { + public final static String EXTRA_MESSAGE = "com.example.myapp.MESSAGE"; + ... +} +</pre> + +<p>It's generally a good practice to define keys for extras with your app's package name as a prefix +to ensure it's unique, in case your app interacts with other apps.</p> + + +<h2 id="StartActivity">Start the Second Activity</h2> + +<p>To start an activity, you simply need to call {@link android.app.Activity#startActivity +startActivity()} and pass it your {@link android.content.Intent}.</p> + +<p>The system receives this call and starts an instance of the {@link android.app.Activity} +specified by the {@link android.content.Intent}.</p> + +<p>With this method included, the complete {@code sendMessage()} method that's invoked by the Send +button now looks like this:</p> + +<pre> +/** Called when the user selects the Send button */ +public void sendMessage(View view) { + Intent intent = new Intent(this, DisplayMessageActivity.class); + EditText editText = (EditText) findViewById(R.id.edit_message); + String message = editText.getText().toString(); + intent.putExtra(EXTRA_MESSAGE, message); + startActivity(intent); +} +</pre> + +<p>Now you need to create the {@code DisplayMessageActivity} class in order for this to +work.</p> + + + +<h2 id="CreateActivity">Create the Second Activity</h2> + +<p>In your project, create a new class file under the <code>src/<package-name>/</code> +directory called <code>DisplayMessageActivity.java</code>.</p> + +<p class="note"><strong>Tip:</strong> In Eclipse, right-click the package name under the +<code>src/</code> directory and select <strong>New > Class</strong>. +Enter "DisplayMessageActivity" for the name and {@code android.app.Activity} for the superclass.</p> + +<p>Inside the class, add the {@link android.app.Activity#onCreate onCreate()} callback method:</p> + +<pre> +public class DisplayMessageActivity extends Activity { + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } +} +</pre> + +<p>All subclasses of {@link android.app.Activity} must implement the {@link +android.app.Activity#onCreate onCreate()} method. The system calls this when creating a new +instance of the activity. It is where you must define the activity layout and where you should +initialize essential activity components.</p> + + + +<h3 id="AddToManifest">Add it to the manifest</h3> + +<p>You must declare all activities in your manifest file, <code>AndroidManifest.xml</code>, using an +<a +href="{@docRoot}guide/topics/manifest/activity-element.html">{@code <activity>}</a> element.</p> + +<p>Because {@code DisplayMessageActivity} is invoked using an explicit intent, it does not require +any intent filters (such as those you can see in the manifest for <code>MyFirstActivity</code>). So +the declaration for <code>DisplayMessageActivity</code> can be simply one line of code inside the <a +href="{@docRoot}guide/topics/manifest/application-element.html">{@code <application>}</a> +element:</p> + +<pre> +<application ... > + <activity android:name="com.example.myapp.DisplayMessageActivity" /> + ... +</application> +</pre> + +<p>The app is now runnable because the {@link android.content.Intent} in the +first activity now resolves to the {@code DisplayMessageActivity} class. If you run the app now, +pressing the Send button starts the +second activity, but it doesn't show anything yet.</p> + + +<h2 id="ReceiveIntent">Receive the Intent</h2> + +<p>Every {@link android.app.Activity} is invoked by an {@link android.content.Intent}, regardless of +how the user navigated there. You can get the {@link android.content.Intent} that started your +activity by calling {@link android.app.Activity#getIntent()} and the retrieve data contained +within it.</p> + +<p>In the {@code DisplayMessageActivity} class’s {@link android.app.Activity#onCreate onCreate()} +method, get the intent and extract the message delivered by {@code MyFirstActivity}:</p> + +<pre> +Intent intent = getIntent(); +String message = intent.getStringExtra(MyFirstActivity.EXTRA_MESSAGE); +</pre> + + + +<h2 id="DisplayMessage">Display the Message</h2> + +<p>To show the message on the screen, create a {@link android.widget.TextView} widget and set the +text using {@link android.widget.TextView#setText setText()}. Then add the {@link +android.widget.TextView} as the root view of the activity’s layout by passing it to {@link +android.app.Activity#setContentView setContentView()}.</p> + +<p>The complete {@link android.app.Activity#onCreate onCreate()} method for {@code +DisplayMessageActivity} now looks like this:</p> + +<pre> +@Override +public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Get the message from the intent + Intent intent = getIntent(); + String message = intent.getStringExtra(MyFirstActivity.EXTRA_MESSAGE); + + // Create the text view + TextView textView = new TextView(this); + textView.setTextSize(40); + textView.setText(message); + + setContentView(textView); +} +</pre> + +<p>You can now run the app, type a message in the text box, press Send, and view the message on the +second activity.</p> + +<img src="{@docRoot}images/training/firstapp/firstapp.png" /> +<p class="img-caption"><strong>Figure 1.</strong> Both activities in the final app, running +on Android 4.0. + +<p>That's it, you've built your first Android app!</p> + +<p>To learn more about building Android apps, continue to follow the +basic training classes. The next class is <a +href="{@docRoot}training/activity-lifecycle/index.html">Managing the Activity Lifecycle</a>.</p> + + + + diff --git a/docs/html/training/displaying-bitmaps/cache-bitmap.jd b/docs/html/training/displaying-bitmaps/cache-bitmap.jd new file mode 100644 index 0000000..94abe21 --- /dev/null +++ b/docs/html/training/displaying-bitmaps/cache-bitmap.jd @@ -0,0 +1,337 @@ +page.title=Caching Bitmaps +parent.title=Displaying Bitmaps Efficiently +parent.link=index.html + +trainingnavtop=true +next.title=Displaying Bitmaps in Your UI +next.link=display-bitmap.html +previous.title=Processing Bitmaps Off the UI Thread +previous.link=process-bitmap.html + +@jd:body + +<div id="tb-wrapper"> +<div id="tb"> + +<h2>This lesson teaches you to</h2> +<ol> + <li><a href="#memory-cache">Use a Memory Cache</a></li> + <li><a href="#disk-cache">Use a Disk Cache</a></li> + <li><a href="#config-changes">Handle Configuration Changes</a></li> +</ol> + +<h2>You should also read</h2> +<ul> + <li><a href="{@docRoot}guide/topics/resources/runtime-changes.html">Handling Runtime Changes</a></li> +</ul> + +<h2>Try it out</h2> + +<div class="download-box"> + <a href="{@docRoot}shareables/training/BitmapFun.zip" class="button">Download the sample</a> + <p class="filename">BitmapFun.zip</p> +</div> + +</div> +</div> + +<p>Loading a single bitmap into your user interface (UI) is straightforward, however things get more +complicated if you need to load a larger set of images at once. In many cases (such as with +components like {@link android.widget.ListView}, {@link android.widget.GridView} or {@link +android.support.v4.view.ViewPager }), the total number of images on-screen combined with images that +might soon scroll onto the screen are essentially unlimited.</p> + +<p>Memory usage is kept down with components like this by recycling the child views as they move +off-screen. The garbage collector also frees up your loaded bitmaps, assuming you don't keep any +long lived references. This is all good and well, but in order to keep a fluid and fast-loading UI +you want to avoid continually processing these images each time they come back on-screen. A memory +and disk cache can often help here, allowing components to quickly reload processed images.</p> + +<p>This lesson walks you through using a memory and disk bitmap cache to improve the responsiveness +and fluidity of your UI when loading multiple bitmaps.</p> + +<h2 id="memory-cache">Use a Memory Cache</h2> + +<p>A memory cache offers fast access to bitmaps at the cost of taking up valuable application +memory. The {@link android.util.LruCache} class (also available in the <a +href="{@docRoot}reference/android/support/v4/util/LruCache.html">Support Library</a> for use back +to API Level 4) is particularly well suited to the task of caching bitmaps, keeping recently +referenced objects in a strong referenced {@link java.util.LinkedHashMap} and evicting the least +recently used member before the cache exceeds its designated size.</p> + +<p class="note"><strong>Note:</strong> In the past, a popular memory cache implementation was a +{@link java.lang.ref.SoftReference} or {@link java.lang.ref.WeakReference} bitmap cache, however +this is not recommended. Starting from Android 2.3 (API Level 9) the garbage collector is more +aggressive with collecting soft/weak references which makes them fairly ineffective. In addition, +prior to Android 3.0 (API Level 11), the backing data of a bitmap was stored in native memory which +is not released in a predictable manner, potentially causing an application to briefly exceed its +memory limits and crash.</p> + +<p>In order to choose a suitable size for a {@link android.util.LruCache}, a number of factors +should be taken into consideration, for example:</p> + +<ul> + <li>How memory intensive is the rest of your activity and/or application?</li> + <li>How many images will be on-screen at once? How many need to be available ready to come + on-screen?</li> + <li>What is the screen size and density of the device? An extra high density screen (xhdpi) device + like <a href="http://www.android.com/devices/detail/galaxy-nexus">Galaxy Nexus</a> will need a + larger cache to hold the same number of images in memory compared to a device like <a + href="http://www.android.com/devices/detail/nexus-s">Nexus S</a> (hdpi).</li> + <li>What dimensions and configuration are the bitmaps and therefore how much memory will each take + up?</li> + <li>How frequently will the images be accessed? Will some be accessed more frequently than others? + If so, perhaps you may want to keep certain items always in memory or even have multiple {@link + android.util.LruCache} objects for different groups of bitmaps.</li> + <li>Can you balance quality against quantity? Sometimes it can be more useful to store a larger + number of lower quality bitmaps, potentially loading a higher quality version in another + background task.</li> +</ul> + +<p>There is no specific size or formula that suits all applications, it's up to you to analyze your +usage and come up with a suitable solution. A cache that is too small causes additional overhead with +no benefit, a cache that is too large can once again cause {@code java.lang.OutOfMemory} exceptions +and leave the rest of your app little memory to work with.</p> + +<p>Here’s an example of setting up a {@link android.util.LruCache} for bitmaps:</p> + +<pre> +private LruCache<String, Bitmap> mMemoryCache; + +@Override +protected void onCreate(Bundle savedInstanceState) { + ... + // Get memory class of this device, exceeding this amount will throw an + // OutOfMemory exception. + final int memClass = ((ActivityManager) context.getSystemService( + Context.ACTIVITY_SERVICE)).getMemoryClass(); + + // Use 1/8th of the available memory for this memory cache. + final int cacheSize = 1024 * 1024 * memClass / 8; + + mMemoryCache = new LruCache<String, Bitmap>(cacheSize) { + @Override + protected int sizeOf(String key, Bitmap bitmap) { + // The cache size will be measured in bytes rather than number of items. + return bitmap.getByteCount(); + } + }; + ... +} + +public void addBitmapToMemoryCache(String key, Bitmap bitmap) { + if (getBitmapFromMemCache(key) == null) { + mMemoryCache.put(key, bitmap); + } +} + +public Bitmap getBitmapFromMemCache(String key) { + return mMemoryCache.get(key); +} +</pre> + +<p class="note"><strong>Note:</strong> In this example, one eighth of the application memory is +allocated for our cache. On a normal/hdpi device this is a minimum of around 4MB (32/8). A full +screen {@link android.widget.GridView} filled with images on a device with 800x480 resolution would +use around 1.5MB (800*480*4 bytes), so this would cache a minimum of around 2.5 pages of images in +memory.</p> + +<p>When loading a bitmap into an {@link android.widget.ImageView}, the {@link android.util.LruCache} +is checked first. If an entry is found, it is used immediately to update the {@link +android.widget.ImageView}, otherwise a background thread is spawned to process the image:</p> + +<pre> +public void loadBitmap(int resId, ImageView imageView) { + final String imageKey = String.valueOf(resId); + + final Bitmap bitmap = getBitmapFromMemCache(imageKey); + if (bitmap != null) { + mImageView.setImageBitmap(bitmap); + } else { + mImageView.setImageResource(R.drawable.image_placeholder); + BitmapWorkerTask task = new BitmapWorkerTask(mImageView); + task.execute(resId); + } +} +</pre> + +<p>The <a href="process-bitmap.html#BitmapWorkerTask">{@code BitmapWorkerTask}</a> also needs to be +updated to add entries to the memory cache:</p> + +<pre> +class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> { + ... + // Decode image in background. + @Override + protected Bitmap doInBackground(Integer... params) { + final Bitmap bitmap = decodeSampledBitmapFromResource( + getResources(), params[0], 100, 100)); + addBitmapToMemoryCache(String.valueOf(params[0]), bitmap); + return bitmap; + } + ... +} +</pre> + +<h2 id="disk-cache">Use a Disk Cache</h2> + +<p>A memory cache is useful in speeding up access to recently viewed bitmaps, however you cannot +rely on images being available in this cache. Components like {@link android.widget.GridView} with +larger datasets can easily fill up a memory cache. Your application could be interrupted by another +task like a phone call, and while in the background it might be killed and the memory cache +destroyed. Once the user resumes, your application it has to process each image again.</p> + +<p>A disk cache can be used in these cases to persist processed bitmaps and help decrease loading +times where images are no longer available in a memory cache. Of course, fetching images from disk +is slower than loading from memory and should be done in a background thread, as disk read times can +be unpredictable.</p> + +<p class="note"><strong>Note:</strong> A {@link android.content.ContentProvider} might be a more +appropriate place to store cached images if they are accessed more frequently, for example in an +image gallery application.</p> + +<p>Included in the sample code of this class is a basic {@code DiskLruCache} implementation. +However, a more robust and recommended {@code DiskLruCache} solution is included in the Android 4.0 +source code ({@code libcore/luni/src/main/java/libcore/io/DiskLruCache.java}). Back-porting this +class for use on previous Android releases should be fairly straightforward (a <a +href="http://www.google.com/search?q=disklrucache">quick search</a> shows others who have already +implemented this solution).</p> + +<p>Here’s updated example code that uses the simple {@code DiskLruCache} included in the sample +application of this class:</p> + +<pre> +private DiskLruCache mDiskCache; +private static final int DISK_CACHE_SIZE = 1024 * 1024 * 10; // 10MB +private static final String DISK_CACHE_SUBDIR = "thumbnails"; + +@Override +protected void onCreate(Bundle savedInstanceState) { + ... + // Initialize memory cache + ... + File cacheDir = getCacheDir(this, DISK_CACHE_SUBDIR); + mDiskCache = DiskLruCache.openCache(this, cacheDir, DISK_CACHE_SIZE); + ... +} + +class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> { + ... + // Decode image in background. + @Override + protected Bitmap doInBackground(Integer... params) { + final String imageKey = String.valueOf(params[0]); + + // Check disk cache in background thread + Bitmap bitmap = getBitmapFromDiskCache(imageKey); + + if (bitmap == null) { // Not found in disk cache + // Process as normal + final Bitmap bitmap = decodeSampledBitmapFromResource( + getResources(), params[0], 100, 100)); + } + + // Add final bitmap to caches + addBitmapToCache(String.valueOf(imageKey, bitmap); + + return bitmap; + } + ... +} + +public void addBitmapToCache(String key, Bitmap bitmap) { + // Add to memory cache as before + if (getBitmapFromMemCache(key) == null) { + mMemoryCache.put(key, bitmap); + } + + // Also add to disk cache + if (!mDiskCache.containsKey(key)) { + mDiskCache.put(key, bitmap); + } +} + +public Bitmap getBitmapFromDiskCache(String key) { + return mDiskCache.get(key); +} + +// Creates a unique subdirectory of the designated app cache directory. Tries to use external +// but if not mounted, falls back on internal storage. +public static File getCacheDir(Context context, String uniqueName) { + // Check if media is mounted or storage is built-in, if so, try and use external cache dir + // otherwise use internal cache dir + final String cachePath = Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED + || !Environment.isExternalStorageRemovable() ? + context.getExternalCacheDir().getPath() : context.getCacheDir().getPath(); + + return new File(cachePath + File.separator + uniqueName); +} +</pre> + +<p>While the memory cache is checked in the UI thread, the disk cache is checked in the background +thread. Disk operations should never take place on the UI thread. When image processing is +complete, the final bitmap is added to both the memory and disk cache for future use.</p> + +<h2 id="config-changes">Handle Configuration Changes</h2> + +<p>Runtime configuration changes, such as a screen orientation change, cause Android to destroy and +restart the running activity with the new configuration (For more information about this behavior, +see <a href="{@docRoot}guide/topics/resources/runtime-changes.html">Handling Runtime Changes</a>). +You want to avoid having to process all your images again so the user has a smooth and fast +experience when a configuration change occurs.</p> + +<p>Luckily, you have a nice memory cache of bitmaps that you built in the <a +href="#memory-cache">Use a Memory Cache</a> section. This cache can be passed through to the new +activity instance using a {@link android.app.Fragment} which is preserved by calling {@link +android.app.Fragment#setRetainInstance setRetainInstance(true)}). After the activity has been +recreated, this retained {@link android.app.Fragment} is reattached and you gain access to the +existing cache object, allowing images to be quickly fetched and re-populated into the {@link +android.widget.ImageView} objects.</p> + +<p>Here’s an example of retaining a {@link android.util.LruCache} object across configuration +changes using a {@link android.app.Fragment}:</p> + +<pre> +private LruCache<String, Bitmap> mMemoryCache; + +@Override +protected void onCreate(Bundle savedInstanceState) { + ... + RetainFragment mRetainFragment = + RetainFragment.findOrCreateRetainFragment(getFragmentManager()); + mMemoryCache = RetainFragment.mRetainedCache; + if (mMemoryCache == null) { + mMemoryCache = new LruCache<String, Bitmap>(cacheSize) { + ... // Initialize cache here as usual + } + mRetainFragment.mRetainedCache = mMemoryCache; + } + ... +} + +class RetainFragment extends Fragment { + private static final String TAG = "RetainFragment"; + public LruCache<String, Bitmap> mRetainedCache; + + public RetainFragment() {} + + public static RetainFragment findOrCreateRetainFragment(FragmentManager fm) { + RetainFragment fragment = (RetainFragment) fm.findFragmentByTag(TAG); + if (fragment == null) { + fragment = new RetainFragment(); + } + return fragment; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + <strong>setRetainInstance(true);</strong> + } +} +</pre> + +<p>To test this out, try rotating a device both with and without retaining the {@link +android.app.Fragment}. You should notice little to no lag as the images populate the activity almost +instantly from memory when you retain the cache. Any images not found in the memory cache are +hopefully available in the disk cache, if not, they are processed as usual.</p> diff --git a/docs/html/training/displaying-bitmaps/display-bitmap.jd b/docs/html/training/displaying-bitmaps/display-bitmap.jd new file mode 100644 index 0000000..7a93313 --- /dev/null +++ b/docs/html/training/displaying-bitmaps/display-bitmap.jd @@ -0,0 +1,400 @@ +page.title=Displaying Bitmaps in Your UI +parent.title=Displaying Bitmaps Efficiently +parent.link=index.html + +trainingnavtop=true +previous.title=Caching Bitmaps +previous.link=cache-bitmap.html + +@jd:body + +<div id="tb-wrapper"> +<div id="tb"> + +<h2>This lesson teaches you to</h2> +<ol> + <li><a href="#viewpager">Load Bitmaps into a ViewPager Implementation</a></li> + <li><a href="#gridview">Load Bitmaps into a GridView Implementation</a></li> +</ol> + +<h2>You should also read</h2> +<ul> + <li><a href="{@docRoot}design/patterns/swipe-views.html">Android Design: Swipe Views</a></li> + <li><a href="{@docRoot}design/building-blocks/grid-lists.html">Android Design: Grid Lists</a></li> +</ul> + +<h2>Try it out</h2> + +<div class="download-box"> + <a href="{@docRoot}shareables/training/BitmapFun.zip" class="button">Download the sample</a> + <p class="filename">BitmapFun.zip</p> +</div> + +</div> +</div> + +<p></p> + +<p>This lesson brings together everything from previous lessons, showing you how to load multiple +bitmaps into {@link android.support.v4.view.ViewPager} and {@link android.widget.GridView} +components using a background thread and bitmap cache, while dealing with concurrency and +configuration changes.</p> + +<h2 id="viewpager">Load Bitmaps into a ViewPager Implementation</h2> + +<p>The <a href="{@docRoot}design/patterns/swipe-views.html">swipe view pattern</a> is an excellent +way to navigate the detail view of an image gallery. You can implement this pattern using a {@link +android.support.v4.view.ViewPager} component backed by a {@link +android.support.v4.view.PagerAdapter}. However, a more suitable backing adapter is the subclass +{@link android.support.v4.app.FragmentStatePagerAdapter} which automatically destroys and saves +state of the {@link android.app.Fragment Fragments} in the {@link android.support.v4.view.ViewPager} +as they disappear off-screen, keeping memory usage down.</p> + +<p class="note"><strong>Note:</strong> If you have a smaller number of images and are confident they +all fit within the application memory limit, then using a regular {@link +android.support.v4.view.PagerAdapter} or {@link android.support.v4.app.FragmentPagerAdapter} might +be more appropriate.</p> + +<p>Here’s an implementation of a {@link android.support.v4.view.ViewPager} with {@link +android.widget.ImageView} children. The main activity holds the {@link +android.support.v4.view.ViewPager} and the adapter:</p> + +<pre> +public class ImageDetailActivity extends FragmentActivity { + public static final String EXTRA_IMAGE = "extra_image"; + + private ImagePagerAdapter mAdapter; + private ViewPager mPager; + + // A static dataset to back the ViewPager adapter + public final static Integer[] imageResIds = new Integer[] { + R.drawable.sample_image_1, R.drawable.sample_image_2, R.drawable.sample_image_3, + R.drawable.sample_image_4, R.drawable.sample_image_5, R.drawable.sample_image_6, + R.drawable.sample_image_7, R.drawable.sample_image_8, R.drawable.sample_image_9}; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.image_detail_pager); // Contains just a ViewPager + + mAdapter = new ImagePagerAdapter(getSupportFragmentManager(), imageResIds.length); + mPager = (ViewPager) findViewById(R.id.pager); + mPager.setAdapter(mAdapter); + } + + public static class ImagePagerAdapter extends FragmentStatePagerAdapter { + private final int mSize; + + public ImagePagerAdapter(FragmentManager fm, int size) { + super(fm); + mSize = size; + } + + @Override + public int getCount() { + return mSize; + } + + @Override + public Fragment getItem(int position) { + return ImageDetailFragment.newInstance(position); + } + } +} +</pre> + +<p>The details {@link android.app.Fragment} holds the {@link android.widget.ImageView} children:</p> + +<pre> +public class ImageDetailFragment extends Fragment { + private static final String IMAGE_DATA_EXTRA = "resId"; + private int mImageNum; + private ImageView mImageView; + + static ImageDetailFragment newInstance(int imageNum) { + final ImageDetailFragment f = new ImageDetailFragment(); + final Bundle args = new Bundle(); + args.putInt(IMAGE_DATA_EXTRA, imageNum); + f.setArguments(args); + return f; + } + + // Empty constructor, required as per Fragment docs + public ImageDetailFragment() {} + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mImageNum = getArguments() != null ? getArguments().getInt(IMAGE_DATA_EXTRA) : -1; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + // image_detail_fragment.xml contains just an ImageView + final View v = inflater.inflate(R.layout.image_detail_fragment, container, false); + mImageView = (ImageView) v.findViewById(R.id.imageView); + return v; + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + final int resId = ImageDetailActivity.imageResIds[mImageNum]; + <strong>mImageView.setImageResource(resId);</strong> // Load image into ImageView + } +} +</pre> + +<p>Hopefully you noticed the issue with this implementation; The images are being read from +resources on the UI thread which can lead to an application hanging and being force closed. Using an +{@link android.os.AsyncTask} as described in the <a href="process-bitmap.html">Processing Bitmaps Off +the UI Thread</a> lesson, it’s straightforward to move image loading and processing to a background +thread:</p> + +<pre> +public class ImageDetailActivity extends FragmentActivity { + ... + + public void loadBitmap(int resId, ImageView imageView) { + mImageView.setImageResource(R.drawable.image_placeholder); + BitmapWorkerTask task = new BitmapWorkerTask(mImageView); + task.execute(resId); + } + + ... // include <a href="process-bitmap.html#BitmapWorkerTask">{@code BitmapWorkerTask}</a> class +} + +public class ImageDetailFragment extends Fragment { + ... + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + if (ImageDetailActivity.class.isInstance(getActivity())) { + final int resId = ImageDetailActivity.imageResIds[mImageNum]; + // Call out to ImageDetailActivity to load the bitmap in a background thread + ((ImageDetailActivity) getActivity()).loadBitmap(resId, mImageView); + } + } +} +</pre> + +<p>Any additional processing (such as resizing or fetching images from the network) can take place +in the <a href="process-bitmap.html#BitmapWorkerTask">{@code BitmapWorkerTask}</a> without affecting +responsiveness of the main UI. If the background thread is doing more than just loading an image +directly from disk, it can also be beneficial to add a memory and/or disk cache as described in the +lesson <a href="cache-bitmap.html#memory-cache">Caching Bitmaps</a>. Here's the additional +modifications for a memory cache:</p> + +<pre> +public class ImageDetailActivity extends FragmentActivity { + ... + private LruCache<String, Bitmap> mMemoryCache; + + @Override + public void onCreate(Bundle savedInstanceState) { + ... + // initialize LruCache as per <a href="cache-bitmap.html#memory-cache">Use a Memory Cache</a> section + } + + public void loadBitmap(int resId, ImageView imageView) { + final String imageKey = String.valueOf(resId); + + final Bitmap bitmap = mMemoryCache.get(imageKey); + if (bitmap != null) { + mImageView.setImageBitmap(bitmap); + } else { + mImageView.setImageResource(R.drawable.image_placeholder); + BitmapWorkerTask task = new BitmapWorkerTask(mImageView); + task.execute(resId); + } + } + + ... // include updated BitmapWorkerTask from <a href="cache-bitmap.html#memory-cache">Use a Memory Cache</a> section +} +</pre> + +<p>Putting all these pieces together gives you a responsive {@link +android.support.v4.view.ViewPager} implementation with minimal image loading latency and the ability +to do as much or as little background processing on your images as needed.</p> + +<h2 id="gridview">Load Bitmaps into a GridView Implementation</h2> + +<p>The <a href="{@docRoot}design/building-blocks/grid-lists.html">grid list building block</a> is +useful for showing image data sets and can be implemented using a {@link android.widget.GridView} +component in which many images can be on-screen at any one time and many more need to be ready to +appear if the user scrolls up or down. When implementing this type of control, you must ensure the +UI remains fluid, memory usage remains under control and concurrency is handled correctly (due to +the way {@link android.widget.GridView} recycles its children views).</p> + +<p>To start with, here is a standard {@link android.widget.GridView} implementation with {@link +android.widget.ImageView} children placed inside a {@link android.app.Fragment}:</p> + +<pre> +public class ImageGridFragment extends Fragment implements AdapterView.OnItemClickListener { + private ImageAdapter mAdapter; + + // A static dataset to back the GridView adapter + public final static Integer[] imageResIds = new Integer[] { + R.drawable.sample_image_1, R.drawable.sample_image_2, R.drawable.sample_image_3, + R.drawable.sample_image_4, R.drawable.sample_image_5, R.drawable.sample_image_6, + R.drawable.sample_image_7, R.drawable.sample_image_8, R.drawable.sample_image_9}; + + // Empty constructor as per Fragment docs + public ImageGridFragment() {} + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mAdapter = new ImageAdapter(getActivity()); + } + + @Override + public View onCreateView( + LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + final View v = inflater.inflate(R.layout.image_grid_fragment, container, false); + final GridView mGridView = (GridView) v.findViewById(R.id.gridView); + mGridView.setAdapter(mAdapter); + mGridView.setOnItemClickListener(this); + return v; + } + + @Override + public void onItemClick(AdapterView<?> parent, View v, int position, long id) { + final Intent i = new Intent(getActivity(), ImageDetailActivity.class); + i.putExtra(ImageDetailActivity.EXTRA_IMAGE, position); + startActivity(i); + } + + private class ImageAdapter extends BaseAdapter { + private final Context mContext; + + public ImageAdapter(Context context) { + super(); + mContext = context; + } + + @Override + public int getCount() { + return imageResIds.length; + } + + @Override + public Object getItem(int position) { + return imageResIds[position]; + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public View getView(int position, View convertView, ViewGroup container) { + ImageView imageView; + if (convertView == null) { // if it's not recycled, initialize some attributes + imageView = new ImageView(mContext); + imageView.setScaleType(ImageView.ScaleType.CENTER_CROP); + imageView.setLayoutParams(new GridView.LayoutParams( + LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); + } else { + imageView = (ImageView) convertView; + } + <strong>imageView.setImageResource(imageResIds[position]);</strong> // Load image into ImageView + return imageView; + } + } +} +</pre> + +<p>Once again, the problem with this implementation is that the image is being set in the UI thread. +While this may work for small, simple images (due to system resource loading and caching), if any +additional processing needs to be done, your UI grinds to a halt.</p> + +<p>The same asynchronous processing and caching methods from the previous section can be implemented +here. However, you also need to wary of concurrency issues as the {@link android.widget.GridView} +recycles its children views. To handle this, use the techniques discussed in the <a +href="process-bitmap#concurrency">Processing Bitmaps Off the UI Thread</a> lesson. Here is the updated +solution:</p> + +<pre> +public class ImageGridFragment extends Fragment implements AdapterView.OnItemClickListener { + ... + + private class ImageAdapter extends BaseAdapter { + ... + + @Override + public View getView(int position, View convertView, ViewGroup container) { + ... + <strong>loadBitmap(imageResIds[position], imageView)</strong> + return imageView; + } + } + + public void loadBitmap(int resId, ImageView imageView) { + if (cancelPotentialWork(resId, imageView)) { + final BitmapWorkerTask task = new BitmapWorkerTask(imageView); + final AsyncDrawable asyncDrawable = + new AsyncDrawable(getResources(), mPlaceHolderBitmap, task); + imageView.setImageDrawable(asyncDrawable); + task.execute(resId); + } + } + + static class AsyncDrawable extends BitmapDrawable { + private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference; + + public AsyncDrawable(Resources res, Bitmap bitmap, + BitmapWorkerTask bitmapWorkerTask) { + super(res, bitmap); + bitmapWorkerTaskReference = + new WeakReference<BitmapWorkerTask>(bitmapWorkerTask); + } + + public BitmapWorkerTask getBitmapWorkerTask() { + return bitmapWorkerTaskReference.get(); + } + } + + public static boolean cancelPotentialWork(int data, ImageView imageView) { + final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView); + + if (bitmapWorkerTask != null) { + final int bitmapData = bitmapWorkerTask.data; + if (bitmapData != data) { + // Cancel previous task + bitmapWorkerTask.cancel(true); + } else { + // The same work is already in progress + return false; + } + } + // No task associated with the ImageView, or an existing task was cancelled + return true; + } + + private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) { + if (imageView != null) { + final Drawable drawable = imageView.getDrawable(); + if (drawable instanceof AsyncDrawable) { + final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable; + return asyncDrawable.getBitmapWorkerTask(); + } + } + return null; + } + + ... // include updated <a href="process-bitmap.html#BitmapWorkerTaskUpdated">{@code BitmapWorkerTask}</a> class +</pre> + +<p class="note"><strong>Note:</strong> The same code can easily be adapted to work with {@link +android.widget.ListView} as well.</p> + +<p>This implementation allows for flexibility in how the images are processed and loaded without +impeding the smoothness of the UI. In the background task you can load images from the network or +resize large digital camera photos and the images appear as the tasks finish processing.</p> + +<p>For a full example of this and other concepts discussed in this lesson, please see the included +sample application.</p> diff --git a/docs/html/training/displaying-bitmaps/index.jd b/docs/html/training/displaying-bitmaps/index.jd new file mode 100644 index 0000000..6755c24 --- /dev/null +++ b/docs/html/training/displaying-bitmaps/index.jd @@ -0,0 +1,78 @@ +page.title=Displaying Bitmaps Efficiently + +trainingnavtop=true +startpage=true +next.title=Loading Large Bitmaps Efficiently +next.link=load-bitmap.html + +@jd:body + +<div id="tb-wrapper"> +<div id="tb"> + +<h2>Dependencies and prerequisites</h2> +<ul> + <li>Android 2.1 (API Level 7) or higher</li> + <li><a href="{@docRoot}sdk/compatibility-library.html">Support Library</a></li> +</ul> + +<h2>Try it out</h2> + +<div class="download-box"> + <a href="{@docRoot}shareables/training/BitmapFun.zip" class="button">Download the sample</a> + <p class="filename">BitmapFun.zip</p> +</div> + +</div> +</div> + +<p>This class covers some common techniques for processing and loading {@link +android.graphics.Bitmap} objects in a way that keeps your user interface (UI) components responsive +and avoids exceeding your application memory limit. If you're not careful, bitmaps can quickly +consume your available memory budget leading to an application crash due to the dreaded +exception:<br />{@code java.lang.OutofMemoryError: bitmap size exceeds VM budget}.</p> + +<p>There are a number of reasons why loading bitmaps in your Android application is tricky:</p> + +<ul> + <li>Mobile devices typically have constrained system resources. Android devices can have as little + as 16MB of memory available to a single application. The <a + href="http://source.android.com/compatibility/downloads.html">Android Compatibility Definition + Document</a> (CDD), <i>Section 3.7. Virtual Machine Compatibility</i> gives the required minimum + application memory for various screen sizes and densities. Applications should be optimized to + perform under this minimum memory limit. However, keep in mind many devices are configured with + higher limits.</li> + <li>Bitmaps take up a lot of memory, especially for rich images like photographs. For example, the + camera on the <a href="http://www.google.com/nexus/">Galaxy Nexus</a> takes photos up to 2592x1936 + pixels (5 megapixels). If the bitmap configuration used is {@link + android.graphics.Bitmap.Config ARGB_8888} (the default from the Android 2.3 onward) then loading + this image into memory takes about 19MB of memory (2592*1936*4 bytes), immediately exhausting the + per-app limit on some devices.</li> + <li>Android app UI’s frequently require several bitmaps to be loaded at once. Components such as + {@link android.widget.ListView}, {@link android.widget.GridView} and {@link + android.support.v4.view.ViewPager} commonly include multiple bitmaps on-screen at once with many + more potentially off-screen ready to show at the flick of a finger.</li> +</ul> + +<h2>Lessons</h2> + +<dl> + <dt><b><a href="load-bitmap.html">Loading Large Bitmaps Efficiently</a></b></dt> + <dd>This lesson walks you through decoding large bitmaps without exceeding the per application + memory limit.</dd> + + <dt><b><a href="process-bitmap.html">Processing Bitmaps Off the UI Thread</a></b></dt> + <dd>Bitmap processing (resizing, downloading from a remote source, etc.) should never take place + on the main UI thread. This lesson walks you through processing bitmaps in a background thread + using {@link android.os.AsyncTask} and explains how to handle concurrency issues.</dd> + + <dt><b><a href="cache-bitmap.html">Caching Bitmaps</a></b></dt> + <dd>This lesson walks you through using a memory and disk bitmap cache to improve the + responsiveness and fluidity of your UI when loading multiple bitmaps.</dd> + + <dt><b><a href="display-bitmap.html">Displaying Bitmaps in Your UI</a></b></dt> + <dd>This lesson brings everything together, showing you how to load multiple bitmaps into + components like {@link android.support.v4.view.ViewPager} and {@link android.widget.GridView} + using a background thread and bitmap cache.</dd> + +</dl>
\ No newline at end of file diff --git a/docs/html/training/displaying-bitmaps/load-bitmap.jd b/docs/html/training/displaying-bitmaps/load-bitmap.jd new file mode 100644 index 0000000..c0a5709 --- /dev/null +++ b/docs/html/training/displaying-bitmaps/load-bitmap.jd @@ -0,0 +1,165 @@ +page.title=Loading Large Bitmaps Efficiently +parent.title=Displaying Bitmaps Efficiently +parent.link=index.html + +trainingnavtop=true +next.title=Processing Bitmaps Off the UI Thread +next.link=process-bitmap.html + +@jd:body + +<div id="tb-wrapper"> +<div id="tb"> + +<h2>This lesson teaches you to</h2> +<ol> + <li><a href="#read-bitmap">Read Bitmap Dimensions and Type</a></li> + <li><a href="#load-bitmap">Load a Scaled Down Version into Memory</a></li> +</ol> + +<h2>Try it out</h2> + +<div class="download-box"> + <a href="{@docRoot}shareables/training/BitmapFun.zip" class="button">Download the sample</a> + <p class="filename">BitmapFun.zip</p> +</div> + +</div> +</div> + +<p>Images come in all shapes and sizes. In many cases they are larger than required for a typical +application user interface (UI). For example, the system Gallery application displays photos taken +using your Android devices's camera which are typically much higher resolution than the screen +density of your device.</p> + +<p>Given that you are working with limited memory, ideally you only want to load a lower resolution +version in memory. The lower resolution version should match the size of the UI component that +displays it. An image with a higher resolution does not provide any visible benefit, but still takes +up precious memory and incurs additional performance overhead due to additional on the fly +scaling.</p> + +<p>This lesson walks you through decoding large bitmaps without exceeding the per application +memory limit by loading a smaller subsampled version in memory.</p> + +<h2 id="read-bitmap">Read Bitmap Dimensions and Type</h2> + +<p>The {@link android.graphics.BitmapFactory} class provides several decoding methods ({@link +android.graphics.BitmapFactory#decodeByteArray(byte[],int,int,android.graphics.BitmapFactory.Options) +decodeByteArray()}, {@link +android.graphics.BitmapFactory#decodeFile(java.lang.String,android.graphics.BitmapFactory.Options) +decodeFile()}, {@link +android.graphics.BitmapFactory#decodeResource(android.content.res.Resources,int,android.graphics.BitmapFactory.Options) +decodeResource()}, etc.) for creating a {@link android.graphics.Bitmap} from various sources. Choose +the most appropriate decode method based on your image data source. These methods attempt to +allocate memory for the constructed bitmap and therefore can easily result in an {@code OutOfMemory} +exception. Each type of decode method has additional signatures that let you specify decoding +options via the {@link android.graphics.BitmapFactory.Options} class. Setting the {@link +android.graphics.BitmapFactory.Options#inJustDecodeBounds} property to {@code true} while decoding +avoids memory allocation, returning {@code null} for the bitmap object but setting {@link +android.graphics.BitmapFactory.Options#outWidth}, {@link +android.graphics.BitmapFactory.Options#outHeight} and {@link +android.graphics.BitmapFactory.Options#outMimeType}. This technique allows you to read the +dimensions and type of the image data prior to construction (and memory allocation) of the +bitmap.</p> + +<pre> +BitmapFactory.Options options = new BitmapFactory.Options(); +options.inJustDecodeBounds = true; +BitmapFactory.decodeResource(getResources(), R.id.myimage, options); +int imageHeight = options.outHeight; +int imageWidth = options.outWidth; +String imageType = options.outMimeType; +</pre> + +<p>To avoid {@code java.lang.OutOfMemory} exceptions, check the dimensions of a bitmap before +decoding it, unless you absolutely trust the source to provide you with predictably sized image data +that comfortably fits within the available memory.</p> + +<h2 id="load-bitmap">Load a Scaled Down Version into Memory</h2> + +<p>Now that the image dimensions are known, they can be used to decide if the full image should be +loaded into memory or if a subsampled version should be loaded instead. Here are some factors to +consider:</p> + +<ul> + <li>Estimated memory usage of loading the full image in memory.</li> + <li>Amount of memory you are willing to commit to loading this image given any other memory + requirements of your application.</li> + <li>Dimensions of the target {@link android.widget.ImageView} or UI component that the image + is to be loaded into.</li> + <li>Screen size and density of the current device.</li> +</ul> + +<p>For example, it’s not worth loading a 1024x768 pixel image into memory if it will eventually be +displayed in a 128x96 pixel thumbnail in an {@link android.widget.ImageView}.</p> + +<p>To tell the decoder to subsample the image, loading a smaller version into memory, set {@link +android.graphics.BitmapFactory.Options#inSampleSize} to {@code true} in your {@link +android.graphics.BitmapFactory.Options} object. For example, an image with resolution 2048x1536 that +is decoded with an {@link android.graphics.BitmapFactory.Options#inSampleSize} of 4 produces a +bitmap of approximately 512x384. Loading this into memory uses 0.75MB rather than 12MB for the full +image (assuming a bitmap configuration of {@link android.graphics.Bitmap.Config ARGB_8888}). Here’s +a method to calculate a the sample size value based on a target width and height:</p> + +<pre> +public static int calculateInSampleSize( + BitmapFactory.Options options, int reqWidth, int reqHeight) { + // Raw height and width of image + final int height = options.outHeight; + final int width = options.outWidth; + int inSampleSize = 1; + + if (height > reqHeight || width > reqWidth) { + if (width > height) { + inSampleSize = Math.round((float)height / (float)reqHeight); + } else { + inSampleSize = Math.round((float)width / (float)reqWidth); + } + } + return inSampleSize; +} +</pre> + +<p class="note"><strong>Note:</strong> Using powers of 2 for {@link +android.graphics.BitmapFactory.Options#inSampleSize} values is faster and more efficient for the +decoder. However, if you plan to cache the resized versions in memory or on disk, it’s usually still +worth decoding to the most appropriate image dimensions to save space.</p> + +<p>To use this method, first decode with {@link +android.graphics.BitmapFactory.Options#inJustDecodeBounds} set to {@code true}, pass the options +through and then decode again using the new {@link +android.graphics.BitmapFactory.Options#inSampleSize} value and {@link +android.graphics.BitmapFactory.Options#inJustDecodeBounds} set to {@code false}:</p> + +<a name="decodeSampledBitmapFromResource"></a> +<pre> +public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, + int reqWidth, int reqHeight) { + + // First decode with inJustDecodeBounds=true to check dimensions + final BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeResource(res, resId, options); + + // Calculate inSampleSize + options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); + + // Decode bitmap with inSampleSize set + options.inJustDecodeBounds = false; + return BitmapFactory.decodeResource(res, resId, options); +} +</pre> + +<p>This method makes it easy to load a bitmap of arbitrarily large size into an {@link +android.widget.ImageView} that displays a 100x100 pixel thumbnail, as shown in the following example +code:</p> + +<pre> +mImageView.setImageBitmap( + decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100)); +</pre> + +<p>You can follow a similar process to decode bitmaps from other sources, by substituting the +appropriate {@link +android.graphics.BitmapFactory#decodeByteArray(byte[],int,int,android.graphics.BitmapFactory.Options) +BitmapFactory.decode*} method as needed.</p>
\ No newline at end of file diff --git a/docs/html/training/displaying-bitmaps/process-bitmap.jd b/docs/html/training/displaying-bitmaps/process-bitmap.jd new file mode 100644 index 0000000..c1450b4 --- /dev/null +++ b/docs/html/training/displaying-bitmaps/process-bitmap.jd @@ -0,0 +1,239 @@ +page.title=Processing Bitmaps Off the UI Thread +parent.title=Displaying Bitmaps Efficiently +parent.link=index.html + +trainingnavtop=true +next.title=Caching Bitmaps +next.link=cache-bitmap.html +previous.title=Loading Large Bitmaps Efficiently +previous.link=load-bitmap.html + +@jd:body + +<div id="tb-wrapper"> +<div id="tb"> + +<h2>This lesson teaches you to</h2> +<ol> + <li><a href="#async-task">Use an AsyncTask</a></li> + <li><a href="#concurrency">Handle Concurrency</a></li> +</ol> + +<h2>You should also read</h2> +<ul> + <li><a href="{@docRoot}guide/practices/design/responsiveness.html">Designing for Responsiveness</a></li> + <li><a + href="http://android-developers.blogspot.com/2010/07/multithreading-for-performance.html">Multithreading + for Performance</a></li> +</ul> + +<h2>Try it out</h2> + +<div class="download-box"> + <a href="{@docRoot}shareables/training/BitmapFun.zip" class="button">Download the sample</a> + <p class="filename">BitmapFun.zip</p> +</div> + +</div> +</div> + +<p>The {@link +android.graphics.BitmapFactory#decodeByteArray(byte[],int,int,android.graphics.BitmapFactory.Options) +BitmapFactory.decode*} methods, discussed in the <a href="load-bitmap.html">Load Large Bitmaps +Efficiently</a> lesson, should not be executed on the main UI thread if the source data is read from +disk or a network location (or really any source other than memory). The time this data takes to +load is unpredictable and depends on a variety of factors (speed of reading from disk or network, +size of image, power of CPU, etc.). If one of these tasks blocks the UI thread, the system flags +your application as non-responsive and the user has the option of closing it (see <a +href="{@docRoot}guide/practices/design/responsiveness.html">Designing for Responsiveness</a> for +more information).</p> + +<p>This lesson walks you through processing bitmaps in a background thread using +{@link android.os.AsyncTask} and shows you how to handle concurrency issues.</p> + +<h2 id="async-task">Use an AsyncTask</h2> + +<p>The {@link android.os.AsyncTask} class provides an easy way to execute some work in a background +thread and publish the results back on the UI thread. To use it, create a subclass and override the +provided methods. Here’s an example of loading a large image into an {@link +android.widget.ImageView} using {@link android.os.AsyncTask} and <a +href="load-bitmap.html#decodeSampledBitmapFromResource">{@code +decodeSampledBitmapFromResource()}</a>: </p> + +<a name="BitmapWorkerTask"></a> +<pre> +class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> { + private final WeakReference<ImageView> imageViewReference; + private int data = 0; + + public BitmapWorkerTask(ImageView imageView) { + // Use a WeakReference to ensure the ImageView can be garbage collected + imageViewReference = new WeakReference<ImageView>(imageView); + } + + // Decode image in background. + @Override + protected Bitmap doInBackground(Integer... params) { + data = params[0]; + return decodeSampledBitmapFromResource(getResources(), data, 100, 100)); + } + + // Once complete, see if ImageView is still around and set bitmap. + @Override + protected void onPostExecute(Bitmap bitmap) { + if (imageViewReference != null && bitmap != null) { + final ImageView imageView = imageViewReference.get(); + if (imageView != null) { + imageView.setImageBitmap(bitmap); + } + } + } +} +</pre> + +<p>The {@link java.lang.ref.WeakReference} to the {@link android.widget.ImageView} ensures that the +{@link android.os.AsyncTask} does not prevent the {@link android.widget.ImageView} and anything it +references from being garbage collected. There’s no guarantee the {@link android.widget.ImageView} +is still around when the task finishes, so you must also check the reference in {@link +android.os.AsyncTask#onPostExecute(Result) onPostExecute()}. The {@link android.widget.ImageView} +may no longer exist, if for example, the user navigates away from the activity or if a +configuration change happens before the task finishes.</p> + +<p>To start loading the bitmap asynchronously, simply create a new task and execute it:</p> + +<pre> +public void loadBitmap(int resId, ImageView imageView) { + BitmapWorkerTask task = new BitmapWorkerTask(imageView); + task.execute(resId); +} +</pre> + +<h2 id="concurrency">Handle Concurrency</h2> + +<p>Common view components such as {@link android.widget.ListView} and {@link +android.widget.GridView} introduce another issue when used in conjunction with the {@link +android.os.AsyncTask} as demonstrated in the previous section. In order to be efficient with memory, +these components recycle child views as the user scrolls. If each child view triggers an {@link +android.os.AsyncTask}, there is no guarantee that when it completes, the associated view has not +already been recycled for use in another child view. Furthermore, there is no guarantee that the +order in which asynchronous tasks are started is the order that they complete.</p> + +<p>The blog post <a +href="http://android-developers.blogspot.com/2010/07/multithreading-for-performance.html">Multithreading +for Performance</a> further discusses dealing with concurrency, and offers a solution where the +{@link android.widget.ImageView} stores a reference to the most recent {@link android.os.AsyncTask} +which can later be checked when the task completes. Using a similar method, the {@link +android.os.AsyncTask} from the previous section can be extended to follow a similar pattern.</p> + +<p>Create a dedicated {@link android.graphics.drawable.Drawable} subclass to store a reference +back to the worker task. In this case, a {@link android.graphics.drawable.BitmapDrawable} is used so +that a placeholder image can be displayed in the {@link android.widget.ImageView} while the task +completes:</p> + +<a name="AsyncDrawable"></a> +<pre> +static class AsyncDrawable extends BitmapDrawable { + private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference; + + public AsyncDrawable(Resources res, Bitmap bitmap, + BitmapWorkerTask bitmapWorkerTask) { + super(res, bitmap); + bitmapWorkerTaskReference = + new WeakReference<BitmapWorkerTask>(bitmapWorkerTask); + } + + public BitmapWorkerTask getBitmapWorkerTask() { + return bitmapWorkerTaskReference.get(); + } +} +</pre> + +<p>Before executing the <a href="#BitmapWorkerTask">{@code BitmapWorkerTask}</a>, you create an <a +href="#AsyncDrawable">{@code AsyncDrawable}</a> and bind it to the target {@link +android.widget.ImageView}:</p> + +<pre> +public void loadBitmap(int resId, ImageView imageView) { + if (cancelPotentialWork(resId, imageView)) { + final BitmapWorkerTask task = new BitmapWorkerTask(imageView); + final AsyncDrawable asyncDrawable = + new AsyncDrawable(getResources(), mPlaceHolderBitmap, task); + imageView.setImageDrawable(asyncDrawable); + task.execute(resId); + } +} +</pre> + +<p>The {@code cancelPotentialWork} method referenced in the code sample above checks if another +running task is already associated with the {@link android.widget.ImageView}. If so, it attempts to +cancel the previous task by calling {@link android.os.AsyncTask#cancel cancel()}. In a small number +of cases, the new task data matches the existing task and nothing further needs to happen. Here is +the implementation of {@code cancelPotentialWork}:</p> + +<pre> +public static boolean cancelPotentialWork(int data, ImageView imageView) { + final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView); + + if (bitmapWorkerTask != null) { + final int bitmapData = bitmapWorkerTask.data; + if (bitmapData != data) { + // Cancel previous task + bitmapWorkerTask.cancel(true); + } else { + // The same work is already in progress + return false; + } + } + // No task associated with the ImageView, or an existing task was cancelled + return true; +} +</pre> + +<p>A helper method, {@code getBitmapWorkerTask()}, is used above to retrieve the task associated +with a particular {@link android.widget.ImageView}:</p> + +<pre> +private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) { + if (imageView != null) { + final Drawable drawable = imageView.getDrawable(); + if (drawable instanceof AsyncDrawable) { + final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable; + return asyncDrawable.getBitmapWorkerTask(); + } + } + return null; +} +</pre> + +<p>The last step is updating {@code onPostExecute()} in <a href="#BitmapWorkerTask">{@code +BitmapWorkerTask}</a> so that it checks if the task is cancelled and if the current task matches the +one associated with the {@link android.widget.ImageView}:</p> + +<a name="BitmapWorkerTaskUpdated"></a> +<pre> +class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> { + ... + + @Override + protected void onPostExecute(Bitmap bitmap) { + <strong>if (isCancelled()) { + bitmap = null; + }</strong> + + if (imageViewReference != null && bitmap != null) { + final ImageView imageView = imageViewReference.get(); + <strong>final BitmapWorkerTask bitmapWorkerTask = + getBitmapWorkerTask(imageView);</strong> + if (<strong>this == bitmapWorkerTask &&</strong> imageView != null) { + imageView.setImageBitmap(bitmap); + } + } + } +} +</pre> + +<p>This implementation is now suitable for use in {@link android.widget.ListView} and {@link +android.widget.GridView} components as well as any other components that recycle their child +views. Simply call {@code loadBitmap} where you normally set an image to your {@link +android.widget.ImageView}. For example, in a {@link android.widget.GridView} implementation this +would be in the {@link android.widget.Adapter#getView getView()} method of the backing adapter.</p>
\ No newline at end of file diff --git a/docs/html/training/tv/index.jd b/docs/html/training/tv/index.jd new file mode 100644 index 0000000..ae13c4a --- /dev/null +++ b/docs/html/training/tv/index.jd @@ -0,0 +1,52 @@ +page.title=Designing for TV + +trainingnavtop=true +startpage=true +next.title=Optimizing layouts for TV +next.link=optimizing-layouts-tv.html + +@jd:body + +<div id="tb-wrapper"> +<div id="tb"> + +<!-- Required platform, tools, add-ons, devices, knowledge, etc. --> +<h2>Dependencies and prerequisites</h2> +<ul> + <li>Android 2.0 (API Level 5) or higher</li> +</ul> + +</div> +</div> +<p> + Smart TVs powered by Android bring your favorite Android apps to the best screen in your house. + Thousands of apps in the Google Play Store are already optimized for TVs. This class shows how + you can optimize your Android app for TVs, including how to build a layout that + works great when the user is ten feet away and navigating with a remote control. +</p> + +<h2>Lessons</h2> + +<dl> + <dt><b><a href="optimizing-layouts-tv.html">Optimizing Layouts for TV</a></b></dt> + <dd>Shows you how to optimize app layouts for TV screens, which have some unique characteristics such as: + <ul> + <li>permanent "landscape" mode</li> + <li>high-resolution displays</li> + <li>"10 foot UI" environment.</li> + </ul> + </dd> + + <dt><b><a href="optimizing-navigation-tv.html">Optimizing Navigation for TV</a></b></dt> + <dd>Shows you how to design navigation for TVs, including: + <ul> + <li>handling D-pad navigation</li> + <li>providing navigational feedback</li> + <li>providing easily-accessible controls on the screen.</li> + </ul> + </dd> + + <dt><b><a href="unsupported-features-tv.html">Handling features not supported on TV</a></b></dt> + <dd>Lists the hardware features that are usually not available on TVs. This lesson also shows you how to + provide alternatives for missing features or check for missing features and disable code at run time.</dd> +</dl>
\ No newline at end of file diff --git a/docs/html/training/tv/optimizing-layouts-tv.jd b/docs/html/training/tv/optimizing-layouts-tv.jd new file mode 100644 index 0000000..e4a8e69 --- /dev/null +++ b/docs/html/training/tv/optimizing-layouts-tv.jd @@ -0,0 +1,246 @@ +page.title=Optimizing Layouts for TV +parent.title=Designing for TV +parent.link=index.html + +trainingnavtop=true +next.title=Optimizing Navigation for TV +next.link=optimizing-navigation-tv.html + +@jd:body + +<div id="tb-wrapper"> +<div id="tb"> + +<h2>This lesson teaches you to</h2> +<ol> + <li><a href="#DesignLandscapeLayouts">Design Landscape Layouts</a></li> + <li><a href="#MakeTextControlsEasyToSee">Make Text and Controls Easy to See</a></li> + <li><a href="#DesignForLargeScreens">Design for High-Density Large Screens</a></li> + <li><a href="#HandleLargeBitmaps">Design to Handle Large Bitmaps</a></li> +</ol> + +<h2>You should also read</h2> +<ul> + <li><a href="{@docRoot}guide/practices/screens_support.html">Supporting Multiple Screens</a></li> +</ul> + +</div> +</div> + +<p> +When your application is running on a television set, you should assume that the user is sitting about +ten feet away from the screen. This user environment is referred to as the +<a href="http://en.wikipedia.org/wiki/10-foot_user_interface">10-foot UI</a>. To provide your +users with a usable and enjoyable experience, you should style and lay out your UI accordingly.. +</p> +<p> +This lesson shows you how to optimize layouts for TV by: +</p> +<ul> + <li>Providing appropriate layout resources for landscape mode.</li> + <li>Ensuring that text and controls are large enough to be visible from a distance.</li> + <li>Providing high resolution bitmaps and icons for HD TV screens.</li> +</ul> + +<h2 id="DesignLandscapeLayouts">Design Landscape Layouts</h2> + +<p> +TV screens are always in landscape orientation. Follow these tips to build landscape layouts optimized for TV screens: +</p> +<ul> + <li>Put on-screen navigational controls on the left or right side of the screen and save the + vertical space for content.</li> + <li>Create UIs that are divided into sections, by using <a href="{@docRoot}guide/topics/fundamentals/fragments.html">Fragments</a> + and use view groups like {@link android.widget.GridView} instead + of {@link android.widget.ListView} to make better use of the + horizontal screen space.</li> + <li>Use view groups such as {@link android.widget.RelativeLayout} + or {@link android.widget.LinearLayout} to arrange views. + This allows the Android system to adjust the position of the views to the size, alignment, + aspect ratio, and pixel density of the TV screen.</li> + <li>Add sufficient margins between layout controls to avoid a cluttered UI.</li> +</ul> + +<p> +For example, the following layout is optimized for TV: +</p> + +<img src="{@docRoot}images/training/panoramio-grid.png" /> + +<p> +In this layout, the controls are on the lefthand side. The UI is displayed within a +{@link android.widget.GridView}, which is well-suited to landscape orientation. +In this layout both GridView and Fragment have the width and height set +dynamically, so they can adjust to the screen resolution. Controls are added to the left side Fragment programatically at runtime. +The layout file for this UI is {@code res/layout-land-large/photogrid_tv.xml}. +(This layout file is placed in {@code layout-land-large} because TVs have large screens with landscape orientation. For details refer to +<a href="{@docRoot}guide/practices/screens_support.html">Supporting Multiple Screens</a>.)</p> + +res/layout-land-large/photogrid_tv.xml +<pre> +<RelativeLayout + android:layout_width="fill_parent" + android:layout_height="fill_parent" > + + <fragment + android:id="@+id/leftsidecontrols" + android:layout_width="0dip" + android:layout_marginLeft="5dip" + android:layout_height="match_parent" /> + + <GridView + android:id="@+id/gridview" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> + +</RelativeLayout> +</pre> + +<p> +To set up action bar items on the left side of the screen, you can also include the <a +href="http://code.google.com/p/googletv-android-samples/source/browse/#git%2FLeftNavBarLibrary"> +Left navigation bar library</a> in your application to set up action items on the left side +of the screen, instead of creating a custom Fragment to add controls: +</p> + +<pre> +LeftNavBar bar = (LeftNavBarService.instance()).getLeftNavBar(this); +</pre> + +<p> +When you have an activity in which the content scrolls vertically, always use a left navigation bar; +otherwise, your users have to scroll to the top of the content to switch between the content view and +the ActionBar. Look at the +<a href="http://code.google.com/p/googletv-android-samples/source/browse/#git%2FLeftNavBarDemo"> +Left navigation bar sample app</a> to see how to simple it is to include the left navigation bar in your app. +</p> + +<h2 id="MakeTextControlsEasyToSee">Make Text and Controls Easy to See</h2> +<p> +The text and controls in a TV application's UI should be easily visible and navigable from a distance. +Follow these tips to make them easier to see from a distance : +</p> + +<ul> + <li>Break text into small chunks that users can quickly scan.</li> + <li>Use light text on a dark background. This style is easier to read on a TV.</li> + <li>Avoid lightweight fonts or fonts that have both very narrow and very broad strokes. Use simple sans-serif + fonts and use anti-aliasing to increase readability.</li> + <li>Use Android's standard font sizes: + <pre> + <TextView + android:id="@+id/atext" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="center_vertical" + android:singleLine="true" + android:textAppearance="?android:attr/textAppearanceMedium"/> + </pre></li> + <li>Ensure that all your view widgets are large enough to be clearly visible to someone sitting 10 feet away + from the screen (this distance is greater for very large screens). The best way to do this is to use + layout-relative sizing rather than absolute sizing, and density-independent pixel units instead of absolute + pixel units. For example, to set the width of a widget, use wrap_content instead of a pixel measurement, + and to set the margin for a widget, use dip instead of px values. + </li> +</ul> +<p> + +</p> + +<h2 id="DesignForLargeScreens">Design for High-Density Large Screens</h2> + +<p> +The common HDTV display resolutions are 720p, 1080i, and 1080p. Design your UI for 1080p, and then +allow the Android system to downscale your UI to 720p if necessary. In general, downscaling (removing pixels) +does not degrade the UI (Notice that the converse is not true; you should avoid upscaling because it degrades +UI quality). +</p> + +<p> +To get the best scaling results for images, provide them as <a href="{@docRoot}guide/developing/tools/draw9patch.html"> +9-patch image</a> elements if possible. +If you provide low quality or small images in your layouts, they will appear pixelated, fuzzy, or grainy. This +is not a good experience for the user. Instead, use high-quality images. +</p> + +<p> +For more information on optimizing apps for large screens see <a href="{@docRoot}training/multiscreen/index.html"> +Designing for multiple screens</a>. +</p> + +<h2 id="HandleLargeBitmaps">Design to Handle Large Bitmaps</h2> + +<p> +The Android system has a limited amount of memory, so downloading and storing high-resolution images can often +cause out-of-memory errors in your app. To avoid this, follow these tips: +</p> + +<ul> + <li>Load images only when they're displayed on the screen. For example, when displaying multiple images in + a {@link android.widget.GridView} or + {@link android.widget.Gallery}, only load an image when + {@link android.widget.Adapter#getView(int, View, ViewGroup) getView()} + is called on the View's {@link android.widget.Adapter}. + </li> + <li>Call {@link android.graphics.Bitmap#recycle()} on + {@link android.graphics.Bitmap} views that are no longer needed. + </li> + <li>Use {@link java.lang.ref.WeakReference} for storing references + to {@link android.graphics.Bitmap} objects in a in-memory + <a href="{@link java.util.Collection}.</li> + <li>If you fetch images from the network, use {@link android.os.AsyncTask} + to fetch them and store them on the SD card for faster access. + Never do network transactions on the application's UI thread. + </li> + <li>Scale down really large images to a more appropriate size as you download them; otherwise, downloading the image + itself may cause an "Out of Memory" exception. Here is sample code that scales down images while downloading: + + <pre> + // Get the source image's dimensions + BitmapFactory.Options options = new BitmapFactory.Options(); + // This does not download the actual image, just downloads headers. + options.inJustDecodeBounds = true; + BitmapFactory.decodeFile(IMAGE_FILE_URL, options); + // The actual width of the image. + int srcWidth = options.outWidth; + // The actual height of the image. + int srcHeight = options.outHeight; + + // Only scale if the source is bigger than the width of the destination view. + if(desiredWidth > srcWidth) + desiredWidth = srcWidth; + + // Calculate the correct inSampleSize/scale value. This helps reduce memory use. It should be a power of 2. + int inSampleSize = 1; + while(srcWidth / 2 > desiredWidth){ + srcWidth /= 2; + srcHeight /= 2; + inSampleSize *= 2; + } + + float desiredScale = (float) desiredWidth / srcWidth; + + // Decode with inSampleSize + options.inJustDecodeBounds = false; + options.inDither = false; + options.inSampleSize = inSampleSize; + options.inScaled = false; + // Ensures the image stays as a 32-bit ARGB_8888 image. + // This preserves image quality. + options.inPreferredConfig = Bitmap.Config.ARGB_8888; + + Bitmap sampledSrcBitmap = BitmapFactory.decodeFile(IMAGE_FILE_URL, options); + + // Resize + Matrix matrix = new Matrix(); + matrix.postScale(desiredScale, desiredScale); + Bitmap scaledBitmap = Bitmap.createBitmap(sampledSrcBitmap, 0, 0, + sampledSrcBitmap.getWidth(), sampledSrcBitmap.getHeight(), matrix, true); + sampledSrcBitmap = null; + + // Save + FileOutputStream out = new FileOutputStream(LOCAL_PATH_TO_STORE_IMAGE); + scaledBitmap.compress(Bitmap.CompressFormat.JPEG, 100, out); + scaledBitmap = null; + </pre> + </li> </ul>
\ No newline at end of file diff --git a/docs/html/training/tv/optimizing-navigation-tv.jd b/docs/html/training/tv/optimizing-navigation-tv.jd new file mode 100644 index 0000000..bb78258 --- /dev/null +++ b/docs/html/training/tv/optimizing-navigation-tv.jd @@ -0,0 +1,206 @@ +page.title=Optimizing Navigation for TV +parent.title=Designing for TV +parent.link=index.html + +trainingnavtop=true +previous.title=Optimizing Layouts for TV +previous.link=optimizing-layouts-tv.html +next.title=Handling Features Not Supported on TV +next.link=unsupported-features-tv.html + +@jd:body + +<div id="tb-wrapper"> +<div id="tb"> + +<h2>This lesson teaches you to</h2> +<ol> + <li><a href="#HandleDpadNavigation">Handle D-pad Navigation</a></li> + <li><a href="#HandleFocusSelection">Provide Clear Visual Indication for Focus and Selection</a></li> + <li><a href="#DesignForEasyNavigation">Design for Easy Navigation</a></li> +</ol> + +<h2>You should also read</h2> +<ul> + <li><a href="{@docRoot}training/design-navigation/index.html">Designing Effective Navigation</a></li> +</ul> + +</div> +</div> + +<p> +An important aspect of the user experience when operating a TV is the direct human interface: a remote control. +As you optimize your Android application for TVs, you should pay special attention to how the user actually navigates +around your application when using a remote control instead of a touchscreen. +</p> +<p> +This lesson shows you how to optimize navigation for TV by: +</p> + +<ul> + <li>Ensuring all layout controls are D-pad navigable.</li> + <li>Providing highly obvious feedback for UI navigation.</li> + <li>Placing layout controls for easy access.</li> +</ul> + +<h2 id="HandleDpadNavigation">Handle D-pad Navigation</h2> + +<p> +On a TV, users navigate with controls on a TV remote, using either a D-pad or arrow keys. +This limits movement to up, down, left, and right. +To build a great TV-optimized app, you must provide a navigation scheme in which the user can +quickly learn how to navigate your app using the remote. +</p> + +<p> +When you design navigation for D-pad, follow these guidelines: +</p> + +<ul> + <li>Ensure that the D-pad can navigate to all the visible controls on the screen.</li> + <li>For scrolling lists with focus, D-pad up/down keys scroll the list and Enter key selects an item in the list. Ensure that users can + select an element in the list and that the list still scrolls when an element is selected.</li> + <li>Ensure that movement between controls is straightforward and predictable.</li> +</ul> + +<p> +Android usually handles navigation order between layout elements automatically, so you don't need to do anything extra. If the screen layout +makes navigation difficult, or if you want users to move through the layout in a specific way, you can set up explicit navigation for your +controls. +For example, for an {@code android.widget.EditText}, to define the next control to receive focus, use: +<pre> +<EditText android:id="@+id/LastNameField" android:nextFocusDown="@+id/FirstNameField"\> +</pre> +The following table lists all of the available navigation attributes: +</p> + +<table> +<tr> +<th>Attribute</th> +<th>Function</th> +</tr> +<tr> +<td>{@link android.R.attr#nextFocusDown}</td> +<td>Defines the next view to receive focus when the user navigates down.</td> +</tr> +<tr> +<td>{@link android.R.attr#nextFocusLeft}</td> +<td>Defines the next view to receive focus when the user navigates left.</td> +</tr> +<tr> +<td>{@link android.R.attr#nextFocusRight}</td> +<td>Defines the next view to receive focus when the user navigates right.</td> +</tr> +<tr> +<td>{@link android.R.attr#nextFocusUp}</td> +<td>Defines the next view to receive focus when the user navigates up.</td> +</tr> +</table> + +<p> +To use one of these explicit navigation attributes, set the value to the ID (android:id value) of another widget in the layout. You should set +up the navigation order as a loop, so that the last control directs focus back to the first one. +</p> + +<p> +Note: You should only use these attributes to modify the navigation order if the default order that the system applies does not work well. +</p> + +<h2 id="HandleFocusSelection">Provide Clear Visual Indication for Focus and Selection</h2> + +<p> +Use appropriate color highlights for all navigable and selectable elements in the UI. This makes it easy for users to know whether the control +is currently focused or selected when they navigate with a D-pad. Also, use uniform highlight scheme across your application. +</p> + +<p> +Android provides <a href="{@docRoot}guide/topics/resources/drawable-resource.html#StateList">Drawable State List Resources</a> to implement highlights +for selected and focused controls. For example: +</p> + +res/drawable/button.xml: +<pre> +<?xml version="1.0" encoding="utf-8"?> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_pressed="true" + android:drawable="@drawable/button_pressed" /> <!-- pressed --> + <item android:state_focused="true" + android:drawable="@drawable/button_focused" /> <!-- focused --> + <item android:state_hovered="true" + android:drawable="@drawable/button_focused" /> <!-- hovered --> + <item android:drawable="@drawable/button_normal" /> <!-- default --> +</selector> +</pre> + +<p> +This layout XML applies the above state list drawable to a {@link android.widget.Button}: +</p> +<pre> +<Button + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:background="@drawable/button" /> +</pre> + +<p> +Provide sufficient padding within the focusable and selectable controls so that the highlights around them are clearly visible. +</p> + +<h2 id="DesignForEasyNavigation">Design for Easy Navigation</h2> + +<p> +Users should be able to navigate to any UI control with a couple of D-pad clicks. Navigation should be easy and intuitive to +understand. For any non-intuitive actions, provide users with written help, using a dialog triggered by a help button or action bar icon. +</p> + +<p> +Predict the next screen that the user will want to navigate to and provide one click navigation to it. If the current screen UI is very sparse, +consider making it a multi pane screen. Use fragments for making multi-pane screens. For example, consider the multi-pane UI below with continent names +on the left and list of cool places in each continent on the right. +</p> + +<img src="{@docRoot}images/training/cool-places.png" alt="" /> + +<p> +The above UI consists of three Fragments - <code>left_side_action_controls</code>, <code>continents</code> and +<code>places</code> - as shown in its layout +xml file below. Such multi-pane UIs make D-pad navigation easier and make good use of the horizontal screen space for +TVs. +</p> +res/layout/cool_places.xml +<pre> +<LinearLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="horizontal" + > + <fragment + android:id="@+id/left_side_action_controls" + android:layout_width="0px" + android:layout_height="match_parent" + android:layout_marginLeft="10dip" + android:layout_weight="0.2"/> + <fragment + android:id="@+id/continents" + android:layout_width="0px" + android:layout_height="match_parent" + android:layout_marginLeft="10dip" + android:layout_weight="0.2"/> + + <fragment + android:id="@+id/places" + android:layout_width="0px" + android:layout_height="match_parent" + android:layout_marginLeft="10dip" + android:layout_weight="0.6"/> + +</LinearLayout> +</pre> + +<p> +Also, notice in the UI layout above action controls are on the left hand side of a vertically scrolling list to make +them easily accessible using D-pad. +In general, for layouts with horizontally scrolling components, place action controls on left or right hand side and +vice versa for vertically scrolling components. +</p> + diff --git a/docs/html/training/tv/unsupported-features-tv.jd b/docs/html/training/tv/unsupported-features-tv.jd new file mode 100644 index 0000000..6b0f8c8 --- /dev/null +++ b/docs/html/training/tv/unsupported-features-tv.jd @@ -0,0 +1,156 @@ +page.title=Handling Features Not Supported on TV +parent.title=Designing for TV +parent.link=index.html + +trainingnavtop=true +previous.title=Optimizing Navigation for TV +previous.link=optimizing-navigation-tv.html + +@jd:body + +<div id="tb-wrapper"> +<div id="tb"> + +<h2>This lesson teaches you to</h2> +<ol> + <li><a href="#WorkaroundUnsupportedFeatures">Work Around Features Not Supported on TV</a></li> + <li><a href="#CheckAvailableFeatures">Check for Available Features at Runtime</a></li> +</ol> + +</div> +</div> + +<p> +TVs are much different from other Android-powered devices: +</p> +<ul> + <li>They're not mobile.</li> + <li>Out of habit, people use them for watching media with little or no interaction.</li> + <li>People interact with them from a distance.</li> +</ul> + +<p> +Because TVs have a different purpose from other devices, they usually don't have hardware features +that other Android-powered devices often have. For this reason, the Android system does not +support the following features for a TV device: +<table> +<tr> +<th>Hardware</th> +<th>Android feature descriptor</th> +</tr> +<tr> +<td>Camera</td> +<td>android.hardware.camera</td> +</tr> +<tr> +<td>GPS</td> +<td>android.hardware.location.gps</td> +</tr> +<tr> +<td>Microphone</td> +<td>android.hardware.microphone</td> +</tr> +<tr> +<td>Near Field Communications (NFC)</td> +<td>android.hardware.nfc</td> +</tr> +<tr> +<td>Telephony</td> +<td>android.hardware.telephony</td> +</tr> +<tr> +<td>Touchscreen</td> +<td>android.hardware.touchscreen</td> +</tr> +</table> +</p> + +<p> +This lesson shows you how to work around features that are not available on TV by: +<ul> + <li>Providing work arounds for some non-supported features.</li> + <li>Checking for available features at runtime and conditionally activating/deactivating certain code + paths based on availability of those features.</li> +</ul> +</p> + + +<h2 id="WorkaroundUnsupportedFeatures">Work Around Features Not Supported on TV</h2> + +<p> +Android doesn't support touchscreen interaction for TV devices, most TVs don't have touch screens, +and interacting with a TV using a touchscreen is not consistent with the 10 foot environment. For +these reasons, users interact with Android-powered TVs using a remote. In consideration of this, +ensure that every control in your app can be accessed with the D-pad. Refer back to the previous two lessons +<a href="{@docRoot}training/tv/optimizing-layouts-tv">Optimizing Layouts for TV</a> and +<a href="{@docRoot}training/tv/optimizing-navigation-tv">Optimize Navigation for TV</a> for more details +on this topic. The Android system assumes that a device has a touchscreen, so if you want your application +to run on a TV, you must <strong>explicitly</strong> disable the touchscreen requirement in your manifest file: +<pre> +<uses-feature android:name="android.hardware.touchscreen" android:required="false"/> +</pre> +</p> + +<p> +Although a TV doesn't have a camera, you can still provide a photography-related application on a TV. +For example, if you have an app that takes, views and edits photos, you can disable its picture-taking +functionality for TVs and still allow users to view and even edit photos. The next section talks about how to +deactivate or activate specific functions in the application based on runtime device type detection. +</p> + +<p> +Because TVs are stationary, indoor devices, they don't have built-in GPS. If your application uses location +information, allow users to search for a location or use a "static" location provider to get +a location from the zip code configured during the TV setup. +<pre> +LocationManager locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE); +Location location = locationManager.getLastKnownLocation("static"); +Geocoder geocoder = new Geocoder(this); +Address address = null; + +try { + address = geocoder.getFromLocation(location.getLatitude(), location.getLongitude(), 1).get(0); + Log.d("Zip code", address.getPostalCode()); + +} catch (IOException e) { + Log.e(TAG, "Geocoder error", e); +} +</pre> +</p> + +<p> +TVs usually don't support microphones, but if you have an application that uses voice control, +you can create a mobile device app that takes voice input and then acts as a remote control for a TV. +</p> + +<h2 id="CheckAvailableFeatures">Check for Available Features at Runtime</h2> + +<p> +To check if a feature is available at runtime, call +{@link android.content.pm.PackageManager#hasSystemFeature(String)}. + This method takes a single argument : a string corresponding to the +feature you want to check. For example, to check for touchscreen, use +{@link android.content.pm.PackageManager#hasSystemFeature(String)} with the argument +{@link android.content.pm.PackageManager#FEATURE_TOUCHSCREEN}. +</p> + +<p> +The following code snippet demonstrates how to detect device type at runtime based on supported features: + +<pre> +// Check if android.hardware.telephony feature is available. +if (getPackageManager().hasSystemFeature("android.hardware.telephony")) { + Log.d("Mobile Test", "Running on phone"); +// Check if android.hardware.touchscreen feature is available. +} else if (getPackageManager().hasSystemFeature("android.hardware.touchscreen")) { + Log.d("Tablet Test", "Running on devices that don't support telphony but have a touchscreen."); +} else { + Log.d("TV Test", "Running on a TV!"); +} +</pre> +</p> + +<p> +This is just one example of using runtime checks to deactivate app functionality that depends on features +that aren't available on TVs. +</p>
\ No newline at end of file |
