#はじめに
Cocos2d-x Advent Calendar 2014の19日目を担当させて頂きます、yosizoと申します。
なんとなく”ネイティブ連携関連の記事書きます”と書いたものの、既に有用な記事がQiita中にあるので、ここは趣向を変えてAndroidでcocos2d-xが起動するまでを追いかけてみます。
#Androidでcocos2d-xが起動するまで
今回の記事では、AndroidのActivityが起動してから、C++側のAppDelegate::applicationDidFinishLaunching() が呼び出されるところがゴールです。
それでは順番に見て行きましょう。
##1:起動するActivityの決定
まず、AndroidManifest.xml で起動時に実行されるActivityが決定されます。
xmlの intent-fiter要素にMAINアクションとLAUNCHERカテゴリを含んだものが設定されているactivity要素が起動対象として選ばれます。
(cocos2d-xのデフォルトでは、org.cocos2dx.cpp.AppActivity)
<activity android:name="org.cocos2dx.cpp.AppActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
##2:Activityの起動
1で決定されたAppActivityクラスが起動されます。
デフォルトではAppActivityの中身は空っぽなので、継承元のCocos2dxActivityのonCreate()が呼ばれます。
※Activityの実行時にどのようなメソッドが呼ばれるかは、Androidのライフサイクルを確認して下さい。
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
onLoadNativeLibraries(); // 2-1参照
sContext = this;
this.mHandler = new Cocos2dxHandler(this);
Cocos2dxHelper.init(this);
this.init(); // 2-2参照
if (mVideoHelper == null) {
mVideoHelper = new Cocos2dxVideoHelper(this, mFrameLayout);
}
}
###2−1:ダイナミックリンクライブラリのロード
onLoadNativeLibraries()メソッドで、C++プログラムをビルドした**ダイナミックリンクライブラリファイル(Shared Object file=.so形式)**をロードします。
このとき、Android.mk中のLOCAL_MODULE_FILENAMEに設定した名称に拡張子「.so」を追加したものがライブラリファイル名になります。
※このあたりの設定項目についてはNDK内のドキュメント(ANDROID-MK.text)に詳しく書かれています。
LOCAL_MODULE_FILENAME := libcocos2dcpp
AndroidManifest.xml中の以下の定義を元につライブラリの名称を決定します。
<meta-data android:name="android.app.lib_name" android:value="cocos2dcpp" />
ライブラリ名が取得出来たら**System.loadliblary()**メソッドでlibcocos2dcpp.soファイルをロードしています。
※ここは自信が無いのですが、loadlibrary()にしていしたライブラリ名の先頭に「lib」、末尾に「.so」を追加したファイルが読み込まれるようです。
protected void onLoadNativeLibraries() {
try {
ApplicationInfo ai = getPackageManager().getApplicationInfo(getPackageName(), PackageManager.GET_META_DATA);
Bundle bundle = ai.metaData;
// ライブラリ名をAndroidManifest.xmlから取得
String libName = bundle.getString("android.app.lib_name");
// ライブラリーをロード
System.loadLibrary(libName);
} catch (Exception e) {
e.printStackTrace();
}
}
※このタイミングでC++側のstatic変数等の初期化が行われるため、この時点ではAndroidやcocos2d-x側の初期化が終わっていない事に注意してください。
static変数にJNI経由で取得したパラメーターを保存するようなコードを書いたりすると、この時点では JniHelper::setJavaVM(vm)が呼ばれておらず、
JniHelperにJavaVMの実体が保存されていない状態でJniHelperを呼び出す事になるため、全く起動しなくなって大い悩んだりする事になります(ました)。
###2−2:cocos2dxActivityの初期化処理
init()メソッドではGLSurfaceViewを作成し、レイアウトに配置しています。
public void init() {
(色々な初期化処理)
this.mGLSurfaceView.setCocos2dxRenderer(new Cocos2dxRenderer());
this.mGLSurfaceView.setCocos2dxEditText(edittext);
// Set framelayout as the content view
setContentView(mFrameLayout);
}
##3:Cocos2dxRendererの生成
Cocos2dxRendererがnewされ、GLSurfaceが作られると、**onSurfaceCreated()**が呼ばれます。
@Override
public void onSurfaceCreated(final GL10 pGL10, final EGLConfig pEGLConfig) {
Cocos2dxRenderer.nativeInit(this.mScreenWidth, this.mScreenHeight);
this.mLastTickInNanoSeconds = System.nanoTime();
}
ここで、nativeInit()メソッドは下記のようにnative宣言されていますので、JNIを使ってC++側の処理を呼び出しています。
private static native void nativeInit(final int pWidth, final int pHeight);
##4:C++側での初期化処理
nativeInitのC++側実体はjavaactivity.cppにあります。
void Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeInit(JNIEnv* env, jobject thiz, jint w, jint h)
{
auto director = cocos2d::Director::getInstance();
auto glview = director->getOpenGLView();
if (!glview)
{
glview = cocos2d::GLView::create("Android app");
glview->setFrameSize(w, h);
director->setOpenGLView(glview);
// AppDelegateの初期化
cocos_android_app_init(env, thiz); // 4-1参照
// cocos2d::Applicationの実行開始
cocos2d::Application::getInstance()->run(); // 4-2参照
}
###4-1:AppDelegateのインスタンス化
**cocos_android_app_init()**はユーザーのプロジェクト内、proj.android/jni/hellocpp/main.cppにあります。
ようやく見慣れた場所にやってきました。
でも、ここではまだAppDelegateのインスタンスを作っただけです。
void cocos_android_app_init (JNIEnv* env, jobject thiz) {
AppDelegate *pAppDelegate = new AppDelegate();
###4-2:cocos2d::Applicationの実行開始
一旦先ほどのnativeInit処理に戻って、続きを見てみます。
cocos2d::Application::getInstance()->run();
ここでcocos2d::Application::run()が呼ばれています。
##5:applicationDidFinishLaunching()の呼び出し
**applicationDidFinishLaunching()**は純粋仮想関数ですので、ApplicationProtocolを継承したクラスで定義される必要があります。
CCApplicationクラスには定義されていないので、AppDelegateクラスのメソッドを呼び出します。
int Application::run()
{
// Initialize instance and cocos2d.
if (! applicationDidFinishLaunching()) // やっと呼び出した!
{
return 0;
}
return -1;
}
##6:ゴール!!
ようやく AppDelegate.cppの**applicationDidFinishLaunching()**が呼ばれました。これで起動までの流れは終了です。
ここからは通常のC++側の処理が実行されて行きます。
#まとめ
こうやって起動までの一連の流れを見て行く事で、cocos2d-x内部でどのような処理を通ってゲームを起動しているのかがよく分かると思います。
興味がある方は、GLSurfaceのレンダラー処理や他のcocos2d-x内部の動作も追いかけてみると、より深くcocos2d-xを理解出来るのではないでしょうか。
この記事が少しでも皆さんの開発の助けになれば幸いです。
#プロフィール
かれこれ20年以上プログラマーやってます。
現在フリーランスで活動中。iPhoneとAndroidでcocos2d-xを使ったゲームアプリを作ってます。あとたまにWebも。
去年のAdventCalendarではSoomlaの事を書いたりもしてました。
個人制作の知育アプリも出してます。
https://www.facebook.com/totekoya
宜しくお願いします。