遊戲中會大量使用到配置文件,每一個項目組根據本身不一樣的需求會選擇不一樣的存儲格式,好比使用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。