文/何晓杰Dev(高级Android架构师)著作权归作者所有,转载请联系作者获得授权。

初看这个标题你可能会不解,SQLite 本身就是一个跨平台的数据库,在这里再说跨平台有什么意义呢?

其实不然,目前我就遇到了一个项目需要使用 SQLite 数据库,而且我甚至完全不想花多套代码在不同的平台上,毕竟每个平台的包含的相关 SDK 并不一致。举个简单的例子,在 Android 上操作 SQLite,需要用到 SQLiteDatabase 这个类,用 Java 来操作;而在 iOS 上,除了需要引入 libsqlite3.tbd 外,还需要引入 sqlite3.h 这个头文件,使用 Objective-C 来操作,到了 PC 上,虽然都是以使用 sqlite3.h 为主,但是依然会有不一致的地方,比如说种类繁多的编程语言,大多都有不同的封装,API 不一致这足以让人头疼。

因此,在不同的平台上操作 SQLite,必定会使用不同的代码。当然了,除了 SQLite 之外,实现相同的功能,在不同平台上使用不同的代码也许已经是惯例,大家也习以为常。

请输入标题     bcdef

Roll your eggs 的习以为常!作为一个懒人,当这样一个锅需要自己背的时候,自然是去找更简单的解决方案了。目标是一套代码走天下!

请输入标题     abcdefg

那么也不多废话了,直接上手写代码,这里有很多种技术可以选择,比如说 C++,sqlite3.h 还是很好用的。不过我依然是折腾自己喜欢的 CodeTyphon,因为它有更让人觉得方便的封装。

很幸运的是,CodeTyphon 已经自带了 sqlite3conn 单元,直接引用之即可。关于如何查找可引用的库,可以看 CTC 的 Typhon-IDE Pkgs 和 FPC Pkgs 这两页,你会找到你要的。

CTC

首先先制作一个简单的数据库吧,用于测试代码能否正常工作:

$ sqlite3 demo.db

> create table user(id INTEGER PRIMARY KEY AUTOINCREMENT, name VARCHAR(32) NOT NULL);

> insert into user(name) value ('ABC');

> insert into user(name) value ('XYZ');

然后根据数据库结构声明一个结构体,后面会用于数据传递:

type

TDemoRec = record

AId: Integer;

AName: PChar;

end;

与这个结构等价的 C++ 的结构体是这样的:

struct DemoRec {

int AId;

char* AName;

};

这一瞬间我们会发现原来操作 SQLite 是如此的简单,在此我定义了一个类,用来保存一些数据:

TSQLite = class

private

FDatabase: TSQLite3Connection;

FQuery: TSQLQuery;

FTransaction: TSQLTransaction;

published

property Database: TSQLite3Connection read FDatabase write FDatabase;

property Transaction: TSQLTransaction read FTransaction write FTransaction;

property Query: TSQLQuery read FQuery write FQuery;

end;

有了这些东西后,就可以方便的玩起来了,比如说执行一个 SQL 语句:

function TSQLite.ExecuteSQL(ASQL: string): Boolean;

begin

FQuery.Close;

FQuery.SQL.Text:= ASQL;

try

FQuery.ExecSQL;

Exit(True);

except

Exit(False);

end;

end;

这段代码似乎太简单了,也许我们更加希望在出错时能够给出一个原因,那么可以改一下:

function TSQLite.ExecuteSQL(ASQL: string; var AError: string): Boolean;

begin

FQuery.Close;

FQuery.SQL.Text:= ASQL;

try

FQuery.ExecSQL;

Exit(True);

except

on E: Exception do begin

AError:= e.Message;

Exit(False);

end;

end;

end;

好了,现在调用这个方法时,只需要额外传入一个字符串参数,就可以获取出错时的信息。

在这个体系下,要进行查询也很简单,需要额外封装两个方法:

// 根据 SQL 语句查询

function TSQLite.Select(ASQL: string; var AError: string): Boolean;

begin

FQuery.Close;

FQuery.SQL.Text:= ASQL;

try

FQuery.Open;

Exit(True);

Except

on E: Exception do begin

AError:= e.Message;

Exit(False);

end;

end;

end;

// 获取查询结果的行数

function dbGetSelectResultCount(APath: PChar): Integer;

var

database: TSQLite;

begin

Result := -1;

if (DatabaseExists(string(APath))) then begin

database := GetDatabase(string(APath));

Result := database.Query.RecordCount;

end;

end;

// 获取指定行号的一条记录

function dbGetSelectResult(APath: PChar; AIndex: Integer): TDemoRec;

var

database: TSQLite;

tmp: string;

begin

Inc(AIndex);

if (DatabaseExists(string(APath))) then begin

database := GetDatabase(string(APath));

if (database.Query.RecordCount >= AIndex) then begin

database.Query.RecNo:= AIndex;

Result.AId:= database.Query.FieldByName('id').AsInteger;

tmp := database.Query.FieldByName('name').AsString;

Result.AName:= StrAlloc(tmp.Length);

strcopy(Result.AName, PChar(tmp));

end;

end;

end;

接下来就是导出函数了,作为一个跨平台的库,它需要被其他程序调用,那么必定有导出函数,而不同的平台下,所需要的函数形态是不一样的,特别是由于 Android 使用 JNI 来调用动态库,导出函数必须符合 JNI 的规范。

下面的例子很好的说明了导出函数的方法:

// iOS, PC

function dbGetSelectResultCount(APath: PChar): Integer; cdecl;

function dbGetSelectResult(APath: PChar; AIndex: Integer): TDemoRec; cdecl;

// Android

function Java_com_sqlite_sample_NativeAPI_dbGetSelectResultCount(env: PJNIEnv; obj: jobject; APath: jstring): jint; stdcall;

function Java_com_sqlite_sample_NativeAPI_dbGetSelectResult(env: PJNIEnv; obj: jobject; APath: jstring; AIndex: jint): jobject; stdcall;

唯一需要注意的是调用协定,用于 JNI 的必须设为 stdcall,而其他的均设为 cdecl。

那么再下一步就是编译,直接使用 FPC 跨平台编译器即可,编译方法很简单:

$ fpc64 -Fisqlite -Fusqlite sample.lpr

此时即可以在 Mac 端生成 libsample.dylib 以及在 Linux 端生成 libsample.so。

要跨平台编译的话,稍微麻烦一点,但是也比想象中简单很多:

$ export ANDROID_LIB=/usr/local/codetyphon/binLibraries/android-5.0-api21-arm/

$ export FPC=/usr/local/codetyphon/fpc/fpc64/bin/x86_64-linux/fpc

$ ${FPC} -Tandroid -Parm -Fl${ANDROID_LIB} -Fiqslite -Fusqlite sample.lpr

此时即可生成一个供 Android 系统使用的,arm 架构的 libsample.so,通过更换 -P 后面的参数,也可以编译 x86,mips 等架构的 so。

完成后再看一下 iOS 的库要怎么编译。由于 iOS 已不再允许动态加载 dylib,我们必须把代码编译为静态库,也就是 .a 文件,并且静态链接到 iOS 项目内。

$ export FPC_ROOT=/usr/local/lib/fpc/3.1.1

$ export FPC=${FPC_ROOT}/ppcrossa64

$ ${FPC} -Tdarwin -dIPHONEALL -Cn -Fisqlite -Fusqlite sample.lpr

$ ar -q libsample.a `grep "\.o$" link.res`

$ ranlib libsample.a

此时可以得到一个用于 64 位真机的 libsample.a 文件,若是要在 32 位的 iOS 和模拟器上完成兼容,还必须再另外编译两个 .a。

32 位真机:替换编译器为 ppcrossarm

模拟器:替换编译器为 ppcx64,并替换 -T 参数为 iphonesim

当我们得到了 3 个不同架构的 .a 后,有些时候需要将它们合并,使用如下命令来合并之:

lipo -create libsample_A64.a libsample_ARM.a libsample_EMU.a -output libsample.a

这样就得到了一个融合了的 .a,它可以用于各种场合。

现在一切都准备好了,看看如何使用我们做好的库吧,以上述的 dbGetSelectResultCount 和 dbGetSelectResult 为例,分别讲述在各平台的使用方法。

Android:

package com.sqlite.sample;

public class NativeAPI {

static {  System.loadLibrary("sample"); }

public static native int dbGetSelectResultCount(String APath);

public static native DemoRec dbGetSelectResult(String APath, int AIndex);

}

iOS:

extern int dbGetSelectResultCount(const char* APath);

extern struct DemoRec dbGetSelectResult(const char* APath, int AIndex);

PC(以 C++ 为例):

typedef int (*dbSelectResultCount)(const char* APath);

typedef struct DemoRec (*dbSelectResult)(const char* APath, int AIndex);

void* handle = dlopen("./libsample.so", RTLD_LAZY);

dbSelectResultCount mSelectResultCount = (dbSelectResultCount) dlsym(handle, "dbGetSelectResultCount");

dbSelectResult mSelectResult = (dbSelectResult) dlsym(handle, "dbGetSelectResult");

可以看到,不论在哪个平台上,最终得到的 API 都是一致的,这样就统一了调用方式。在此基础上,要做二次封装也是非常方便。另外,由于代码耦合几乎没有,也能够很方便的对 SQLite 的底层库的逻辑进行修改,只要 API 不变,就不会影响上层的调用。

以下是一个完整的调用代码,以 iOS 端为例,其他各端均一致:

// 复制数据库文件

NSString * originPath = [[NSBundle mainBundle] pathForResource:@"demo" ofType:@"db"];

NSString * destPath = [ViewController getDocumentPath];

NSString * dbFile = [destPath stringByAppendingPathComponent:@"demo.db"];

[ViewController copyFile:originPath destFile:dbFile];

// 打开数据库

int b = dbOpen([dbFile UTF8String]);

printf("Open Database => %d\n", b);

// 执行查询

b = dbSelect([dbFile UTF8String], "select * from user");

printf("Select => %d\n", b);

// 获取查询结果的行数

int count = dbGetSelectResultCount([dbFile UTF8String]);

printf("Select Rows => %d\n", count);

// 取出查到的每一条数据

for (int i = 0; i < count; i++) {

struct DemoRec r = dbGetSelectResult([dbFile UTF8String], i);

printf("Data %d => {id => %d, name => %s}\n", i, r.AId, r.AName);

}

// 关闭数据库

b = dbClose([dbFile UTF8String]);

printf("Close Database => %d\n", b);

这段代码的输出为:

可以看到,调用成功,并且正确的传递了数据。在其他平台上的效果也是完全一样的。

这个用于演示的项目已经开源,请访问我的 github 获取,地址:

https://github.com/rarnu/cross_sqlite

sqlite3源码编译到Android,实现SQLite跨全平台使用相关推荐

  1. ROS_java源码编译开发android APP项目

    Windows平台安装Android studio环境 首先,下载配置android studio环境编译器,地址:https://developer.android.google.cn/studio ...

  2. Android高版本P/Q/R源码编译指南

           Android高版本P/Q/R源码编译指南 Android源码编译系列博客: Android.bp你真的了解吗 Android.bp入门指南之Android.mk转换成Android.b ...

  3. OpenSSL之六:OpenSSL源码编译安装

    OpenSSL源码编译安装 一.OpenSSL在Linux平台的编译 打包环境 源码准备 编译安装 二.OpenSSL在Windows平台的编译 打包环境 编译32位库 编译64位库 错误处理 编译低 ...

  4. 车载技术—CarFramework框架【AOSP 源码编译】

    repo 安卓源码包含数百个git库, googole开发了 repo工具来管理安卓版本库. repo 使用python对git进行了封装. 内核源码 AOSP源码中不包含内核源码,需要单独下载. c ...

  5. gcc编译器和mysql源码哪个难_源码编译mysql 5.5

    http://blog.csdn.net/aidenliu/article/details/6586610 源码编译mysql 5.5+ 安装过程全记录 分类: Mysql 2011-07-05 21 ...

  6. Win10 下Visual Studio 2017源码编译Paddle

    Win10 下Visual Studio 2017源码编译Paddle 1.准备工作 Visual Studio 2017 Python 3.8.10 CMake 3.22.0-rc2 熟悉官方文档: ...

  7. SQLite源码编译教程

    SQLite源码编译安装 SQLite简介 编译安装 源码下载 编译过程 demo实例 总结 SQLite简介 你好! 这是你第一次使用 Markdown编辑器 所展示的欢迎页.如果你想学习如何使用M ...

  8. Android 源码编译及常见错误及解决方法

    Android 源码编译及常见错误及解决方法 参考文章: (1)Android 源码编译及常见错误及解决方法 (2)https://www.cnblogs.com/kyyblabla/p/360393 ...

  9. 【Android Gradle】安卓应用构建流程 ( Java 源码编译 和 AIDL 文件编译 )

    文章目录 一.安卓应用构建简介 二.Java 源码编译 三.AIDL 源码编译 一.安卓应用构建简介 使用 Android Studio 开发 Android 应用时 , 编译应用后在 Module ...

最新文章

  1. Redis API的原子性分析
  2. Shell编程—【01】shell中常用的字符串操作
  3. PCL:关于pcd数据显示乱码
  4. deltasql 1.5.5 发布,数据库模型版本控制
  5. 3.6评分卡极端值的识别以及处理
  6. 二维傅里叶变换是怎么进行的?
  7. NSAssert和NSParameterAssert
  8. 函数函数sigaction、signal
  9. Java并发教程–锁定:内在锁
  10. Exchange 2016部署实施案例篇-04.Ex基础配置篇(下)
  11. 石子合并(洛谷-P1880)
  12. @codeforces - 553E@ Kyoya and Train
  13. 找不到图片素材,看这里
  14. mysqldump快速导数据
  15. viewgroup的使用方法
  16. mongodb 高可用分布式原理 ---------搭建高可用mongo集群前需要温习的知识-火
  17. 【大数据部落】文本挖掘:twitter推特LDA主题情感分析
  18. *[topcoder]HexagonalBoard
  19. linux ms08 067漏洞,MS08067攻击实验失败的原因是什么?
  20. POI导出excel文件之取消合并单元格、删除列、移动列

热门文章

  1. 线段树专辑——pku 3667 Hotel
  2. 简单易变的CSS阴影效果
  3. 全国计算机等级考试题库二级C操作题100套(第65套)
  4. 全国计算机等级考试题库二级C操作题100套(第02套)
  5. 数据迁移,不停机上线的正确姿势
  6. oracle11 删除表空间,oracle11g启动停止服务,修改字符集,导入导出,创建删除表空间,卸载oracle等...
  7. linux sli 提高效率,从原理到性能提升 MCP78智能SLI全解析
  8. eclipse安卓工程的构建、配置连接模拟器、安卓工程结构介绍
  9. java color类 蓝色_java中Color类的简单总结
  10. java 中 if与while的区别