當前位置:
首頁 > 知識 > 擴展SQLite使其能從apk文件中讀取db

擴展SQLite使其能從apk文件中讀取db

遊戲中會大量使用到配置文件,每個項目組根據自己不同的需求會選擇不同的存儲格式,比如使用Json或者SQLite來存儲數據。此處我們只對使用SQLite的情況來做討論。一般情況下會選擇把它放在可讀寫目錄裡面,這樣SQLite可以直接使用它原來的io API來對db文件進行讀取。在PC或者iOS平台上這不是問題。但是如果在Android平台上,遊戲安裝後還是以一個apk文件的形式存在。如果我們的數據放在了db中,使用SQLite原來自帶的io功能是不能進行讀取的。這裡有3種方式可以供選擇:

  1. 在程序第一次啟動時,把apk中的所有文件解壓出來放到可讀寫目錄中,這樣存在的問題是第一次打開程序時會比較慢。
  2. 在需要的讀取某個db的時候才把這個文件從apk中解壓出來,這樣的話可能會導致卡頓,或者使用協程等非同步操作來完成,但是這樣對於邏輯層的代碼書寫成本比較高。
  3. 對SQLite做一定的改動,使它可以讀取apk中的db文件。

一般大家可能會選擇第一種方法,這沒有什麼好說的。我們接下來看看第3種方法的可能性。


理論

為了實現上述的想法,我們需要兩個條件:

  1. android提供對apk中單個文件的讀取的能力。
  2. 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官方給出的架構圖:

擴展SQLite使其能從apk文件中讀取db

我們這裡主要關注的就是OS Interface這一層,它使用了VFS這一對象來為不同系統之間的可移植性提供了保證,。具體細節可以參照官網對於VFS的介紹。SQLite目前提供了對類unix系統和windows系統的支持,分別在os_unix.c以及os_win.c中實現的。其中os_unix.c提供了對mac os、iOS、Android以及Linux的支持。如果讀取想對這塊有一個比較深入了了解可以看官方文檔以及查看一些示例如test_demovfs.c等來深入了解。


實現

有了上面的理論支持,那麼我們就可以著手可以寫代碼了。我們目前有兩種方案可以選擇:

  1. 單獨為安卓實現一個VFS。
  2. 在os_unix.c的基礎上進行修改。

第一種方案需要寫的代碼比較多,而且需要讀者對SQLite有一個比較深入的了解。所以我們這裡選擇第二種方案。在原來的os_unix.c的基礎上進行改動。

SQLite改造

通過分析os_unix.c文件,我們能得出它主要使用了open read write等io操作。這跟我們上面Android NDK提供的AAsset_openFileDescriptor正好完美的結合起來。我們正好可以使用它返回的句柄進行類似的操作。只不過在Android的情況下特殊一點。

這樣下來,我們基本上大體需要改的幾個函數和結構體如下所示

  1. struct unixFile 我們需要在其中添加文件在apk中的偏移以及大小 。
  2. posixOpen 通過openFileDescriptor返迴文件描述符。
  3. seekAndRead 讀取時要加上文件偏移。
  4. 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。

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 達人科技 的精彩文章:

ASP.NET CORE小試牛刀:乾貨
業餘草教你解讀Spark源碼閱讀之HistoryServer
「PHP」PHP面向對象編程——phpOOP入門
將git版本號編譯進程序

TAG:達人科技 |

您可能感興趣

Python使用pandas讀取Excel文件多個WorkSheet的數據並繪製柱狀圖和熱力圖
讀取ClassPath下resource文件的正確姿勢
Python讀取與更改xml文件
iMessage漏洞致iPhone上任意文件讀取
不完整的Http讀取和Python中的Requests庫
SpringBoot如何讀取資源文件
Hooking Chrome瀏覽器的SSL函數來讀取SSL通信數據
volume讀取模型color信息
Spring Boot 讀取靜態資源文件
TensorFlow 數據讀取
在iPhone上讀取micro SD存儲卡的方法!
如何用 Python 讀取 Outlook 中的電子郵件
解決SpringBoot無法讀取js css靜態資源的新方法
tensorflow的模型保存於讀取
Python實現讀取PDF文件案例
python筆記14-讀取yaml配置文件
群聯首發SD/microSD Express存儲卡主控:讀取速度達900MB/s
群聯首發SD/microSD Express存儲卡主控:讀取速度達900MB/s
通過讀取ASP.NET應用泄露的secrets獲得bug賞金17000美刀
FSO應用 讀取txt文件