LoginSignup
44
44

More than 5 years have passed since last update.

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

Last updated at Posted at 2014-12-18

はじめに

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

宜しくお願いします。

44
44
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
44
44