游戏中会大量使用到配置文件,每一个项目组根据本身不一样的需求会选择不一样的存储格式,好比使用Json或者SQLite来存储数据。此处咱们只对使用SQLite的状况来作讨论。通常状况下会选择把它放在可读写目录里面,这样SQLite能够直接使用它原来的io API来对db文件进行读取。在PC或者iOS平台上这不是问题。可是若是在Android平台上,游戏安装后仍是以一个apk文件的形式存在。若是咱们的数据放在了db中,使用SQLite原来自带的io功能是不能进行读取的。这里有3种方式能够供选择:html
通常你们可能会选择第一种方法,这没有什么好说的。咱们接下来看看第3种方法的可能性。android
为了实现上述的想法,咱们须要两个条件:git
固然上述两个条件是知足的,下面咱们来具体看看这两个条件。github
1 Open a new file descriptor that can be used to read the asset data. If the start or length cannot be represented by a 32-bit number, it will be truncated. If the file is large, use AAsset_openFileDescriptor64 instead. 2 Returns < 0 if direct fd access is not possible (for example, if the asset is compressed). 3 int AAsset_openFileDescriptor (AAsset * asset, off_t * outStart, off_t * outLength )
从这个API能够看出,它能够返回一个用于读取当前asset的一个文件描述符。可是若是当前asset被压缩了,那么就回返回一个小于0的值。若是咱们想要读取db的话,那么它必须是没有压缩过的。sql
示例代码大致以下所示:windows
1 AAsset* asset = AAssetManager_open(mgr, filename, AASSET_MODE_UNKNOWN); 2 if (NULL == asset) 3 { 4 //LOGD("file not found! Stop preload file: %s", filename); 5 return FILE_NOT_FOUND; 6 } 7 8 // open asset as file descriptor 9 int fd = AAsset_openFileDescriptor(asset, &start, &length); 10 assert(0 <= fd); 11 AAsset_close(Asset);
注意,这个fd返回的是整个apk的句柄,start表明这个文件在apk中的偏移,length表明长度,使用的时候要注意。架构
下图是 SQLite官方给出的架构图:app
咱们这里主要关注的就是OS Interface这一层,它使用了VFS这一对象来为不一样系统之间的可移植性提供了保证,。具体细节能够参照官网对于VFS的介绍。SQLite目前提供了对类unix系统和windows系统的支持,分别在os_unix.c以及os_win.c中实现的。其中os_unix.c提供了对mac os、iOS、Android以及Linux的支持。若是读取想对这块有一个比较深刻了了解能够看官方文档以及查看一些示例如test_demovfs.c等来深刻了解。异步
有了上面的理论支持,那么咱们就能够着手能够写代码了。咱们目前有两种方案能够选择:函数
第一种方案须要写的代码比较多,并且须要读者对SQLite有一个比较深刻的了解。因此咱们这里选择第二种方案。在原来的os_unix.c的基础上进行改动。
经过分析os_unix.c文件,咱们能得出它主要使用了open() read() write()等io操做。这跟咱们上面Android NDK提供的AAsset_openFileDescriptor正好完美的结合起来。咱们正好可使用它返回的句柄进行相似的操做。只不过在Android的状况下特殊一点。
这样下来,咱们基本上大致须要改的几个函数和结构体以下所示
以上基本是须要改到的函数,固然根据实现的不一样可能具体须要改动的函数不同。这只是比较粗暴的改法。像咱们须要支持从apk里面读取以及从一个散文件里面读取,因此跟上面的改动多少有一些不同的地方,可是基本思想是通的。固然因为本人对SQLite不了 解,可能有须要改动的地方没有注意到,若是说的有错误但愿能及时指正。方法已经说的比较明白了,这里也就不贴代码了。
上面的例子提到的AAssetManager_open在打开时须要一个AAssetManager的对象,这个对象只能从Java里面获取。若是你是直接使用Android开发那么这个对象就比较容易获取,那么若是你是使用Unity或者UE4开发怎么获取这个对象呢。
SQLite的修改跟上面是同样的,只是咱们在Unity中如何获取这个对象呢。读者能够具体对照一下这个类AndroidJNI AndroidJNIHelper AndroidJavaClass AndroidJavaObject AndroidJavaProxy这几个类。
示例代码以下所示:
1 IntPtr cls_Activity = (IntPtr)AndroidJNI.FindClass("com/unity3d/player/UnityPlayer"); 2 IntPtr fid_Activity = AndroidJNI.GetStaticFieldID(cls_Activity, "currentActivity", "Landroid/app/Activity;"); 3 IntPtr obj_Activity = AndroidJNI.GetStaticObjectField(cls_Activity, fid_Activity); 4 IntPtr obj_cls = AndroidJNI.GetObjectClass(obj_Activity); 5 IntPtr asset_func = AndroidJNI.GetMethodID(obj_cls, "getAssets", "()Landroid/content/res/AssetManager;"); 6 jvalue[] asset_array = new jvalue[2]; // <- ? 7 IntPtr assetManager = AndroidJNI.CallObjectMethod(obj_Activity, asset_func, asset_array);
这样咱们就获得了这个AssetManager,这个时候咱们就能够经过C#把这个对象传递给SQLite库了。
UE4在C++中能够直接拿到AAssetManager对象,具体实现细节UE4已经帮咱们作了,具体能够查看AndroidJNI.cpp中的代码。咱们拿到AAssetManager这个对象并把它设置给SQLite就能够了。
到此,咱们对SQLite扩展读取apk中db的方法已经写完了。因为Android NDK返回了文件描述符以及SQLite提供的OS Interface层让咱们很比较容易的实现了对SQLite扩展。因为做者对SQLite原来并无了解,因此不免有错误之处,若是有错误请及时指正。若是读者想对SQLite有一个比较深刻的认识,也能够看看参考文章6。