日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > Android >内容正文

Android

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

發布時間:2023/12/9 Android 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 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?這兩頁,你會找到你要的。

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跨全平台使用的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。