开发文章

cocos2d-x android 直接加载下载到sd的zip里的资源文件

最近公司要做的一个cocos-x项目,这个项目用的是2.2.6版的cocos-x c++ 版,cocos比较老的版本。由于打包出来的apk超过了300M,而且资源无法热更新。面临这两条问题。我们讨论了一下如何尽快的把包改到50m以内和 在线更新新的主题,对此研究了一下cocos的底层。了解到cocos可以通过

复制内容到剪贴板
  1. CCFileUtils::sharedFileUtils()->addSearchPath(路径);    

这样的方法来加载sd 里的资源,然后我们做了第一版 大。资源的加载方式:

1.将资源下载到sd对应的目录中。结合xutils 下载到对应的sd目录中。

复制内容到剪贴板
  1. HttpUtils http = new HttpUtils();    
  2.       http.configRequestThreadPoolSize(MAXDOWNLOADTHREAD);    
  3.       HttpHandler<File> handler = http.download(downloadInfo.getDownloadUrl(), downloadInfo.getFileSavePath(), downloadInfo.isAutoResume(), downloadInfo.isAutoRename(), managerCallBack);    

2.然后解压到对应的目录。

复制内容到剪贴板
  1. /**  
  2.  * 文件解决  
  3.  *  
  4.  * @param file  
  5.  *            要解压的 zip文件  
  6.  * @param savePath  
  7.  *            解压到的路径  
  8.  * @return  
  9.  */    
  10. private Void doUnZip(String filePath, String savePath, UnZipListener unZipListener) {    
  11.     File file = new File(filePath);    
  12.     // Debug.printlili("SDCardManger doUnZip");    
  13.     
  14.     String unzipfile = file.getAbsolutePath(); // 解压缩的文件名包含路径    
  15.     try {    
  16.         // File olddirec = file; // 解压缩的文件路径(为了获取路径)    
  17.         // 保存的文件夹    
  18.         String parent = savePath;    
  19.         ZipInputStream zin = new ZipInputStream(new FileInputStream(unzipfile));    
  20.         ZipEntry entry;    
  21.     
  22.         // 创建文件夹    
  23.         while ((entry = zin.getNextEntry()) != null) {    
  24.             if (entry.isDirectory()) {    
  25.                 File directory = new File(parent, entry.getName());    
  26.                 if (!directory.exists())    
  27.                     if (!directory.mkdirs())    
  28.                         break;    
  29.                 zin.closeEntry();    
  30.             }    
  31.             if (!entry.isDirectory()) {    
  32.                 File myFile = new File(entry.getName());    
  33.                 // 输出路径    
  34.                 String ofile = parent;    
  35.                 File fo = new File(ofile);    
  36.                 if (!fo.exists()) {    
  37.                     fo.mkdir();    
  38.                 }    
  39.     
  40.                 String fileSavePath = ofile + myFile.getPath();    
  41.                 // Debug.printlili("unzip path:" + fileSavePath);    
  42.                 FileOutputStream fout = new FileOutputStream(ofile + myFile.getPath());    
  43.                 DataOutputStream dout = new DataOutputStream(fout);    
  44.                 byte[] b = new byte[1024];    
  45.                 int len = 0;    
  46.                 while ((len = zin.read(b)) != -1) {    
  47.                     dout.write(b, 0, len);    
  48.                 }    
  49.                 dout.close();    
  50.                 fout.close();    
  51.                 zin.closeEntry();    
  52.     
  53.                 if (unZipListener != null) {    
  54.                     unZipListener.onZip(myFile, fileSavePath);    
  55.                 }    
  56.     
  57.             }    
  58.         }    
  59.     
  60.         // file.delete();    
  61.     
  62.     } catch (IOException e) {    
  63.         e.printStackTrace();    
  64.         exception = e;    
  65.     }    
  66.     
  67.     return null;    
  68. }    

3.添加路径到 到 CCFileUtils->SearchPath。这个通过jni 调用一下

复制内容到剪贴板
  1. public static native void addSearchPath(String path);    
复制内容到剪贴板
  1. <p style="margin-top: 0px; margin-bottom: 0px; font-size: 14px; line-height: normal; font-family: Menlo;"><span style="font-variant-ligatures: no-common-ligatures"> </span><span style="font-variant-ligatures: no-common-ligatures; color: #0433ff">void</span><span style="font-variant-ligatures: no-common-ligatures"> Java_com_xxxxx_BaseGameActivity_addSearchPath(JNIEnv*  env, jobject thiz,jstring path)</span></p><p style="margin-top: 0px; margin-bottom: 0px; font-size: 14px; line-height: normal; font-family: Menlo;"><span style="font-variant-ligatures: no-common-ligatures">    {</span></p><p style="margin-top: 0px; margin-bottom: 0px; font-size: 14px; line-height: normal; font-family: Menlo; min-height: 16px;"><span style="font-variant-ligatures: no-common-ligatures">    </span></p><p style="margin-top: 0px; margin-bottom: 0px; font-size: 14px; line-height: normal; font-family: Menlo;"><span style="font-variant-ligatures: no-common-ligatures">        </span><span style="font-variant-ligatures: no-common-ligatures; color: #0433ff">const</span><span style="font-variant-ligatures: no-common-ligatures"> </span><span style="font-variant-ligatures: no-common-ligatures; color: #0433ff">char</span><span style="font-variant-ligatures: no-common-ligatures"> *char_path = (env)->GetStringUTFChars(path, </span><span style="font-variant-ligatures: no-common-ligatures; color: #0433ff">NULL</span><span style="font-variant-ligatures: no-common-ligatures">);</span></p><p style="margin-top: 0px; margin-bottom: 0px; font-size: 14px; line-height: normal; font-family: Menlo;"><span style="font-variant-ligatures: no-common-ligatures">        CCFileUtils::sharedFileUtils()->addSearchPath(char_path);</span></p><p style="margin-top: 0px; margin-bottom: 0px; font-size: 14px; line-height: normal; font-family: Menlo; min-height: 16px;"><span style="font-variant-ligatures: no-common-ligatures">        </span></p><p style="margin-top: 0px; margin-bottom: 0px; font-size: 14px; line-height: normal; font-family: Menlo;"><span style="font-variant-ligatures: no-common-ligatures">    }</span></p>    

经过时间的考验,这样的方式有不好毛病。1.解压到sd大的资源不但大大占用用户的sd卡。2.是一些资源会被当做缓存被一些管理软件清理。3.解压过程会占用线程卡顿。

 

对此出了第二版 直接加载下载好的zip包里的资源。第二版的是在第一版的基础上 修改的。继续研究cocos的资源加载方式,翻阅一下cocos-x 源码,知道了cocos是如何通过一个简单的名字像xxxxbg.png 得到对应的图片资源的。

 

1.获得xxxxbg.png的fullpath。这个fullpath 有两种可能一种是apk中的assets中的绝对路径,一种是sd中的绝对路径。fullpath = searchpath + orderpath + filename

查阅CCFileUtils.cpp

->std::string CCFileUtils::fullPathForFilename(const char* pszFileName) 

->std::string CCFileUtils::getPathForFilename(const std::string& filename, const std::string& resolutionDirectory, const std::string& searchPath)

->std::string CCFileUtils::getFullPathForDirectoryAndFilename(const std::string& strDirectory, const std::string& strFilename)

->bool CCFileUtilsAndroid::isFileExist(const std::string& strFilePath)

当进入到isFileExit知道了这个方法时 跳转到了CCFileUtilsAndroid.cpp

大概知道了 fullPathForFilename这个方法如何工作的,大概意思是 searchpath数组 + orderpath数字 双层循环遍历一下 filename 的fullpath,如果存在这个文件就返回fullpath,进入下一步读取数据。 isFileExist是关键的方法我们来看看这方法。

复制内容到剪贴板
  1. <pre name="code" class="cpp">bool CCFileUtilsAndroid::isFileExist(const std::string& strFilePath)  
  2. {  
  3.     //拼接好的fullpath 长度是否为0,等于0 这个文件就标记为不存当前的临时fullpath  
  4.     if (0 == strFilePath.length())  
  5.     {  
  6.         return false;  
  7.     }  
  8.   
  9.     bool bFound = false;  
  10.       
  11.     // Check whether file exists in apk.  
  12.     //如果fullpath是/开头说明这个路径是在assets中的 s_pZipFile指向apk assets  
  13.     if (strFilePath[0] != '/')  
  14.     {  
  15.         std::string strPath = strFilePath;  
  16.         if (strPath.find(m_strDefaultResRootPath) != 0)  
  17.         {// Didn't find "assets/" at the beginning of the path, adding it.  
  18.             strPath.insert(0, m_strDefaultResRootPath);  
  19.         }  
  20.   
  21.         if (s_pZipFile->fileExists(strPath))  
  22.         {  
  23.             bFound = true;  
  24.         }   
  25.     }  
  26.     //否则就是sd卡的路径 简单的读取,指针不是空的就是存在咯  
  27.     else  
  28.     {  
  29.         FILE *fp = fopen(strFilePath.c_str(), "r");  
  30.         if(fp)  
  31.         {  
  32.             bFound = true;  
  33.             fclose(fp);  
  34.         }  
  35.     }  
  36.     return bFound;  
  37. }  

2.用fullpath 获得 图片或者声音,plist的 unsigned char* 数据。

->unsigned char* CCFileUtils::getFileData(const char* pszFileName, const char* pszMode, unsigned long * pSize) 

->unsigned char* CCFileUtilsAndroid::doGetFileData(const char* pszFileName, const char* pszMode, unsigned long * pSize, bool forAsync)

复制内容到剪贴板
  1. unsigned char* CCFileUtilsAndroid::doGetFileData(const char* pszFileName, const char* pszMode, unsigned long * pSize, bool forAsync)    
  2. {    
  3.     unsigned char * pData = 0;    
  4.         
  5.     if ((! pszFileName) || (! pszMode) || 0 == strlen(pszFileName))    
  6.     {    
  7.         return 0;    
  8.     }    
  9.         
  10.     string fullPath = fullPathForFilename(pszFileName);    
  11.         
  12.     //如果是assets路径下的 加载异步或同步的数据    
  13.     if (fullPath[0] != '/')    
  14.     {    
  15.         if (forAsync)    
  16.         {    
  17.             pData = s_pZipFile->getFileData(fullPath.c_str(), pSize, s_pZipFile->_dataThread);    
  18.         }    
  19.         else    
  20.         {    
  21.             pData = s_pZipFile->getFileData(fullPath.c_str(), pSize);    
  22.         }    
  23.     }    
  24.     //简单的打开file读取文件    
  25.     else    
  26.     {    
  27.         do    
  28.         {    
  29.             // read rrom other path than user set it    
  30.             //CCLOG("GETTING FILE ABSOLUTE DATA: %s", pszFileName);    
  31.             FILE *fp = fopen(fullPath.c_str(), pszMode);    
  32.             CC_BREAK_IF(!fp);    
  33.                 
  34.             unsigned long size;    
  35.             fseek(fp,0,SEEK_END);    
  36.             size = ftell(fp);    
  37.             fseek(fp,0,SEEK_SET);    
  38.             pData = new unsigned char[size];    
  39.             size = fread(pData,sizeof(unsigned char), size,fp);    
  40.             fclose(fp);    
  41.                 
  42.             if (pSize)    
  43.             {    
  44.                 *pSize = size;    
  45.             }    
  46.         } while (0);    
  47.     }    
  48.         
  49.     if (! pData)    
  50.     {    
  51.         std::string msg = "Get data from file(";    
  52.         msg.append(pszFileName).append(") failed!");    
  53.         CCLOG("%s", msg.c_str());    
  54.     }    
  55.         
  56.     return pData;    
  57. }    

既然知道安卓这边是如何读取数据的,接下来我们来思考一下如何对zip里的一个文件也可以检查 并返回fullpath 然后读取数据。

好比如下的路径

/storage/emulated/0/DonutABC/unitRes/game_22.zip里的/res/pub_element/L1U1/audio/pub_unit1_blue_audio.wav

1.获得fullpath。cocos中获得fullpath 就两种ask assets中的和sd中的,想想sd卡中的路径只需要添加searchpath就能找到对应资源,我们也可以把zip文件当做path添加到 searchpath中,在 检测文件是否存在中。我们追加检测一下zip里的文件是否存在不就可以获得fullpath了吗。

获得如下的fullpath,用‘#'结尾,区别他是sd卡中的searchpath,还是zip中的searchpath

searchpath = /storage/emulated/0/DonutABC/unitRes/game_22.zip#

orderpath + filename = /res/pub_element/L1U1/audio/pub_unit1_blue_audio.wav

fullpath = /storage/emulated/0/DonutABC/unitRes/game_22.zip#/res/pub_element/L1U1/audio/pub_unit1_blue_audio.wav

复制内容到剪贴板
  1. bool CCFileUtilsAndroid::isFileExist(const std::string& strFilePath)  
  2. {  
  3.     if (0 == strFilePath.length())  
  4.     {  
  5.         return false;  
  6.     }  
  7.   
  8.     bool bFound = false;  
  9.       
  10.     // Check whether file exists in apk.  
  11.     if (strFilePath[0] != '/')  
  12.     {  
  13.         std::string strPath = strFilePath;  
  14.         if (strPath.find(m_strDefaultResRootPath) != 0)  
  15.         {// Didn't find "assets/" at the beginning of the path, adding it.  
  16.             strPath.insert(0, m_strDefaultResRootPath);  
  17.         }  
  18.   
  19.         if (s_pZipFile->fileExists(strPath))  
  20.         {  
  21.             bFound = true;  
  22.         }   
  23.     }  
  24.     else  
  25.     {  
  26.   
  27.         //看是否为#的路径 用zip方法里的方法检测文件的存在 zlib库检测一下文件是否存在  
  28.         // /storage/emulated/0/DonutABC/unitRes/game_22.zip#/res/pub_element/L1U1/audio/pub_unit1_blue_audio.wav  
  29.           
  30.         std::string pszFileName = strFilePath;  
  31.         std::string pszZipFilePath = "";  
  32.         size_t pos = strFilePath.find_last_of("#");  
  33.         if (pos != std::string::npos)  
  34.         {  
  35.             //CCLOG("isFileExist###########strFilePath:%s", strFilePath.c_str());  
  36.             // file_path = /storage/emulated/0/DonutABC/unitRes/game_22.zip  
  37.             pszZipFilePath = strFilePath.substr(0, pos);  
  38.             // file = res/pub_element/L1U1/audio/pub_unit1_blue_audio.wav  
  39.             pszFileName = strFilePath.substr(pos+2);  
  40.   
  41.             //CCLOG("isFileExist###########zip path:file_path:%s  file:%s",pszZipFilePath.c_str(),pszFileName.c_str());  
  42.   
  43.             unzFile pFile = NULL;  
  44.   
  45.             do   
  46.              {  
  47.                 CC_BREAK_IF(!pszZipFilePath.c_str() || !pszFileName.c_str());  
  48.                 CC_BREAK_IF(strlen(pszZipFilePath.c_str()) == 0);  
  49.   
  50.                 pFile = unzOpen(pszZipFilePath.c_str());  
  51.   
  52.                 int nRet = unzLocateFile(pFile, pszFileName.c_str(), 1);  
  53.                 //CCLOG("isFileExist###########nRet:%d",nRet);  
  54.   
  55.                 if (UNZ_OK == nRet)  
  56.                 {  
  57.                    // CCLOG("isFileExist###########UNZ_OK");  
  58.   
  59.                      bFound = true;  
  60.                 }  
  61.   
  62.                 if (pFile)  
  63.                 {  
  64.                     unzClose(pFile);  
  65.                 }  
  66.              } while (0);  
  67.         }else {  
  68.             FILE *fp = fopen(strFilePath.c_str(), "r");  
  69.             if(fp)  
  70.             {  
  71.                 bFound = true;  
  72.                 fclose(fp);  
  73.             }   
  74.         }  
  75.   
  76.     }  
  77.     return bFound;  
  78. }  

2.获得资源数据。仔细分析CCFileUtils.cpp 会看到一个方法unsigned char* CCFileUtils::getFileDataFromZip(const char* pszZipFilePath, const char* pszFileName, unsigned long * pSize) 是的这个方法就是读取zip文件里的数据的。给力吧

我们 修改一下 doGetFileData 方法 读取zip数据 就好了 ^ ^!

复制内容到剪贴板
  1. unsigned char* CCFileUtilsAndroid::doGetFileData(const char* pszFileName, const char* pszMode, unsigned long * pSize, bool forAsync)  
  2. {  
  3.     unsigned char * pData = 0;  
  4.       
  5.     if ((! pszFileName) || (! pszMode) || 0 == strlen(pszFileName))  
  6.     {  
  7.         return 0;  
  8.     }  
  9.       
  10.     string fullPath = fullPathForFilename(pszFileName);  
  11.       
  12.     if (fullPath[0] != '/')  
  13.     {  
  14.         if (forAsync)  
  15.         {  
  16.             pData = s_pZipFile->getFileData(fullPath.c_str(), pSize, s_pZipFile->_dataThread);  
  17.         }  
  18.         else  
  19.         {  
  20.             pData = s_pZipFile->getFileData(fullPath.c_str(), pSize);  
  21.         }  
  22.     }  
  23.     else  
  24.     {  
  25.   
  26.         do  
  27.         {  
  28.   
  29.            // CCLOG("doGetFileData###########strFilePath:%s", fullPath.c_str());  
  30.              //看是否为#的路径  
  31.         // /storage/emulated/0/DonutABC/unitRes/game_22.zip#/res/pub_element/L1U1/audio/pub_unit1_blue_audio.wav  
  32.   
  33.             std::string pszFileNameTemp = fullPath;  
  34.             std::string pszZipFilePath = "";  
  35.             size_t pos = fullPath.find_last_of("#");  
  36.             if (pos != std::string::npos)  
  37.             {  
  38.                   
  39.                  // file_path = /storage/emulated/0/DonutABC/unitRes/game_22.zip  
  40.                 pszZipFilePath = fullPath.substr(0, pos);  
  41.                 // file = res/pub_element/L1U1/audio/pub_unit1_blue_audio.wav  
  42.                 pszFileNameTemp = fullPath.substr(pos+2);  
  43.   
  44.                 //CCLOG("doGetFileData path:file_path:%s  file:%s",pszZipFilePath.c_str(),pszFileNameTemp.c_str());  
  45.   
  46.                   
  47.                pData = getFileDataFromZip(pszZipFilePath.c_str(),pszFileNameTemp.c_str(),pSize);  
  48.   
  49.             }else {  
  50.   
  51.                 // read rrom other path than user set it  
  52.                //CCLOG("GETTING FILE ABSOLUTE DATA: %s", pszFileName);  
  53.                 FILE *fp = fopen(fullPath.c_str(), pszMode);  
  54.                 CC_BREAK_IF(!fp);  
  55.               
  56.                 unsigned long size;  
  57.                 fseek(fp,0,SEEK_END);  
  58.                 size = ftell(fp);  
  59.                 fseek(fp,0,SEEK_SET);  
  60.                 pData = new unsigned char[size];  
  61.                 size = fread(pData,sizeof(unsigned char), size,fp);  
  62.                 fclose(fp);  
  63.               
  64.                 if (pSize)  
  65.                 {  
  66.                     *pSize = size;  
  67.                 }  
  68.   
  69.             }  
  70.         } while (0);  
  71.         //by sc Load  
  72.         if (pData) {  
  73.             pData = ResourcesDecode::sharedDecode()->decodeData(pData, *pSize, pSize);  
  74.         }  
  75.     }  
  76.       
  77.     if (! pData)  
  78.     {  
  79.         std::string msg = "Get data from file(";  
  80.         msg.append(pszFileName).append(") failed!");  
  81.         CCLOG("%s", msg.c_str());  
  82.     }  
  83.       
  84.     return pData;  
  85. }  

接下来,我们只要将下载的zip路径添加到searchpath 就可以读取了数据啦! 

安卓这边得到sd卡路径

复制内容到剪贴板
  1. public static String getSDcardDir() {  
  2.        return Environment.getExternalStorageDirectory().getPath() + "/";  
  3.    }  

searchpath = sd路径+下载路径+“#”

 

CCFileUtils::sharedFileUtils()->addSearchPath("/storage/emulated/0/DonutABC/unitRes/game_22.zip#");


有同学就问了cocos里某张图片的路径如何填

当然是 zip里的路径啦。/res/pub_element/L1U1/audio/pub_unit1_blue_audio.wav

打开压缩包 就看见啦。

感谢 Joy 支持 磐实编程网 原文地址:
blog.csdn.net/frabbit_on_fire/article/details/51518780

文章信息

发布时间:2016-05-27

作者:Joy

发布者:aquwcw

浏览次数: