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

最近公司要做的一个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传送门 }}}}}}}}}}

时间: 2024-10-10 11:47:11

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

android动态加载已安装和未安装的apk资源

在android开发中动态加载已安装和未安装的apk资源,是很有用的,可以用来实现换肤功能等等.今天我们来学习. 首先新建一个工程plugpicinstall,我们需要往该工程的asset目录和drawable目录下拷贝一些呆会需要加载的图片.运行该工程,即安装. 我们先看看如何实现加载已经安装的apk中的资源: 我们需要先写两个方法,用来获取对应的已安装的apk的context对象和resource对应的id,如下: /** * 该方法用来获取已经安装的apk对应的context对象 * @r

Android异步加载全解析之Bitmap

Android异步加载全解析之Bitmap 在这篇文章中,我们分析了Android在对大图处理时的一些策略--Android异步加载全解析之大图处理  戳我戳我 那么在这篇中,我们来对图像--Bitmap进行一个更加细致的分析,掌握Bitmap的点点滴滴. 引入 Bitmap这玩意儿号称Android App头号杀手,特别是3.0之前的版本,简直就是皇帝般的存在,碰不得.摔不得.虽然后面的版本Android对Bitmap的管理也进行了一系列的优化,但是它依然是非常难处理的一个东西.在Androi

Android异步加载全解析之开篇瞎扯淡

Android异步加载 概述 Android异步加载在Android中使用的非常广泛,除了是因为避免在主线程中做网络操作,更是为了避免在显示时由于时间太长而造成ANR,增加显示的流畅性,特别是像ListView.GridView这样的控件,如果getView的时间太长,就会造成非常严重的卡顿,非常影响性能. 本系列将展示在Android中如何进行异步加载操作,并使用ListView来作为演示的对象. 如何下载图像 下载自然是需要使用网络,使用网络就不能在主线程,在主线程就会爆炸.所以我们必须要在

Android异步加载全解析之使用多线程

异步加载之使用多线程 初次尝试 异步.异步,其实说白了就是多任务处理,也就是多线程执行,多线程那就会有各种问题,我们一步步来看,首先,我们创建一个class--ImageLoaderWithoutCaches,从命名上,大家也看出来,这个类,我们实现的是不带缓存的图像加载,不多说,我们再创建一个方法--showImageByThread,通过多线程来加载图像: /** * Using Thread * @param imageView * @param url */ public void sh

Android异步加载全解析之使用AsyncTask

Android异步加载全解析之使用AsyncTask 概述 既然前面提到了多线程,就不得不提到线程池,通过线程池,不仅可以对并发线程进行管理,更可以提高他们执行的效率,优化整个App.当然我们可以自己创建一个线程池,不过这样是很烦的,要创建一个高效的线程池还是挺费事的,不过,Android系统给我吗提供了AsyncTask这样一个类,来帮助我们快速实现多线程开发,它的底层实现,其实就是一个线程池. AsyncTask初探 AsyncTask,顾名思义就是用来做异步处理的.通过AsyncTask,

Android异步加载全解析之大图处理

Android异步加载全解析之大图处理 异步加载中非常重要的一部分就是对图像的处理,这也是我们前面用异步加载图像做演示例子的原因.一方面是因为图像处理不好的话会非常占内存,而且容易OOM,另一方面,图像也比文字要大,加载比较慢.所以,在讲解了如何进行多线程.AsyncTask进行多线程加载后,先暂停下后面的学习,来对图像的异步处理进行一些优化工作. 为什么要对图像处理 为什么要对图像进行处理,这是一个很直接的问题,一张图像,不管你拿手机.相机.单反还是什么玩意拍出来,它就有一定的大小,但是在不同

Android异步加载

Android异步加载 一.为什么要使用异步加载? 1.Android是单线程模型 2.耗时操作阻碍UI线程 二.异步加载最常用的两种方式 1.多线程.线程池 2.AsyncTask 三.实现ListView图文混排 3-1 实现读取网页中的json数据到ListView中 (图片首先为默认图片) 3.1.1:主布局只有一个ListView和一个listView_item的布局 3.1.2:网页json数据的链接(http://www.imooc.com/api/teacher?type=4&n

Android动态加载框架DL的架构与基本原理解析

转载请注明出处,本文来自[ Mr.Simple的博客 ]. 我正在参加博客之星,点击这里投我一票吧,谢谢~ 前言 最近这一两年,Android App使用插件化技术开发的数量越来越大,其实还是业务地快速膨胀导致,需求越来越多,App越来越臃肿.虽然手机的内存空间不断地的增大,但是太大的安装包给用户也造成了心理压力.于是大家都会想到插件化的开发方式,把App做成一个平台,而不是一个独立的app.平台上可以集成各种各样的功能,功能模块也插件的形式添加进来,这些插件不需要安装,只需要用户按需下载到某个

一起写一个Android图片加载框架

本文会从内部原理到具体实现来详细介绍如何开发一个简洁而实用的Android图片加载缓存框架,并在内存占用与加载图片所需时间这两个方面与主流图片加载框架之一Universal Image Loader做出比较,来帮助我们量化这个框架的性能.通过开发这个框架,我们可以进一步深入了解Android中的Bitmap操作.LruCache.LruDiskCache,让我们以后与Bitmap打交道能够更加得心应手.若对Bitmap的大小计算及inSampleSize计算还不太熟悉,请参考这里:高效加载Bit