sqlite3源码编译到Android,实现SQLite跨全平台使用
文/何曉杰Dev(高級(jí)Android架構(gòu)師)著作權(quán)歸作者所有,轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán)。
初看這個(gè)標(biāo)題你可能會(huì)不解,SQLite 本身就是一個(gè)跨平臺(tái)的數(shù)據(jù)庫,在這里再說跨平臺(tái)有什么意義呢?
其實(shí)不然,目前我就遇到了一個(gè)項(xiàng)目需要使用 SQLite 數(shù)據(jù)庫,而且我甚至完全不想花多套代碼在不同的平臺(tái)上,畢竟每個(gè)平臺(tái)的包含的相關(guān) SDK 并不一致。舉個(gè)簡單的例子,在 Android 上操作 SQLite,需要用到?SQLiteDatabase?這個(gè)類,用?Java?來操作;而在 iOS 上,除了需要引入?libsqlite3.tbd?外,還需要引入?sqlite3.h?這個(gè)頭文件,使用?Objective-C?來操作,到了 PC 上,雖然都是以使用?sqlite3.h?為主,但是依然會(huì)有不一致的地方,比如說種類繁多的編程語言,大多都有不同的封裝,API 不一致這足以讓人頭疼。
因此,在不同的平臺(tái)上操作 SQLite,必定會(huì)使用不同的代碼。當(dāng)然了,除了 SQLite 之外,實(shí)現(xiàn)相同的功能,在不同平臺(tái)上使用不同的代碼也許已經(jīng)是慣例,大家也習(xí)以為常。
請(qǐng)輸入標(biāo)題 ? ? bcdef
Roll your eggs 的習(xí)以為常!作為一個(gè)懶人,當(dāng)這樣一個(gè)鍋需要自己背的時(shí)候,自然是去找更簡單的解決方案了。目標(biāo)是一套代碼走天下!
請(qǐng)輸入標(biāo)題 ? ? abcdefg
那么也不多廢話了,直接上手寫代碼,這里有很多種技術(shù)可以選擇,比如說?C++,sqlite3.h?還是很好用的。不過我依然是折騰自己喜歡的?CodeTyphon,因?yàn)樗懈屓擞X得方便的封裝。
很幸運(yùn)的是,CodeTyphon?已經(jīng)自帶了?sqlite3conn?單元,直接引用之即可。關(guān)于如何查找可引用的庫,可以看?CTC?的?Typhon-IDE Pkgs?和?FPC Pkgs?這兩頁,你會(huì)找到你要的。
CTC
首先先制作一個(gè)簡單的數(shù)據(jù)庫吧,用于測試代碼能否正常工作:
$ 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');
然后根據(jù)數(shù)據(jù)庫結(jié)構(gòu)聲明一個(gè)結(jié)構(gòu)體,后面會(huì)用于數(shù)據(jù)傳遞:
type
TDemoRec = record
AId: Integer;
AName: PChar;
end;
與這個(gè)結(jié)構(gòu)等價(jià)的 C++ 的結(jié)構(gòu)體是這樣的:
struct DemoRec {
int AId;
char* AName;
};
這一瞬間我們會(huì)發(fā)現(xiàn)原來操作 SQLite 是如此的簡單,在此我定義了一個(gè)類,用來保存一些數(shù)據(jù):
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;
有了這些東西后,就可以方便的玩起來了,比如說執(zhí)行一個(gè) SQL 語句:
function TSQLite.ExecuteSQL(ASQL: string): Boolean;
begin
FQuery.Close;
FQuery.SQL.Text:= ASQL;
try
FQuery.ExecSQL;
Exit(True);
except
Exit(False);
end;
end;
這段代碼似乎太簡單了,也許我們更加希望在出錯(cuò)時(shí)能夠給出一個(gè)原因,那么可以改一下:
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;
好了,現(xiàn)在調(diào)用這個(gè)方法時(shí),只需要額外傳入一個(gè)字符串參數(shù),就可以獲取出錯(cuò)時(shí)的信息。
在這個(gè)體系下,要進(jìn)行查詢也很簡單,需要額外封裝兩個(gè)方法:
// 根據(jù) 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;
// 獲取查詢結(jié)果的行數(shù)
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;
// 獲取指定行號(hào)的一條記錄
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;
接下來就是導(dǎo)出函數(shù)了,作為一個(gè)跨平臺(tái)的庫,它需要被其他程序調(diào)用,那么必定有導(dǎo)出函數(shù),而不同的平臺(tái)下,所需要的函數(shù)形態(tài)是不一樣的,特別是由于 Android 使用 JNI 來調(diào)用動(dòng)態(tài)庫,導(dǎo)出函數(shù)必須符合 JNI 的規(guī)范。
下面的例子很好的說明了導(dǎo)出函數(shù)的方法:
// 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;
唯一需要注意的是調(diào)用協(xié)定,用于 JNI 的必須設(shè)為?stdcall,而其他的均設(shè)為?cdecl。
那么再下一步就是編譯,直接使用?FPC?跨平臺(tái)編譯器即可,編譯方法很簡單:
$ fpc64 -Fisqlite -Fusqlite sample.lpr
此時(shí)即可以在 Mac 端生成?libsample.dylib?以及在 Linux 端生成?libsample.so。
要跨平臺(tái)編譯的話,稍微麻煩一點(diǎn),但是也比想象中簡單很多:
$ 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
此時(shí)即可生成一個(gè)供 Android 系統(tǒng)使用的,arm 架構(gòu)的?libsample.so,通過更換?-P?后面的參數(shù),也可以編譯 x86,mips 等架構(gòu)的 so。
完成后再看一下 iOS 的庫要怎么編譯。由于 iOS 已不再允許動(dòng)態(tài)加載 dylib,我們必須把代碼編譯為靜態(tài)庫,也就是?.a?文件,并且靜態(tài)鏈接到 iOS 項(xiàng)目內(nèi)。
$ 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
此時(shí)可以得到一個(gè)用于 64 位真機(jī)的?libsample.a?文件,若是要在 32 位的 iOS 和模擬器上完成兼容,還必須再另外編譯兩個(gè)?.a。
32 位真機(jī):替換編譯器為 ppcrossarm
模擬器:替換編譯器為 ppcx64,并替換 -T 參數(shù)為 iphonesim
當(dāng)我們得到了 3 個(gè)不同架構(gòu)的?.a?后,有些時(shí)候需要將它們合并,使用如下命令來合并之:
lipo -create libsample_A64.a libsample_ARM.a libsample_EMU.a -output libsample.a
這樣就得到了一個(gè)融合了的?.a,它可以用于各種場合。
現(xiàn)在一切都準(zhǔn)備好了,看看如何使用我們做好的庫吧,以上述的?dbGetSelectResultCount?和?dbGetSelectResult?為例,分別講述在各平臺(tái)的使用方法。
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");
可以看到,不論在哪個(gè)平臺(tái)上,最終得到的 API 都是一致的,這樣就統(tǒng)一了調(diào)用方式。在此基礎(chǔ)上,要做二次封裝也是非常方便。另外,由于代碼耦合幾乎沒有,也能夠很方便的對(duì) SQLite 的底層庫的邏輯進(jìn)行修改,只要 API 不變,就不會(huì)影響上層的調(diào)用。
以下是一個(gè)完整的調(diào)用代碼,以 iOS 端為例,其他各端均一致:
// 復(fù)制數(shù)據(jù)庫文件
NSString * originPath = [[NSBundle mainBundle] pathForResource:@"demo" ofType:@"db"];
NSString * destPath = [ViewController getDocumentPath];
NSString * dbFile = [destPath stringByAppendingPathComponent:@"demo.db"];
[ViewController copyFile:originPath destFile:dbFile];
// 打開數(shù)據(jù)庫
int b = dbOpen([dbFile UTF8String]);
printf("Open Database => %d\n", b);
// 執(zhí)行查詢
b = dbSelect([dbFile UTF8String], "select * from user");
printf("Select => %d\n", b);
// 獲取查詢結(jié)果的行數(shù)
int count = dbGetSelectResultCount([dbFile UTF8String]);
printf("Select Rows => %d\n", count);
// 取出查到的每一條數(shù)據(jù)
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);
}
// 關(guān)閉數(shù)據(jù)庫
b = dbClose([dbFile UTF8String]);
printf("Close Database => %d\n", b);
這段代碼的輸出為:
可以看到,調(diào)用成功,并且正確的傳遞了數(shù)據(jù)。在其他平臺(tái)上的效果也是完全一樣的。
這個(gè)用于演示的項(xiàng)目已經(jīng)開源,請(qǐng)?jiān)L問我的 github 獲取,地址:
https://github.com/rarnu/cross_sqlite
總結(jié)
以上是生活随笔為你收集整理的sqlite3源码编译到Android,实现SQLite跨全平台使用的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java 栈空间_初学JAVA——栈空间
- 下一篇: android onlescan 参数,