广州找人做网站,做网站网关备案,建立主题网站的顺序是,wordpress雪箭淘客文#xff0f;何晓杰Dev(高级Android架构师)著作权归作者所有#xff0c;转载请联系作者获得授权。初看这个标题你可能会不解#xff0c;SQLite 本身就是一个跨平台的数据库#xff0c;在这里再说跨平台有什么意义呢#xff1f;其实不然#xff0c;目前我就遇到了一个项目…文何晓杰Dev(高级Android架构师)著作权归作者所有转载请联系作者获得授权。初看这个标题你可能会不解SQLite 本身就是一个跨平台的数据库在这里再说跨平台有什么意义呢其实不然目前我就遇到了一个项目需要使用 SQLite 数据库而且我甚至完全不想花多套代码在不同的平台上毕竟每个平台的包含的相关 SDK 并不一致。举个简单的例子在 Android 上操作 SQLite需要用到 SQLiteDatabase 这个类用 Java 来操作而在 iOS 上除了需要引入 libsqlite3.tbd 外还需要引入 sqlite3.h 这个头文件使用 Objective-C 来操作到了 PC 上虽然都是以使用 sqlite3.h 为主但是依然会有不一致的地方比如说种类繁多的编程语言大多都有不同的封装API 不一致这足以让人头疼。因此在不同的平台上操作 SQLite必定会使用不同的代码。当然了除了 SQLite 之外实现相同的功能在不同平台上使用不同的代码也许已经是惯例大家也习以为常。请输入标题 bcdefRoll your eggs 的习以为常作为一个懒人当这样一个锅需要自己背的时候自然是去找更简单的解决方案了。目标是一套代码走天下请输入标题 abcdefg那么也不多废话了直接上手写代码这里有很多种技术可以选择比如说 Csqlite3.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);然后根据数据库结构声明一个结构体后面会用于数据传递typeTDemoRec recordAId: Integer;AName: PChar;end;与这个结构等价的 C 的结构体是这样的struct DemoRec {int AId;char* AName;};这一瞬间我们会发现原来操作 SQLite 是如此的简单在此我定义了一个类用来保存一些数据TSQLite classprivateFDatabase: TSQLite3Connection;FQuery: TSQLQuery;FTransaction: TSQLTransaction;publishedproperty 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;beginFQuery.Close;FQuery.SQL.Text: ASQL;tryFQuery.ExecSQL;Exit(True);exceptExit(False);end;end;这段代码似乎太简单了也许我们更加希望在出错时能够给出一个原因那么可以改一下function TSQLite.ExecuteSQL(ASQL: string; var AError: string): Boolean;beginFQuery.Close;FQuery.SQL.Text: ASQL;tryFQuery.ExecSQL;Exit(True);excepton E: Exception do beginAError: e.Message;Exit(False);end;end;end;好了现在调用这个方法时只需要额外传入一个字符串参数就可以获取出错时的信息。在这个体系下要进行查询也很简单需要额外封装两个方法// 根据 SQL 语句查询function TSQLite.Select(ASQL: string; var AError: string): Boolean;beginFQuery.Close;FQuery.SQL.Text: ASQL;tryFQuery.Open;Exit(True);Excepton E: Exception do beginAError: e.Message;Exit(False);end;end;end;// 获取查询结果的行数function dbGetSelectResultCount(APath: PChar): Integer;vardatabase: TSQLite;beginResult : -1;if (DatabaseExists(string(APath))) then begindatabase : GetDatabase(string(APath));Result : database.Query.RecordCount;end;end;// 获取指定行号的一条记录function dbGetSelectResult(APath: PChar; AIndex: Integer): TDemoRec;vardatabase: TSQLite;tmp: string;beginInc(AIndex);if (DatabaseExists(string(APath))) then begindatabase : GetDatabase(string(APath));if (database.Query.RecordCount AIndex) then begindatabase.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, PCfunction dbGetSelectResultCount(APath: PChar): Integer; cdecl;function dbGetSelectResult(APath: PChar; AIndex: Integer): TDemoRec; cdecl;// Androidfunction 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 后面的参数也可以编译 x86mips 等架构的 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 为例分别讲述在各平台的使用方法。Androidpackage 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);}iOSextern 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