并行 Android 开发





5.00/5 (1投票)
Android 原生代码中的并行处理。
Android 原生代码中的并行处理。
对于那些一直居住在巨大矿石堆下面的人来说,最近为移动设备构建游戏和其他娱乐应用程序已经变得很时尚。事实上,一个非常有利的论点是,构成移动计算领域的数十亿(如果不是万亿)美元产业很大程度上是由娱乐产业驱动的。用户可以轻松地为其设备购买游戏,随身携带这些设备,并在我母亲过去称之为“生活中的小小宁静时刻”玩它们——例如排队、乘坐交通工具,或者等待约会对象出现——这是消费者一次又一次地花费大量金钱的原因。
但前提是游戏不能很糟糕。
在移动设备上构建流畅动画、响应迅速、性能良好的游戏是开发者(及其公司)利用这个利润丰厚的收入来源的关键——消费者对滞后、卡顿或运行缓慢的游戏耐心微乎其微。(消费者可以轻松获取你的应用程序,这也意味着他们可以轻松地放弃你的应用程序去购买别人的。)虽然这不仅仅适用于游戏,但大多数商业应用程序根本不会利用游戏通常需要的 CPU 性能。
对于 Android 平台上的开发者来说,让事情快速运行有时意味着要放弃 Java 并编写原生代码。事实上,普遍的观点认为,如果一个 Android 游戏有任何程度的中等动画需求,那么这个游戏的大部分(如果不是全部)核心都应该用原生代码编写。然而,对于许多开发者来说,转向原生代码意味着用 C++ 编写,而对于许多开发者来说,用 C++ 编写代码,特别是编写真正快速的 C++ 代码,意味着要花费大量时间在调试器上。如果还需要编写能够同时在多个线程上执行的代码,那么就会有想法干脆等待下一代 Android 硬件,理由是“在新硬件上运行得很好(然后我就不必调试它了)”。
幸运的是,英特尔已经挺身而出,为 Android 社区提供了一个适用于 Android 平台的 Threading Building Blocks (TBB) 库版本。有了它,再加上 Google Native Development Kit,用 C++ 编写 Android 应用的部分,并通过 JNI 接口从 Java 访问,就不会那么可怕了,这样可以获得两全其美的好处。如果您不熟悉该库,英特尔有一个网站,其中包含所有 TBB 文档,网址为 http://software.intel.com/en-us/intel-tbb/,并且下载本身在安装根目录下的“samples”目录中包含了各种使用 TBB 的示例。在本文中,我们将只关注 TBB/Android 集成。
顺便说一下,英特尔在 Android 上的投入肯定不止于此——他们最近还发布了一个针对基于英特尔 CPU 的主机平台的改进版 Android 模拟器,所以如果您还没有更新您的 Android SDK Manager 来下载它,请立即更新,或者直接在此 处下载新的模拟器。速度差异非常显著,而且鉴于其成本(免费),这似乎是一个非常容易的胜利。
入门
与所有库一样,使用 TBB 的起点是下载开源网站上的最新版本,网址为 http://software.intel.com/en-us/intel-tbb/。从顶部菜单选择“Download”,然后选择“Stable Releases”,Windows、Linux 和 Mac OSX 的三个 TBB 版本等待您的鼠标点击。不过,值得注意的是,Android 开发者想要的版本是 Linux 版本,无论他们在哪种平台上编写 Android 应用程序代码。TBB 网站在 Linux 下载链接上指出了这一点,但开发者经常为他们选择的开发平台下载大量东西,以至于有时需要花点时间才能意识到 TBB 库是针对目标平台——Android——而不是开发者主机平台的。
下载后,打开存档并将它存储在方便的地方。存档的“lib”文件夹中,“android”目录包含 Android 兼容的二进制文件,这些文件将用于链接 TBB 库,而头文件(如常例)则位于存档的“include”目录中。具体来说,TBB 的 include 文件位于“include/tbb”目录中,但由于通用的 C++ 实践是使用目录名作为头文件 include 的一部分(例如,include 看起来像“#include <tbb/parallel_for.h>”),以便区分来自不同库的头文件,因此最好将“<TBB_SDK>/include”目录添加到 C++ 编译器的 include 路径中。
基础
从架构上看,使用 TBB 的 Android 应用程序将具有双重性格:一部分用典型的 Java/Android 编写,另一部分用 C++ 编写并通过 JNI 访问。从技术上讲,不可能为 Android 编写一个完整的 C++ 应用程序,尽管有一些“技巧”可以绕过此限制。这意味着任何希望开始使用 TBB for Android 的开发者都需要熟悉 JNI 工具和 API,以及 Java 和 C++,否则(更有可能的是)工作将分散在开发者之间——一些熟悉 Java,一些熟悉 C++,使用 JNI 层作为“黑盒子”。毫无疑问,这将使开发过程变得复杂,但原生代码和充分利用设备硬件的性能优势可以大大弥补成本。
创建一个使用 JNI 的 Android 项目比一个“普通”的纯 Java Android 项目要棘手一些;它需要使用 Android Native Development Kit (NDK),而 NDK 又需要原生工具和构建脚本 (Makefile)。因此,在这里演示 TBB 将是一个极其简单的演示,以使事情尽可能简单。
步骤 1:“推我!”
首先,我们需要一个 Android 应用程序,它将响应按钮点击“做某事”,并在 Toast 消息中显示按钮点击的结果。因此,在您选择的工具集(Eclipse、IDEA,或者我最喜欢的命令行)中创建项目后,在主 Activity 布局中添加一个大的按钮,类似这样:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<Button
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:text="Push Me!"
android:onClick="onButtonPushed" />
</LinearLayout>
请记住,还要在 Activity 中添加一个名为“onButtonPushed
”的消息处理程序。
package com.tedneward.tbbdemo; import android.app.Activity; import android.os.Bundle; import android.view.*; import android.widget.*; public class MainActivity extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); } public void onButtonPushed(View view) { Toast.makeText(this, "Pushed me!", Toast.LENGTH_SHORT).show(); } }
现在还没有太多可看的。好吧,不完全是:构建它,安装它,运行它,它就会完全按照看起来的样子执行:显示一个按钮,按下后,向用户弹出“Pushed me!”的 Toast 消息。 hardlyimpressive,但这有一个可用的脚手架,可以添加 JNI 功能,然后是 TBB 功能。
步骤 2:“原生化!”
接下来,我们需要将原生 JNI 库(以及 NDK)集成到构建过程中。Android NDK 有许多示例和文档,描述了如何在 Android 项目中使用 JNI,但简要回顾/总结总是有帮助的。假设 NDK 已安装并在 PATH 中,将 JNI 代码集成到 Android 项目中与任何 Java/JNI 项目中的工作方式非常相似:一个 Java 类包含本机修饰的静态 Java 方法,这些方法将是原生代码库的入口点;一个单独的目录包含 C/C++ 源文件和头文件,这些文件由“javah”Java 工具生成;一个单独的构建脚本(一个Android.mk Makefile)描述了“ndk_build”工具执行实际原生编译所需的必要组件。
首先,我们创建将包含本机调用的 Java 类。
package com.tedneward.tbbdemo; public class TBBNative { static { System.loadLibrary("tbb-native"); } public static native int calculate(); }
虽然可以创建非静态本机方法,但通常更容易、更清晰地将本机调用视为完全静态的,不维护任何内部实例状态。另请注意,静态初始化块调用 System.loadLibrary();
,它使用适合底层平台的任何平台修饰来加载指定的本机库,无论是“.dll”(Windows)还是前缀为“lib”并后缀为“.so”(Linux,以及本例中的 Android)。
接下来,我们需要编写 C/C++ 源文件(NDK 期望它们位于“jni”目录中,该目录与 Android“src”目录同级,如果尚未创建,请创建它),但获取将从 JVM 链接的代码的确切声明可能很棘手,这就是 JDK 提供“javah”命令的原因:一旦 Java 类被编译,“javah”接受命令行中的类名并生成一个骨架 C 头文件和实现文件。因为它接受 Java 类名(而不是源文件)作为输入,所以项目的 Java 部分需要在我们运行“javah”之前构建。
javah -classpath ../bin/classes com.tedneward.tbbdemo.TBBNative
“-classpath”参数是必需的,用于指向作为 Android 构建一部分使用的临时“classes”目录,它会输出一个 C 风格的头文件,所以我们从“jni”目录运行它以将生成的头文件放在正确的位置。技术上讲,头文件不是必需的,但它包含 C 实现的声明,根据上面的示例,该声明如下:
/* * Class: com_tedneward_tbbdemo_TBBNative * Method: calculate * Signature: ()I */ JNIEXPORT jint JNICALL Java_com_tedneward_tbbdemo_TBBNative_calculate (JNIEnv *, jclass);
所有这些都在 Java JNI 文档中详细描述,所以如果这一切都是全新的,请务必查看一下,或者查看 Sheng Liang 的相关书籍(http://www.amazon.com/The-Java-Native-Interface-Specification/dp/0201325772)。
实现本机库是 TBB 的重点,但现在,让我们确保构建过程正常工作,因此一个简单的 C/C++ 存根就足够了。
#include "com_tedneward_tbbdemo_TBBNative.h" JNIEXPORT jint JNICALL Java_com_tedneward_tbbdemo_TBBNative_calculate (JNIEnv* jniEnv, jclass jniClass) { return 0; }
NDK 需要知道如何构建它,并且期望一个Android.mk Makefile 来帮助它处理特定于应用程序的细节。
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := tbb-native LOCAL_SRC_FILES := com_tedneward_tbbdemo_TBBNative.cpp include $(BUILD_SHARED_LIBRARY)
LOCAL_MODULE
设置必须(在拼写和大小写上)与 Native.java 源文件中传递给 System.loadLibrary()
调用的值相匹配——这是将要生成的本机库的名称。当然,源文件名可以是 C/C++ 编译器接受的任何合法文件名——javah 生成的文件名通常有点不方便长期使用。
假设头文件、源文件和Android.mk 文件没有拼写错误,并且 NDK 在 PATH 中,那么从“jni”目录发出“ndk-build”应该会产生类似以下的结果:
Ted-Newards-MacBook-Pro:jni ted$ ndk-build [armeabi] Compile++ thumb: tbb-native <= com_tedneward_tbb_Native.cpp [armeabi] StaticLibrary : libstdc++.a [armeabi] SharedLibrary : libtbb-native.so [armeabi] Install : libtbb-native.so => libs/armeabi/libtbb-native.so Ted-Newards-MacBook-Pro:jni ted$ ls ../libs/ armeabi/ Ted-Newards-MacBook-Pro:jni ted$ ls ../libs/armeabi/ libtbb-native.so*
一旦本机库构建完成,再次运行“ant debug install”应该可以干净地编译,并将包含本机代码的新应用程序安装到设备上。
步骤 3:“并行化!”
接下来的明显步骤是集成 TBB 本身,这意味着修改本机代码以使用 TBB 库来执行某种并行计算。例如,在游戏中,如果场上有多个棋子都需要计算下一步,那么 TBB 的“parallel_for”就很容易处理,将工作分配到并行 for 循环的每个迭代中。
TBB 库需要在 C++ 编译过程中启用特定设置,因此需要对Android.mk 文件进行一些更改,如下所示:
LOCAL_PATH := $(call my-dir) TBB_PATH := /opt/local/tbb42_20131003oss/ include $(CLEAR_VARS) LOCAL_MODULE := tbb-native LOCAL_SRC_FILES := com_tedneward_tbbdemo_TBBNative.cpp LOCAL_CFLAGS += -DTBB_USE_GCC_BUILTINS -std=c++11 –I$(TBB_PATH)/include LOCAL_LDLIBS := -ltbb -L./ -L$(TBB_PATH)/<path_to_libtbb_so> include $(BUILD_SHARED_LIBRARY) LOCAL_PATH := $(TBB_PATH)/lib/android include $(CLEAR_VARS) LOCAL_MODULE := libtbb LOCAL_SRC_FILES := libtbb.so include $(PREBUILT_SHARED_LIBRARY)
相应地,本机代码现在包含 TBB 头文件,并使用“parallel_for
”执行了一个相当无意义的计算。计算本身并不重要——重要的是我们有并行计算正在进行。从这里开始,将您的游戏所需的任何并行/并发计算改编过来应该不难,无论它是什么。
摘要
英特尔在支持 Android 平台的原生编码方面付出了巨大的努力,而 Threading Building Blocks 就是一个完美的例子。准备好构建混合原生/Java Android 应用的必要组件并非易事,但一旦准备就绪,其余的工作就会变得容易得多。享受吧!
要详细了解 Intel 的 Android 开发工具,请访问 Intel® Android 开发者专区。
其他相关文章
- Intel® Atom™ 平台上的 Android* 应用程序开发和优化
- 在基于Intel® Atom™处理器的Android*手机和平板电脑上开发传感器应用程序
- 开发具有语音识别功能的Android*应用程序
- 基于Intel®架构的平台上的NDK驱动Android游戏应用程序的开发和优化
- 用于开发移动应用的 Intel® HTML5 工具
要详细了解 Intel 的 Android 开发工具,请访问 Intel® Android 开发者专区。