ipaファイルサイズ、apkファイルサイズについて
ご存知の方も多いかと思うのですが、
非wifi環境ではiOSアプリのipaファイルサイズは
100MBまで、
Androidアプリのapkファイルサイズは
50MBまでとなっています。
Androidはapkとは別に拡張ファイルという物を登録すると最大4GBまでいけます。
アプリをマーケットに登録する際にファイル追加の手順が必要だったり、
拡張ファイルを更新する時にアプリバージョンも更新しないとならないため、
ちょっと使いにくいです
そのため、大体どのアプリもマーケット上では50MB以下に抑えておいて、
インストールして、起動した時に必要なファイルを追加でDLするような
仕組みを持っているのではないでしょうか。
今回はその時に有用(かもしれない)zipファイル周りのTipsを投稿します。
ソースコードはcocos2d-x v3.1.1で記載していますが、3.0でも動作するのは確認済みです。
必要な所だけの抜粋で、多少省略していることをお許し下さい。
zipファイルを通信で取得後、保存
#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);
};
#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ファイルの中にあるファイル名一覧を取り出す
これも以下のようなコードで可能です。
#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で表示する
これはお馴染みですね。まとめって事で記載しておきます。
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。気になるよね?
これこれ、一際怪しい光を放つこれ。
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に載るため、二回目以降は高速になります。
- そのため、念を入れて片方ずつ計測します。
ソースはこんな感じです。
// 片方ずつ行います。(コメントアウトで簡単に)
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()の辺りにブレークポイント貼ると分かりやすいよ。
とまだまだネタはあるんですが、とりあえずここまで!