65.9K
CodeProject 正在变化。 阅读更多。
Home

并行 Android 开发

starIconstarIconstarIconstarIconstarIcon

5.00/5 (1投票)

2013年12月12日

CPOL

10分钟阅读

viewsIcon

14965

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 的 Android 开发工具,请访问 Intel® Android 开发者专区

© . All rights reserved.