使用NDK直接把C/C++代码编译成动态库很简单,主要就是要把NDK配置好,以及写Android.mk文件,网上搜一下有很多教程。现在要实现在NDK编译出来的动态库中还要调用别的第三方动态库,就不知道该怎么做了。后来网上搜了很多,发现遇到跟我一样问题的人很多,但是并没有找到解决方法,帖子内容都是千篇一律,copy来copy去,根本没有可用的内容。后来在stackoverflow的网站上找到了解决办法,发现其实很简单。
先明确一下需求和遇到的问题。在我的Android项目中,要用到一个动态库test2.so,而在test2的实现部分,又需要调用到test1.so的接口。最初我是直接在test2的Android.mk文件中添加了这样一行代码:
...
LOCAL_SHARED_LIBRARY := libtest1.so
...这样编译没有问题,但是在Android工程中调用test2.so的时候就会抛出UnsatisfiedLinkError:
'06-1722:35:28.741:INFO/dalvikvm(298):Unableto dlopen(/data/data/com.foo/lib/libndkfoo.so):Cannotload library:link_image[1721]:29could not load needed library'libtest1.so 后来知道,是不能这样直接调用第三方动态库的,而是要通过预编译模块来调用。
首先声明,libtest1.so也许是别人提供给你的现成的动态库,你只需要拿来调用。但是在调用之前,你必须保证两点:
1. 别人编译这个第三方动态库时使用的编译器必须与你使用的NDK编译器一样。比如我在编译test2.so时使用的是android-ndk-r9/toolchains/arm-linux-androideabi-4.6,那边编译test1.so的编译器必须也是这个。
2. 在Android工程中要使用到的所有动态库的目标系统的ABI必须要一致。至于什么是目标系统ABI请自行百度。也就是说,不管在编译test1.so的Android.mk文件中,还是在编译test2.so时所要使用到的Android.mk文件中,以及这些文件的每一个module中,都要出现这样一句话:
TARGET_ARCH_ABI := armeabi-v7a如果这两点无法同时保证的话,那么你的test1.so和test2.so是无法编译通过的,在NDK进行编译的时候会提示你:
arm-linux-androideabi-strip: Unable to recognise the format of the input files
在以上两点得到保证之后,开始我们调用动态库的流程。
首先实现简单的test1.h和test1.cpp,因为只是测试用,简单实现即可,我的测试代码如下:
test1.h:
#ifndef TEST1
#define TEST1
#include
#include
using namespace std;
class Test1
{
public:
Test1();
~Test1();
};
#endiftest1.cpp:
#include "test1.h"
#include
Test1::Test1()
{
__android_log_print(ANDROID_LOG_INFO, "WTT", "------Print---------I'm Test1----------------");
}
Test1::~Test1()
{
}这两个文件完成以后,开始写test1的Android.mk文件:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := test1
LOCAL_SRC_FILES := test1.cpp
LOCAL_LDLIBS +=-L$(SYSROOT)/usr/lib -llog -lz
LOCAL_ALLOW_UNDEFINED_SYMBOLS := true
LOCAL_CPP_EXTENSION += .cpp
TARGET_ARCH_ABI := armeabi-v7a
include $(BUILD_SHARED_LIBRARY)Application.mk文件:
APP_STL := gnustl_static
APP_ABI := armeabi-v7a
APP_CPPFLAGS += -fexceptions
文件结构如上,以上4个文件全都做好之后,在终端里面执行NDK编译(前提是你的NDK编译器已经配置好):
wtt@ubuntu:~/workspace/test1/jni$ $NDK/ndk-build 执行完之后就会在目标文件夹中自动得到一个libtest1.so文件。
接着,我们要在test2中调用这个libtest1.so中的接口。首先实现代码部分很简单,直接调用:
test2.h
#ifndef TEST2
#define TEST2
#include
#include
using namespace std;
class Test2
{
public:
Test2();
~Test2();
};
#endiftest2.cpp
#include "test1.h"
#include "test2.h"
#include
Test2::Test2()
{
__android_log_print(ANDROID_LOG_INFO, "WTT", "------Print---------I'm Test2----------------");
Test1 *test1 = new Test1;
__android_log_print(ANDROID_LOG_INFO, "WTT", "------Print---------I'm Test2----------------");
}
Test2::~Test2()
{
}
下面是最关键的,在test2的Android.mk文件中,必须将我们所需要调用到的所有动态库声明为预编译库,且每个预编译库必须声明为独立的module。
具体做法如下:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := Test1
LOCAL_SRC_FILES := libtest1.so
TARGET_ARCH_ABI := armeabi-v7a
include $(PREBUILT_SHARED_LIBRARY)这里是把libtest1.so声明为一个PREBUILT_SHARED_LIBRARY,也就是预编译库。然后直接在test2的module中引用这个与编译库
include $(CLEAR_VARS)
LOCAL_MODULE := test2
LOCAL_SRC_FILES := test2.cpp
LOCAL_LDLIBS +=-L$(SYSROOT)/usr/lib -llog -lz
LOCAL_ALLOW_UNDEFINED_SYMBOLS := true
LOCAL_SHARED_LIBRARIES := Test1
LOCAL_CPP_EXTENSION += .cpp
TARGET_ARCH_ABI := armeabi-v7a
include $(BUILD_SHARED_LIBRARY)
test2的Application.mk文件与test1一致即可,没有特别要求。做完这些之后,要同时把test1的.so文件和头文件放在本目录下,最终的目录结构如下:
接着执行$NDK/ndk-build
就会在目标目录下生成两个库文件:
至此,我们就实现了在动态库中调用第三方动态库。
要想在Android工程中调用到NDK动态库,还需要做一系列的工作,具体做法可以百度或者谷歌,这部分内容不在我们今天讨论的问题之内。
实现之后把Android程序跑起来,可以在logcat中看到这样的输出信息:
说明我们成功调用到动态库的接口。
参考文献: