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

来源:互联网 发布:excel表去掉重复数据 编辑:程序博客网 时间:2024/05/22 11:42

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


CCFileUtils::sharedFileUtils()->addSearchPath(路径);

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

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

  HttpUtils http = new HttpUtils();        http.configRequestThreadPoolSize(MAXDOWNLOADTHREAD);        HttpHandler<File> handler = http.download(downloadInfo.getDownloadUrl(), downloadInfo.getFileSavePath(), downloadInfo.isAutoResume(), downloadInfo.isAutoRename(), managerCallBack);        


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

            /**             * 文件解决             *             * @param file             *            要解压的 zip文件             * @param savePath             *            解压到的路径             * @return             */            private Void doUnZip(String filePath, String savePath, UnZipListener unZipListener) {                File file = new File(filePath);                // Debug.printlili("SDCardManger doUnZip");                String unzipfile = file.getAbsolutePath(); // 解压缩的文件名包含路径                try {                    // File olddirec = file; // 解压缩的文件路径(为了获取路径)                    // 保存的文件夹                    String parent = savePath;                    ZipInputStream zin = new ZipInputStream(new FileInputStream(unzipfile));                    ZipEntry entry;                    // 创建文件夹                    while ((entry = zin.getNextEntry()) != null) {                        if (entry.isDirectory()) {                            File directory = new File(parent, entry.getName());                            if (!directory.exists())                                if (!directory.mkdirs())                                    break;                            zin.closeEntry();                        }                        if (!entry.isDirectory()) {                            File myFile = new File(entry.getName());                            // 输出路径                            String ofile = parent;                            File fo = new File(ofile);                            if (!fo.exists()) {                                fo.mkdir();                            }                            String fileSavePath = ofile + myFile.getPath();                            // Debug.printlili("unzip path:" + fileSavePath);                            FileOutputStream fout = new FileOutputStream(ofile + myFile.getPath());                            DataOutputStream dout = new DataOutputStream(fout);                            byte[] b = new byte[1024];                            int len = 0;                            while ((len = zin.read(b)) != -1) {                                dout.write(b, 0, len);                            }                            dout.close();                            fout.close();                            zin.closeEntry();                            if (unZipListener != null) {                                unZipListener.onZip(myFile, fileSavePath);                            }                        }                    }                    // file.delete();                } catch (IOException e) {                    e.printStackTrace();                    exception = e;                }                return null;            }



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

public static native void addSearchPath(String path);


    void Java_com_xxx_base_BaseGameActivity_addSearchPath(JNIEnv*  env, jobject thiz,jstring path)    {            const char *char_path = (env)->GetStringUTFChars(path, NULL);        CCFileUtils::sharedFileUtils()->addSearchPath(char_path);            }


经过时间的考验,这样的方式有不好毛病。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是关键的方法我们来看看这方法。

<pre name="code" class="cpp">bool CCFileUtilsAndroid::isFileExist(const std::string& strFilePath){    //拼接好的fullpath 长度是否为0,等于0 这个文件就标记为不存当前的临时fullpath    if (0 == strFilePath.length())    {        return false;    }    bool bFound = false;        // Check whether file exists in apk.    //如果fullpath是/开头说明这个路径是在assets中的 s_pZipFile指向apk assets    if (strFilePath[0] != '/')    {        std::string strPath = strFilePath;        if (strPath.find(m_strDefaultResRootPath) != 0)        {// Didn't find "assets/" at the beginning of the path, adding it.            strPath.insert(0, m_strDefaultResRootPath);        }        if (s_pZipFile->fileExists(strPath))        {            bFound = true;        }     }    //否则就是sd卡的路径 简单的读取,指针不是空的就是存在咯    else    {        FILE *fp = fopen(strFilePath.c_str(), "r");        if(fp)        {            bFound = true;            fclose(fp);        }    }    return bFound;}


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)

unsigned char* CCFileUtilsAndroid::doGetFileData(const char* pszFileName, const char* pszMode, unsigned long * pSize, bool forAsync){    unsigned char * pData = 0;        if ((! pszFileName) || (! pszMode) || 0 == strlen(pszFileName))    {        return 0;    }        string fullPath = fullPathForFilename(pszFileName);        //如果是assets路径下的 加载异步或同步的数据    if (fullPath[0] != '/')    {        if (forAsync)        {            pData = s_pZipFile->getFileData(fullPath.c_str(), pSize, s_pZipFile->_dataThread);        }        else        {            pData = s_pZipFile->getFileData(fullPath.c_str(), pSize);        }    }    //简单的打开file读取文件    else    {        do        {            // read rrom other path than user set it            //CCLOG("GETTING FILE ABSOLUTE DATA: %s", pszFileName);            FILE *fp = fopen(fullPath.c_str(), pszMode);            CC_BREAK_IF(!fp);                        unsigned long size;            fseek(fp,0,SEEK_END);            size = ftell(fp);            fseek(fp,0,SEEK_SET);            pData = new unsigned char[size];            size = fread(pData,sizeof(unsigned char), size,fp);            fclose(fp);                        if (pSize)            {                *pSize = size;            }        } while (0);    }        if (! pData)    {        std::string msg = "Get data from file(";        msg.append(pszFileName).append(") failed!");        CCLOG("%s", msg.c_str());    }        return pData;}


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

好比如下的路径

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

1.获得fullpath。cocos中获得fullpath 就两种apk 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


bool CCFileUtilsAndroid::isFileExist(const std::string& strFilePath){    if (0 == strFilePath.length())    {        return false;    }    bool bFound = false;        // Check whether file exists in apk.    if (strFilePath[0] != '/')    {        std::string strPath = strFilePath;        if (strPath.find(m_strDefaultResRootPath) != 0)        {// Didn't find "assets/" at the beginning of the path, adding it.            strPath.insert(0, m_strDefaultResRootPath);        }        if (s_pZipFile->fileExists(strPath))        {            bFound = true;        }     }    else    {        //看是否为#的路径 用zip方法里的方法检测文件的存在 zlib库检测一下文件是否存在        // /storage/emulated/0/DonutABC/unitRes/game_22.zip#/res/pub_element/L1U1/audio/pub_unit1_blue_audio.wav                std::string pszFileName = strFilePath;        std::string pszZipFilePath = "";        size_t pos = strFilePath.find_last_of("#");        if (pos != std::string::npos)        {            //CCLOG("isFileExist###########strFilePath:%s", strFilePath.c_str());            // file_path = /storage/emulated/0/DonutABC/unitRes/game_22.zip            pszZipFilePath = strFilePath.substr(0, pos);            // file = res/pub_element/L1U1/audio/pub_unit1_blue_audio.wav            pszFileName = strFilePath.substr(pos+2);            //CCLOG("isFileExist###########zip path:file_path:%s  file:%s",pszZipFilePath.c_str(),pszFileName.c_str());            unzFile pFile = NULL;            do              {                CC_BREAK_IF(!pszZipFilePath.c_str() || !pszFileName.c_str());                CC_BREAK_IF(strlen(pszZipFilePath.c_str()) == 0);                pFile = unzOpen(pszZipFilePath.c_str());                int nRet = unzLocateFile(pFile, pszFileName.c_str(), 1);                //CCLOG("isFileExist###########nRet:%d",nRet);                if (UNZ_OK == nRet)                {                   // CCLOG("isFileExist###########UNZ_OK");                     bFound = true;                }                if (pFile)                {                    unzClose(pFile);                }             } while (0);        }else {            FILE *fp = fopen(strFilePath.c_str(), "r");            if(fp)            {                bFound = true;                fclose(fp);            }         }    }    return bFound;}


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

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

unsigned char* CCFileUtilsAndroid::doGetFileData(const char* pszFileName, const char* pszMode, unsigned long * pSize, bool forAsync){    unsigned char * pData = 0;        if ((! pszFileName) || (! pszMode) || 0 == strlen(pszFileName))    {        return 0;    }        string fullPath = fullPathForFilename(pszFileName);        if (fullPath[0] != '/')    {        if (forAsync)        {            pData = s_pZipFile->getFileData(fullPath.c_str(), pSize, s_pZipFile->_dataThread);        }        else        {            pData = s_pZipFile->getFileData(fullPath.c_str(), pSize);        }    }    else    {        do        {           // CCLOG("doGetFileData###########strFilePath:%s", fullPath.c_str());             //看是否为#的路径        // /storage/emulated/0/DonutABC/unitRes/game_22.zip#/res/pub_element/L1U1/audio/pub_unit1_blue_audio.wav            std::string pszFileNameTemp = fullPath;            std::string pszZipFilePath = "";            size_t pos = fullPath.find_last_of("#");            if (pos != std::string::npos)            {                                 // file_path = /storage/emulated/0/DonutABC/unitRes/game_22.zip                pszZipFilePath = fullPath.substr(0, pos);                // file = res/pub_element/L1U1/audio/pub_unit1_blue_audio.wav                pszFileNameTemp = fullPath.substr(pos+2);                //CCLOG("doGetFileData path:file_path:%s  file:%s",pszZipFilePath.c_str(),pszFileNameTemp.c_str());                               pData = getFileDataFromZip(pszZipFilePath.c_str(),pszFileNameTemp.c_str(),pSize);            }else {                // read rrom other path than user set it           //CCLOG("GETTING FILE ABSOLUTE DATA: %s", pszFileName);                FILE *fp = fopen(fullPath.c_str(), pszMode);                CC_BREAK_IF(!fp);                            unsigned long size;                fseek(fp,0,SEEK_END);                size = ftell(fp);                fseek(fp,0,SEEK_SET);                pData = new unsigned char[size];                size = fread(pData,sizeof(unsigned char), size,fp);                fclose(fp);                            if (pSize)                {                    *pSize = size;                }            }        } while (0);        //by sc Load        if (pData) {            pData = ResourcesDecode::sharedDecode()->decodeData(pData, *pSize, pSize);        }    }        if (! pData)    {        std::string msg = "Get data from file(";        msg.append(pszFileName).append(") failed!");        CCLOG("%s", msg.c_str());    }        return pData;}


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

安卓这边得到sd卡路径 

 public static String getSDcardDir() {        return Environment.getExternalStorageDirectory().getPath() + "/";    }

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

打开压缩包 就看见啦。


下载完整的修改↓

{{{{{{{{{ github传送门 }}}}}}}}}}



本文出处

http://blog.csdn.net/frabbit_on_fire/article/details/51518780

0 0