扩展SQLite使其能从apk文件中读取db
游戏中会大量使用到配置文件,每个项目组根据自己不同的需求会选择不同的存储格式,比如使用Json或者SQLite来存储数据。此处我们只对使用SQLite的情况来做讨论。一般情况下会选择把它放在可读写目录里面,这样SQLite可以直接使用它原来的io API来对db文件进行读取。在PC或者iOS平台上这不是问题。但是如果在Android平台上,游戏安装后还是以一个apk文件的形式存在。如果我们的数据放在了db中,使用SQLite原来自带的io功能是不能进行读取的。这里有3种方式可以供选择:
- 在程序第一次启动时,把apk中的所有文件解压出来放到可读写目录中,这样存在的问题是第一次打开程序时会比较慢。
- 在需要的读取某个db的时候才把这个文件从apk中解压出来,这样的话可能会导致卡顿,或者使用协程等异步操作来完成,但是这样对于逻辑层的代码书写成本比较高。
- 对SQLite做一定的改动,使它可以读取apk中的db文件。
一般大家可能会选择第一种方法,这没有什么好说的。我们接下来看看第3种方法的可能性。
理论
为了实现上述的想法,我们需要两个条件:
- android提供对apk中单个文件的读取的能力。
- SQLite提供了对io层(不同平台)进行快速修改的能力,这就要求SQLite有比较好的抽象以较少的模块之间的耦合。
当然上述两个条件是满足的,下面我们来具体看看这两个条件。
Android对apk中单个文件的读取能力
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. Returns < 0 if direct fd access is not possible (for example, if the asset is compressed). int AAsset_openFileDescriptor (AAsset * asset, off_t * outStart, off_t * outLength ) |
从这个API可以看出,它可以返回一个用于读取当前asset的一个文件描述符。但是如果当前asset被压缩了,那么就回返回一个小于0的值。如果我们想要读取db的话,那么它必须是没有压缩过的。
示例代码大体如下所示:
AAsset* asset = AAssetManager_open(mgr, filename, AASSET_MODE_UNKNOWN); if (NULL == asset) { //LOGD("file not found! Stop preload file: %s", filename); return FILE_NOT_FOUND; } // open asset as file descriptor int fd = AAsset_openFileDescriptor(asset, &start, &length); assert(0 <= fd); AAsset_close(Asset); |
注意,这个fd返回的是整个apk的句柄,start代表这个文件在apk中的偏移,length代表长度,使用的时候要注意。
SQLite对于文件io层的支持
下图是SQLite官方给出的架构图:
我们这里主要关注的就是OS Interface这一层,它使用了VFS这一对象来为不同系统之间的可移植性提供了保证,。具体细节可以参照官网对于VFS的介绍。SQLite目前提供了对类unix系统和windows系统的支持,分别在os_unix.c以及os_win.c中实现的。其中os_unix.c提供了对mac os、iOS、Android以及Linux的支持。如果读取想对这块有一个比较深入了了解可以看官方文档以及查看一些示例如test_demovfs.c等来深入了解。
实现
有了上面的理论支持,那么我们就可以着手可以写代码了。我们目前有两种方案可以选择:
- 单独为安卓实现一个VFS。
- 在os_unix.c的基础上进行修改。
第一种方案需要写的代码比较多,而且需要读者对SQLite有一个比较深入的了解。所以我们这里选择第二种方案。在原来的os_unix.c的基础上进行改动。
SQLite改造
通过分析os_unix.c文件,我们能得出它主要使用了open() read() write()等io操作。这跟我们上面Android NDK提供的AAsset_openFileDescriptor正好完美的结合起来。我们正好可以使用它返回的句柄进行类似的操作。只不过在Android的情况下特殊一点。
这样下来,我们基本上大体需要改的几个函数和结构体如下所示
- struct unixFile 我们需要在其中添加文件在apk中的偏移以及大小 。
- posixOpen 通过openFileDescriptor返回文件描述符。
- seekAndRead 读取时要加上文件偏移。
- unixFileSize 返回前面unixFile中记录的文件大小的值。
以上基本是需要改到的函数,当然根据实现的不同可能具体需要改动的函数不一样。这只是比较粗暴的改法。像我们需要支持从apk里面读取以及从一个散文件里面读取,所以跟上面的改动多少有一些不一样的地方,但是基本思想是通的。当然由于本人对SQLite不了 解,可能有需要改动的地方没有注意到,如果说的有错误希望能及时指正。方法已经说的比较明白了,这里也就不贴代码了。
上面的例子提到的AAssetManager_open在打开时需要一个AAssetManager的对象,这个对象只能从Java里面获取。如果你是直接使用Android开发那么这个对象就比较容易获取,那么如果你是使用Unity或者UE4开发怎么获取这个对象呢。
Unity实现细节
SQLite的修改跟上面是一样的,只是我们在Unity中如何获取这个对象呢。读者可以具体对照一下这个类AndroidJNI AndroidJNIHelper AndroidJavaClass AndroidJavaObject AndroidJavaProxy这几个类。
示例代码如下所示:
IntPtr cls_Activity = (IntPtr)AndroidJNI.FindClass("com/unity3d/player/UnityPlayer"); IntPtr fid_Activity = AndroidJNI.GetStaticFieldID(cls_Activity, "currentActivity", "Landroid/app/Activity;"); IntPtr obj_Activity = AndroidJNI.GetStaticObjectField(cls_Activity, fid_Activity); IntPtr obj_cls = AndroidJNI.GetObjectClass(obj_Activity); IntPtr asset_func = AndroidJNI.GetMethodID(obj_cls, "getAssets", "()Landroid/content/res/AssetManager;"); jvalue[] asset_array = new jvalue[2]; // <- ? IntPtr assetManager = AndroidJNI.CallObjectMethod(obj_Activity, asset_func, asset_array); |
这样我们就得到了这个AssetManager,这个时候我们就可以通过C#把这个对象传递给SQLite库了。
UE4实现细节
UE4在C++中可以直接拿到AAssetManager对象,具体实现细节UE4已经帮我们做了,具体可以查看AndroidJNI.cpp中的代码。我们拿到AAssetManager这个对象并把它设置给SQLite就可以了。
总结
到此,我们对SQLite扩展读取apk中db的方法已经写完了。由于Android NDK返回了文件描述符以及SQLite提供的OS Interface层让我们很比较容易的实现了对SQLite扩展。由于作者对SQLite原来并没有了解,所以难免有错误之处,如果有错误请及时指正。如果读者想对SQLite有一个比较深入的认识,也可以看看参考文章6。
参考文章:
- http://sqlite.org/arch.html
- http://www.sqlite.org/vfs.html
- https://developer.android.com/ndk/reference/group___asset.html#ga1af4ffd050016e99961e24f550981677
- https://docs.unity3d.com/560/Documentation/ScriptReference/AndroidJNI.html
- http://answers.unity3d.com/questions/205212/android-file-open.html
扩展SQLite使其能从apk文件中读取db的更多相关文章
- [Android Pro] 查看 keystore文件的签名信息 和 检查apk文件中的签名信息
1: 查看 keystore文件的签名信息 keytool -list -v -keystore keystoreName -storepass keystorePassword 2: 检查apk文件 ...
- 从Excel文件中读取内容
从Excel文件中读取内容 global::System.Web.HttpPostedFileBase file = Request.Files["txtFile"]; strin ...
- 条形码的应用三-----------从Excel文件中读取条形码
条形码的应用三------从Excel文件中读取条形码 介绍 上一篇文章,我向大家展示了生成多个条形码并存储到Excel文件中的一个方法.后来我又有了个想法:既然条码插入到excel中了,我可不可以从 ...
- Servlet从本地文件中读取图片,并显示在页面中
import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpSer ...
- 文件_ _android从资源文件中读取文件流并显示的方法
======== 1 android从资源文件中读取文件流并显示的方法. 在android中,假如有的文本文件,比如TXT放在raw下,要直接读取出来,放到屏幕中显示,可以这样: private ...
- 在C#中用Linq从属性文件中读取键值对Key-Value Pair
博客搬到了fresky.github.io - Dawei XU,请各位看官挪步.最新的一篇是:在C#中用Linq从属性文件中读取键值对Key-Value Pair.
- Python3 将configparser从ini文件中读取的内容转换成字典格式
因为写脚本的用到了,所以研究了下怎么将configparser从ini文件中读取的内容转换成字典格式. 整理一下,希望能对大家有帮助. 从http://stackoverflow.com/questi ...
- Python3实现从文件中读取指定行的方法
from:http://www.jb51.net/article/66580.htm 这篇文章主要介绍了Python3实现从文件中读取指定行的方法,涉及Python中linecache模块操作文件的使 ...
- Java将对象保存到文件中/从文件中读取对象
1.保存对象到文件中 Java语言只能将实现了Serializable接口的类的对象保存到文件中,利用如下方法即可: public static void writeObjectToFile(Obje ...
随机推荐
- grunt基础配置
grunt基础配置 要使用grunt来管理项目,一般需要如下的几个步骤: 安装grunt命令行工具grunt-cli 在项目中安装grunt 安装grunt插件 建立并配置Gruntfile.js 安 ...
- JavaSE教程-02Java基本语法-思维导图
思维导图看不清楚时: 1)可以将图片另存为图片,保存在本地来查看 2)右击在新标签中打开放大查看 1.注释 定义:用于解释说明程序作用的文字 注释类别 单行注释 格式: //注释文字 多行注释 格式: ...
- Java常用类之【字符串相关类型】
一.字符相关类型 分类: 1.不可变的字符序列: String类 2.可变的字符序列: StringBuilder类--->线程不安全的 执行效率相对较高 StringBuffer类---> ...
- javaSE_07Java中类和对象-封装特性
一.谈谈什么是面向对象的思维 理解面向对象,重点是要思考以下的问题 面向过程 vs 面向对象 Ø 谈谈什么是面向过程的编程思想? Ø 为什么有面向过程还要有面向对象? Ø 谈谈什么是面向对象的编程思想 ...
- Dom元素的Property和Attribute
Attribute就是DOM节点自带的属性,例如html中常用的id.class.title.align等: 而Property是这个DOM元素作为对象,其附加的内容,例如childNodes.fir ...
- 4.Node.js 微信消息管理
一.写在前面的话 当用户发送消息给公众号时(或某些特定的用户操作引发的事件推送时),会产生一个POST请求,开发者可以在响应包(Get)中返回特定XML结构,来对该消息进行响应. 消息推送也是 ...
- java中调用本地动态链接库(*.DLL)的两种方式详解和not found library、打包成jar,war包dll无法加载等等问题解决办法
我们经常会遇到需要java调用c++的案例,这里就java调用DLL本地动态链接库两种方式,和加载过程中遇到的问题进行详细介绍 1.通过System.loadLibrary("dll名称,不 ...
- 掌握Chrome Developer Tools:下一阶段前端开发技术
Tips 原文作者:Ben Edelstein 原文地址:Mastering Chrome Developer Tools: Next Level Front-End Development Tech ...
- 用php+mysql+ajax实现淘宝客服或阿里旺旺聊天功能 之 后台页面
在上一篇随笔中,我们已经看了如何实现前台的对话功能:前台我限定了店主只有一人,店铺只有一个,所有比较单一,但后台就不一样了,而后台更像是我们常见的聊天软件:当然,前台也应该实现这种效果,但原理懂了,可 ...
- [HNOI2007]紧急疏散EVACUATE (湖南2007年省选)
[HNOI2007]紧急疏散EVACUATE 题目描述 发生了火警,所有人员需要紧急疏散!假设每个房间是一个N M的矩形区域.每个格子如果是'.',那么表示这是一块空地:如果是'X',那么表示这是一面 ...