安卓秘籍:问题解决方法(五)
八、附录 A:Android 脚本层
Android 脚本层(SL4A),之前被称为 Android 脚本环境,是一个在 Android 设备上安装脚本语言解释器并通过这些解释器运行脚本的平台。脚本可以访问 Android 应用可用的许多 API,但有一个大大简化的界面,使事情更容易完成。
注意: SL4A 目前只支持 Python、Perl、JRuby、Lua、BeanShell、Rhino JavaScript 和 Tcl 脚本语言。
您可以在终端窗口(命令窗口)、后台或通过区域设置([www.twofortyfouram.com/](http://www.twofortyfouram.com/))交互运行脚本。 Locale 是一款 Android 应用,可以让你在预定的时间或者满足其他条件时运行脚本(例如,当你进入剧院或法庭时,运行脚本将你的手机铃声模式改为振动)。
安装 SL4A
在使用 SL4A 之前,您必须安装它。你可以从谷歌托管的项目网站([code.google.com/p/android-scripting](http://code.google.com/p/android-scripting))下载最新版本的 APK 文件(sl4a_r3.apk)到你的设备上。为此,请使用您的条形码阅读器应用扫描网站上显示的条形码图像。
如果你使用的是 Android 模拟器,点击条形码图片下载sl4a_r3.apk。然后执行adb install sl4a_r3.apk在当前运行的仿真设备上安装这个应用。(如果收到设备离线消息,您可能需要尝试多次。)图 A–1 显示了 SL4A 在应用启动器屏幕上的图标。
图 A–1。 点击 SL4A 图标,开始探索 Android 应用的脚本层。
探索 SL4A
现在你已经安装了 SL4A,你会想要学习如何使用这个应用。单击 SL4A 图标,您将被带到一个显示已安装脚本(和其他项目)列表的脚本屏幕。单击菜单按钮,SL4A 将显示脚本菜单。Figure A–2 显示了一个最初为空的列表和该菜单的选项。
图 A–2。 SL4A 的脚本屏幕显示还没有安装脚本。
脚本菜单分为以下六类:
- **添加:**将文件夹(用于组织脚本和其他项目)、嵌入 JavaScript 代码的 HTML 页面、外壳脚本和通过扫描条形码图像获得的脚本添加到脚本屏幕。文件夹和其他项目存储在设备的
/sdcard/sl4a/scripts目录中。 - View: 查看已安装的解释器(如 Python 解释器) triggers (一种无论设备是否睡眠都重复运行脚本的意图,或者根据振铃模式变化有条件运行脚本的意图) logcat (查看系统调试输出的工具)。SL4A 只附带了 shell 解释器以及 HTML 和 JavaScript。此外,Android 模拟器似乎不支持触发器。
- Search: 创建并显示与输入的搜索文本相匹配的脚本和其他项目的列表。当没有匹配时,搜索逻辑输出“没有找到匹配”。
- **首选项:**配置通用、脚本管理器、脚本编辑器和终端选项。
- **刷新:**重新显示脚本屏幕以显示任何更改;也许在后台运行的脚本已经更新了这个列表。
- **帮助:**从 SL4A 的 wiki 文档(
[code.google.com/p/android-scripting/wiki/TableOfContents?tm=6](http://code.google.com/p/android-scripting/wiki/TableOfContents?tm=6))、YouTube 截屏和终端帮助文档中获得关于使用 SL4A 的帮助。
添加外壳脚本
让我们向脚本屏幕添加一个简单的 shell 脚本。通过完成以下步骤来完成此任务:
- 单击电话控件中的菜单按钮。
- 单击屏幕底部出现的菜单中的添加菜单项。
- 从弹出的添加菜单中单击 Shell。
- 将
hw.sh输入到脚本编辑器屏幕顶部的单行文本字段中;这是 shell 脚本的文件名。 - 在多行文本字段中输入
#! /system/bin/sh,然后输入echo "hello, world"。前一行告诉 Android 在哪里可以找到sh(shell 程序),但看起来并不重要;第二行告诉 Android 向标准输出设备输出一些文本。 - 单击电话控件中的菜单按钮。
- 从出现的菜单中单击保存并退出菜单项。
图 A–3 显示了点击保存&退出前编辑屏幕的样子。
图 A–3。 SL4A 的脚本编辑器屏幕提示输入文件名和脚本。
脚本屏幕现在应该显示一个 hw.sh 项目。点击此项,您将看到出现在图 A–4 中的图标菜单。
图 A–4。 图标菜单可让您在终端窗口中运行脚本、在后台运行脚本、编辑脚本、重命名脚本或删除脚本。
您可以选择在终端窗口(最左边的图标)或后台(紧挨着最左边的“齿轮”图标)运行脚本。单击任一图标运行该 shell 脚本。然而,如果您在装有 Android 模拟器的 Windows 平台上运行这个脚本,您可能看不到任何输出(可能是由于 SL4A 本身的一个错误)。
访问 Linux Shell
如果您不能通过以前面提到的方式运行这个脚本来观察hw.sh的输出,您仍然可以通过 Linux shell 运行这个脚本来观察它的输出。按照以下步骤完成此任务:
- 从脚本屏幕的菜单中选择查看。
- 从弹出的可视列表中选择口译员。
- 从解释器屏幕中选择 Shell 以显示终端窗口。
- 在终端窗口的$提示符下执行
cd /sdcard/sl4a/scripts,切换到包含hw.sh的目录。 - 在$提示符下执行
sh hw.sh,运行hw.sh。
图 A–5 向您展示了如何从 shell 运行hw.sh。它还揭示了当您单击电话控制中的后退按钮时会发生什么。
图 A–5。 点击返回按钮得到一个“确认退出。杀死过程?”消息,并单击 Yes 按钮退出 shell。
安装 Python 解释器
虽然你不能用 SL4A 做很多事情,但是你可以用这个特殊的 app 来安装 Python 或者另一种脚本语言。完成以下步骤来安装 Python:
-
从主菜单中选择查看。
-
从弹出的可视列表中选择口译员。
-
按菜单电话控制按键。
-
Select Add from the menu. Figure A–6 reveals the Add interpreters list.
图 A–6。 添加菜单让你选择想要安装的脚本语言解释器。
-
点击 Python 2.6.2。SL4A 会开始从 SL4A 网站下载这个解释器。下载完成后,SL4A 呈现图 A–7 的通知。
图 A–7。 点击通知告诉 SL4A 你要安装 Python。
单击通知,SL4A 会显示一个对话框(参见图 A–8)询问您是否真的要安装 Python 应用。
图 A–8 点击安装开始安装。
单击安装按钮。SL4A 呈现图 A–9 的安装屏幕。
图 A–9。 安装屏幕让你在安装过程中尽情娱乐。
最后,当安装完成时,SL4A 显示如图图 A–10 所示的应用安装屏幕。
图 A–10。 点击打开按钮下载支持文件。
虽然安装了 Python 应用,但尚未安装包含示例脚本等项目的支持归档。单击“打开”按钮下载这些档案。Figure A–11 显示了结果屏幕的一部分,其中仅包含一个安装按钮。
图 A–11。 点击安装按钮开始下载安装支持文件。
点击安装后,SL4A 开始下载这些归档文件并提取它们的文件的任务。例如,图 A–12 显示了正在提取的python_r7.zip文件的内容。
图 A–12。 下载并解压 Android 模拟器上的所有支持文件需要几分钟时间。
当此过程完成时,您将看到一个类似于图 A–11 所示的屏幕,但带有一个卸载按钮。此时不要单击卸载。但是,如果您单击 BACK 按钮,您现在应该看到 Python 2.6.2 出现在解释器列表中,如图 Figure A–13 所示。
图 A–13。 点击 Python 2.6.2 运行 Python 解释器。
如果您现在单击 Python 2.6.2,则可以运行 Python 解释器。图 A–14 显示了介绍性屏幕。
图 A–14。 继续输入一些 Python 代码。如果你是 Python 新手,输入 help 。
独立于 SL4A 安装解释器
当你访问 SL4A 的项目网站([code.google.com/p/android-scripting](http://code.google.com/p/android-scripting))时,你会发现几个独立的解释器 apk,比如PythonForAndroid_r4.apk。这些 apk 包含比您从 SL4A 中安装解释器时获得的版本更新的版本。
例如,如果您想要安装最新的 Python 版本(在撰写本文时),请单击PythonForAndroid_r4.apk链接。在生成的网页上,用您的 Android 设备扫描条形码,或者(对于 Android 模拟器)单击PythonForAndroid_r4.apk链接将这个 APK 保存到您的硬盘上,然后执行adb install PythonForAndroid_r4.apk将这个 APK 安装到模拟设备上。图 A–15 显示了生成的图标。
图 A–15。 点击 Python for Android 图标,安装支持文件并执行其他操作。
单击 Python for Android 图标,该应用会显示用于安装支持文件和执行其他任务的按钮(参见图 A–16)。
图 A–16。 Python for Android 的屏幕可以让你安装支持文件和执行其他操作。它还显示版本信息等。
您可以用类似的方式安装其他独立的解释器 apk。
用 Python 编写脚本
现在您已经安装了 Python 2.6.2,您会想要尝试这个解释器。图 A–17 展示了 Python 的一个示例会话,包括打印版本号(从sys模块的version成员获得)、打印math模块的pi常量,以及执行exit()函数来终止 Python 解释器。
图 A–17。 终止 Python 解释器的一种方法是执行 Python 的exit()函数。
您还想从这个解释器访问 Android API。您可以通过导入android模块、实例化该模块的Android类并调用该类的方法来完成这项任务。图 A–18 展示了一个遵循此方法的会议,以展示祝酒词。
图 A–18。 Android方法返回带有标识符、结果和错误信息的Result对象。
Android类的方法返回Result对象。每个对象都提供了id、result和error字段:id惟一地标识对象,result包含方法的返回值(如果方法不返回值,则为None),而error标识可能发生的任何错误(如果没有发生错误,则为None)。
如果你对一个更有雄心的 Python 脚本感兴趣,你会想看看随 Python 解释器一起安装的示例脚本,这些脚本可以从脚本屏幕访问(见图 A–2)。例如,say_time.py脚本(其代码如以下代码所示)使用Android的ttsSpeak()函数说出当前时间:
import android; import time droid = android.Android() droid.ttsSpeak(time.strftime("%_I %M %p on %A, %B %_e, %Y "))
九、附录 B:Android NDK
Android 原生开发套件(NDK)通过将 C/C++ 源代码(在其中编写应用的性能关键部分)转换为运行在 Android 设备上的原生代码库,帮助您提升应用的性能。NDK 为构建活动、处理用户输入、使用硬件传感器等提供了头文件和库。您的应用文件(包括您创建的任何本机代码库)打包到 apk 中;它们在 Android 设备的 Dalvik 虚拟机中执行。
**注意:**仔细考虑是否需要在 app 中集成原生代码。即使应用的一部分基于本机代码,也会增加其复杂性,并使其更难调试。此外,并不是每个应用都经历了性能提升(除了在 Android 2.2 中引入的 Dalvik 的即时编译器已经提供的性能提升)。本机代码通常最适用于处理器密集型应用,但只有在性能分析发现存在瓶颈的情况下,才能通过在本机代码中重新编码这部分应用来解决。例如,一个具有计算密集型物理模拟的游戏应用,分析显示运行不佳,将受益于本地执行这些计算。
安装 NDK
如果你认为你的应用可以从用 C/C++ 部分表达中获益,你需要安装 NDK。在此之前,请完成以下准备工作:
- 验证您的开发平台是 Windows XP (32 位)或 Vista (32 位或 64 位)、Mac OS X 10.4.8 或更高版本(仅限 x86)还是 Linux (32 位或 64 位,在 Linux Ubuntu Dapper Drake 上测试)。NDK 官方只支持这些开发平台。
- 如果尚未安装 Android SDK(NDK 支持 1.5 或更高版本),请安装该软件。
- 验证您的平台包含 GNU Make 3.81 或更高版本以及 GNU Awk 的最新版本。要在 Windows 平台上运行 Make 和 Awk,必须先安装 Cygwin ,这是一个基于命令行的、类似 Unix 的 shell 工具,用于在 Windows 上运行类似 Linux 的程序。
安装 CYGWIN
必须安装 Cygwin1.7 或更高版本才能在 Windows 平台上运行 Make 和 Awk。完成以下步骤来安装 Cygwin:
-
将浏览器指向
[cygwin.com/](http://cygwin.com/)。 -
点击
setup.exe链接,将该文件保存到硬盘上。 -
在您的 Windows 平台上运行这个程序,开始安装 Cygwin 版本 1.7.8-1(撰写本文时的最新版本)。如果选择不同的安装位置,请确保目录路径不包含空格。
-
When you reach the Select Packages screen, select the Devel category and look for an entry in this category whose Package column presents make: The GNU version of the ‘make' utility. In the entry's New column, click the word Skip; this word should change to 3.81-2. Also, the Bin? column's checkbox should be checked – see Figure B–1.
图 B–1。 确保 3.81-2 出现在新列中,并且复选框在 Bin?在单击“下一步”之前,请检查列。
-
单击“下一步”按钮,继续安装。
Cygwin 在开始菜单中安装一个条目,并在桌面上安装一个图标。点击这个图标,您将看到 Cygwin 控制台(基于 Bash shell),如图图 B–2 所示。
图 B–2。 Cygwin 的控制台第一次开始运行时显示初始化信息。
如果您想验证 Cygwin 是否提供了对 GNU Make 3.81 或更高版本以及 GNU Awk 的访问,请输入图 B–3 中所示的命令来完成这项任务。
图 B–3。??awk工具不显示版本号。
你可以通过查看cygwin.com以及维基百科的 Cygwin 条目([en.wikipedia.org/wiki/Cygwin](http://en.wikipedia.org/wiki/Cygwin))来了解更多关于 Cygwin 的信息。
继续,将您的浏览器指向[developer.android.com/sdk/ndk/index.html](http://developer.android.com/sdk/ndk/index.html)并为您的平台下载以下 NDK 软件包之一——修订版 2011 年 1 月)是撰写本文时的最新版本:
android-ndk-r5B–windows.zip(Windows)android-ndk-r5B–darwin-x86.tar.bz2(Mac OS X:英特尔)android-ndk-r5B–linux-x86.tar.bz2(Linux 32/64 位:x86)
下载完您选择的包后,将其解压缩并将其android-ndk-r5b主目录移动到一个更合适的位置,也许是包含 Android SDK 主目录的同一个目录。
探索 NDK
现在,您已经在平台上安装了 NDK,您可能想要浏览它的主目录以发现 NDK 提供了什么。以下列表描述了位于基于 Windows 的 NDK 主目录中的目录和文件:
- 包含组成 NDK 构建系统的文件。
docs包含 NDK 的基于 HTML 的文档文件。Platforms包含子目录,这些子目录包含 Android SDK 安装的每个 Android 平台的头文件和共享库。samples包含展示 NDK 不同方面的各种示例应用。sources包含各种共享库的源代码和预构建的二进制文件,比如cpufeatures(检测目标设备的 CPU 家族及其支持的可选特性)和stlport(多平台 C++ 标准库)。Android NDK 1.5 要求开发者在这个目录下组织他们的本地代码库项目。从 Android NDK 1.6 开始,原生代码库存储在其 Android SDK 项目目录的jni子目录中。tests包含执行 NDK 自动化测试的脚本和源代码。它们对于测试定制的 NDK 非常有用。toolchains包含用于在 Linux、OS X 和 Windows(使用 Cygwin)平台上生成原生 ARM(高级 Risc 机器,Android 使用的 CPU,参见[en.wikipedia.org/wiki/ARM_architecture](http://en.wikipedia.org/wiki/ARM_architecture))二进制文件的编译器、连接器和其他工具。- 是进入 NDK 文档的入口。
GNUmakefile是 GNU make 使用的默认 Make 文件。ndk-build是一个简化构建机器码的 shell 脚本。ndk-gdb是一个 shell 脚本,用于轻松启动 NDK 生成的机器码的本地调试会话。README.TXT欢迎您来到 NDK,并确定各种文档文件,通知您当前版本的变化,提供 NDK 的概述,等等。RELEASE.TXT包含 NDK 的发布号。
每个platforms目录的子目录都包含头文件和面向稳定的本地 API 的共享库。Google 保证该平台的所有后续版本将支持以下 API:
- 安卓日志(
liblog) - Android 原生应用 API
- C 库(
libc) - C++ 最小支持(
stlport) - JNI 接口 API
- 数学库(
libm) - OpenGL ES 1.1 和 OpenGL ES 2.0 (3D 图形库)API
- OpenSL ES 本地音频库 API
- Android 2.2 及以上版本的像素缓冲区访问(
libjnigraphics) - Zlib 压缩(
libz)
**注意:**此列表中未列出的本机系统库不稳定,可能会在 Android 平台的未来版本中发生变化。不要使用它们。
来自 NDK 的问候
也许熟悉 NDK 编程最简单的方法是创建一个调用返回 Java String对象的本地函数的小应用。例如,清单 B–1 的NDKGreetings基于单个活动的应用调用一个native getGreetingMessage()方法来返回一条问候消息,该消息通过一个对话框显示。
清单 B–1。??NDKGreetings.java
`// NDKGreetings.java
package com.apress.ndkgreetings;
import android.app.Activity; import android.app.AlertDialog;
import android.os.Bundle;
public class NDKGreetings extends Activity { static { System.loadLibrary("NDKGreetings"); } private native String getGreetingMessage(); @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); String greeting = getGreetingMessage(); new AlertDialog.Builder(this).setMessage(greeting).show(); } }`
清单 B–1 的NDKGreetings类揭示了每个包含本机代码的应用的以下三个重要特性:
- 本机代码存储在外部库中,必须在调用其代码之前加载该库。库通常在类加载时通过调用
System.loadLibrary()方法来加载。这个方法使用一个单独的String参数来标识库,没有前缀lib和后缀.so。在这个例子中,实际的库文件被命名为libNDKGreetings.so。 - 声明了一个或多个对应于位于库中的函数的本机方法。Java 通过在返回类型前加上关键字
native来识别本地方法。 - 本地方法像任何其他 Java 方法一样被调用。在幕后,Dalvik 确保在库中调用相应的本机函数(用 C/C++ 表示)。
清单 B–2 将 C 源代码呈现给通过 Java 本地接口(JNI)实现getGreetingMessage()的本地代码库。
清单 B–2。??NDKGreetings.c
`// NDKGreetings.c
#include <jni.h>
jstring Java_com_apress_ndkgreetings_NDKGreetings_getGreetingMessage(JNIEnv env,* jobject this) { *return (env)->NewStringUTF(env, "Greetings from the NDK!"); }`
这个清单首先指定了一个#include预处理器指令,它包含了编译源代码时的jni.h头文件的内容。
清单然后声明了 Java 的getGreetingMessage()方法的本地函数等价物。这个本地函数的头揭示了几个重要的项目:
- 本机函数的返回类型被指定为
jstring。这个类型在jni.h中定义,代表 Java 在本地代码级别的String对象类型。 - 函数名必须以 Java 包名和类名开头,这些名称标识了关联的本机方法的声明位置。
- 函数的第一个参数
env的类型被指定为一个JNIEnv指针。jni.h中定义的JNIEnv,是一个 C 结构,标识可以被调用与 Java 交互的 JNI 函数。 - 函数的第二个参数
this的类型被指定为jobject。这种类型在jni.h中定义,在本地代码级别标识任意 Java 对象。传递给这个参数的实参是 JVM 传递给任何 Java 实例方法的隐式this实例。
该函数的单行代码取消了对其参数env的引用,以便调用NewStringUTF() JNI 函数。NewStringUTF()将它的第二个参数,一个 C 字符串,转换成它的等价的jstring(这个字符串是通过 Unicode UTF 编码标准编码的),并返回这个等价的 Java 字符串,然后这个字符串被返回给 Java。
**注意:**当在 C 语言的上下文中使用 JNI 时,您必须取消引用JNIEnv参数(例如*env,以便调用 JNI 函数。此外,您必须将JNIEnv参数作为第一个参数传递给 JNI 函数。相比之下,C++ 不需要这种冗长:您不必解引用JNIEnv参数,也不必将该参数作为第一个参数传递给 JNI 函数。例如,清单 B–2 的基于 C 的(*env)->NewStringUTF(env, "Greetings from the NDK!")函数调用在 C++ 中表示为env->NewStringUTF("Greetings from the NDK!")。
使用 Android SDK 构建和运行 NDKGreetings
要用 Android SDK 构建NDKGreetings,首先使用 SDK 的android工具创建一个NDKGreetings项目。假设一个 Windows XP 平台,一个存储NDKGreetings项目的C:\prj\dev层次结构(在C:\prj\dev\NDKGreetings中),并且 Android 2.3 平台目标对应于整数 ID 1,从文件系统中的任何地方调用下面的命令(为了可读性分成两行)来创建NDKGreetings:
android create project -t 1 -p C:\prj\dev\NDKGreetings -a NDKGreetings -k com.apress.ndkgreetings
该命令在C:\prj\dev\NDKGreetings中创建各种目录和文件。例如,src目录包含com\apress\ndkgreetings目录结构,最后的ndkgreetings目录包含一个骨架NDKGreetings.java源文件。用清单 B–1 替换这个框架文件的内容。
继续,在C:\prj\dev\NDKGreetings中创建一个jni目录,并将清单 B–2 复制到C:\prj\dev\NDKGreetings\jni。另外,将清单 B–3 复制到C:\prj\dev\NDKGreetings\jni\Android.mk,这是一个 GNU make 文件(在 NDK 文档中有解释),用于创建libNDKGreetings.so库。
清单 B–3。??Android.mk
`LOCAL_PATH := ./jni
include $(CLEAR_VARS)
LOCAL_MODULE := NDKGreetings LOCAL_SRC_FILES := NDKGreetings.c
include $(BUILD_SHARED_LIBRARY)`
如果您在 Windows 平台上工作,运行 Cygwin(如果没有运行),在 Cygwin 中,将当前目录设置为C:\prj\dev\NDKGreetings。参见图 B–4。
图 B–4。 到/prj/dev/NDKGreetings的路径以前缀/cygdrive/c开始。
假设 NDK 主目录是android-ndk-r5b,并且它位于驱动器 C 的根目录中,执行以下命令来构建库:
../../../android-ndk-r5b/ndk-build
如果 Cygwin 成功构建了库,它会显示以下消息:
Compile thumb : NDKGreetings <= NDKGreetings.c SharedLibrary : libNDKGreetings.so Install : libNDKGreetings.so => libs/armeabi/libNDKGreetings.so
这个输出表明libNDKGreetings.so位于您的NDKGreetings项目目录的libs子目录的armeabi子目录中。
**提示:**如果该命令输出包含短语No rule to make target的消息,编辑Android.mk删除多余的空格字符,然后重试。
假设C:\prj\dev\NDKGreetings是当前的,执行ant debug(从 Cygwin 的 shell 或普通的 Windows 命令窗口)来创建NDKGreetings-debug.apk。
这个 APK 文件放在NDKGreetings项目目录的bin子目录中。要验证libNDKGreetings.so是否是该 APK 的一部分,请从bin运行以下命令:
jar tvf NDKGreetings-debug.apk
您应该在jar命令的输出中看到包含lib/armeabi/libNDKGreetings.so的一行。
要验证应用是否工作,请启动模拟器,这可以通过在命令行执行以下命令来完成:
emulator -avd test_AVD
该命令假设您已经创建了第一章中指定的test_AVD设备配置。
继续,通过以下命令在仿真设备上安装NDKGreetings-debug.apk:
adb install NDKGreetings-debug.apk
该命令假设adb位于您的路径中。它还假设bin是当前目录。
当adb指示已经安装了NDKGreetings-debug.apk时,导航到应用启动器屏幕并点击 NDKGreetings 图标。figure B–5 向您展示了结果。
图 B–5。 按 Esc 键(在 Windows 上)使对话框消失。
对话框显示“来自 NDK 的问候!”通过调用本机代码库中的本机函数获得的消息。它还在屏幕顶部附近显示了一条微弱的“Hello World,NDKGreetings”消息。该消息源自项目的默认main.xml文件,该文件由android工具创建。
用 Eclipse 构建和运行 NDKGreetings
要用 Eclipse 构建NDKGreetings,首先创建一个新的 Android 项目,如第一章的秘籍 1-10 所述。为了方便起见,下面列出了完成此任务所需的步骤:
- 从“文件”菜单中选择“新建”,并从出现的弹出菜单中选择“项目”。
- 在 New Project 对话框中,展开向导树中的 Android 节点,选择该节点下的 Android 项目分支,点击 Next 按钮。
- 在弹出的新 Android 项目对话框中,在项目名称文本框中输入
NDKGreetings,取消勾选使用默认位置,在位置文本框中输入不带空格的路径;C:\prj\dev\NDKGreetings(假设 Windows),例如。这个输入的名称标识了存储NDKGreetings项目的文件夹。 - 如果未选中,请选择“在工作区中创建新项目”单选按钮。
- 在构建目标下,选中要用作
NDGreetings构建目标的适当 Android 目标的复选框。这个目标指定了您希望您的应用在哪个 Android 平台上构建。假设您只安装了 Android 2.3 平台,那么只有这个构建目标应该出现,并且应该已经被选中。 - 在属性下,在应用名称文本字段中输入
NDK Greetings。这个人类可读的标题将出现在 Android 设备上。继续,在包名文本字段中输入com.apress.ndkgreetings。该值是包名称空间(遵循与 Java 编程语言中的包相同的规则),所有源代码都将驻留在该名称空间中。如果未选中,请选中“创建活动”复选框,并在该复选框旁边的文本字段中输入NDKGreetings作为应用的启动活动的名称。未选中此复选框时,文本字段被禁用。最后,在 Min SDK Version 文本字段中输入整数9,以确定在 Android 2.3 平台上正确运行NDKGreetings所需的最低 API 级别。 - 单击完成。
接下来,使用 Eclipse 的包资源管理器来定位NDKGreetings.java源文件节点。双击这个节点,用清单 B–1 替换编辑窗口中显示的框架内容。
使用 Package Explorer,在 NDKGreetings 项目节点下创建一个 jni 节点,添加一个 jni 的 NDKGreetings.csubnode,用清单 B–2 替换这个节点的空内容,添加一个新的 jni 的 Android.mk 子节点,用清单 B–3 替换它的空内容。
启动 Cygwin 并使用cd命令切换到项目的文件夹;比如cd /cygdrive/c/prj/dev/NDKGreetings。然后,如前一节所示执行ndk-build;比如../../../android-ndk-r5b/ndk-build。如果一切顺利,NDKGreetings项目目录的libs子目录应该包含一个armeabi子目录,其中应该包含一个libNDKGreetings.so库文件。
最后,从项目菜单中选择构建项目;bin子目录应该包含一个NDKGreetings.apk文件(如果成功)。您可能想要执行jar tvfNDKGreetings.apk来验证这个文件是否包含lib/armeabi/libNDKGreetings.so。
要从 Eclipse 运行NDKGreetings,从菜单栏中选择 run,并从下拉菜单中选择 Run。如果出现 运行方式】对话框,选择 Android 应用并点击确定。Eclipse 使用test_AVD设备启动emulator,安装NDKGreetings.apk,并运行这个应用,其输出显示在图 B–5 中。
NDK 采样
NDK 安装主目录的samples子目录包含几个示例应用,展示了 NDK 的不同方面:
bitmap-plasma:一个应用,演示了如何从本机代码访问 Androidandroid.graphics.Bitmap对象的像素缓冲区,并使用该功能生成一个老派的“等离子体”效果。hello-gl2:一款使用 OpenGL ES 2.0 顶点和片段着色器渲染三角形的 app。(如果您在 Android 模拟器上运行此应用,您可能会收到一条错误消息,指出应用已意外停止,因为模拟器不支持 OpenGL ES 2.0 硬件模拟。)hello-jni:一个应用,从共享库中实现的本地方法中加载一个字符串,然后将其显示在应用的用户界面中。这个 app 和NDKGreetings很像。hello-neon:一个展示如何使用cpufeatures库在运行时检查 CPU 能力,然后在 CPU 支持的情况下使用 NEON(ARM 架构的 SIMD 指令集的市场名称)内部函数的应用。具体来说,该应用为 FIR 滤波器环路([en.wikipedia.org/wiki/Finite_impulse_response](http://en.wikipedia.org/wiki/Finite_impulse_response))实现了两个版本的微型基准,一个 C 版本和一个支持它的设备的 NEON 优化版本。native-activity:演示如何使用native-app-glue静态库创建本地活动(完全用本地代码实现的活动)的应用。native-audio:演示如何使用原生方法通过 OpenSL ES 播放声音的 app。native-plasma:用本地活动实现的bitmap-plasma的一个版本。san-angeles:通过原生 OpenGL ES APIs 渲染 3D 图形的应用,同时用android.opengl.GLSurfaceView对象管理活动生命周期。two-libs:动态加载共享库,调用库提供的原生方法的 app。在这种情况下,该方法在由共享库导入的静态库中实现。
您可以使用 Eclipse 以类似于NDKGreetings的方式构建这些应用。例如,执行以下步骤来构建san-angeles:
- 从“文件”菜单中选择“新建”,并从出现的弹出菜单中选择“项目”。
- 在 New Project 对话框中,展开向导树中的 Android 节点,选择该节点下的 Android 项目分支,点击 Next 按钮。
- 在出现的 New Android Project 对话框中,在项目名称文本字段中输入
san-angeles,并选择 Create Project from existing source 单选按钮。 - 点击位置字段旁边的浏览按钮,通过浏览文件夹对话框,选择 NDK 安装主目录下
samples子目录下的san-angeles子目录。单击确定。 - 选中构建目标区域中的 Android 2.3 目标复选框(或 Android 2.3.1 或 2.3.3 复选框,如果这是您的版本)。单击完成。
Eclipse 通过创建一个包含这个示例应用文件的DemoActivity项目做出响应,并在其包浏览器中显示这个项目的名称。
启动 Cygwin 并切换到项目的文件夹;比如cd/cygdrive/c/android-ndk-r5b/samples/san-angeles。然后,执行ndk-build;例如,../../ndk-build。如果一切顺利,san-angeles项目目录的libs子目录应该包含一个包含libsanangeles.so的armeabi子目录。
最后,从包资源管理器中选择DemoActivity,从项目菜单中选择构建项目;bin子目录应该包含一个DemoActivity.apk文件(如果成功)。您可能想要执行jar tvfDemoActivity.apk来验证这个文件是否包含lib/armeabi/libsanangeles.so。
从菜单栏中选择运行,并从下拉菜单中选择运行。如果出现运行方式对话框,选择 Android 应用并点击确定。Eclipse 用test_AVD设备启动emulator,安装DemoActivity.apk,并运行这个应用。如果成功,您应该会看到类似于图 B–6 中所示的屏幕。
图 B–6。 DemoActivity带你畅游立体城市。
十、附录 C:应用设计指南
这本书关注于使用各种 Android 技术开发应用的机制。然而,如果你想成为一名成功的 Android 开发者,知道如何创建一个应用是不够的。你还必须知道如何设计只有兼容设备的用户才能使用的应用,这些应用性能良好,响应用户,并能与其他应用正常交互。这个附录的秘籍给你必要的设计知识,让你的应用大放异彩。
C–1。设计过滤的应用
问题
当您将应用发布到 Google 的 Android Market 时,您不希望该应用对不兼容的设备可见。你希望 Android Market 过滤你的应用,让这些不兼容设备的用户无法下载应用。
解决办法
Android 运行在许多设备上,这给了开发者一个巨大的潜在市场。但是,并非所有设备都包含相同的功能(例如,一些设备有摄像头,而其他设备没有),因此某些应用可能无法在某些设备上正常运行。
认识到这个问题,谷歌提供了各种市场过滤器,每当用户通过 Android 设备访问 Android Market 时都会触发这些过滤器。如果一个应用不满足过滤器,该应用不会对用户可见。Table C–1 确定了当特定元素出现在应用清单文件中时触发的三个市场过滤器。
**表 C–1。**基于清单元素的市场过滤器
| **过滤器名称** | **清单元素** | **过滤器如何工作** | | :-- | :-- | :-- | | 最低框架版本 | `` | 一个应用需要最低的 API 级别。不支持该级别的设备将无法运行该应用。 API 等级用整数表示。比如整数 9 对应 Android 2.3(API 9 级)。 举例:``告知 Android Market 该应用仅支持 Android 2.3 及更高版本。 如果不声明该属性,Android Market 会假设默认值为`"` 1,`"`,表示该应用兼容所有版本的 Android。 | | 设备功能 | `` | 一个应用可以要求某些设备功能出现在设备上。这个功能是在 Android 2.0 (API Level 5)中引入的。 例子:``告诉 Android Market 设备必须有指南针。 抽象的`android.content.pm.PackageManager`类为`"android.hardware.sensor.compass"`和其他特性 id 定义了 Java 常量。 | | 屏幕大小 | `` | 一个应用通过设置``元素的属性来指示它能够支持的屏幕尺寸。当应用发布时,Android Market 根据用户设备的屏幕大小,使用这些属性来决定是否向用户显示应用。 示例:``告知 Android Market,该应用无法在 QVGA(240 x 320 像素)屏幕的设备上运行。 使用 API 级或更高级别的 app 默认`smallScreens`到`"true;"`之前的级别默认此属性为`"false."` Android Market 一般假设设备可以使较小的布局适应较大的屏幕,但不能使较大的布局适应较小的屏幕。因此,如果一个应用声明只支持“正常”屏幕尺寸,Android Market 会让该应用适用于正常和大屏幕设备,但会过滤该应用,使其不适用于小屏幕设备。 |Android Market 还使用其他应用特征(如使用该设备的用户当前所在的国家)来确定是否显示或隐藏应用。表 C–2 确定了三种市场过滤器,当这些附加特征中的一些出现时,这些过滤器就会被触发。
**表 C–2。**基于清单元素的市场过滤器
| **过滤器名称** | **过滤器如何工作** | | :-- | :-- | | 发布状态 | 只有已发布的应用才会出现在 Android Market 的搜索中。即使应用未发布,如果用户可以在他们购买、安装或最近卸载的应用的下载区域中看到它,也可以安装它。如果应用已被暂停,用户将无法重新安装或更新它,即使它出现在他们的下载中。 | | 定价状态 | 并非所有用户都能看到付费应用。要显示付费应用,设备必须有 SIM 卡,运行 Android 1.1 或更高版本,并且必须位于可以使用付费应用的国家(由 SIM 卡运营商确定)。 | | 国家/运营商目标 | 当你将应用上传到 Android Market 时,你可以选择特定的国家作为目标。该应用仅对您选择的国家/地区(运营商)可见,如下:- The operator of the device (if available) determines its country/region. If the operator cannot be determined, Android Market tries to determine the country based on IP.
- Determine the operator according to the SIM card of the device (applicable to GSM devices), not the currently roaming operator.
|
]
C–2。设计高性能应用
问题
应用应该表现良好,尤其是在内存有限的设备上。此外,性能更好的应用消耗的电池电量更少。你想知道如何设计你的应用以获得良好的性能。
解决办法
Android 设备在很多方面都有所不同。一些设备可能具有比其他设备更快的处理器,一些设备可能具有比其他设备更大的内存,并且一些设备可能包括实时(JIT)编译器,而其他设备不具有通过将字节码指令序列动态转换为等效的本机代码序列来加速可执行代码的技术。以下列表列出了编写代码时需要考虑的一些事项,以便您的应用能够在任何设备上良好运行:
- **仔细优化你的代码:**在考虑优化代码之前,努力用一个坚实的架构来编写不影响性能的应用。一旦应用正确运行,在各种设备上分析其代码,并寻找使应用变慢的瓶颈。请记住,模拟器会给你一个错误的应用性能的印象。例如,它的网络连接基于您的开发平台的网络连接,比您可能在许多 Android 设备上遇到的要快得多。
- **最小化对象创建:**对象创建会影响性能,尤其是在垃圾收集方面。你应该尽可能地重用现有的对象,以尽量减少垃圾收集周期,垃圾收集周期会暂时降低应用的速度。例如,使用一个
java.lang.StringBuilder对象(或者当多个线程可能访问这个对象时使用一个java.lang.StringBuffer对象)来构建字符串,而不是在一个循环中使用字符串连接操作符,这会导致创建不必要的中间String对象。 - **尽量减少浮点运算:**浮点运算在 Android 设备上比整数运算慢一倍左右;例如,无浮点单元和无 JIT 的 G1 设备。此外,请记住,一些设备缺乏基于硬件的整数除法指令,这意味着整数除法是在软件中执行的。当涉及到哈希表(依赖于余数操作符)时,由此产生的缓慢尤其令人烦恼。
- 在任何需要执行复制的地方使用 system . array copy():
java.lang.System类的static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)方法比用 JIT 在 Nexus One 上手工编码的循环快 9 倍左右。 - **避免枚举:**枚举虽然方便,但会增加
.dex文件的大小并影响速度。例如,public enum Directions { UP, DOWN, LEFT, RIGHT }向一个.dex文件添加了几百个字节,相比之下,等效的类有四个public static final int - **使用增强的 for 循环语法:**一般来说,在没有 JIT 的设备上,增强的 for 循环(如
for (String s: strings) {})比常规的 for 循环(如for (int i = 0; i < strings.length; i++))要快,当涉及 JIT 时,也不比常规的 for 循环慢。因为增强的 for 循环在迭代一个java.util.ArrayList实例时会慢一些,所以应该使用常规的 for 循环来代替 arraylist 遍历。
您还需要仔细选择算法和数据结构。例如,线性搜索算法(从头到尾搜索一系列项目,将每个项目与一个搜索值进行比较)平均检查一半的项目,而二分搜索法算法使用递归除法技术来定位搜索值,只需很少的比较。例如,对 40 亿个条目的线性搜索平均有 20 亿次比较,而二分搜索法最多执行 32 次比较。
C–3。设计响应式应用
问题
对用户响应缓慢的应用,或者看起来挂起或冻结的应用,有触发应用不响应对话框的风险(见图 C–1),这给用户机会杀死应用(并可能卸载它)或继续等待,希望应用最终会响应。
图 C–1。 可怕的应用不响应对话框可能会导致用户卸载应用。
你想知道如何设计响应性应用,这样你就可以避免这个对话框(很可能会给不感兴趣的用户带来坏名声)。
解决办法
当应用无法响应用户输入时,Android 会显示应用不响应对话框。例如,应用阻塞 I/O 操作(通常是网络访问)会阻止主应用线程处理传入的用户输入事件。在 Android 确定的时间长度后,Android 得出应用被冻结的结论,并显示此对话框,让用户选择终止应用。
同样,当一个应用花费太多时间来构建一个复杂的内存数据结构,或者该应用正在执行一个密集的计算(例如计算象棋或其他一些游戏的下一步棋),Android 会认为该应用已经挂起。因此,使用方法 C–2 中描述的技术来确保这些计算是有效的,这一点很重要。
在这些情况下,应用应该创建另一个线程,并在该线程上执行大部分工作。对于活动来说尤其如此,活动应该在关键的生命周期回调方法中做尽可能少的工作,比如onCreate(Bundle)和onResume()。因此,主线程(驱动用户界面事件循环)保持运行,Android 不会得出应用冻结的结论。
**注:**活动管理器和窗口管理器(见第一章、图 1-1 )监控 app 响应性。当他们检测到在 5 秒内没有对输入事件(例如,按键或触摸屏幕)做出响应,或者广播接收器在 10 秒内没有完成执行时,他们断定应用已经冻结,并显示应用没有响应对话框。
C–4。设计无缝应用
问题
你想知道如何设计你的应用,以便与其他应用正常交互。具体来说,你想知道你的应用应该避免做哪些事情,这样才不会给用户带来问题(并面临被卸载的可能性)。
解决办法
你的应用必须与其他应用公平竞争,这样它们就不会在用户与某个活动交互时弹出对话框之类的事情来打扰用户。此外,您不希望应用的某个活动在暂停时丢失状态,让用户在返回到该活动时困惑于为什么之前输入的数据会丢失。换句话说,你希望你的应用与其他应用很好地协作,这样它就不会扰乱用户的体验。
实现无缝体验的应用必须考虑以下规则:
- **不要丢弃数据:**因为 Android 是一个移动平台,所以可以在你的应用的活动上弹出另一个活动(可能是一个来电触发了电话应用)。当这种情况发生时,你的 activity 的
void onSaveInstanceState(Bundle outState)和onPause()回调方法被调用,你的 app 很可能会被杀死。如果用户当时正在编辑数据,除非通过onSaveInstanceState()保存,否则数据将会丢失。数据随后以onCreate()或void onRestoreInstanceState(Bundle savedInstanceState)方式恢复。 - **不要暴露原始数据:**暴露原始数据不是一个好主意,因为其他 app 必须理解你的数据格式。如果您更改格式,这些其他应用将会中断,除非进行更新以考虑格式更改。相反,您应该创建一个通过精心设计的 API 公开数据的
ContentProvider实例。 - **不要打断用户:**当用户正在与一个活动交互时,如果被一个弹出对话框打断(可能是由于
startActivity(Intent)方法调用而通过后台服务激活),用户会不高兴。通知用户的首选方式是通过android.app.NotificationManager类发送消息。该消息出现在状态栏上,用户可以在方便时查看该消息。 - **将线程用于长时间的活动:**执行长时间计算或涉及其他耗时活动的组件应该将这项工作转移到另一个线程。这样做可以防止应用不响应对话框出现,并减少用户从设备上卸载应用的机会。
- **不要让单个活动屏幕过载:**用户界面复杂的应用应该通过多个活动来呈现用户界面。这样,用户就不会被屏幕上出现的大量项目淹没。此外,您的代码变得更易于维护,并且它也可以很好地与 Android 的活动堆栈模型兼容。
- **设计您的用户界面以支持多种屏幕分辨率:**不同的 Android 设备通常支持不同的屏幕分辨率。一些设备甚至可以动态改变屏幕分辨率,例如切换到横向模式。因此,重要的是要确保你的布局和绘图能够灵活地在不同的设备屏幕上正确显示。通过为关键屏幕分辨率提供不同版本的图稿(如果有),然后设计布局以适应各种尺寸,可以完成这项任务。(例如,避免使用硬编码的位置,而是使用相对布局。)做这么多,系统处理其他任务;结果是一个在任何设备上都很棒的应用。
- 假设网络很慢: Android 设备有多种网络连接选项,有些设备比其他设备快。然而,最小公分母是 GPRS(GSM 网络的非 3G 数据服务)。即使支持 3G 的设备也要在非 3G 网络上花费大量时间,因此在未来很长一段时间内,慢速网络仍将是现实。因此,在编写应用时,一定要尽量减少网络访问和带宽。不要假设网络快;计划它是缓慢的。如果你的用户碰巧在更快的网络上,他们的体验只会得到改善。
- 不要假设触摸屏或键盘: Android 支持各种类型的输入设备:一些 Android 设备具有完整的“QWERTY”键盘,而其他设备具有 40 键、12 键或其他键配置。同样,一些设备有触摸屏,但许多设备没有。设计应用时,请记住这些差异。不要假设特定的键盘布局,除非你想限制你的应用只能在某些设备上使用。
- **节省设备电池:**移动设备由电池供电,最大限度地减少电池消耗非常重要。两个最大的电池功耗是处理器和无线电,这就是为什么编写使用尽可能少的处理器周期和尽可能少的网络活动的应用很重要。最大限度地减少应用占用的处理器时间归结为编写高效的代码。最大限度地减少使用无线电的功耗归结为优雅地处理错误条件和只获取需要的数据。例如,如果一次尝试失败,不要不断地重试网络操作。如果它失败了一次,另一次立即尝试很可能会失败,因为用户没有接收;你只会浪费电池的能量。请记住,用户会注意到一个耗电的应用,并且最有可能卸载该应用。