这个问题的起因是,某项目需要在 NDK 中使用 SQLite,并且这个库同时也需要在 iOS 端使用。一开始的开发均很顺利,已有文章予以总结,点击查看该文章

但是当程序运行到 Android N 上时,情况就不对了,整个程序直接崩溃,报的错误是 Can not load dynamic library "libsqlite.so"。保险起见,我检查了一下 /system/lib/system/lib64,确保了 libsqlite.so 是存在的。那么问题就变成了,无法调用这个存在的库?

经过一番搜索,找到了问题的原因,点此查看原文,具体的原因是,Android N 以后,不再允许直接调用 /system/lib 内的内容,而允许调用的库,如 liblog.so,均被移至 vendor 下,并符号链接至 /system/lib

所以,libsqlite.so 既便存在,也无法再直接调用了。再深入讲一句,其实 libdl.so 也无法再使用了,也就是说,在 NDK 中 dlopendlsym 这类函数也已被禁用。


既然不能动态调用,那解决方案就是静态调用了,我们需要一个 libsqlite.a,并把它静态链接到目标库里。这一步很简单,下载 SQLite 源码后,将它编译成适用于 Android 的 libsqlite.a

$ curl http://www.sqlite.org/2017/sqlite-amalgamation-3180000.zip > sqlite.zip
$ unzip sqlite.zip

此时可以得到 SQLite 的源码,总共 4 个文件,写一个 Android.mk 来编译之:

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)
LOCAL_MODULE            := sqlite3-a
LOCAL_MODULE_FILENAME   := libsqlite3
TARGET_ARCH             := arm
TARGET_PLATFORM         := android-14
LOCAL_SRC_FILES         := ./sqlite/sqlite3.c
LOCAL_C_INCLUDES        := ./sqlite
LOCAL_EXPORT_C_INCLUDES := ./sqlite
LOCAL_CFLAGS            := -DSQLITE_THREADSAFE=1
include $(BUILD_STATIC_LIBRARY)

同时还需要再写一个 Application.mk 来使用 STL:

APP_ABI := armeabi
APP_CPPFLAGS += -fexceptions -frtti
APP_STL := stlport_shared

执行一下 ndk-build 命令即可得到一个 libsqlite3.a


要完成静态链接,可以很简单的使用 linklib 这个宏命令,同时修改 sqlite3.inc 文件,将 external 指令后的库名称全部删去。

关于 external,详细的用法是:

function sqlite3_libversion(): pansichar; cdecl; external 'libsqlite.so'; // 指定从 libsqlite.so 获取函数
function sqlite3_libversion(): pansichar; cdecl; external;  // 从内链的静态库获取函数

关于 linklib,详细的用法是:

{$IFDEF ANDROID}
  {$LINKLIB libsqlite3.a}
{$ENDIF}

此处需要注意的是,我们仅针对 Andorid 平台进行入理,而其他平台上静态链接并没有意义,因此使用 Android 的定义宏将 linklib 包起来即可。这样在编译时,静态库就链接到目标文件里去了。


到了这一步,可以说是成功了一半,这个时候运行程序,还是会崩的,主要会崩的地方有以下几个:

sqlite3.c: 23167: __sync_synchronize();
sqlite3.c: 23842: __sync_synchronize();

这两个函数的调用,须注释掉,在这里并不需要使用,而且放着会引起找不到函数的运行时异常。

另一处崩溃在于 Android 老版本的兼容,在 Android M 以后,调用 NDK 时,不再检查 __aeabi_d2ulz__aeabi_d2lz(虽然这两个函数具体做了什么我也不知道,但是反编译看函数体,是可以直接留空的),而老版本的 Android 会在调用 NDK 时进行导出函数检查,从而引发一个崩溃。要解决这个问题,只需要造出这两个函数即可:

{$i debug.inc}
unit osfunc;
{$mode objfpc}{$H+}

interface

{$IFDEF ANDROID}
procedure __aeabi_d2ulz(); cdecl;
procedure __aeabi_d2lz(); cdecl;
{$ENDIF}

implementation

{$IFDEF ANDROID}
procedure __aeabi_d2ulz; cdecl; begin end;
procedure __aeabi_d2lz; cdecl; begin end;
{$ENDIF}

end.

这样就完成了对老版本 Android 的兼容。到了这一步,在 Android N 以上以 NDK 调用 SQLite 即告完成。