Java
C++
Android
cocos2d-x
JNI
Cocos2d-xDay 19

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

More than 3 years have passed since last update.


はじめに

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)


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>



2:Activityの起動

1で決定されたAppActivityクラスが起動されます。

デフォルトではAppActivityの中身は空っぽなので、継承元のCocos2dxActivityのonCreate()が呼ばれます。

※Activityの実行時にどのようなメソッドが呼ばれるかは、Androidのライフサイクルを確認して下さい。


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);
}
}



2−1:ダイナミックリンクライブラリのロード

onLoadNativeLibraries()メソッドで、C++プログラムをビルドしたダイナミックリンクライブラリファイル(Shared Object file=.so形式)をロードします。

このとき、Android.mk中のLOCAL_MODULE_FILENAMEに設定した名称に拡張子「.so」を追加したものがライブラリファイル名になります。

※このあたりの設定項目についてはNDK内のドキュメント(ANDROID-MK.text)に詳しく書かれています。


Android.mk

LOCAL_MODULE_FILENAME := libcocos2dcpp


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


AndroidManifest.xml

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


ライブラリ名が取得出来たらSystem.loadliblary()メソッドでlibcocos2dcpp.soファイルをロードしています。

※ここは自信が無いのですが、loadlibrary()にしていしたライブラリ名の先頭に「lib」、末尾に「.so」を追加したファイルが読み込まれるようです。


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を呼び出す事になるため、全く起動しなくなって大い悩んだりする事になります(ました)。


2−2:cocos2dxActivityの初期化処理

init()メソッドではGLSurfaceViewを作成し、レイアウトに配置しています。


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);
}



3:Cocos2dxRendererの生成

Cocos2dxRendererがnewされ、GLSurfaceが作られると、onSurfaceCreated()が呼ばれます。


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++側の処理を呼び出しています。


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


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


4:C++側での初期化処理

nativeInitのC++側実体はjavaactivity.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のインスタンスを作っただけです。


proj.android/jni/hellocpp/main.cpp

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クラスのメソッドを呼び出します。


platform/android/CCApplication.cpp

int Application::run()

{
// Initialize instance and cocos2d.
if (! applicationDidFinishLaunching()) // やっと呼び出した!
{
return 0;
}

return -1;
}



6:ゴール!!

ようやく AppDelegate.cppapplicationDidFinishLaunching()が呼ばれました。これで起動までの流れは終了です。

ここからは通常のC++側の処理が実行されて行きます。


まとめ

こうやって起動までの一連の流れを見て行く事で、cocos2d-x内部でどのような処理を通ってゲームを起動しているのかがよく分かると思います。

興味がある方は、GLSurfaceのレンダラー処理や他のcocos2d-x内部の動作も追いかけてみると、より深くcocos2d-xを理解出来るのではないでしょうか。

この記事が少しでも皆さんの開発の助けになれば幸いです。


プロフィール

かれこれ20年以上プログラマーやってます。

現在フリーランスで活動中。iPhoneとAndroidでcocos2d-xを使ったゲームアプリを作ってます。あとたまにWebも。

去年のAdventCalendarではSoomlaの事を書いたりもしてました。

個人制作の知育アプリも出してます。

https://www.facebook.com/totekoya

宜しくお願いします。