cocos2d-xでAndroidの起動シーケンスを追いかけてみる

#はじめに
[Cocos2d-x Advent Calendar 2014](http://qiita.com/advent-calendar/2014/cocos2d-x)の19日目を担当させて頂きます、yosizoと申します。
なんとなく”ネイティブ連携関連の記事書きます”と書いたものの、既に有用な記事がQiita中にあるので、ここは趣向を変えてAndroidでcocos2d-xが起動するまでを追いかけてみます。

#Androidでcocos2d-xが起動するまで
今回の記事では、AndroidのActivityが起動してから、C++側のAppDelegate::applicationDidFinishLaunching() が呼び出されるところがゴールです。

それでは順番に見て行きましょう。


##１：起動するActivityの決定
まず、AndroidManifest.xml で起動時に実行されるActivityが決定されます。

xmlの **intent-fiter要素**にMAINアクションとLAUNCHERカテゴリを含んだものが設定されている**activity要素**が起動対象として選ばれます。
（cocos2d-xのデフォルトでは、org.cocos2dx.cpp.AppActivity）

```xml:proj.android/AndroifManifest.xml
        <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>
```


##２：Activityの起動
１で決定されたAppActivityクラスが起動されます。
デフォルトではAppActivityの中身は空っぽなので、継承元の**Cocos2dxActivity**のonCreate()が呼ばれます。
※Activityの実行時にどのようなメソッドが呼ばれるかは、[Androidのライフサイクル](http://developer.android.com/intl/ja/guide/components/activities.html)を確認して下さい。

```java:cocos2d/cocos/platform/android/java/src/org/cocos2dx/lib/Cocos2dxActivity.java
	@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);
		}
	}
```

###２−１：ダイナミックリンクライブラリのロード
onLoadNativeLibraries()メソッドで、C++プログラムをビルドした**ダイナミックリンクライブラリファイル（Shared Object file＝.so形式）**をロードします。

このとき、Android.mk中の**LOCAL_MODULE_FILENAME**に設定した名称に拡張子「.so」を追加したものがライブラリファイル名になります。
※このあたりの設定項目についてはNDK内のドキュメント（ANDROID-MK.text）に詳しく書かれています。

```Make:Android.mk
LOCAL_MODULE_FILENAME := libcocos2dcpp
```

**AndroidManifest.xml**中の以下の定義を元につライブラリの名称を決定します。

```xml:AndroidManifest.xml
	<meta-data android:name="android.app.lib_name" android:value="cocos2dcpp" />
```

ライブラリ名が取得出来たら**System.loadliblary()**メソッドでlibcocos2dcpp.soファイルをロードしています。
※ここは自信が無いのですが、loadlibrary()にしていしたライブラリ名の先頭に「lib」、末尾に「.so」を追加したファイルが読み込まれるようです。

```java:cocos2d/cocos/platform/android/java/src/org/cocos2dx/lib/Cocos2dxActivity.java
	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を呼び出す事になるため、全く起動しなくなって大い悩んだりする事になります（ました）。_ 

###２−２：cocos2dxActivityの初期化処理
init()メソッドではGLSurfaceViewを作成し、レイアウトに配置しています。


```java:cocos2d/cocos/platform/android/java/src/org/cocos2dx/lib/Cocos2dxActivity.java
 public void init() {
		
    　（色々な初期化処理）
     
        this.mGLSurfaceView.setCocos2dxRenderer(new Cocos2dxRenderer());
        this.mGLSurfaceView.setCocos2dxEditText(edittext);

        // Set framelayout as the content view
		setContentView(mFrameLayout);
	}
``` 

##３：Cocos2dxRendererの生成
Cocos2dxRendererがnewされ、GLSurfaceが作られると、**onSurfaceCreated()**が呼ばれます。

```java:cocos2d/cocos/platform/android/java/src/org/cocos2dx/lib/Cocos2dxRenderer.java

	@Override
	public void onSurfaceCreated(final GL10 pGL10, final EGLConfig pEGLConfig) {
		Cocos2dxRenderer.nativeInit(this.mScreenWidth, this.mScreenHeight);
		this.mLastTickInNanoSeconds = System.nanoTime();
	}
```


ここで、**nativeInit()**メソッドは下記のように**native宣言**されていますので、JNIを使ってC++側の処理を呼び出しています。

```java:cocos2d/cocos/platform/android/java/src/org/cocos2dx/lib/Cocos2dxRenderer.java

	private static native void nativeInit(final int pWidth, final int pHeight);
```

##４：C++側での初期化処理
nativeInitのC++側実体は**javaactivity.cpp**にあります。

```cpp:cocos2d/cocos/platform/android/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のインスタンスを作っただけです。

```cpp:proj.android/jni/hellocpp/main.cpp
void cocos_android_app_init (JNIEnv* env, jobject thiz) {

    AppDelegate *pAppDelegate = new AppDelegate();
```

###4-2：cocos2d::Applicationの実行開始
一旦先ほどのnativeInit処理に戻って、続きを見てみます。

```cpp:
        cocos2d::Application::getInstance()->run();
```

ここでcocos2d::Application::run()が呼ばれています。

##5：applicationDidFinishLaunching()の呼び出し
**applicationDidFinishLaunching()**は*純粋仮想関数*ですので、ApplicationProtocolを継承したクラスで定義される必要があります。
CCApplicationクラスには定義されていないので、AppDelegateクラスのメソッドを呼び出します。

```cpp:platform/android/CCApplication.cpp
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の事](http://qiita.com/yosizo@github/items/ae3364160689645b1d8a)を書いたりもしてました。

個人制作の知育アプリも出してます。
https://www.facebook.com/totekoya

宜しくお願いします。