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

文/何晓杰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 这两页,你会找到你要的。

0818b9ca8b590ca3270a3433284dd417.png

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);

这段代码的输出为:

0818b9ca8b590ca3270a3433284dd417.png

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

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

https://github.com/rarnu/cross_sqlite

0818b9ca8b590ca3270a3433284dd417.png

0818b9ca8b590ca3270a3433284dd417.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/275458.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

在Red Hat 4 AS U7上安装oracle10gR2

软件&#xff1a;Red Hat 4 AS U7, Oracle 10g R2 for linux32, VMWare 7, Windows 7详细步骤清单&#xff1a;在Red Hat 4 AS U7上安装oracle10gR2 1. 硬件需求&#xff1a; &#xff1d;&#xff1d;&#xff1d;&#xff1d;&#xff1d;&#xff1d;&#xff1d;&#xff1…

illustrator下载_平面设计:16个Illustrator快捷方式可加快工作流程

illustrator下载I know, I know — keyboard shortcuts sound so nerdy, and you’re a graphic designer, not an IT Director, why should you learn keyboard shortcuts?我知道&#xff0c;我知道—键盘快捷键听起来很书呆&#xff0c;而且您是图形设计师&#xff0c;而不是…

手把手教你五分钟扒个源码写个无敌外挂

大家好&#xff0c;我是若川。源码共读《1个月&#xff0c;200人&#xff0c;一起读了4周源码》 活动进行到第五期了&#xff0c;欢迎点链接加我微信 ruochuan12 报名参加。前言前段时间群里分享了一个小游戏&#xff0c;多次怀疑自己的眼睛以后&#xff0c;尝试去写个外挂。中…

Kubernetes 1.14重磅来袭,多项关键特性生产可用

走过了突飞猛进的2018年&#xff0c;Kubernetes在2019年终于迎来了第一个大动作&#xff1a;Kubernetes 1.14版本的正式发布&#xff01;Kubernetes 本次发布的 1.14 版本&#xff0c;包含了 31 项增强&#xff0c;其中 10 项为 GA&#xff0c;12 项进入 beta 试用阶段&#xf…

中英文

http://it.freesion.com/3220/4888028/13606306/#转载于:https://www.cnblogs.com/yqskj/articles/2082326.html

open ai gpt_让我们来谈谈将GPT-3 AI推文震撼到核心的那条推文

open ai gpt重点 (Top highlight)“设计师”插件 (The ‘Designer’ plugin) A couple days ago, a tweet shared by Jordan Singer turned the heads of thousands of designers. With the capabilities of GPT-3 (from OpenAI), he shared a sample of what he was able to c…

我历时3年才写了10余篇源码文章,但收获了100w+阅读

你好&#xff0c;我是若川。最近来了一些读者朋友&#xff0c;在这里简单介绍自己的经历&#xff0c;也许对你有些启发。之前发过这篇文章&#xff0c;现在修改下声明原创&#xff0c;方便保护版权。最近组织了源码共读活动1个月&#xff0c;200人&#xff0c;一起读了4周源码&…

android onlescan 参数,Android BLE:从iOS外设广告时,在onLeScan()回调中检索服务UUID

我正在使用Nexus 4(4.4 kitkat)作为中央和iPad作为外设.外围设备有广告服务.广告包有一些数据(22字节)的服务UUID.当我尝试从Android扫描外围设备时,iPad外围设备被发现.但是当我尝试从回调中的scanRecord参数获取服务UUID时,我找不到它.我得到的是外设发送的20byte数据.当我尝…

第 8 章 容器网络 - 061 - flannel 的连通与隔离

flannel 的连通与隔离 测试 bbox1 和 bbxo2 的连通性&#xff1a; bbox1 能够 ping 到位于不同 subnet 的 bbox2&#xff0c;通过 traceroute 分析一下 bbox1 到 bbox2 的路径。 1&#xff09; bbox1 与 bbox2 不是一个 subnet&#xff0c;数据包发送给默认网关 10.2.9.1&#…

Javascript 检测 页面是否在iframe中

//检测是否在iframe中if(self.frameElement ! null && (self.frameElement.tagName "IFRAME" || self.frameElement.tagName "iframe")){parent.parent.location "login.jsp";}转载于:https://www.cnblogs.com/kenkofox/archive/2011…

写给前端的算法进阶指南,我是如何两个月零基础刷200题 等推荐

大家好&#xff0c;我是若川。话不多说&#xff0c;这一次花了几小时精心为大家挑选了20余篇好文&#xff0c;供大家阅读学习。本文阅读技巧&#xff0c;先粗看标题&#xff0c;感兴趣可以都关注一波&#xff0c;一起共同进步。前端从进阶到入院作者ssh就职于字节跳动基础工程团…

计算机视觉笔记本推荐_视觉灵感:Mishti笔记本

计算机视觉笔记本推荐The Mishti Notebook is a project close to my heart, wherein I experimented with screen printing techniques at the Print Labs at the National Institute of Design, Ahmedabad. Dating back to the year 2012 when the NID Print Labs was first …

Google工程师:如何看待程序员普遍缺乏数据结构和算法知识?

出处&#xff1a;极客时间《数据结构与算法之美》很多技术人都很迷茫&#xff0c;觉得自己做的项目没有技术含量&#xff0c;成天就是卖苦力。技术的东西&#xff0c;日新月异&#xff0c;有些人总在忙于追求热点新技术&#xff0c;东学学、西学学&#xff0c;平时泛泛地看技术…

android guide 中文版,Sky Guide

Sky Guide是一款能让小伙伴们观察银河的手机软件&#xff0c;尤其是喜欢行星、星座的小伙伴们来讲&#xff0c;这款软件能很好的帮助小伙伴们观看这些&#xff0c;让小伙伴们体验不一样的观星乐趣&#xff0c;因此想要观看的小伙伴们&#xff0c;赶紧来试试吧。软件介绍&#x…

Kinect for Windows SDK发布

转载请注明出处为KlayGE游戏引擎&#xff0c;本文地址为http://www.klayge.org/2011/06/17/kinect-for-windows-sdk%e5%8f%91%e5%b8%83/ 前一段时间Microsoft研究院宣布了Kinect for Windows SDK。在众人的期盼下&#xff0c;Kinect for Windows SDK Beta终于发布了&#xff01…

layui选项卡嵌套选项卡_在ProtoPie中使用嵌套组件构建选项卡栏

layui选项卡嵌套选项卡One of the powerful features of ProtoPie is the ability to build fully portable and interactive UI components. We are going to make use of nested components, SVG icons, and layout constraints to build a tab bar UI component that is sel…

50行代码串行Promise,koa洋葱模型原来这么有趣?

1. 前言大家好&#xff0c;我是若川&#xff0c;最近组织了源码共读活动《1个月&#xff0c;200人&#xff0c;一起读了4周源码》&#xff0c;感兴趣的可以加我微信 ruochuan12 参与&#xff0c;长期交流学习。之前写的《学习源码整体架构系列》 包含jQuery、underscore、lodas…

如何定位死循环或高CPU使用率(linux)

如何定位死循环或高CPU使用率(linux) 确定是CPU过高 使用top观察是否存在CPU使用率过高现象 找出线程 对CPU使用率过高的进程的所有线程进行排序 ps H -e -o pid,tid,pcpu,cmd --sortpcpu |grep xxx 得到如下结果,其中线程2909使用了7.8%的CPU. 2907 2913 0.0 ./xxx 2907…

js 用迭代器模式优雅的处理递归问题

2019独角兽企业重金招聘Python工程师标准>>> 什么是迭代器 循环数组或对象内每一项值&#xff0c;在 js 里原生已经提供了一个迭代器。 var arr [1, 2, 3] arr.forEach(function (item) {console.log(item) })实现一个迭代器 var iterator function (arr, cb) {fo…

如何抓取html请求,请求获取网页的response,获取网页的html 怎么那么慢

HttpEntity multipart builder.build();httppost.setEntity(multipart);long start System.currentTimeMillis();// 发送请求response httpclient.execute(httppost);long end System.currentTimeMillis();System.out.println("查询upsfreight消耗的时间是(毫秒):&quo…