Android 是基于 Linux 的 OS,所以想要在 ARM 的 Linux 上,比如树莓派,是有可能的。但事实上其中还是有问题,主要是因为 Android 的 so 库是通过 Bionic libc 代替了 glibc 作为标准库,两者有很多的差别,无法直接移用。

为了解决这个问题,出现了 libhybris 这个库,其主要作用是将 Android 库对 Bionic libc 的方法引用,Link 到 glibc 上,以实现对 Android 库在 ARM Linux 上的支持。libhybris 原来是用于让各 Linux 发行版支持 Android 的驱动,比如 Ubuntu Touch 和 Sailfish OS 都是使用这个库来兼容 Android 设备的。原理图如下:

libhybris 原理图

今天我尝试将 Bionic-jpeg 这个 Android 上的 jpeg 库,通过 libhybris 的包装,运行到装有 Raspbian 的树莓派上。原教程来源:https://mcaleely.com/jh/ToT/2013/09/06/bionic-jpeg-a-libhybris-exam

准备工作

设备

我准备了一个树莓派 3b+, 装了 Raspbian buster。

环境

需要 Android 的一个 ROM 的 zip 包,用于提供 bionic libc 相关库的内容。实践中我使用的是魔趣的 ROM 包,随便什么版本,解压以后提取其 /system/lib/ 文件夹到 Pi 的 /system/lib/ (目录一致)。我使用的是 MK60.1-flounder_lte-170418-HISTORY 这个版本,因为他是 Android 6.0, 且同时内含 lib 和 lib64 两个架构的库,后面用起来比较方便。

另外为了编译 Android 的 libjpeg.so, PC 上要准备 Android-NDK,版本对应上面下载的 ROM 包即可。当然如果直接使用 ROM 包里的 libjpeg.so 也可以,但在后面操作时要注意修改一下实验用的源码。

树莓派上要安装好编译用的基本环境

sudo apt-get install make automake autoconf libtool pkg-config

测试项目

下载测试项目的源码:https://launchpad.net/bionic-jpeg/+download,并解压。其中包含的目录如下:

  • jpeg-9: 从 IJG 下载的官方源码http://www.ijg.org/files/jpegsrc.v9.tar.gz
  • include: 可在 Android 和 Ubuntu 适用的头文件 jconfig.h
  • jpeg-library: 用 NDK 编译 libjpeg 的脚本
  • jpeg-client: 用于 Ubuntu 的 jpeg 客户端 (cjpeg, djpeg, jpegtran, rdjpgcom, wrjpgcom)
  • jpeg-bridge: 将 libhybaris 包装 jpeglib.h 的源码

本次实验的目标是在树莓派上用 Ubuntu 版本的 jpeg-client 调用 Android 版本的 libjpeg.so 后端。

开始编译

编译 Android jpeg-library

在 PC 上安装 NDK,然后编译 jpeg-library。为了区分原本的 libjpeg.so,本项目中用 libjpeg2.so 代替。

cd jpeg-library
<path-to-ndk>/ndk-build

编译 libhybris

libhybris 在树莓派上编译。首先下载 android-headers,注意和之前下载的 ROM 版本保持兼容。我使用的是 API Level 23 (Android 6.0.1)

wget https://launchpad.net/ubuntu/+archive/primary/+files/android-headers_23.orig.tar.gz
tar -xvzf android-headers_23.orig.tar.gz
sudo mv -f android-headers-23/23 /usr/include/android-headers
rm -rf android-headers_23.orig.tar.gz android-headers-23

然后下载 libhybris https://github.com/libhybris/libhybris

git clone git@github.com:libhybris/libhybris.git
cd libhybris/hybris

安装一个依赖 libwayland-dev

sudo apt install libwayland-dev

否则会报错 1

make[2]: *** No rule to make target '../egl/platforms/common/libwayland-egl.la', needed by

编译安装

./autogen.sh --with-android-headers=/usr/include/android-headers --prefix=/opt/libhybris --enable-wayland
make
sudo make install

安装好的 libhybirs 库在 /opt/libhybris/ 下。

编译 jpeg-client 和 jpeg-bridge

首先修改一处源码,在 jpeg-bridge/libjpeg-bridge.c #11

#include <hybris/internal/binding.h>

/* 改成 */
#include <hybris/common/binding.h>

设置环境变量到 libhybirs

export LD_LIBRARY_PATH="/opt/libhybris/lib/"

然后编译

# jpeg-bridge
cd jpeg-bridge
./configure --with-hybris-internal-include-path=/opt/libhybris/include/
make

# jepg-client
cd jpeg-client
./configure
make

测试运行

  • 将 NDK 编译好的 libjpeg2.so (在 jpeg-library/libs/armeabi/libjpeg2.so)放入 Pi 上的 /system/lib/ 下,同 Android 的 libc.so 等文件在一起。
  • 将 jpeg-client/cjpet,jpeg-bridge/.lib/libjpeg-bridge.so, jpeg-bridge/.lib/libjpeg-bridge.so.0 放在同一目录下(可能需要 LD_LIBRARY_PATH 指定)
  • 执行 ./cjpeg -outfile out.jpg testimg.bmp 即可看到效果

关于架构的问题 (Arm or Arm64)

默认的,libhybris 使用 32bit arm 进行编译和部署,在 Raspbian 上是没有任何问题的,因为 Raspbian 只有 32bit, 所以不存在架构的问题,但如果使用的是 Arm64 的 Ubuntu,则编译 libhybris 的时候会出现如下错误:

dlfcn.c:205:17: error: initializer element is not constant

这时应该选择一下编译的架构:

./autogen.sh --with-android-headers=/usr/include/android-headers --prefix=/opt/libhybris --enable-arch=arm64 --enable-wayland

之后编译出来的 libhybirs 就是 64 位的了,此时他使用的 bionic libc 也应该是 64bit 的,应将相关的 Android 库放在 /system/lib64 下。

总结

通过实验,libhybris 的原理还是简单的,在有处理好的 bridge 的情况下,基本没有什么坑。但最主要的还是这个 bridge 的处理,特别是各种符号之间的映射,处理不好就容易 Segmentation fault,调试起来就更难了。后续我再学习一下符号映射和调试的技巧。


  1. https://github.com/libhybris/libhybris/issues/421 ↩︎