cocos2dxでAndroidアプリを開発をしてくうちに、だんだんと「どれくらいメモリを使ってるかみたい…」という心の声が大きく聞こえてくるようになったんで、それを簡易ながら表示することにしてみました。
iPhone版はいくつか方法が本にもネットにも乗ってるんだけど(参考:[Cocos2d-x] FPS 表示の横に残りメモリを表示する)、このやり方はどうもAndroidでは使えないみたい。いや、もしかしたら使えるのかもしれないけど、ぼくはやり方がわからなかったです(´・ω・`)
/System/Libraryディレクトリをインクルードしてみたりと色んなことをしてみたけどだめだったorz
絶対やり方あると思うんだけどなぁ。AndroidNDK側にも。
ただ、AndroidSDK側にはちゃんと使用メモリを表示する関数があるんだから、JNIを使って使用メモリ量を呼び出すことにしました(これでいいのかどうかは自信ありませんが…)。
Java側でメモリ使用量を返す関数を作成
まずはここから。AndroidにはネイティブヒープとDalvikヒープという二つのメモリ領域があるらしく、それを返す関数を作成します。
参考:Androidアプリ開発でのメモリ使用量確認
ちなみに、わかりやすくする為に割合を返すことに。あとパッケージ名はここでは便宜的に「com.package.lib」とします。
<br />public class MyClass {<br /> public static String getNativeAndDalvikFreeMemory(){<br /> StringBuffer buf = new StringBuffer();<br /> buf.append("NativeHeap:" + (Math.round((double)Debug.getNativeHeapAllocatedSize() / Debug.getNativeHeapSize() * 1000) / 10d) + "%");<br /> buf.append("\nDalvikHeap:" + (Math.round(((double)Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) / Runtime.getRuntime().maxMemory() * 1000) / 10d) + "%");<br /> return buf.toString();<br /> }<br /><br />}<br />
ただネイティブヒープとDalvikヒープの使用率をパーセンテージで返すだけの関数です(でもこれでちゃんと使用メモリがわかるのか自信ない…)。
ネイティブ側で受け取るクラスを作成
Java側の作業はこれで終わりで、次はネイティブ側です。
CCLayerを作成し、そこの上の方に表示されるようにします。
あと、デバッグが終わったら表示されないようにCOCOS2D_DEBUG定数で表示/非表示を実装します。
<br />#ifndef DEBUGLAYER_H_<br />#define DEBUGLAYER_H_<br />#include "cocos2d.h"<br />using namespace cocos2d;<br />class DebugLayer : public CCLayer{<br /> CREATE_FUNC(DebugLayer);<br /> virtual bool init();<br /> std::string getFreeMemory();<br /> void updateString(float $fm);<br />public:<br /> virtual ~DebugLayer(){};<br /> static void setDebugLayer(CCNode* $layer);<br />};<br />#endif /* DEBUGLAYER_H_ */<br />
実際に使用する際にはDebugLayer::setDebugLayerを呼び出すという感じです。
DebugLayerの実装
<br />#include "DebugLayer.h"<br />#include <jni.h><br />#include "cocoa/CCString.h"<br />#include "platform/android/jni/JniHelper.h"<br /><br />#define UTILCLASSNAME "com/package/lib/MyUtil"<br />#define UTILFUNCGETFREEMEMORY "getNativeAndDalvikFreeMemory"<br />#define DEBUG_UPDATE_INTERVAL 1<br />#define DEBUG_TAG 1<br />using namespace cocos2d;<br /><br />bool DebugLayer::init(){<br /> if(!CCLayer::init()) return false;<br /> CCSize winSize = CCDirector::sharedDirector()->getVisibleSize();<br /> CCLabelTTF* pTTF = CCLabelTTF::create();<br /> pTTF->setFontName("Arial");<br /> pTTF->setFontSize(30.0f);<br /> pTTF->setFontFillColor(ccc3(0,0,0));<br /> pTTF->setPosition(ccp(winSize.width / 2 , winSize.height - pTTF->getContentSize().height));<br /> pTTF->setTag(DEBUG_TAG);<br /> this->addChild(pTTF);<br /> updateString(0.0f);<br /> this->schedule(schedule_selector(DebugLayer::updateString) , DEBUG_UPDATE_INTERVAL);<br /> return true;<br />}<br /><br />void DebugLayer::updateString(float $fm){<br /> CCLabelTTF* pTTF = (CCLabelTTF *)this->getChildByTag(DEBUG_TAG);<br /> std::string mstr = getFreeMemory();<br /> CCString *str = CCString::create(mstr);<br /> pTTF->setString(str->getCString());<br />}<br /><br />void DebugLayer::setDebugLayer(CCNode *$layer){<br />#if COCOS2D_DEBUG > 0<br /> DebugLayer* layer = DebugLayer::create();<br /> $layer->addChild(layer , 10);<br />#endif<br />}<br /><br />std::string DebugLayer::getFreeMemory(){<br /> std::string str;<br /> JniMethodInfo methodInfo;<br /> if(JniHelper::getStaticMethodInfo(methodInfo , UTILCLASSNAME , UTILFUNCGETFREEMEMORY , "()Ljava/lang/String;")){<br /> jstring jStr = (jstring)methodInfo.env->CallStaticObjectMethod(methodInfo.classID , methodInfo.methodID);<br /> const char* req = methodInfo.env->GetStringUTFChars(jStr , NULL);<br /> str = req;<br /> methodInfo.env->ReleaseStringUTFChars(jStr , req);<br /> methodInfo.env->DeleteLocalRef(methodInfo.classID);<br /> }<br /> return str;<br />}<br />
長いですけど、要はgetFreeMemoryでjniを使って取得し、CCLabelTTFで表示させるってだけの話です。それでsetDebugLayerではプリプロセッサを使ってデバッグ時じゃない時には何もしないようにしてます。
で、多分毎フレームごとに呼び出すとなんか重そうかなって気がしたんで、1秒に1回だけ更新するようにしています。
実装の仕方
表示させたいシーンに以下のように入れるだけ。
<br />#include "DebugLayer.h"<br />...<br />bool MainScene::init(){<br />...<br /> DebugLayer::setDebugLayer(this);<br />}<br />
実際に見てみる
実際に起動させてみてみます。
うん、なんかできた気がする!
あ、画像のことはそっとしといてください。今作成中のアプリのなんだけど、まだ色んな意味でちゃっちいです(´・ω・`)
でもこれでいいのかなぁ。どこか間違えてる気もするし、もっとスマートな方法もある気がするけど…。まあ、その方法を知ったら乗り換えるし、しばらくこれでやっていこ。
ただ、この表示だけでネイティブヒープの50%近くつかっちゃってるとなると、Android2.2とかじゃOutofMemoryErrorってなっちゃいそうだなぁ。