移植libev事件库到Android中

因为libev库是使用C语言写的,所以在Android项目中使用此库的方法是把libev编译成.so文件,在Android中使用jni方式来调用libev的.so文件。

我们大家都知道android的ndk开发可以编译c,c++代码,不过需要自己写Android.mk文件。但是对于大多数开源项目来说,代码量很大,而且在编译前需要通过configure来配置编译选项,这种情况下,自己写Android.mk文件是很不现实的。

在这里,先参考如何使用android独立工具链快速移植开源项目中的方法实现配置好Android交叉编译工具链。因为我需要把我的应用运行在x86和ARM平台的Android系统中,所以我通过文中的方法编译出了两种平台的.so文件,分别位于xxx/libev-4.19/android/x86/lib和xxx/libev-4.19/android/arm/lib下面。
接下来参考Android NDK编译本地文件以及引用第三方so文件 ,在jni目录下面新建prebuild文件夹。
Android NDK 编程中提供了编译不同平台.so文件的方法,在jni目录下面新建Application.mk文件,在文件中加入以下内容。

#APP_ABI := all
#APP_ABI := armeabi armeabi-v7a x86 
APP_ABI := x86
APP_PLATFORM := android-17

其中APP_ABI指定了平台的类型,APP_PLATFORM指定了Android的版本。在Android NDK 编译本地代码的过程中会对$(TARGET_ARCH)分别赋不同的值,并执行Android.mk文件,生成对应平台的.so文件。比如当
APP_ABI := armeabi armeabi-v7a x86
时,在lib目录下面会生成如下文件夹:
技术分享
在不同平台运行时,Android系统会自动加载不同的.so文件。

为了使Android系统编译不同平台时,加载不同的libev.so文件,我使用$(TARGET_ARCH)变量进行区分。在prebuild文件下面新建android.mk文件,
并把事先编译的x86和ARM平台的libev.so文件复制到prebuild文件夹中。这里要注意,在linux平台编译完成的libev库的后缀名是libev.so.4.0.0,但是android只能识别.so后缀的文件,我这里把libev.so.4.0.0改成了libev.so,这里有个隐患,后面会提到。

此时的目录结构如下:
技术分享
prebuild中的Android.mk的内容如下:

include $(CLEAR_VARS)
ifeq ($(TARGET_ARCH) ,x86) 
    include $(CLEAR_VARS)
    LOCAL_MODULE := ev-x86
    LOCAL_SRC_FILES := $(LOCAL_PATH)/prebuild/libev-x86.so
    include $(PREBUILT_SHARED_LIBRARY)
else
    include $(CLEAR_VARS) 
    LOCAL_MODULE := ev-arm
    LOCAL_SRC_FILES := $(LOCAL_PATH)/prebuild/libev-arm.so
    include $(PREBUILT_SHARED_LIBRARY)
endif

这样,做到了区分不同的平台。

在jni目录下面的Android.mk内容如下:

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := conn
LOCAL_SRC_FILES := net_service.c 

ifeq ($(TARGET_ARCH) ,x86) 
    LOCAL_SHARED_LIBRARIES := ev-x86
else
    LOCAL_SHARED_LIBRARIES := ev-arm
endif

LOCAL_LDLIBS+= -L$(SYSROOT)/usr/lib -llog
LOCAL_C_INCLUDES := $(LOCAL_PATH)/include

LOCAL_CFLAGS := -D_ANDROID_
include $(BUILD_SHARED_LIBRARY)

include $(LOCAL_PATH)/prebuild/Android.mk

这里要注意设置LOCAL_SHARED_LIBRARIES 和include $(LOCAL_PATH)/prebuild/Android.mk,这样我的本地代码net_service.c才能访问libev库。在net_service.c中,我调用了libev中的函数。

通过这种方法,生成我的libconn.so,在Android实体机上运行,会出现以下错误:
技术分享
可以看到在加载libconn.so后,然后起加载libev.so.4文件。但是我们的文件是libev-x86.so或者libev-arm.so,为什么会是libev.so.4呢。
这个问题我找了很久,最后我去看了一下在linux下make libev-4.19时的信息。信息如下:
技术分享
从信息里找到了libev.so.4这个字符串,图中标注部分可以猜到大体意思,是把相关文件编译成了文件名为libev.so.4.0.0,但是soname为libev.so.4的一个so格式的文件。
这里关键词是soname,详细信息可以参考我对动态链接库的一些理解 ,大体来说就是soname是so文件的一个别名,因为我们在jni的主目录下的Android.mk中,加载了libev-x86.so文件(以下以x86平台为例),链接器生成libconn.so的时候,把libev-x86.so文件中的soname也就是libev.so.4放在了libconn.so的依赖中,当系统加载了libconn.so后,就会根据依赖信息去加载libev.so.4这个文件。而我们的文件名是libev-x86.so,所以会出现找不到libev.so.4的错误提示。
找到了原因,就知道解决办法了,解决办法就是把libev-86.so文件中的soname改成libev-x86.so,就这个简单。
具体方法如下:(以x86为例)
在libev-4.19源码文件夹下面执行configure进行配置

./configure --host=i686-linux-android --prefix=`pwd`/android/x86

执行make
此时根据信息,可以看到生成的文件在.libs下面,注意这是个隐藏目录。
进入.libs目录下面,目录结构如下:
技术分享
这里的libev.so.4.0.0就是通过
技术分享
生成的,因此我们在.libs目录下手工输入这个命令,生成soname为libev-x86.so的so文件,命令如下:

i686-linux-android-gcc -shared  -fPIC -DPIC  .libs/ev.o .libs/event.o   -lm  -O3   -Wl,-soname -Wl,libev-x86.so -o .libs/libev-x86.so

这样就生成了soname为libev-x86.so的libev-x86.so文件。

生成libev-arm.so文件的过程如上。

把libev-x86.so libev-arm.so文件复制到prebuild目录下面。这样生成的apk文件可以正常运行。

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。