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

遊戲中會大量使用到配置文件,每一個項目組根據本身不一樣的需求會選擇不一樣的存儲格式,好比使用Json或者SQLite來存儲數據。此處咱們只對使用SQLite的狀況來作討論。通常狀況下會選擇把它放在可讀寫目錄裏面,這樣SQLite能夠直接使用它原來的io API來對db文件進行讀取。在PC或者iOS平臺上這不是問題。可是若是在Android平臺上,遊戲安裝後仍是以一個apk文件的形式存在。若是咱們的數據放在了db中,使用SQLite原來自帶的io功能是不能進行讀取的。這裏有3種方式能夠供選擇:html

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

  通常你們可能會選擇第一種方法,這沒有什麼好說的。咱們接下來看看第3種方法的可能性。android

理論

爲了實現上述的想法,咱們須要兩個條件:git

  1. android提供對apk中單個文件的讀取的能力。
  2. SQLite提供了對io層(不一樣平臺)進行快速修改的能力,這就要求SQLite有比較好的抽象以較少的模塊之間的耦合。

  固然上述兩個條件是知足的,下面咱們來具體看看這兩個條件。github

Android對apk中單個文件的讀取能力

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對於文件io層的支持

下圖是 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等來深刻了解。異步

實現

有了上面的理論支持,那麼咱們就能夠着手能夠寫代碼了。咱們目前有兩種方案能夠選擇:函數

  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這幾個類。

示例代碼以下所示:

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實現細節

UE4在C++中能夠直接拿到AAssetManager對象,具體實現細節UE4已經幫咱們作了,具體能夠查看AndroidJNI.cpp中的代碼。咱們拿到AAssetManager這個對象並把它設置給SQLite就能夠了。

總結

到此,咱們對SQLite擴展讀取apk中db的方法已經寫完了。因爲Android NDK返回了文件描述符以及SQLite提供的OS Interface層讓咱們很比較容易的實現了對SQLite擴展。因爲做者對SQLite原來並無瞭解,因此不免有錯誤之處,若是有錯誤請及時指正。若是讀者想對SQLite有一個比較深刻的認識,也能夠看看參考文章6。

參考文章:

  1. http://sqlite.org/arch.html
  2. http://www.sqlite.org/vfs.html
  3. https://developer.android.com/ndk/reference/group___asset.html#ga1af4ffd050016e99961e24f550981677
  4. https://docs.unity3d.com/560/Documentation/ScriptReference/AndroidJNI.html
  5. http://answers.unity3d.com/questions/205212/android-file-open.html
相關文章
相關標籤/搜索