50
51

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

【cocos2d-x v3.1】cocos2dxでzipファイルを扱う場合のまとめ。load timeの計測もあるよ。

Last updated at Posted at 2014-06-03

ipaファイルサイズ、apkファイルサイズについて

ご存知の方も多いかと思うのですが、
非wifi環境ではiOSアプリのipaファイルサイズは
100MBまで、
Androidアプリのapkファイルサイズは
50MBまでとなっています。

Androidはapkとは別に拡張ファイルという物を登録すると最大4GBまでいけます。
アプリをマーケットに登録する際にファイル追加の手順が必要だったり、
拡張ファイルを更新する時にアプリバージョンも更新しないとならないため、
ちょっと使いにくいです

そのため、大体どのアプリもマーケット上では50MB以下に抑えておいて、
インストールして、起動した時に必要なファイルを追加でDLするような
仕組みを持っているのではないでしょうか。
今回はその時に有用(かもしれない)zipファイル周りのTipsを投稿します。

ソースコードはcocos2d-x v3.1.1で記載していますが、3.0でも動作するのは確認済みです。

必要な所だけの抜粋で、多少省略していることをお許し下さい。

zipファイルを通信で取得後、保存

HelloWorldScene.h

#include "cocos2d.h"
#import "network/HttpClient.h"

USING_NS_CC;
using namespace cocos2d::network;

class HelloWorld : public cocos2d::Layer
{
public:
    // there's no 'id' in cpp, so we recommend returning the class instance pointer
    static cocos2d::Scene* createScene();

    // Here's a difference. Method 'init' in cocos2d-x returns bool, instead of returning 'id' in cocos2d-iphone
    virtual bool init();  
    
    // a selector callback
    void menuCloseCallback(cocos2d::Ref* pSender);
    
    // implement the "static create()" method manually
    CREATE_FUNC(HelloWorld);
    
    void httpCallBackResponse(HttpClient* client , HttpResponse* response);
};

HelloWorldScene.cpp

#include "HelloWorldScene.h"
#import "external/unzip/unzip.h"

Scene* HelloWorld::createScene(){ // 略 }

// on "init" you need to initialize your instance
bool HelloWorld::init()
{
    // 略
    
    auto request = new HttpRequest();
    request->setUrl("http://*****.***.amazonaws.com/img_100.zip");
    request->setRequestType(cocos2d::network::HttpRequest::Type::GET);
    // cocos v3.0 はこう
    //request->setResponseCallback(this, httpresponse_selector(HelloWorld::httpCallBackResponse));
    
    // cocos v3.1 はこう
    request->setResponseCallback(CC_CALLBACK_2(HelloWorld::httpCallBackResponse ,this));
    network::HttpClient::getInstance()->send(request);
    
    return true;
}

void HelloWorld::httpCallBackResponse(HttpClient* client , HttpResponse* response)
{
    if (response->isSucceed()) {
        std::vector<char>* buffer = response->getResponseData();
        
        CCLOG("buffet size :: %lu" , buffer->size());
        
        std::string filePath = FileUtils::getInstance()->getWritablePath();
        std::string zipFile = "img_100.zip";
        std::string path = filePath.c_str() + zipFile;
        
        // zipファイルの保存
        FILE* fp = fopen(path.c_str() , "wb");
        CCLOG("debug writtern :: %zu" , fwrite(buffer->data() , 1, buffer->size() , fp));
        fclose(fp);
    }
}    


void HelloWorld::menuCloseCallback(Ref* pSender){ // 略 }

以上のコードで保存が可能です。

zipファイルの中にあるファイル名一覧を取り出す

これも以下のようなコードで可能です。

HelloWorldScene.cpp

#import "external/unzip/unzip.h"

void readInZipFileName()
{
        std::string filePath = FileUtils::getInstance()->getWritablePath();
        std::string zipFile = "img_100.zip";
        std::string path = filePath.c_str() + zipFile;
        
        // zipファイルをopenし、ファイル数を取得します。
        unzFile zipfile = unzOpen(path.c_str());

        unz_global_info global_info;
        if ( unzGetGlobalInfo( zipfile, &global_info ) != UNZ_OK )
        {
            printf( "could not read file global infon" );
            unzClose( zipfile );
            return;
        }
        
        // ファイルの先頭にカーソルを合わせます
        unzGoToFirstFile(zipfile);
        uLong i;
        for ( i = 0; i < global_info.number_entry; ++i )
        {
            // Get info about current file.
            char filename[ 100 ];
            if ( unzGetCurrentFileInfo64(zipfile, 
                                         NULL, 
                                         filename, 
                                         100, 
                                         NULL, 0, NULL, 0)

                != UNZ_OK )
            {
                CCLOG( "could not read file" );
                unzClose( zipfile );
                return;
            }
            std::string str(filename);
            
            CCLOG("file[%lu] name == %s" , i , str.c_str());
            // ここでstd::vectorにでも詰めればOK

            // 次にカーソルを進める
            unzGoToNextFile(zipfile);
        }
        // 終わったらcloseを忘れずに。
        unzClose(zipfile);
}

zipファイルの中にある画像をSpriteで表示する

これはお馴染みですね。まとめって事で記載しておきます。

HelloWorld.cpp
        std::string filePath = FileUtils::getInstance()->getWritablePath();
        std::string zipFile = "img_100.zip";
        std::string imgFile = "img_100/f001.png"
        std::string path = filePath.c_str() + zipFile;
        
        // zip fileから指定した画像を読み出して表示
        unsigned long size = 0;
        unsigned char* buf = FileUtils::getInstance()->getFileDataFromZip(
                                                     path.c_str() , 
                                                     zipFile.c_str(), 
                                                     (ssize_t*)&size);
        
        auto img = new Image();
        img->initWithImageData(buf , size);
        
        auto texture = new Texture2D();
        texture->initWithImage(img);
        
        auto sprite = Sprite::createWithTexture(texture);
        
        this->addChild(sprite);

気になるread time。気になるよね?

これこれ、一際怪しい光を放つこれ。

HelloWorld.cpp

unsigned char* buf = FileUtils::getInstance()->getFileDataFromZip(
                                                     path.c_str() , 
                                                     zipFile.c_str(), 
                                                     (ssize_t*)&size);

これ気になりますよねぇ。zipファイルから読み出すって単純に考えて遅いでしょう。
で、どのぐらい遅いのか、計測してみました。

  • 画像を100枚用意。(全て別画像、一枚辺り、600x800 600kb前後)
  • zipファイルに圧縮後のサイズ:47.3MB
  • zipファイルに圧縮しないと:58MB
  • 100枚読み出してaddChildするまでの時間を比較します。
  • 一度読みだすとTextureCacheに載るため、二回目以降は高速になります。
  • そのため、念を入れて片方ずつ計測します。

ソースはこんな感じです。

HelloWorldScene.cpp

// 片方ずつ行います。(コメントアウトで簡単に)
void exec(){
    // resourceから読み出す
    const auto resourceStartTime = std::chrono::system_clock::now();
    for(int i = 1 ; i < 101 ; i++){
        sprintf(str, "f%03d.png" , i);
        readFromResource(str);
    }
    const auto resourceEndTime = std::chrono::system_clock::now();
    const auto resourceExecTime = resourceEndTime - resourceStartTime;

    CCLOG("resourceExecTime : %lld ms" , std::chrono::duration_cast<std::chrono::milliseconds>(resourceExecTime).count());
    
    // zipから読み出す
    const auto zipStartTime = std::chrono::system_clock::now();
    for(int i = 1 ; i < 101 ; i++){
        sprintf(str, "f%03d.png" , i);
        readFromZip(str);
    }
    const auto zipEndTime = std::chrono::system_clock::now();
    const auto zipExecTime = zipEndTime - zipStartTime;
    
    CCLOG("zipExecTime : %lld ms" , std::chrono::duration_cast<std::chrono::milliseconds>(zipExecTime).count());
}

void HelloWorld::readFromResource(const char* _fileName){
    auto sprite = Sprite::create(_fileName);
    this->addChild(sprite);
}

void HelloWorld::readFromZip(const char* _fileName){
    
    std::string fileName = "img_100/";
    fileName = fileName + _fileName;
    // zip fileから指定した画像を読み出して表示
    unsigned long size = 0;
    unsigned char* buf = FileUtils::getInstance()->getFileDataFromZip(path.c_str() , fileName.c_str(), (ssize_t*)&size);
    
    auto img = new Image();
    img->initWithImageData(buf , size);
    
    auto texture = new Texture2D();
    texture->initWithImage(img);
    
    auto sprite = Sprite::createWithTexture(texture);
    
    this->addChild(sprite);
}

結果

cocos2d: resourceExecTime : 4592 ms

cocos2d: zipExecTime : 4757 ms

という結果になりました。
当初の予想通りzipの方が遅い!って事がわかりましたが、
思ったよりもそこまで差が付きませんでした。
一度読んでしまえば、textureCacheに載せる事が出来るので、
全く使えないって事は無さそうです。

その他

zipについて色々とまとめてみましたが、
実運用上では、

  • バージョン管理
  • 差分更新
  • Androidの挙動問題(端末やOSverによってはApplication.mkにunzip.h入れないとダメかも。未確認)
    などなど、まだまだ考える事がありそうです。

あ、削除。remove(writablePath + "xxx.zip")で出来るよ。
あ、zipから読みだした後にtextureCacheに明示的に入れないとダメかも?
あ、zipからファイル読み出す時に、NULLになっちゃうんだけど…って人は、external/unzip/unzip.h 1300行目辺りint ZEXPORT unzLocateFile()の辺りにブレークポイント貼ると分かりやすいよ。

とまだまだネタはあるんですが、とりあえずここまで!

50
51
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
50
51

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?