超〜簡単にcocos2d-xでネイティブ連携を行う方法

cocos2d-xを使っていると指定URLをWebブラウザで開きたいときがあると思います。
そんなときは、iOS/Androidのネイティブ機能を使いますが、NativeBridgeを使って
下記の様にしてみましょう。

ポイントは、NativeBridgeというクラス（機能毎にクラス分けしておくと後で
必要な機能のみを別アプリに組み込む事も可能ですね）を作成してiOS/Androidの
ネイティブ実装を隠蔽し、cocos2d-x側ではどちらを呼び出すのか意識させない事です。
注意点として、`BrowserNativeBridge.cpp`は`Xcodeプロジェクトに含めない`で下さい。

以下の例ではブラウザー機能の実装のため「BrowserNativeBridge」クラスを
用意しています。

#BrowserNativeBridge
##BrowserNativeBridge.hを用意する
```objc//
//  BrowserNativeBridge.h
//  testApp
//
//  Created by takataka5845 on 2014/02/24.
//
//

#ifndef __testApp__BrowserNativeBridge__
#define __testApp__BrowserNativeBridge__

class BrowserNativeBridge {
    
public :
    
    static bool openURL(const char *url);
    
};

#endif /* defined(__testApp__BrowserNativeBridge__) */
```

##iOS用にBrowserNativeBridge.mmを用意する
```objc//
//  BrowserNativeBridge.mm
//  testApp
//
//  Created by takataka5845 on 2014/02/24.
//
//

#import "BrowserNativeBridge.h"

// NativeBridgeを使う場合は下記の１行は必須
#include "cocoa/CCString.h"

using namespace cocos2d;

bool BrowserNativeBridge::openURL(const char *pURL) {
    
    NSString *urlText = [NSString stringWithCString:pURL
                                          encoding:NSUTF8StringEncoding];
    
    NSLog(@"input URL[%@]", urlText);
    
    BOOL result = YES;
    
    NSURL *url = [NSURL URLWithString:urlText];
    
    // 別スレッドで使える様に「__block修飾子」を付けてローカル変数を宣言する
    __block BOOL isOpneApp = false;
    
    // GCDで実行する
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),^{
        
        // 以下は別スレッドで処理される //////////////////////////////////////////
        NSLog(@"Start canOpenURL");
        // そのURLが使えるかチェック
        if ([[UIApplication sharedApplication] canOpenURL:url]) {
            
            NSLog(@"End canOpenURL is Done");
            NSLog(@"Start openURL");
            
            // 実際にそのURLを使う
            BOOL ret = [[UIApplication sharedApplication] openURL:url];
            
            // URLが使えなかった
            if (ret == NO) {
                NSLog(@"End openURL is Error");
                NSLog(@"URL Opne Error!! [%@]", url);
            }
            // URLが使えた
            else {
                NSLog(@"End openURL is Done");
                isOpneApp = true;
            }
        }
        // 使えなかった場合
        else {
            NSLog(@"End canOpenURL is Error");
            NSLog(@"Can not open the URL using the safari.app... URL[%@]", url);
        }
        /////////////////////////////////////////////////////////////////////
        
        // 以下はメインスレッドで処理される ///////////////////////////////////////
        dispatch_sync(dispatch_get_main_queue(), ^{
            
            //
            if (isOpneApp == false) {
                UIAlertView *alert = [[[UIAlertView alloc]initWithTitle:@"Error"
                                                                message:@"「Mobile Safari」アプリが\n起動できませんでした"
                                                               delegate:nil
                                                      cancelButtonTitle:nil
                                                      otherButtonTitles:@"OK", nil]autorelease];
                [alert show];
            }
            
        });
        /////////////////////////////////////////////////////////////////////
        
    });
    
    // C++のbool型とObjective-CのBOOL型は変換が必要だよ!!
    if (result == YES) {
        return true;
    }
    return false;
}
```

##Android用にBrowserNativeBridge.cppを用意する
```objc//
//  BrowserNativeBridge.cpp
//  testApp
//
//  Created by takataka5845 on 2014/02/24.
//
//

#include "BrowserNativeBridge.h"
#include <android/log.h>
#include <jni.h>
#include "cocoa/CCString.h"
#include "platform/android/jni/JniHelper.h"
#include "cocos2d.h"

// Eclipse側のパッケージ名とjavaファイル名を指定
#define kCLASS_NAME "jp/mycompany/test/testApp"

// 呼び出すJavaメソッド名
static const char* const kMETHOD_NAME01 = "onOpenURL";

using namespace std;
using namespace cocos2d;

// 指定URLをWebブラウザで開く
bool BrowserNativeBridge::openURL(const char *url)
{
    JavaVM* jvm = JniHelper::getJavaVM();
    int status;
    JNIEnv *env;
    jmethodID mid;

    bool isAttached = false;

	CCLOG("start Static InterfaceJNI::openURL():[%s]", url);

    // Get Status（JNIのバージョンチェック）
    status = jvm->GetEnv((void **) &env, JNI_VERSION_1_6);
    if(status < 0)
	{
		// JNI インタフェースポインタ (JNIEnv) は、現在のスレッドでのみ有効です
		// 別のスレッドが Java VM にアクセスする必要がある場合、これは最初に AttachCurrentThread() を呼び出し、
		// それ自体を VM に接続し JNI インタフェースポインタを取得する必要があります。
        CCLOG("callback_handler: failed to get JNI environment, assuming native thread...");
        status = jvm->AttachCurrentThread(&env, NULL);
        CCLOG("Status 2: %d", status);
        if(status < 0)
        {
            CCLOG("callback_handler: failed to attach current thread");
            return false;
        }
        isAttached = true;
        CCLOG("Status isAttached: %d", isAttached);
    }
    //-----------------------------------------------------------

    CCLOG("Status: %d", status);

    // Javaクラスを探す
    jclass mClass = env->FindClass(kCLASS_NAME);

    CCLOG("jClass ");

    // メソッドを探す
	mid = env->GetStaticMethodID(mClass, kMETHOD_NAME01, "(Ljava/lang/String;)V");

	// 引数をJavaのString型に変換する
	jstring stringArg = env->NewStringUTF(url);

    CCLOG("mID: %d", mid);

    // メソッドを呼び出す
    if (mid!=0) {
		env->CallStaticVoidMethod(mClass, mid, stringArg);
		CCLOG("Call Method Finish");
	}
	else {
		CCLOG("failed:Method could not be found... ClassName[%s] Method[%s]", kCLASS_NAME, kMETHOD_NAME01);
	}

	if(isAttached) {
		// デタッチする（後処理）
        jvm->DetachCurrentThread();
    }

    return true;
}
```

##Cocos2dxActivityを継承したクラスに以下を追加する（Java）
```java
import android.net.Uri;
import android.util.Log;

public class testApp extends Cocos2dxActivity{
	
    private static Cocos2dxActivity myActivity;

    protected void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        // Activityを保持する
        myActivity = this;
    }
	
    // 指定URLをWebブラウザで開く
    public static void onOpenURL(String msg)
    {
        Log.i ("INFO", "Start testApp:onOpenURL() URL[" + msg + "]");
        Uri uri = Uri.parse(msg);
        Intent i = new Intent(Intent.ACTION_VIEW,uri);
        myActivity.startActivity(i);
    }
}
```
##cocos2d-x側にMenu等でコールバックを実装する
```objc
bool HelloWorld::init() {

    if ( !CCLayer::init() )
    {
        return false;
    }

    CCMenuItemImage *pMenuItem = CCMenuItemImage::create(
                                        "Normal.png",
                                        "Selected.png",
                                        this,
                                        menu_selector(HelloWorld::menuCallback) );

    CCSize size = CCDirector::sharedDirector()->getWinSize();
    pMenuItem->setPosition( ccp(size.width/2, size.height/2) );

    CCMenu* pMenu = CCMenu::create(pMenuItem,
                                   NULL);
    pMenu->setPosition( CCPointZero );
    this->addChild(pMenu, 1);

    return true;
}

void HelloWorld::menuCallback(CCObject* pSender)
{
    CCLog("HelloWorld::menuCloseCallback2() start!!");

    bool bResult = BrowserNativeBridge::openURL("http://qiita.com/takataka5845");
    if (bResult == false) {
    	CCLOG("http://qiita.com/takataka5845 is OPEN Error...");
    }
    else {
    	CCLOG("http://qiita.com/takataka5845 is OPEN Done!!");
    }
}
```
#まとめ
上記の様に、ネイティブでの実装を隠蔽することによってcocos2d-x側の実装が
シンプルになります。
しかし、インターフェースをそろえる必要があったりしますが、ここはみなさんの
腕のみせどころです（笑