在您的设备上运行本地代码有两种可能性:使用 NDK 或将应用程序嵌入框架中。据我所知,第一种方法不被考虑,因此我认为您可以看看第二种方法。这里是一个实现第二种方法的示例。
将现有代码移植到自定义 Android 设备的示例
又到了在博客上发布技术文章的时候了。这篇文章将介绍如何将现有的 C 库移植到 Android 上,这是我作为 Enea 开发板演示项目的一部分所做的。
平台添加或 NDK
将本地代码引入 Android 设备有两种方法:将其添加到平台本身并与框架集成,或将其包含在应用程序包中。后者已经得到了很大的发展,在 NDK 版本 5 的发布中甚至允许您直接从 NDK 中钩入应用程序生命周期http://developer.android.com/reference/android/app/NativeActivity.html。NDK 对于任何需要本地性能、需要重用可移植 C 库或只是需要包含某些遗留本地代码的应用程序都非常有用。NDK 与 Android SDK 集成良好,是在应用程序中包含本地功能的绝佳方式。对于需要在许多 Android 设备上重复使用的任何应用程序,它都应该是首选的方式。
另一种选择是将您的功能(可以是本地或 Java)作为 API 扩展包含在所有应用程序中以供使用。这仅适用于实现这些扩展的设备,并且可能是设备制造商的合适选择。这是我们在这里的目标。
分析现有项目
将本地代码移植到 Android 并不总是直截了当的,特别是如果我们谈论 C++ 代码,因为 Android 使用自己的 c 运行时,对异常等方面的支持有限。如果您想了解有关 bionic 的详细信息,请参阅 NDK 文档中的概述。
我想要为此项目移植的代码是 Enea LINX for Linux 框架,它是一个快速的 IPC 框架。我的目的是能够与运行我们 OSE 实时操作系统的控制系统进行交互,该操作系统也实现了这种 IPC。LINX 由几个内核驱动程序模块、用户空间库和一些配置和控制实用程序组成。它是用 C 写的。我之前已经在 Android 中使用 LINX 创建了一个小型演示,其中我单独编译了它并使用了静态链接,但是对于这个项目,我想要完全移植到 Android 构建系统中。它没有任何与 bionic 兼容性相关的问题,因此移植应该很简单。
我只想添加一个关于 LINX 的简短免责声明。我在这里使用它,因为它是将解决方案集成到 Android 中的内核驱动程序级别的良好示例。这个特定的代码片段确实向系统添加了其他 IPC 机制,这或多或少破坏了安全模型,因此除非您了解其影响,否则不要使用它。然而,本文中描述的将代码移植到 Android 的步骤适用于您可能想要包含在产品中的任何类型的驱动程序/框架/库。
添加内核驱动程序模块
第一步是将内核模块添加到 Android 构建中。一种方法是构建一个新内核并直接将它们包含在其中,但是对于这个项目,我选择将它们保持为单独的模块。
LOCAL_PATH := $(my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := linx.ko
LOCAL_MODULE_CLASS := SHARED_LIBRARY
LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/modules
LOCAL_SRC_FILES := $(LOCAL_MODULE)
include $(BUILD_PREBUILT)
Android系统镜像上模块的标准位置是System/lib/modules,因此我们将它们复制到那里。如果现在构建平台,构建系统将把我们预编译的模块linx.ko复制到我们用于设备的系统镜像中。下一步是确保我们在运行时安装了该模块。这可以通过shell手动完成,也可以通过在init期间运行的脚本完成。
在这种情况下,我创建了一个shell脚本,以以下内容从init.rc启动:
#linx init
insmod /lib/modules/linx.ko
insmod /lib/modules/linx_tcp_cm.ko
netcfg eth0 up
ifconfig eth0 192.168.1.12
mktcpcon --ipaddr=192.168.1.21 ControlConn
mklink --connection=tcpcm/ControlConn control_link
这包括安装模块和配置网络和LINX-link。我们通过在init.rc中添加以下内容来启动它:
...
service linx-setup /system/etc/linx_setup.sh
oneshot
...
安装脚本可以通过将其作为预构建目标包含在系统镜像中的方式来添加。
LOCAL_PATH := $(my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := linx_setup.sh
LOCAL_MODULE_CLASS := ETC
LOCAL_MODULE_PATH := $(TARGET_OUT)/etc
LOCAL_SRC_FILES := $(LOCAL_MODULE)
include $(BUILD_PREBUILT)
为用户空间代码创建Android make文件
现在我们已经安装好了驱动程序,下一步是考虑移植用户空间库。默认的LINX构建系统使用标准的GNU make文件,但我们需要创建适应Android构建系统的新文件。首先将所需的源文件添加到在Android源树中创建的linx目录中。这样就得到了以下结构:
Android.mk
include
liblinx
linx_basic
linxcfg
linx_setup.sh
modules
我有linx设置脚本和主要的Android.mk文件在顶层目录,然后我们有源文件在不同的文件夹中,包含文件在include文件夹中。为了说明每个源组件的Android make文件是如何创建的,我们可以以liblinx为例。Android.mk文件如下:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES := linx.c
LOCAL_C_INCLUDES += $(LOCAL_PATH)/../include
LOCAL_MODULE := liblinx
LOCAL_PRELINK_MODULE := false
include $(BUILD_SHARED_LIBRARY)
我们通过指定LOCAL_SRC_FILES来设置源文件,通过指定LOCAL_MODULE来设置库的名称。我们还需要通过指定LOCAL_C_INCLUDES来提供include目录中的头文件。最后,由于我们正在移植共享库,因此使用BUILD_SHARED_LIBRARY模板。这将使用Android构建系统构建库,并将其作为名为liblinx.so的共享库添加到系统映像中。
代码的其余部分以相同的方式移动到Android构建系统中,通过创建Android.mk文件并指定类型和任何依赖项。例如,我们可以看一下构建mktcpcon配置程序的语法。这取决于我们刚刚创建的库,因此makefile的条目如下所示:
LOCAL_SRC_FILES := mktcpcon.c
LOCAL_C_INCLUDES += $(LOCAL_PATH)/../include
LOCAL_STATIC_LIBRARIES += liblinxcfg
LOCAL_SHARED_LIBRARIES += liblinx
LOCAL_MODULE := mktcpcon
include $(BUILD_EXECUTABLE)
在这里,我们使用BUILD_EXECUTABLE模板,并且还需要指定我们链接的静态和共享库。
总结
我希望这提供了一些关于如何设置现有Linux项目在Android系统上运行的构建的见解。遵循以下步骤:
- 使用正确的内核构建系统和配置为您的设备构建任何与内核相关的内容
- 将内核模块(和/或内核)添加到平台构建系统中,并使用预构建模板为它们创建Android.mk文件。
- 如果需要,请为您的驱动程序创建配置和初始化服务,并将它们添加到init。
- 将其余代码(用户空间)移动到Android源树中,并为它们创建Android.mk文件。
- 如果遇到构建错误,请在源代码中解决它们,并查看您的代码与Android C运行时的特定不兼容性。
这就是今天的帖子。通过这样做,我们现在能够在shell中运行的本地程序中使用我们添加的驱动程序和API。下一步是创建JNI层和Java库,以允许常规的Android应用程序利用我们的平台增强功能。
我休了半年的产假(美好的瑞典福利),但现在又全职从事Android黑客活动,并推动团队发布东西。希望您会在这里看到更多的活动,包括关于此帖子讨论应用程序API的后续内容。