Lua的檔案操作自定義上傳頭像
先簡單介紹一下被迫使用Lua的IO的情境:
遊戲支援玩家自定義上傳頭像,在排行榜中會顯示玩家列表(包括本服、跨服),原有的做法是先檢測CCUserDefault中是否存在指定圖片的key以及它的狀態。然後在下載頭像、下載完成後設定對應的狀態。這樣導致的一個問題就是CCUserDefault的讀寫完全失效了。整個遊戲下載的補丁包判斷和其它判斷就完全失效了,不得解除安裝遊戲後重裝。個人目前的推測是由於多執行緒引起的,暫時沒有有效的依據
下載頭像使用的是libcurl,嗯,又是它,在做專案這麼久的過程中,發現它其實有很多地方比較坑。其中有一點我一直沒搞明白,同樣的一樣地址,系統自帶的瀏覽器(IOS、Android均支援)就能正常返回,而遊戲中使用libcurl去下載就是死活返回errcode 28 (CURLE_OPERATION_TIMEDOUT),libcurl我設定的是60秒超時,絕對足夠了
之後我做了優化,在設定自定義頭像的時候,先檢測本地是否有該檔案,如果有直接就設定了,如果沒有就放置在載入佇列中,等下載完成後再設定頭像,只開一條執行緒去下載圖片。(同一張頭像的url只請求一次,也避免對CCUserDefault的讀寫操作)。
通過libcurl下載一個“頭像id.jpg.partial”的檔案,然後下載完成重新寫一個“頭像id.jpg”的檔案。在下載完成的時候,只做了簡單的一個檔案大小判斷,如果檔案小於300B就認為它是有問題的,直接刪除相應的檔案
-- filePath為當前下載完成的臨時頭像檔案路徑 local targetIconUrl = string.gsub(filePath, ".partial", "") local inpFile = io.open(filePath, "rb") local outFile = io.open(targetIconUrl, "wb") if inpFile ~= nil then -- 最大8KB的記憶體 local buffSize = 2^13 while true do local bytes = inpFile:read(buffSize) if not bytes then break end outFile:write(bytes) end inpFile:close() end -- 獲取下載icon的大小 if outFile ~= nil then local current = outFile:seek() local fileSize = outFile:seek("end") outFile:seek("set", current) cclog("==> targetIconUrl : "..tostring(targetIconUrl)..", fileSize : "..tostring(fileSize)) outFile:close() -- 小於300位元組均認為不正常的資料 if fileSize < 300 then FileUtil:DeleteFile_(filePath) FileUtil:DeleteFile_(targetIconUrl) self:DownloadNextIconHandler() do return end end end
本來,直接呼叫對應的FileUtil中的FileRename方法就可以實現檔案的重新命名,但是線上的版本沒有匯出相應的方法,導致目前只能通過Lua的IO來實現。
最近再看lua的原始碼時,才真正意識到luaconf.h中定義的 LUAI_MAXCSTACK 是 cclosure的upvalue上限,而lua記憶體上限似乎沒有找到明確的程式碼。
而file:read呼叫的是liolib.c
底層通過呼叫fread方法來獲得檔案的內容,預設每次最多讀取512(LUAL_BUFFERSIZE的值)
然後呼叫file:seek(“end”)來獲取檔案大小
底層呼叫feek方法來實現
本以為到這裡就結束了,實際上我遇到另外一個問題。如果頭像因稽核問題被刪除了,導致404,結果底層libcurl方法沒有判斷http status code,直接判斷CURLcode的值是否為CURLE_OK,導致將得到的檔案直接寫入了。但我從崩潰的日誌上得到的資訊是,小米4這臺裝置上獲得的檔案大小為18378
之後就直接報
invalid address or address of corrupt block 0x7c0eaa40 passed to dlfree
之後我修改了libcurl下載檔案的程式碼,但要等下次打整包的時候才能用上
把不是jpeg的圖片直接對CCSprite進行路徑賦值的時候就over了,所以需要一個檢測檔案是否為jpeg的方法
-- 判斷資源是否為jpg
function PCUtils:CheckIsJpeg(filePath)
local isJpeg = false
if FileUtil:CheckFileExistWithFullPath(filePath) then
local inpFile = io.open(filePath, "rb")
-- 讀取前三位
local bytes = inpFile:read(3)
if bytes then
local fileHeadIden = ""
for _, b in ipairs{string.byte(bytes, 1, -1)} do
local val = string.format("%02X", b)
fileHeadIden = fileHeadIden..val
end
if string.upper(fileHeadIden) == "FFD8FF" then
isJpeg = true
else
cclog("==> filePath : "..tostring(filePath)..", fileHeadIden : "..tostring(fileHeadIden))
end
end
inpFile:close()
end
return isJpeg
end
讀取檔案的前三位,轉換為16進位制,然後對比JPEG的頭部,判斷是否為JPEG格式的檔案,這個是我想起自己之前寫過的一篇文章《node.js獲取圖片檔案的真實型別》
檔案一些方法和程式碼,比如為何是r + b,以及2^13(8KB記憶體)這種技巧,都是參考《Lua程式設計 第二版》第21章 I/O庫,網上應該有中文版的PDF下載,自行搜尋吧…
本文參考: