如何测试并调试基于 NDK 的 Android 应用

本文概述了如何为 x86 平台测试和调试基于 NDK 的应用。 本文将从头至尾完整展示如何创建示例应用,并演示如何测试和调试。

1. 开发环境

请确保已安装了 Android 应用开发环境。 如果尚未安装,请查看 Android 开发人员网站上的说明[1]。 在下文中,我们仅列出了所需的主要组件。

  • Android SDK: Android 软件开发套件可提供必要的工具和库,帮助开发在基于 Android 的设备上运行的应用。
  • Android NDK: Android NDK 是一款支持您在 Android 应用中嵌入使用原生代码的组件的工具套件。

在该示例中,我们仅需要上述两个组件,但是如果您想要使用 IDE,则需要下载并安装下列组件:

Eclipse*:多个项目中的常用 IDE,包括通过安装 ADT 插件获得的 Android 项目。

ADT: Android 开发工具套件,面向 Eclipse 的插件。

CDT: C/C++ 开发工具套件 — 如果您希望在 Eclipse 中构建 C/C++ 代码。

Cygwin*: Cygwin 是可为 Windows* 提供 Linux* 界面(look-and-feel)环境的工具集。 在 Windows OS 中,我们需要用它运行“ndk-build”来构建原生代码。

2. 测试环境

对于 x86 平台目标,需要使用基于 x86 的设备或 x86 模拟器进行测试。 随着今年四月 IA 手机的上市,如 Lava Xolo X900 和 Lenovo K800,采用 Android 的 x86 平板电脑和手机将越来越普遍。 对于没有 x86 设备需要测试的用户,使用 x86 模拟器即可。 Android SDK 管理器可用于安装 x86 模拟器,且面向英特尔凌动平台的 Android 4.0.3 和 Android 2.3.3 映像已在官方软件版本中提供。 请见图 1 和 2。


图 1. 面向 Android* 4.0.3 的英特尔® 凌动? x86 系统映像


图 2. 面向 Android* 2.3.3 的英特尔® 凌动? x86 系统映像

对于无法在官方版本中找到的 Android 版本,请尝试从 Android 源代码构建并将其放入相应的文件夹下(例如对于 Android 2.3.3,放在 $ANDROID_NDK_ROOT/platforms/android-10/images/ 下)。

英特尔® 硬件加速执行管理器(英特尔® HAXM)也可帮助开发人员获得更快的 Android 模拟速度。 HAXM 是一个硬件辅助虚拟化引擎(管理程序),该引擎可以使用英特尔® 虚拟化技术 (Intel®VT) 加快主机上的 Android 应用模拟速度。 配合使用英特尔提供的 Android x86 模拟器映像和正式版的 Android SDK 管理器,HAXM 可以帮助您在英特尔 VT 支持的系统上更快地进行模拟。


图 3.英特尔® 硬件加速执行管理器

3. 编写示例代码

现在,我们将创建一个名为 CPUIdApp [3] 的简单应用,它可以返回主机 CPU 的主要特性。 原生代码有两种主要功能:一种是获取 CPU 信息;另一种是分析字符串。 您可以将其写入 cpuid.c 文件,在 Linux 操作系统中使用 gcc 构建,或在 Windows 操作系统中使用 Cygwin 构建。 运行示例应用后,解析的 CPU ID 信息将会出现在屏幕上。 我们的目的是在示例活动中展示解析的 CPU ID 信息。 关于如何创建该项目的更多详细信息,请参阅 [1, 4]。

第一步是创建一个 Android 项目。 请从附件的 cupid.c 中查看源代码。

$mkdir jni-cpuid && cd jni-cpuid

该命令将创建一个简单的“Hello World”Android 项目。

$android create project –package “com.test.cpuid� –activity “CPUIdApp� –path . –target “android-9�

为了实现我们的目标,我们需要将 src/com/test/cupid/CPUIdApp.java 更改为下列内容:

……
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
		TextView tv = new TextView(this);   //create a TextView as a container for the result
		tv.setText(cpuid_get());  //get parsed CPU ID information from native function and set to tv
		setContentView(tv);
    }
 
	private static native String cpuid_get();  //declare a native function but do not implement
	static { System.loadLibrary("cpuid"); }   //load the native library contains the native function we need
}

下一步是在原生代码中实现 cupid_get 功能。

$ant debug
$javah –d jni –classpath bin/classes com.test.cpuid.CPUIdApp

然后系统将自动创建包含 com_test_cpuid_CPUIdApp.h 的 jni 目录,并在头文件中声明需要实现的功能。

JNIEXPORT  jstring  JNICALL  Java_com_test_cpuid_CPUIdApp_cpuid_1get
  (JNIEnv *, jclass);

然后,将 cupid.c 移至 jni 并创建一个 com_test_cpuid_CPUIdApp.c 文件来实现 Java_com_test_cpuid_CPUIdApp_cpuid_1get 功能。

#include "com_test_cpuid_CPUIdApp.h"
extern void cpuid_parse(char *buf);
 
JNIEXPORT jstring JNICALL Java_com_example_cpuid_CPUIdApp_cpuid_1get(JNIEnv *env, jclass jc)
{
        char buf[1024];
        cpuid_parse(buf);
        return (*env)->NewStringUTF(env, buf);
}
The last step is to create Android.mk file for building the native code in the next session.
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE    := cpuid
LOCAL_SRC_FILES := cpuid.c com_test_cpuid_CPUIdApp.c
include $(BUILD_SHARED_LIBRARY)

4. 在 x86 模拟器上构建、部署和测试

现在开始运行 “ndk-build”来构建原生代码吧。 如要为 x86 架构构建,需要将 “APP_ABI=x86”添加为参数或 Android.mk 文件上。

$ndk-build APP_ABI=x86

Compile x86    : cpuid <= cpuid.c
Compile x86    : cpuid <= com_test_cpuid_CPUIdApp.c
SharedLibrary  : libcpuid.so
Install        : libcpuid.so => libs/x86/libcpuid.so
 

然后使用 “ant” 构建整个项目。 请在构建项目前先清理项目。

$ant clean && ant debug

CPUIdApp-debug.apk 将在“bin”目录中创建并可通过“adb install”命令进行安装。

$adb install bin/CPUIdApp-debug.apk

最后,在模拟器中运行 CPUIdApp 来查看输出。

5. 调试以检查错误

有两种方式可以调试 Android NDK 应用: adb logcat and ndk-gdb. 我们将在本部分讨论这两种方法。

5.1 使用 adb logcat 获取日志输出

对于基于 Java* 的应用,如果使用了 Log.d(),可以通过运行“adb logcat”来获取日志信息。 运行 “adb logcat”命令时,也会显示其它应用和系统的日志。

运行我们之前在模拟器上创建的 CPUIdApp 后,将会出现一个错误对话框,应用将崩溃。 使用“adb logcat”检查出现了什么问题。

$adb logcat

…
D/dalvikvm( 1616): No JNI_OnLoad found in /data/data/com.test.cpuid/lib/libcpuid.so 0xb49300f8, skipping init
W/dalvikvm( 1616): No implementation found for native Lcom/test/cpuid/CPUIdApp;.cpuid_get ()Ljava/lang/String;
…

日志表示 Lcom/test/cupid/CPUIdApp.cpuid_get 功能未实现。 在 jni/com_test_cpuid_CPUIdApp.c 中检查代码后,我们发现 Java_com_example_cpuid_CPUIdApp_cpuid_1get 应命名为 Java_com_test_cpuid_CPUIdApp_cpuid_1get。 请注意, Typos 是开发中经常会出现的一个问题。 因此,需要在 jni/com_test_cpuid_CPUIdApp.c 中将 Java_com_example_cpuid_CPUIdApp_cpuid_1get 重命名为 Java_com_test_cpuid_CPUIdApp_cpuid_1get 并重新构建该项目。

$ndk-build APP_ABI=x86
$ant clean && ant debug
$adb install –r bin/CPUIdApp-debug.apk

然后,在模拟器中重新运行 CPUIdApp。 正常输出如下图所示。 如果您希望了解这些 CPUID 操作码的具体信息,请参阅 [4, 10]。


图 4. CPUIdApp 结果

如欲了解其他信息,可使用 android.util.Log 提供的一系列 Log.*() 功能来打印所需信息。 这在 Java 文件中非常简单:

…
import android.util.Log;
…
Log.d(“CPUIdApp”, “try to get cupid_get…”);  //print useful information as needed
…

重新构建并运行 CPUIdApp 后,同样使用“adb logcat”获取日志输出:

D/CPUIdApp( 2327): try to get cpuid_get...

事实上,它不仅能够在 Java 代码中使用,也能够在 C/C++ 代码中使用。 按照下列方式修改 com_test_cpuid_CPUIdApp.c:

…
#include <android> 
#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, “CPUIdApp”, __VA_ARGS__)) 
… 
JNIEXPORT jstring JNICALL Java_com_test_cpuid_CPUIdApp_cpuid_1get(JNIEnv *env, jclass jc) 
{ 
        char buf[1024]; 
        cpuid_parse(buf); 
        LOGI("cpuid_1get buf value: "); //the same way in native side to print useful information 
        LOGI(buf); 
        return (*env)->NewStringUTF(env, buf); 
} 
To build this, we need to modify Android.mk, by adding: 
LOCAL_LDLIBS := -llog 
LOCAL_C_INCLUDES += system/core/include/cutils 
LOCAL_SHARED_LIBRARIES := libcutils 
</android>

然后:

$ndk-build APP_ABI=x86 && ant clean && ant debug && adb install –r bin/CPUIdApp-debug.apk

运行后您将会获得下列日志信息:

I/CPUIdApp( 2475): cpuid_1get buf value:
I/CPUIdApp( 2475): CPUID Features(EAX=1): ECX = 0x00000201 EDX = 0x0781abfd
I/CPUIdApp( 2475): HT - NO
I/CPUIdApp( 2475): VT - NO
I/CPUIdApp( 2475): SSE - YES
I/CPUIdApp( 2475): SSE2 - YES
I/CPUIdApp( 2475): SSE3 - YES
I/CPUIdApp( 2475): SSSE3 - YES
I/CPUIdApp( 2475): SSE41 - NO
I/CPUIdApp( 2475): SSE42 - NO
I/CPUIdApp( 2475): AES - NO
I/CPUIdApp( 2475): AVX - NO

5.2 使用 ndk-gdb 调试 C/C++ 代码

Android NDK r4 采用了一个名为 “ndk-gdb”的辅助 shell 脚本,以便能够在 NDK 生成的机器代码中轻松启动原生调试会话。 脚本位于 NDK 的顶级目录,可通过应用项目目录或其他子目录中的命令行来调用。

我们试一下:

在项目目录的根目录中,运行“ndk-gdb --start”,以下的输出将会出现在终端上:

发现的可调试标记: false
错误: 软件包 com.test.cpuid 不可调试! 您可以通过以下两种方式进行修复:
— 调用“ndk-build”时,使用 NDK_DEBUG=1 重新构建。
— 修改您的清单以设置 android:将可调试属性设置为“true”,然后按照正常步骤重新构建。

使用上述两种方式: 1)修改清单,并在应用标签中添加 android:debuggable=”true” ;并 2) 通过运行 “ndk-build APP_ABI=x86 NDK_DEBUG=1” 进行重新构建。

$ ndk-build APP_ABI=x86 NDK_DEBUG=1
$ ant clean && ant debug && adb install –r bin/CPUIdApp-debug.apk
$ndk-gdb –start com.test.cpuid.CPUIdApp –force –verbose

应用被启动,并将显示:

…
…
and track explicitly loaded dynamic code.
0xb7eb2ff9 in __futex_syscall4 ()
   from /home/user/labs/cpuid/obj/local/x86/libc.so
(gdb)

所有“gdb”命令均可用于调试原生代码。 有时,在我们看到 “gdb”提示前,原生部分已完成。 此时,在 src/com/test/cpuid/CPUIdApp.java 处添加 “android.os.Debug.waitForDebugger()”:

…
import android.os.Debug;
…
        Log.d("CPUIdApp", "waiting for debug ...");
        android.os.Debug.waitForDebugger();  //make app pause
        Log.d("CPUIdApp", "try to get cpuid_get...");
        tv.setText(cpuid_get());
…

通过这种方式,我们可以对 cupid_get() 功能进行调试并获得更多详细信息。

6. 结论

在本文中,我们借助简单的示例应用介绍了基于 Android NDK 的应用的创建、安装、测试和调试。您可以在真正的 x86 设备或 x86 模拟器上,并通过“adb logcat”和 Android SDK/NDK 工具中提供的“ndk-gdb”命令来测试应用。 这些设备可在真正的 x86 设备或 x86 模拟器上测试,并可通过 “adb logcat”(由 Android SDK/NDK 工具提供)进行调试。

使用“adb logcat”获取简单信息或与系统相关的信息。

如欲了解详细信息,需要使用“ndk-gdb”。

我们仅介绍了如何通过命令行执行这些步骤,对于想要使用 Eclipse 的开发人员,请参阅 [7 和 8]。

参考:

[1] Android 开发人员网站。 http://developer.android.com/index.html

[2] 示例 NDK 应用。 http://developer.android.com/sdk/ndk/overview.html#samples=

[3] 英特尔硬件加速执行管理器安装指南。 https://software.intel.com/en-us/articles/installation-instructions-for-intel-hardware-accelerated-execution-manager-windows/

[4] Wiki 上有关 CPUID opcode 的信息。 http://en.wikipedia.org/wiki/CPUID

[5] 面向英特尔架构的基于 NDK 的 Android 应用。 https://software.intel.com/en-us/articles/creating-and-porting-ndk-based-android-apps-for-ia/

[6] 借助 ndk-debug 使用 cgdb。 http://mhandroid.wordpress.com/2011/01/23/using-cgdb-with-= ndk-debug-and-cgdb-tutorial/

[7] 使用用于 Android C/C++ 调试的 Eclipse。 http://mhandroid.wordpress.com/2011/01/23/using-eclipse-fo= r-android-cc-debugging/

[8] 使用用于 Android C/C++ 开发的 Eclipse。 http://mhandroid.wordpress.com/2011/01/23/using-eclipse-fo= r-android-cc-development/

[9] 面向英特尔架构的 Android NDK。 https://software.intel.com/en-us/articles/ndk-for-ia/

[10] 英特尔® 64 和 IA-32 架构开发人员手册: Vol.2A. http://www.intel.com/content/www/cn/zh/architecture-and-technology/64-ia-32-archit= ectures-software-developer-vol-2a-manual.html

[11] NDK-GDB 介绍。 http://source-android.frandroid.com/ndk/docs/NDK-GDB.html

声明

本文件中包含关于英特尔产品的信息。 本文件不构成对任何知识产权的授权,包括明示的、暗示的,也无论是基于禁止反言的原则或其他。 英特尔不承担任何其他责任。英特尔在此作出免责声明:本文件不构成英特尔关于其产品的使用和/或销售的任何明示或暗示的保证,包括不就其产品的(i)对某一特定用途的适用性、(ii)适销性以及(iii)对任何专利、版权或其他知识产权的侵害的承担任何责任或作出任何担保。

除非经过英特尔的书面同意认可,英特尔的产品无意被设计用于或被用于以下应用:即在这样的应用中可因英特尔产品的故障而导致人身伤亡。

英特尔有权随时更改产品的规格和描述而毋需发出通知。 设计者不应信赖任何英特产品所不具有的特性,设计者亦不应信赖任何标有“保留权利”或“未定义”的说明或特性描述。 对此,英特尔保留将来对其进行定义的权利,同时,英特尔不应为因其日后更改该等说明或特性描述而产生的冲突和不相容承担任何责任。 此处的信息可能随时更改,恕不另行通知。 请勿使用本文信息完成一项产品设计。

本文件所描述的产品可能包含使其与宣称的规格不符的设计缺陷或失误。 这些缺陷或失误已收录于勘误表中,可索取获得。

在发出订单之前,请联系当地的英特尔营业部或分销商以获取最新的产品规格。

索取本文件中或英特尔的其他材料中提的、包含订单号的文件的复印件,可拨打1-800-548-4725,或登陆 http://www.intel.com/design/literature.htm

在性能检测过程中涉及的软件及其性能只有在英特尔微处理器的架构下方能得到优化。 诸如 SYSmark 和 MobileMark 等测试均系基于特定计算机系统、硬件、软件、操作系统及功能。 上述任何要素的变动都有可能导致测试结果的变化。 请参考其他信息及性能测试(包括结合其他产品使用时的运行性能)以对目标产品进行全面评估。

对本文件中包含的软件源代码的提供均依据相关软件许可而做出,任何对该等源代码的使用和复制均应按照相关软件许可的条款执行。

英特尔、Atom、凌动和 Intel 标识是英特尔在美国和/或其他国家的商标。

英特尔公司 © 2013 年版权所有。 保留所有权利。
*其他的名称和品牌可能是其他所有者的资产。

有关编译器优化的更完整信息,请参阅优化通知