cocos2d-xを使っていると指定URLをWebブラウザで開きたいときがあると思います。
そんなときは、iOS/Androidのネイティブ機能を使いますが、NativeBridgeを使って
下記の様にしてみましょう。
ポイントは、NativeBridgeというクラス(機能毎にクラス分けしておくと後で
必要な機能のみを別アプリに組み込む事も可能ですね)を作成してiOS/Androidの
ネイティブ実装を隠蔽し、cocos2d-x側ではどちらを呼び出すのか意識させない事です。
注意点として、BrowserNativeBridge.cpp
はXcodeプロジェクトに含めない
で下さい。
以下の例ではブラウザー機能の実装のため「BrowserNativeBridge」クラスを
用意しています。
#BrowserNativeBridge
##BrowserNativeBridge.hを用意する
// 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を用意する
// BrowserNativeBridge.mm
// testApp
//
// Created by takataka5845 on 2014/02/24.
//
//
#import "BrowserNativeBridge.h"
// NativeBridgeを使う場合は下記の1行は必須
#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を用意する
// 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)
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等でコールバックを実装する
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側の実装が
シンプルになります。
しかし、インターフェースをそろえる必要があったりしますが、ここはみなさんの
腕のみせどころです(笑